diff --git a/src/agents/subagent-registry-cleanup.test.ts b/src/agents/subagent-registry-cleanup.test.ts index ed97add7162..474421bc08e 100644 --- a/src/agents/subagent-registry-cleanup.test.ts +++ b/src/agents/subagent-registry-cleanup.test.ts @@ -78,4 +78,19 @@ describe("resolveDeferredCleanupDecision", () => { expect(decision).toEqual({ kind: "retry", retryCount: 2, resumeDelayMs: 2_000 }); }); + + it("uses retry backoff for non-completion flows so cleanup can settle after announce failures", () => { + const decision = resolveDeferredCleanupDecision({ + entry: makeEntry({ expectsCompletionMessage: false, announceRetryCount: 1 }), + now, + activeDescendantRuns: 0, + announceExpiryMs: 5 * 60_000, + announceCompletionHardExpiryMs: 30 * 60_000, + maxAnnounceRetryCount: 3, + deferDescendantDelayMs: 1_000, + resolveAnnounceRetryDelayMs: (retryCount) => retryCount * 1_000, + }); + + expect(decision).toEqual({ kind: "retry", retryCount: 2, resumeDelayMs: 2_000 }); + }); }); diff --git a/src/agents/subagent-registry-cleanup.ts b/src/agents/subagent-registry-cleanup.ts index 716e6e2a72a..9bcc6c37b3b 100644 --- a/src/agents/subagent-registry-cleanup.ts +++ b/src/agents/subagent-registry-cleanup.ts @@ -66,9 +66,6 @@ export function resolveDeferredCleanupDecision(params: { return { kind: "retry", retryCount, - resumeDelayMs: - params.entry.expectsCompletionMessage === true - ? params.resolveAnnounceRetryDelayMs(retryCount) - : undefined, + resumeDelayMs: params.resolveAnnounceRetryDelayMs(retryCount), }; } diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index 709cd3b5977..f9b77692dc6 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -173,6 +173,7 @@ export function createSessionsSpawnTool( const mode = params.mode === "run" || params.mode === "session" ? params.mode : undefined; const cleanup = params.cleanup === "keep" || params.cleanup === "delete" ? params.cleanup : "keep"; + const expectsCompletionMessage = params.expectsCompletionMessage !== false; const sandbox = params.sandbox === "require" ? "require" : "inherit"; const streamTo = params.streamTo === "parent" ? "parent" : undefined; const lightContext = params.lightContext === true; @@ -289,7 +290,7 @@ export function createSessionsSpawnTool( cleanup: trackedCleanup, label: label || undefined, runTimeoutSeconds, - expectsCompletionMessage: true, + expectsCompletionMessage, spawnMode: trackedSpawnMode, }); } catch (err) { @@ -320,7 +321,7 @@ export function createSessionsSpawnTool( cleanup, sandbox, lightContext, - expectsCompletionMessage: true, + expectsCompletionMessage, attachments, attachMountPath: params.attachAs && typeof params.attachAs === "object" diff --git a/src/scripts/test-projects.test.ts b/src/scripts/test-projects.test.ts index ecfbfb29abe..a5b53c0b082 100644 --- a/src/scripts/test-projects.test.ts +++ b/src/scripts/test-projects.test.ts @@ -64,7 +64,15 @@ const { targetArgs: string[]; watchMode: boolean; }; - resolveParallelFullSuiteConcurrency: (specCount: number, env?: NodeJS.ProcessEnv) => number; + resolveParallelFullSuiteConcurrency: ( + specCount: number, + env?: NodeJS.ProcessEnv, + hostInfo?: { + cpuCount?: number; + loadAverage1m?: number; + totalMemoryBytes?: number; + }, + ) => number; }; const VITEST_NODE_PREFIX = [ @@ -486,9 +494,17 @@ describe("test-projects args", () => { it("uses a bounded local default for full-suite project parallelism", () => { expect( - resolveParallelFullSuiteConcurrency(58, { - OPENCLAW_TEST_PROJECTS_LEAF_SHARDS: "1", - }), + resolveParallelFullSuiteConcurrency( + 58, + { + OPENCLAW_TEST_PROJECTS_LEAF_SHARDS: "1", + }, + { + cpuCount: 8, + loadAverage1m: 0, + totalMemoryBytes: 16 * 1024 ** 3, + }, + ), ).toBe(4); });