Skip to content

fix(coding-agent): keep subagents alive across compaction - #313

Open
li-xiu-qi wants to merge 1 commit into
stepfun-ai:mainfrom
li-xiu-qi:fix/307-compact-aborts-subagents
Open

li-xiu-qi wants to merge 1 commit into
stepfun-ai:mainfrom
li-xiu-qi:fix/307-compact-aborts-subagents

Conversation

@li-xiu-qi

@li-xiu-qi li-xiu-qi commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #307

Summary

Manual /compact used to abort the active run, so a foreground subagent child was killed before compaction finished. This PR makes compaction safe for subagents and lets manual compaction hand a running foreground subagent to a background lane instead of waiting for it. Automatic compaction already used the non-aborting path and is unchanged.

Problem

AgentSession.compact() called abort() on the active run. A foreground subagent call shares the agent-run abort signal, so its child process was torn down when the user ran /compact. The reported order was: subagent aborted, parent error, compaction completed.

The earlier commit in this PR removed that abort, but manual compaction then waited for the active run to settle. A foreground subagent therefore blocked /compact until it finished, even though compaction only needs to rewrite the parent session context.

Automatic threshold/overflow compaction did not abort the run. This PR keeps that behavior and makes the survival contract explicit for both paths: neither manual nor automatic compaction may kill subagents.

Design

The session core does not know what foreground work a host is running. It only knows that a run is active. So manual compaction does not call into subagent code directly. It broadcasts a detach offer before it waits, and the owner of the foreground work decides whether to claim it.

  1. AgentSession.compact() emits session_detach_foreground before waitForIdle().
  2. This is the "ask extensions" step. Any extension that owns foreground work may claim it and move it to a background lane. A host with no such work ignores the event.
  3. Step's subagent extension claims its foreground subagent calls. Each call runs inside a detachable runner that races the real child run against the detach request.
  4. On detach, adoptForegroundRun() hands the in-flight run to the existing background-lane lifecycle. The lane owns the original promise and AbortController; the child process keeps running.
  5. The foreground tool call returns immediately with the lane id. When the child finishes, the lane sends the normal final <agent-notification>.
  6. If no extension claims the work, or the host cannot own lanes, compaction falls back to waiting for the run.

This indirection is deliberate: AgentSession must not know about subagents, lanes, or child processes. The event lets the owner of the foreground work decide how to hand it off, and lets other extensions reuse the same hook for their own foreground work.

Automatic compaction keeps its existing path. _runAutoCompaction() does not abort the agent run and does not stop background lanes, so subagents survive automatic compaction as well. The detach event is manual-only because automatic compaction is dispatched at assistant-message/prompt boundaries, not while the session is blocked on a foreground tool call.

Changes

  • Add the session_detach_foreground extension event and emit it from AgentSession.compact() before waitForIdle().
  • Add detachable foreground subagent execution in step-subagent.ts.
  • Add adoptForegroundRun() to the lane lifecycle and extract shared attachRunPromise() final handling for started and adopted lanes.
  • Keep the foreground subagent child process alive across detach: no respawn, no abort.
  • Harden adoption: a detach request that arrives after the foreground call settled does not adopt a dead run, and later turns started from the adopted lane use the configured background timeout.
  • Respect the same host/config gates as run_in_background, plus the running-lane limit; fall back to waiting when detach is unavailable.
  • Document the new event in docs/extensions.md.
  • Add regression coverage for manual compaction: non-abort and detach-to-lane.
  • Make the lane notification settle loop yield to the real event loop, so the idle keep-alive regression is stable on loaded runners.
  • Choose the detached foreground child's session id before the run starts, so an early detach still reuses the same child transcript for later replies; foreground telemetry stays labeled blocking.

Behavior and compatibility

  • Manual /compact with a foreground subagent and available lanes: the subagent is handed to a background lane, compaction proceeds, and the subagent reports back when it finishes.
  • Manual /compact without available lanes: compaction waits for the foreground subagent, but does not abort it.
  • Automatic threshold/overflow compaction: unchanged; it does not abort the run and does not stop subagents or lanes.
  • User cancellation, session shutdown, and timeouts still stop foreground subagents and lanes.
  • No persisted data format, default settings, prompts, or CLI inputs change. One extension event is added; hosts that do not handle it ignore it.

Validation

Local platform: Windows 11 with Node.js 24.21.0.

  • pnpm exec vitest --run test/suite/regressions/307-compact-aborts-subagent.test.ts: 2 passed.
  • pnpm exec vitest --run test/compaction-extensions.test.ts test/extensions-runner.test.ts test/suite/agent-session-compaction.test.ts test/suite/agent-session-auto-clm.test.ts: passed.
  • pnpm exec vitest --run test/subagent-lane-notifications.test.ts test/subagent-background-tui.test.ts: passed.
  • pnpm exec vitest --run test/step-subagent-events.test.ts -t "a detached foreground subagent keeps its child session id for later replies": passed; reverting the session-id fix makes it fail.
  • pnpm exec tsgo --noEmit: passed.
  • pnpm exec biome check --error-on-warnings on changed files: passed.
  • pnpm run check:public-boundary and pnpm run check:coding-agent-entry-freeze: passed.
  • GitHub CI: build-check-test and Check commit attribution are green for head 3d872ed.

Known unrelated local failures on Windows: test/step-subagent-events.test.ts has one output-path formatting failure, and test/subagent-invocation.test.ts hits a local spawnSync timeout/EPERM; neither exercises the compaction or lane-adoption paths changed here.

Limitations

  • Detach is manual-compaction only. Automatic compaction does not need it at its current dispatch points and already preserves subagents.
  • If a new foreground subagent starts after the detach event but before the run settles, compaction waits for that new work; the event is not re-emitted.
  • Hosts that cannot own lanes, sessions at the running-lane limit, and print/json runs fall back to waiting for the foreground subagent.
  • No interactive native TUI smoke run was performed; coverage is harness-level regression tests plus local static checks.

@li-xiu-qi
li-xiu-qi marked this pull request as ready for review October 27, 2026 08:17
@li-xiu-qi
li-xiu-qi marked this pull request as draft October 27, 2026 09:10
@li-xiu-qi
li-xiu-qi force-pushed the fix/307-compact-aborts-subagents branch from 7f3bd67 to 6e3227e Compare October 27, 2026 10:20
@li-xiu-qi
li-xiu-qi marked this pull request as ready for review October 27, 2026 10:28
@li-xiu-qi
li-xiu-qi marked this pull request as draft October 27, 2026 11:50
@li-xiu-qi
li-xiu-qi marked this pull request as ready for review October 27, 2026 12:16
@li-xiu-qi li-xiu-qi changed the title fix(coding-agent): keep foreground subagents alive across manual compaction fix(coding-agent): keep subagents alive across compaction Oct 27, 2026
@li-xiu-qi
li-xiu-qi marked this pull request as draft October 27, 2026 14:02
@li-xiu-qi
li-xiu-qi marked this pull request as ready for review October 27, 2026 14:06
@li-xiu-qi
li-xiu-qi marked this pull request as draft October 27, 2026 14:24
@li-xiu-qi
li-xiu-qi marked this pull request as ready for review October 27, 2026 14:33
@li-xiu-qi
li-xiu-qi marked this pull request as draft October 27, 2026 15:35
@li-xiu-qi
li-xiu-qi force-pushed the fix/307-compact-aborts-subagents branch from cb7c8f7 to 3d872ed Compare October 27, 2026 15:35
@li-xiu-qi
li-xiu-qi marked this pull request as ready for review October 27, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/compact on the main session aborts running subagents

1 participant