Skip to content

acp: keep prompt runs executing when the transport drops - #11668

Open
damienrj wants to merge 6 commits into
aaif-goose:mainfrom
damienrj:remote-detached-runs
Open

acp: keep prompt runs executing when the transport drops#11668
damienrj wants to merge 6 commits into
aaif-goose:mainfrom
damienrj:remote-detached-runs

Conversation

@damienrj

@damienrj damienrj commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Problem

The ACP server runs each session/prompt inside a connection-scoped task (cx.spawn queues it into the connection's event loop). When the client's transport closes — network drop, laptop sleep, client quit — the event loop ends, the task is dropped, and ActiveRunDropGuard::drop() cancels the run's CancellationToken. The in-flight turn dies with the connection.

For remote clients (an ACP client over an SSH tunnel, goose roam peers), this means any disconnect kills work in progress, even though the server, the session, and every subprocess it spawned are perfectly healthy.

Change

Detach prompt execution from the transport:

  • server/dispatch.rs — the PromptRequest arm runs on_prompt on tokio::spawn, with the Responder moved into the task. The responder is the completion channel: it replies as before while the client is connected, and the reply send fails harmlessly (debug log) when it isn't. This also removes a latent hazard where a failed respond inside cx.spawn (whose contract shuts the server down on task error) could kill the whole connection loop.
  • server.rsActiveRunDropGuard moves with the detached task, so it fires only if the task itself dies (panic) before the explicit clear_active_run; its registry-leak protection is unchanged. Mid-turn client interactions become fail-soft: message-content, tool-notification, and usage-notification send failures warn and continue instead of aborting the turn. Provider-fatal errors (auth_required, credits exhausted) still abort.
  • Permissionshandle_tool_permission_request resolves a send failure on a dead connection through the existing deny path (Permission::Cancel), so a detached turn never hangs waiting for an answer that cannot arrive. Elicitation already cancelled on send failure. If the connection dies while an answer is pending, the existing resend_pending_tool_permissions on session/load re-asks when a client reconnects.
  • No config gate — detached execution is the new default everywhere. For stdio transports it is behaviorally identical (process exit tears down the runtime and the task with it).

Persistence needed no changes: Agent::reply writes messages incrementally via session_manager.add_message, and the detached task keeps driving the stream after disconnect, so a reconnecting client sees the completed turn via session/load.

Out of scope (follow-up): live re-attach — streaming a still-running turn's updates to a reconnecting client. The shared ActiveRunRegistry is the natural anchor (updatable notification sink per run + an attach step on session/load + a bounded replay buffer).

Tests

  • New integration test test_prompt_keeps_running_after_client_disconnect: raw JSON-RPC over the duplex fixture, drops the transport while the provider call is in flight, then polls session storage until the assistant reply persists. Non-vacuous: fails (timeout) without the dispatch change, passes in ~1.8s with it.
  • New cancel_from_another_connection_cancels_a_detached_run: session/cancel still cancels a detached run via the shared registry.
  • dropping_a_prompt_future_releases_the_shared_run renamed to a_dying_prompt_task_releases_the_shared_run — same registry-leak protection, re-documented for task-death semantics.
  • Cross-connection steer and shared-registry tests unchanged and passing.
  • cargo test -p goose --lib 'acp::' (333) and all seven acp_* integration binaries pass; cargo fmt --check and cargo clippy -p goose --all-targets -- -D warnings clean.

Also verified against a live deployment: a desktop ACP client connected over an SSH local port-forward sent a ~60s tool-using prompt, the tunnel was killed 20 seconds in, the remote turn ran to completion and persisted its final assistant reply, and the reconnected client loaded it via session/load.

Review follow-ups (second commit)

  • Revocation cancels detached runs. goose-roaming gains a per-connection RevocationSignal; enforce_trust (and the trust-file watcher) set it before force-closing a revoked peer's connection, and it is passed to AcpStreamServer::serve_stream. After the stream ends, the full ACP bridge checks it and calls the new GooseAcpAgent::cancel_own_active_runs(), which cancels only runs this connection started (via the shared registry tokens), leaving entries for the detached tasks to clear — ordinary transport loss still detaches. Note: the extra serve_stream parameter is a breaking change for out-of-tree AcpStreamServer implementors.
  • Startup notifications are fail-soft. The pre-stream send_active_run_update, local-inference progress, and cancelled-path clear sends now warn-and-continue, so a disconnect between dispatch and Agent::reply no longer silently discards the prompt. Pinned deterministically by prompt_on_dead_connection_still_runs_and_persists (runs on_prompt on a fully dead connection; fails on the old code), plus an earliest-possible transport-drop variant of the integration test.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36c63a5385

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/acp/server/dispatch.rs
Comment thread crates/goose/src/acp/server.rs
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T05:38:13.288825Z e8098fb New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 691369bd24

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose-cli/src/commands/roam_full_bridge.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7964ac39d9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +78 to +79
if revocation.is_revoked() {
let cancelled = agent.revoke_and_cancel_own_runs().await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep detached runs revocable after disconnect

Fresh evidence beyond the earlier live-connection revocation fix: when an authorized roaming peer disconnects before being revoked, serve returns, this one-time check sees false, and RoamingAcpHandler subsequently removes the connection from the node's live map. A later peers revoke or trust-file refresh therefore has neither a LiveConnection to signal nor the connection's GooseAcpAgent to cancel, so its detached prompt can continue provider calls and tool execution after the peer's authority is withdrawn; detached run ownership must remain associated with the peer until each run finishes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 (sent by damienrj's AI agent) Addressed in 8347e58: detached-run ownership now outlives the connection, keyed by peer. goose-roaming gains a RevocableWork trait + a peer-keyed registry on RoamingNode; the connection's RevocationSignal doubles as the registrar (no further serve_stream signature change). The bridge registers a Weak<GooseAcpAgent> handle before serve(), and the detached prompt tasks hold the agent Arc for their full duration, so the handle stays reachable exactly while runs exist and prunes once they finish. enforce_trust (manual + trust-file watcher) drains and revokes parked work for keys leaving the allowlist — covering revoke-after-normal-disconnect — while re-accepted peers register fresh unfenced agents. Full-path test over real iroh transport: revoking_a_disconnected_peer_stops_its_detached_work, plus registry mechanics in revocation_registry_is_peer_keyed_and_prunes_dead_work.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8347e584b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/acp/server.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: df7d912dc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/acp/server/dispatch.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e8098fbcd4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1634 to +1635
if !callback_pending_request.try_complete() {
return Ok(());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Complete the interaction before disarming its fallback

When a client response and a transport I/O failure occur back-to-back, the SDK can drop this connection-owned callback while it is awaiting handle_confirmation; however, try_complete() has already cancelled the independent transport-termination fallback. Neither path then delivers a confirmation, leaving the detached run blocked indefinitely. Fresh evidence beyond the prior transport-termination comment is this new ordering: the request is marked complete before the awaited resolution finishes (the elicitation callback has the same ordering). Keep the fallback armed until resolution completes, or move the resolution to independently owned work before claiming completion.

Useful? React with 👍 / 👎.

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.

1 participant