Editor integration¶
Two ways in, both spawned by the editor over stdio: ACP drives whole runs, and agent6 mcp serve exposes a few tools to another agent (see As an MCP server).
Agent Client Protocol¶
agent6 acp runs agent6 as an Agent Client Protocol agent: an editor spawns it, sends prompts, and renders the run as it happens.
It uses the same engine, config, and jail as agent6 run.
Any ACP client works the same way, and the command above is the whole configuration.
What the editor sees¶
Every run writes one event journal; the CLI, TUI, web UI, and ACP all render it the same way.
An editor sees what agent6 attach shows: reasoning, each tool call and its outcome, auto-commits, and how the run ended.
ACP carries a tool call as two messages.
tool_call(in_progress) when the run dispatches it,tool_call_update(completedorfailed, with the output) when its result lands- built-in calls carry their ACP kind, and an edit result carries each journaled path as an absolute follow-along location
- a call waiting on an approval or an
ask_useranswer is updated topendingwhile its prompt is open, and back toin_progressonce answered - a long verify shows as in progress while it runs; a call the run never returned from settles as
failedwhen the run'ssession.endis written, or when the tail ends without one (a worker killed mid-call) toolCallIdis<run id>:<turn>:<call>, unique for the life of the session: each turn is one leg of the run, and a leg's call numbers start at 1
Worker text and thinking deltas arrive in journal order as they stream; side-role output stays out of the conversation.
Everything the lifecycle prints arrives as an [agent6] agent message as it is printed, whatever state the journal is in.
That is the agent6 run footer: where the changes are, the auto-stash notice and how to restore it, a refusal's reason.
The cost receipt goes to stderr only, where a client that shows the agent's log picks it up.
Approvals¶
session/request_permission carries every approval the CLI would prompt for.
- a command under
run_commands = "ask" - an MCP tool call the server's
approvedoes not cover - a
fetchto a host outside the allow-list - an unsandboxed autorun
The editor renders the buttons.
The request names the tool call it gates and carries the prompt as that call's title, the text the editor renders.
It is sent once the run's journal tail has announced that call; a tail that stopped reading, or a cancelled turn, releases the request.
A prompt that gates no call (a pre-run question) announces a tool call of its own and closes it with the answer.
The prompt and its answer are journaled as approval.prompt / approval.answer (question.* for an ask_user) by the same gate every front-end answers through, so agent6 attach and the web show the run as awaiting the answer.
The answer carries source: "acp", or "headless" when the client declared it cannot be asked.
Three rules:
- An unanswered request denies: after five minutes with no reply the approval is refused and the run continues without it.
- An off-list
fetchhost is offered asallow_onceonly, so an editor's "always allow" cannot cover a different host later. - A standing "allow all" recorded on the run by an earlier front-end (a CLI leg's
a) answers that scope's later prompts without asking the editor; the answer journalssource: "session".
Sessions¶
A session is one conversation in one directory.
session/newcarries an absolutecwd; config is that directory's own layered config (global, repo, preset)- the directory must be a git repository (the jail's writable mount; runs branch and commit each step)
- the first prompt starts an
agent6 run; every later prompt resumes it with the text as its steering instruction (resume --steersemantics) - a prompt whose prior turn left no resume snapshot starts a new run when that turn recorded one (it died before its first checkpoint; the editor is told), and starts the same id when nothing was recorded
- a busy session refuses a prompt rather than queueing it; the editor can offer it again
- one connection runs one prompt at a time across its sessions (the commit cwd is process-global)
- a prompt on another session waits its turn and tells the editor which session it waits for; a
session/cancelwhile it waits answerscancelledat once
- a prompt on another session waits its turn and tells the editor which session it waits for; a
session/canceldrops theagent6 stop --after-stepmarker: the step in flight finishes and commits first
Not implemented¶
session/load: ACP v2 reorganises it, and resume carries agent6's own semantics (agent6 resume,agent6 fork), soinitializereports the capability as absent.- Mid-run steering: ACP has no message for a prompt while a turn is running. A session's follow-up is the next prompt, which resumes the run with that text as its first steering instruction.
fs/*andterminal/*: ACP lets the client own the filesystem and the terminal, and agent6 keeps both behind the jail the operator configured.- Embedded resources in a prompt: text and
resource_linkblocks are read (a link rides in as its uri; the workspace boundary still decides what it reaches)- images and embedded resources are dropped;
promptCapabilities.embeddedContextsays so
- images and embedded resources are dropped;
Troubleshooting¶
- stdout is the protocol stream: nothing but JSON-RPC
- everything agent6 would print goes to stderr (the editor's agent logs)
- a wrapper echoing to stdout before exec'ing
agent6breaks the connection irrecoverably; write to stderr
As an MCP server¶
agent6 mcp serve speaks MCP over stdio, so another agent (an editor's own, or a second agent6 with [mcp.servers]) can use agent6's jail and run state.
It is the inverse of [mcp] in the config, which is agent6 as an MCP client.
The cwd's config decides everything: the sandbox the commands run in, and which tools exist at all.
Five tools, of which a default config publishes two:
| Tool | Withheld when |
|---|---|
query_dag (a run's task graph) |
never |
list_sessions (this repository's sessions) |
never |
run_verify (the configured gate, jailed) |
no [workflow] verify_command, or [sandbox] run_commands is not yes |
run_in_sandbox (an argv, jailed) |
[sandbox] run_commands is not yes |
apply_patch_in_sandbox (a patch, then the gate) |
either of the two above |
run_commands = "ask" withholds the command tools: the MCP boundary has no operator to prompt.
A client that calls a withheld tool by name is told which setting withheld it.
// Zed: settings.json -- agent6's jail as another agent's tool surface
{
"context_servers": {
"agent6": { "command": { "path": "agent6", "args": ["mcp", "serve"] } }
}
}
The tools reach the repository the server was started in, under that repository's sandbox policy; list_sessions and query_dag read its state dir and nothing else.