diff --git a/apps/server/src/process/agentScope.test.ts b/apps/server/src/process/agentScope.test.ts index 44e9d6dbf4c2a..94ca314ac1f33 100644 --- a/apps/server/src/process/agentScope.test.ts +++ b/apps/server/src/process/agentScope.test.ts @@ -227,6 +227,24 @@ describe("AgentScope service", () => { ), ); + it.effect("delegates every agent scope so rootless containers can manage their cgroups", () => + withFakeSystemd(({ scope }) => + Effect.gen(function* () { + const agent = yield* scope.wrap({ command: "agent", args: [], name: "t", threadId: "a" }); + const codex = yield* launchCodex(scope, "a"); + const terminal = yield* scope.wrap({ + command: "agent", + args: [], + name: "terminal", + survivesRestart: true, + }); + for (const launch of [agent, codex, terminal]) { + expect(launch.args).toContain("--property=Delegate=yes"); + } + }), + ), + ); + it.effect("stops agents a killed server left before the first launch", () => withFakeSystemd(({ scope, units, calls, stateDir }) => Effect.gen(function* () { diff --git a/apps/server/src/process/agentScope.ts b/apps/server/src/process/agentScope.ts index 1e54691161ae4..cc6d3a04121f6 100644 --- a/apps/server/src/process/agentScope.ts +++ b/apps/server/src/process/agentScope.ts @@ -83,7 +83,8 @@ function parseCgroupCounter(text: string, key: string): number | undefined { } // systemd's default 90 s stop timeout would outlast our leftover-agent wait. -const STOP_TIMEOUT_OPTION = "--property=TimeoutStopSec=10s"; +// Delegation lets rootless containers (Podman, kind) manage their own cgroups. +const SCOPE_OPTIONS = ["--property=TimeoutStopSec=10s", "--property=Delegate=yes"]; const STOPPING_POLL_INTERVAL = "100 millis"; const STOPPING_POLL_ATTEMPTS = 20; const CLEAR_FAILED_INTERVAL = "1 second"; @@ -167,8 +168,8 @@ export const make = Effect.gen(function* () { "true", ]).pipe(Effect.map((result) => result._tag === "Some" && result.value.code === 0)); // Scopes take OOMPolicy from systemd 253; older managers still get scopes. - const supportsOomPolicy = yield* probe([STOP_TIMEOUT_OPTION, "--property=OOMPolicy=stop"]); - if (!supportsOomPolicy && !(yield* probe([STOP_TIMEOUT_OPTION]))) { + const supportsOomPolicy = yield* probe([...SCOPE_OPTIONS, "--property=OOMPolicy=stop"]); + if (!supportsOomPolicy && !(yield* probe(SCOPE_OPTIONS))) { yield* Effect.logInfo("Agent scopes are off: no systemd user manager"); return undefined; } @@ -359,7 +360,7 @@ export const make = Effect.gen(function* () { // Without an owning thread nothing reads the result, so let systemd // unload the unit even when it fails. options: [ - STOP_TIMEOUT_OPTION, + ...SCOPE_OPTIONS, ...(scope.supportsOomPolicy ? [`--property=OOMPolicy=${continuing ? "continue" : "stop"}`] : []),