Skip to content

Send a subagent's permission requests to the ACP client - #1524

Closed
jakeleventhal wants to merge 1 commit into
vercel-labs:mainfrom
jakeleventhal:acp-subagent-permissions
Closed

jakeleventhal wants to merge 1 commit into
vercel-labs:mainfrom
jakeleventhal:acp-subagent-permissions

Conversation

@jakeleventhal

Copy link
Copy Markdown

Fixes #1465.

Summary

Under fx acp, a subagent that needs approval now sends its request to the client, and the client's answer goes back to that subagent. Before this change, the child moved to awaiting_approval and waited until the turn was cancelled: its request went to the subagent approval registry, which only the terminal app reads.

  • While a child's turn runs, the ACP host sends each of its pending approvals as session/request_permission on the parent's session. Child sessions are internal and the client never sees them, so the parent's sessionId is the one the client can answer on.
  • The request names the child in _meta.fx.subagent, with the child's sessionId, its workId, and the child's own toolCallId. A request without _meta.fx.subagent comes from the parent. toolCall.toolCallId is <child sessionId>/<child toolCallId>, so it can't match one of the parent's calls.
  • The request uses the same fields and options as the parent's own requests: the title, kind, and rawInput describe the child's tool call. The registry now keeps that call with its pending approval.
  • Several children can wait on the client at once, each with its own request. Each answer goes only to the child that asked.
  • Cancelling the turn denies an open request, as it does for the parent's own requests. When a child's turn ends while its request is open, for example because the child was stopped, fx withdraws the request with $/cancel_request, and a late answer has no effect.

Behavior that doesn't change

  • Permission policy. Every action still goes through permissions.zig and the child's admission first. Only requests that already reached the approval registry are sent, and they are the same ones the terminal app shows.
  • auto mode. A security review's hold still returns guidance to the agent without a prompt, because a hold never reaches the registry.
  • Resume and reload. Reopening a session already marks a child that was running or awaiting_approval as interrupted, so nothing is left to send. When the child runs again, it asks again and the client gets a new request.
  • Questions. Children run with interactive = false in every host, so ask_user_question never reaches any client from a child. This change covers permission requests only.
  • The WebAssembly host, which starts no threads, doesn't forward child approvals.

Relation to #1132

#1132 adds the experimental --subagents-v2 tool, which runs each child as a separate fx process, and notes that "in fx ask and ACP, a child's prompts don't reach the user yet." This change targets the default subagent tool on main, whose children run inside the parent's process. It doesn't touch the v2 code path and doesn't conflict with it. #1088 deliberately leaves ACP and subagent approvals out of scope.

Verification

  • zig fmt --check src/ and zig build pass, and zig build fx-core-wasm -Dwasm-surface=core builds.
  • zig build test on macOS arm64, with a clean HOME: 9857 pass, 4 skipped, 1 failed. The failure is the MCP HTTP test "a kept connection the server reset or closed while idle...", which is intermittent locally. It passed on a later run and doesn't touch this code.
  • New unit tests cover the request params in src/acp/permission_requests.zig and the per-child lookup in the approval registry.
  • Three new tests in tests/e2e/acp.test.ts:
    • A child's request arrives on the parent's session with _meta.fx.subagent, and allow_once lets the child write its file.
    • Two children wait on the client at once. Rejecting one and allowing the other writes only the allowed file.
    • session/cancel with a child's request open sends $/cancel_request for it, the prompt ends cancelled, a late allow_once changes nothing, and the server answers the next prompt.
  • On a build of main without this change, all three tests hang until the 30-second timeout.
  • All 5 subagent tests in acp.test.ts pass. The full file run locally had 14 MCP and session-load failures. Running those tests against a build of main without this change failed 6 of them, and running them again on this branch failed 5, with a different subset failing each time. CI's Linux run is the gate for those.
  • Ran ./zig-out/bin/fx acp with a temporary HOME, FX_DISABLE_KEYCHAIN=1, permission_mode: "ask", and a fake gateway, using a client like the one in ACP: a sub-agent that needs approval waits forever; the request never reaches the client #1465. The parent started two subagents: one ran touch made-by-child.txt and one read package.json. The shell child's request arrived as session/request_permission on the parent session with _meta.fx.subagent. The client answered allow_once, the file was created, the prompt ended with end_turn, fx exited 0, and stderr was empty.

Docs

🤖 Generated with Claude Code

Under `fx acp`, a subagent that needed approval waited until the turn was
cancelled: its request went to the subagent approval registry, which only
the terminal app reads. While a child's turn runs, the ACP host now sends
each of its pending approvals as `session/request_permission` on the
parent's session and gives the client's answer back to that child.

The request names the child in `_meta.fx.subagent` with its session id,
work id, and own tool call id. Its `toolCall.toolCallId` adds the child's
session id, so it can't match one of the parent's calls. Several children
can wait on the client at once. Cancelling the turn denies an open request,
and a request is withdrawn with `$/cancel_request` when the child's turn
ends first. Permission policy is unchanged: only requests that already
reached the registry, the same ones the terminal app shows, are sent.

Fixes vercel-labs#1465.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@jakeleventhal

Copy link
Copy Markdown
Author

@fazxes says we should wait until subagent v2

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.

ACP: a sub-agent that needs approval waits forever; the request never reaches the client

1 participant