cyberine
UsageRun recipes

Machine pressure preflight

Owner ruling 2026-10-29 (D1-A): Cyberine must not make a laptop that is already out of memory or disk worse. Before it spawns a NEW worker process, Cyberine reads a memory signal, free disk on the repo's volume (statfs), and the 1-minute load average per core. The memory signal is the kernel's memory pressure level on macOS (sysctl kern.memorystatus_vm_pressure_level: 1 normal, 2 warn, 4 critical) and swap use on Linux (/proc/meminfo). On macOS swap use is shown but never gates, because macOS grows its swap files on demand, so used/total sits near 100% whenever any swap exists (measured 2026-10-29: swap 94% while the pressure level was 1 and 51% of memory was free). While any threshold is crossed it does not spawn more and says why. Work that is already running is never paused or killed.

cyberine doctor | grep -A1 '^Machine:'
# Machine:
#   [PASS] memory pressure normal (level 1, limit warn), swap 94% (info), disk 119 GB free (limit 20), load 0.6/core (limit 2.0) - ok   (macOS)
#   [WARN] memory pressure warn (level 2, limit warn), swap 93% (info), ... - PRESSURE: memory pressure warn (level 2, limit warn, CYBERINE_PRESSURE_MEM_LEVEL)   (macOS)
#   [PASS] swap 62% (limit 80), disk 120 GB free (limit 20), load 0.9/core (limit 2.0) - ok   (Linux)
cyberine run --ignore-pressure "<task>"           # start anyway, and lift the hold for this run
CYBERINE_PRESSURE_MEM_LEVEL=4 cyberine run "<task>"   # macOS: hold only at critical
CYBERINE_PRESSURE_SWAP_PCT=90 cyberine run "<task>"   # Linux
CYBERINE_PRESSURE=off cyberine autopilot           # no preflight for this process

What each front end does when a threshold is crossed:

  • cyberine run first holds for up to 2 minutes, printing a [pressure] holding cyberine run ... line with the reasons and re-checking every 10 s; it starts as soon as the reading clears (a [pressure] resuming line), and refuses with exit 2, printing the reading and the env var to change, only when the limit still holds after the wait. CYBERINE_PRESSURE_HOLD_MS sets the wait (0 refuses at once) and CYBERINE_PRESSURE_RECHECK_MS the re-check interval. cyberine run --bg does not hold: it refuses at once, so the caller is never blocked. --ignore-pressure skips the check and sets CYBERINE_PRESSURE=off for the run, its background child included. launchBackgroundRun (chat /run, the TUI's new-task key, the web control route) and Orchestrator.dispatch (npm run cli, the MCP orchestrate tool, the SDK, the web chat-completions bridge) refuse the same way; they have no --ignore-pressure flag, so use the env var there.
  • Long-running loops hold the next spawn: the workflow engine before each worker (WorkflowEngine.runWorker, so every fleet wave and autopilot fleet dispatch), the autopilot session loop before each new session and before the brainstorm session, the autopilot lanes scheduler before each lane task, a workflow's child-run launch, and the autonomy loop's non-code gate. A held loop logs one [pressure] holding ... line, re-checks every 30 s, and logs one [pressure] resuming ... line when the reading clears. A stop request ends the hold: the STOP file for autopilot sessions, cyberine stop <runId> for a workflow run; the autopilot lanes scheduler waits until the reading clears.
  • The queue consumer leaves pending entries pending and reports paused machine-pressure; cron skips due jobs until the next tick.

Configure the limits globally in ~/.cyberine/config.json ($CYBERINE_HOME/config.json); an env var always wins over the config key:

{ "pressure": { "enabled": true, "memLevel": 2, "swapPct": 80, "diskFreeGb": 20, "loadPerCore": 2.0 } }

The env vars are in the Environment variables table: CYBERINE_PRESSURE, CYBERINE_PRESSURE_MEM_LEVEL, CYBERINE_PRESSURE_SWAP_PCT, CYBERINE_PRESSURE_DISK_FREE_GB, CYBERINE_PRESSURE_LOAD_PER_CORE, CYBERINE_PRESSURE_HOLD_MS, CYBERINE_PRESSURE_RECHECK_MS, and the test-only CYBERINE_PRESSURE_FAKE. npm run selftest:machine-pressure covers the evaluator (a macOS reading with swap 96% at level 1 passes, level 2 holds), env-over-config precedence, unknown readings, off, and a faked reading that makes cyberine run refuse. The selftest sweep runs every smoke with CYBERINE_PRESSURE=off unless the shell sets it.

Known Limitations and Deferred Work — machine pressure preflight

  • Windows reads unknown. There is no memory or load reading on Windows, and an unknown reading never blocks, so only free disk is checked there.
  • macOS gates on the kernel's level, not on how much memory is free. The level moves between normal and warn within seconds on a busy machine, so a hold can start and end quickly; raise CYBERINE_PRESSURE_MEM_LEVEL to 4 to hold only at critical. macOS swap use is informational. Linux uses swap percent only; the PSI file /proc/pressure/memory is not read. Any reading that fails on any platform also counts as unknown, never as pressure.
  • A reading is a snapshot. It is taken just before a spawn (cached for 5 s), so a burst of workers admitted in the same instant all see the same numbers; the gate limits new waves, not the size of one wave.
  • Only new spawns are held. Workers already running keep running even if they are what is filling swap; Cyberine never pauses or kills them.
  • Judges are not gated. Judge calls (LLM, adversarial, diff) finish work already admitted, so they run even under pressure.
  • Remote runs are not checked. With CYBERINE_SERVER_URL set, cyberine run does not read this machine, because the work runs on the server.
  • A smoke run alone is gated. npm run selftest:<name> outside the sweep inherits the live gate, so an engine smoke on a machine under pressure holds with a [pressure] holding line; set CYBERINE_PRESSURE=off for it.