Skip to main content
Most of ACP flows client-to-agent, but three families of call run the other way, mid-prompt, back into the client: file access, terminal execution, and permission requests. The agent calls them exactly like any other request; the client answers them with a handler it registered on its builder. The prompt context keeps a convenience layer directly on it (sendMessage, readFile, askPermission, execute, and the rest); the raw, full request/response form of each call below lives one step down, on ctx.client().

File access

acp-autoconfig’s file capabilities default to false. A Spring Boot client built with the starter advertises neither fs.readTextFile nor fs.writeTextFile until you register a handler with an AcpClientCustomizer and turn on the matching property (spring.acp.client.capabilities.read-text-file/write-text-file). See the Boot HTTP page. A handler built with the plain SDK builder, as above, advertises the capability for you from whether a handler is registered.
The agent checks NegotiatedCapabilities.supportsReadTextFile()/supportsWriteTextFile() before calling; calling without the capability throws AcpCapabilityException locally, before anything is sent.

Terminal execution

A four-step lifecycle, because the client (not the agent) controls what actually runs and may want to stream output as it happens. Each step is a call on ctx.client(): client().killTerminal(...) ends a still-running terminal early, outside the normal wait-then-release flow. All five terminal methods check NegotiatedCapabilities.supportsTerminal() before calling, not only createTerminal: calling any of them without the capability throws AcpCapabilityException locally, before anything is sent. Before 0.80.0, only createTerminal was checked, so a terminal id from a connection where the client did advertise terminal could reach waitForTerminalExit, getTerminalOutput, releaseTerminal, or killTerminal on a different connection that never advertised it. ctx.execute(Command) is a convenience that runs all four steps for the common case: spawn, wait, read, release, in one call, returning a CommandResult (output(), exitCode(): Integer, nullable for a process a signal terminated, signal(), truncated() (whether the output was cut to the client’s output limit), and a success() convenience for exitCode() == 0; no more timedOut()):
The canonical constructor is new CommandResult(output, exitCode, signal, truncated); the older 3-argument form (no truncated) still exists as a convenience for complete output. See Module 18: Terminal Operations for the full four-step version and why you’d use it instead of execute(...) (streaming output while the command is still running, for one).
execute releases the terminal when the prompt is cancelled, not only when the command ends. ACP requires an agent to release every terminal it creates; execute now sends terminal/release exactly once on every path, including a cancellation that interrupts it while it’s waiting in waitForTerminalExit. See Cancellation for the triggers that count.

Permission

PermissionOptionKind (allow-once, allow-always, reject-once, reject-always, and others) is an open value, the same discipline as StopReason: compare with .equals(...), and keep a default case for a kind a newer agent might send. See Forward Compatibility for why.
askPermission/askChoice announce a real tool call. Both send a tool_call session update (status pending) before asking, so a client that looks the tool call up by ID finds it, and settle it afterward with a tool_call_update (completed once answered, failed if the client cancelled the request): a session update handler sees two more updates per call than it used to. askPermission(action) alone uses kind other; pass a ToolKind explicitly (as above) for anything else, including ToolKind.EDIT if you relied on the old always-edit behavior. askChoice fails with AcpProtocolException (-32603) naming the option if the client answers with one it was never offered, instead of the old unclear NumberFormatException/ArrayIndexOutOfBoundsException.

Concepts

Capabilities and NegotiatedCapabilities, which every call on this page is gated by

Testing

Exercising these calls without a real client or agent process