Skip to content

feat(acp): harden resident session lifecycle - #1494

Merged
snimu merged 48 commits into
mainfrom
v080/acp-minimal-verifiers
Aug 20, 2026
Merged

feat(acp): harden resident session lifecycle#1494
snimu merged 48 commits into
mainfrom
v080/acp-minimal-verifiers

Conversation

@sethkarten

@sethkarten sethkarten commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • keep session-backed ACP workers resident while leaving ACP --no-session, RPC, and print invocations client-owned
  • serialize ACP updates with monotonic event sequences and causal prompt-turn IDs, emit explicit response boundaries, and emit terminal quiescence only when the authoritative child roster has no outstanding work
  • publish the initial nested-child roster, preserve child origins across turns, and drain admitted updates across cancellation and close races
  • persist identity-only v2 worker descriptors and a typed host-only supervisor configuration; never persist launch environments or arbitrary runtime/model/provider configuration
  • migrate v1 descriptors by lifting only sessionDir and monotonic telemetryDisabled, and require a fresh client launch environment before replacing a failed resident

Related Linear: ENG-4600, ENG-4603, ENG-4685.

Recovery and security contract

Live residents remain reattachable from their worker identity and socket. If a worker is dead or unreachable after supervisor recovery, it remains failed until a new client supplies fresh transient launch context. A verified-current failed resident is stopped only when its stored process-start identity still matches; replaced PIDs are never signaled, and unknown identities fail closed.

Durable worker state contains routing/session identity, local worker authentication, lifecycle markers, and the two typed host-policy fields above. It excludes launchEnv, apiKey, provider/model selection, headers, extension values, runtime metadata, and the rest of createCommand.config.

Validation

  • npm run check
  • 20 focused Vitest files: 626 tests passed after merging current main
  • git diff --check

This standalone PR supersedes the broader #1236 / #1239 stack once its own baseline checks and review complete. It intentionally does not include relay IDs/replay machinery, intercept retries, daemon discovery or shutdown CLI changes, or release cleanup behavior.


Note

High Risk
Changes span ACP completion semantics, concurrent session input admission, multi-layer daemon fencing, and persisted worker recovery—any ordering bug could mis-report turn completion or admit input during teardown.

Overview
ACP session-backed workers stay resident for normal ACP mode; only --no-session (and non-ACP clients) use client-owned workers via isClientOwnedDaemonSession.

ACP updates are now producer-ordered and causally tagged. AcpUpdateProducer stamps every notification with promptTurnId, monotonic eventSequence, phase (event / responseBoundary / terminalQuiescence), and outcome. Prompt handling emits a response boundary with terminalQuiescenceExpected, then a terminal quiescence update only after headless completion with waitForRlmQuiescence and an authoritative child roster with no outstanding subagents. session/new admission is gated until the JSON-RPC response is written so buffered events cannot race ahead of the reply.

Session input is fail-closed during stop/close/cancel. acquireSessionInputPause leases fence new turns through the session, daemon, supervisor, and DaemonAgentConnection; disconnect invalidates pauses and closes the connection.

RLM child lifecycle is settlement-aware. Child runs track settlement, quiescence abandonment, and recursive waitForRlmQuiescence; headless completion can opt into the strong barrier. Daemon adds get_rlm_children, schema revision 19 capabilities, and always includes children on attach snapshots.

Recovery persistence is narrowed. Worker descriptors migrate to v2 with durableDaemonWorkerDescriptor and durableAgentSessionRuntimeConfig—only host routing/policy fields (e.g. sessionDir, telemetryDisabled), not models, credentials, or launchEnv. Failed owned workers wait for fresh recoveryConfig and launch env on reattach before relaunch; stale failed residents can be reclaimed when process identity is verified.

Reviewed by Cursor Bugbot for commit 095ba1e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Harden resident session lifecycle with input fencing, RLM quiescence, and descriptor v2 migration

  • Adds per-session input pause leases scoped to client connections, with epoch-based fencing and automatic cleanup on disconnect/detach across daemon-mode.ts, daemon-supervisor.ts, and daemon-agent-connection.ts
  • Introduces strong RLM quiescence waiting in agent-session.ts: waitForRlmQuiescence blocks until all descendant child runs settle, deletion cleanup completes (or records retry), and deferred terminal notices flush
  • Reworks RLM child deletion into a detached flow with shared cleanup, retry-on-failure reporting, and durable terminal/failure notice admission via private parent channels
  • Migrates persisted worker descriptors to version 2 in daemon-worker-protocol.ts, lifting sessionDir/telemetryDisabled to top level, stripping secrets from createCommand/config/lastError, and redacting on both load and persist
  • Adds an AcpUpdateProducer in acp-mode.ts that serializes session updates with producer-stamped promptTurnId, eventSequence, and phase/outcome classification; session/new response acts as the admission barrier before any session-scoped update publishes
  • Bumps DAEMON_SCHEMA_REVISION to 19 in daemon-protocol.ts with new capabilities: authoritative_child_roster, owned_session_recovery_context, rlm_quiescence_barrier, session_input_pause
  • Behavioral Change: failed client-owned workers now park in lifecycle: 'failed' awaiting a fresh recoveryConfig on attach instead of auto-relaunching; launchEnv is always collected (not just for client-owned); isClientOwnedDaemonSession returns false for ACP sessions without noSession; snapshot responses always include a children array (possibly empty) instead of eliding the field; getRlmChildSnapshots returns authoritative rosters gated by authoritative_child_roster capability; waitForHeadlessCompletion defaults to waitForHeadlessIdle (not full waitForIdle)

Macroscope summarized f18de4b.

Loading
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.

2 participants