Public: anyone can read this; the crew can change it.
A machine's memory and CPU ceilings are declared in forge.json and enforced on the crew run #9479
Check · Review · Land · Live done — Landed and live in production.
A machine in `forge.json` can now set `memory` (whole MiB, 256–65536) and `cpus` (cores, 0.25–64), and the crew run is held to them by the kernel. Example: `"machines": { "audit": { "memory": 2048, "cpus": 2 } }`.
shipped production is serving it deploy f21b126
A machine in forge.json can now set memory (whole MiB, 256–65536) and cpus (cores, 0.25–64), and the crew run is held to them by the kernel. Example: "machines": { "audit": { "memory": 2048, "cpus": 2 } }.
- Parsing (
src/crew/machines.ts): a value out of bounds or of the wrong type rejects the declaration. As with any invalid declaration, the crew then runs instandard.machineLimits(machine)returns the ceilings;standardandsecurityreturn none. - Enforcement (new
src/crew/run-cell.ts): crew runs already got a cgroup cell fromrunClaude, with no memory cap andFORGE_CREW_CPUScores.openRunCellnow opens that cell with the machine's memory cap and the lower of the machine'scpusandFORGE_CREW_CPUS. A machine can only narrow the cell, never widen it. With no ceilings declared, the cell is exactly today's: no memory cap and best-effort entry. - Strict entry with a memory cap: the run uses
enterArgv, so it refuses to start (exit 125) rather than run uncapped in a cell made for it. Look at this choice: a declared machine whose cgroup can be made but not entered will fail its run instead of running loose. - Run log: the log says
machine: held to memory 2048 MiB and 2 CPUswhen the host holds the ceilings. Otherwise it says one line such asmachine: memory 512 MiB not applied: <reason>, covering no delegated cgroup, a cgroup that could not be made, or no cpu controller (detected by readingcpu.maxback). The runner passes the limits in fromsrc/crew/runner.ts. - Tests (
test/crew-machines.test.ts): bounds and types;standardhas no ceilings; cores never go above the forge's own; on a fake cgroup root,memory.maxandcpu.maxare written and the spawn argv enterscgroup.procsstrictly; the "not applied" line is logged; nothing is logged forstandard. - Docs:
docs/features/machines.mdsays how to set the ceilings and what the log lines mean.
The issue named src/crew/sandbox.ts, but that file only builds Claude Code's sandbox settings. The process is started in src/crew/claude.ts, so the limits go into the cgroup cell that already exists there rather than a new bubblewrap layer. Disk ceilings are not done.
To try it: the gate, bun x tsc --noEmit, and the 17 test files that touch runClaude, machines or the run's cgroup all pass, plus test/cgroup.test.ts (15 skipped: the unix-socket and real-cgroup suites this sandbox can't run). scripts/fast.ts named 429 files (about 38 minutes), so the full suite was not run here.
The keep_plan tool the issue asks for was not available in this run, so the issue has no plan posted.
🤖 Generated with Claude Code
Memory and cpus parse with bounds, fall back to standard when invalid, and are written into the run cell's cgroup. A host that cannot hold them logs one line. Tests cover parsing, the cell files, the strict-entry argv and the log lines. The how-to is in docs/features/machines.md, not README. No new dependency, and no web or terminal surface was added. Not run here: the real-cgroup suites, which need a delegated cgroup v2. Two things I would do next: (1) the held line is logged before the run enters the cell, so a strict-entry failure (exit 125) says held and then fails; log the entry outcome, or say in the run failure that the cell could not be entered. (2) For memory, read memory.max back the way cpu.max is, so a missing memory controller logs not applied instead of held. Also add one test that runClaude receives limits from the runner, since only openRunCell is exercised now.
A machine in
forge.jsoncan now setmemory(whole MiB, 256–65536) andcpus(cores, 0.25–64), and the crew run is held to them by the kernel. Example:"machines": { "audit": { "memory": 2048, "cpus": 2 } }.src/crew/machines.ts): a value out of bounds or of the wrong type rejects the declaration. As with any invalid declaration, the crew then runs instandard.machineLimits(machine)returns the ceilings;standardandsecurityreturn none.src/crew/run-cell.ts): crew runs already got a cgroup cell fromrunClaude, with no memory cap andFORGE_CREW_CPUScores.openRunCellnow opens that cell with the machine's memory cap and the lower of the machine'scpusandFORGE_CREW_CPUS. A machine can only narrow the cell, never widen it. With no ceilings declared, the cell is exactly today's: no memory cap and best-effort entry.enterArgv, so it refuses to start (exit 125) rather than run uncapped in a cell made for it. Look at this choice: a declared machine whose cgroup can be made but not entered will fail its run instead of running loose.machine: held to memory 2048 MiB and 2 CPUswhen the host holds the ceilings. Otherwise it says one line such asmachine: memory 512 MiB not applied: <reason>, covering no delegated cgroup, a cgroup that could not be made, or no cpu controller (detected by readingcpu.maxback). The runner passes the limits in fromsrc/crew/runner.ts.test/crew-machines.test.ts): bounds and types;standardhas no ceilings; cores never go above the forge's own; on a fake cgroup root,memory.maxandcpu.maxare written and the spawn argv enterscgroup.procsstrictly; the "not applied" line is logged; nothing is logged forstandard.docs/features/machines.mdsays how to set the ceilings and what the log lines mean.The issue named
src/crew/sandbox.ts, but that file only builds Claude Code's sandbox settings. The process is started insrc/crew/claude.ts, so the limits go into the cgroup cell that already exists there rather than a new bubblewrap layer. Disk ceilings are not done.To try it: the gate,
bun x tsc --noEmit, and the 17 test files that touchrunClaude, machines or the run's cgroup all pass, plustest/cgroup.test.ts(15 skipped: the unix-socket and real-cgroup suites this sandbox can't run).scripts/fast.tsnamed 429 files (about 38 minutes), so the full suite was not run here.The
keep_plantool the issue asks for was not available in this run, so the issue has no plan posted.🤖 Generated with Claude Code
Closes #9469