Zed’s external-agent feature speaks ACP natively — meaning Agentao drops into Zed with no custom glue. The same pattern applies to any IDE / editor that supports external ACP agents. This section shows the end-to-end wiring.
An IDE that implements the ACP client side can:
session/promptsession/update notifications as a scrolling chat UIsession/request_permission as a modalcwd + prompt textThe user just adds your agent in the IDE’s settings — everything else is transport.
Zed reads its agent config from ~/.config/zed/settings.json (or the UI’s Settings → Agents). Add an entry that points to agentao --acp --stdio:
{
"agents": [
{
"name": "agentao",
"command": "agentao",
"args": ["--acp", "--stdio"],
"env": {
"OPENAI_API_KEY": "sk-...",
"OPENAI_MODEL": "gpt-4o-mini"
}
}
]
}
Restart Zed, open any project, and Agentao appears in the agent picker.
command must be on PATH. If you installed via uv tool install agentao, it’s already there. Otherwise use the absolute path: /Users/you/.local/bin/agentaoenv block is merged with Zed’s own environment. Prefer pulling API keys from ~/.zshenv / ~/.bashrc and leaving them out of settings.jsoncwd in session/new — so AGENTAO.md, .agentao/mcp.json, skills, memory are all workspace-scoped automaticallyWhat happens when the user types a message in Zed’s agent pane:
USER types "find TODOs" in Zed time
────▶
Zed → agentao: initialize (once per launch)
agentao → Zed: capabilities + extensions
Zed → agentao: session/new {cwd: "/workspace"}
agentao → Zed: {sessionId: "sess-1"}
Zed → agentao: session/prompt {sessionId, prompt:[{"type":"text",
"text":"find TODOs"}]}
agentao → Zed: session/update agent_message_chunk "Let me search..."
agentao → Zed: session/update tool_call {title:"grep -r TODO"}
agentao → Zed: session/update tool_call_update {output:"src/x.py:42: TODO ..."}
agentao → Zed: session/update agent_message_chunk "Found 3 TODOs in..."
agentao → Zed: response {stopReason: "end_turn"}
(ongoing) session/update notifications stream to Zed's UI
Zed renders each session/update as you’d expect: text chunks stream into the reply, tool calls show as inline cards, permission requests pop as modals.
Agentao advertises the following in initialize:
{
"agentCapabilities": {
"loadSession": true,
"promptCapabilities": { "image": false, "audio": false, "embeddedContext": false },
"mcpCapabilities": { "http": false, "sse": true }
}
}
Meaning for Zed:
loadSession: can restore prior conversations from diskimage / audio: text-only prompts in v1embeddedContext: Zed can’t push embedded resource fetchessse: Zed can forward SSE MCP servershttp: Agentao doesn’t support HTTP MCP transportZed falls back gracefully on unsupported capabilities.
Zed spawns one agentao subprocess per agent instance, and creates separate sessions via session/new for each project. That means:
agentao process.agentao/memory.db is isolated by cwdmcpServers are scoped to that sessionIf you prefer one process per workspace (tighter isolation, higher RAM), configure Zed to spawn a separate agent per workspace — consult Zed’s own docs for the latest toggle name.
The pattern repeats:
agentao --acp --stdio as a child processsession/update → UI, session/request_permission → modalA VS Code extension using the TypeScript ACPClient from 3.3.4 is outlined in Blueprint B.
JetBrains IDEs can spawn subprocesses from plugins; the ACP client can be implemented in Kotlin or Java using the same three-loop pattern. No JetBrains-specific complications.
Use vim.fn.jobstart() (Lua) or similar to spawn agentao --acp --stdio. Forward session/update to a floating window. Community plugins for LSP transport make this straightforward.
Across every IDE:
security on macOS, Credential Manager on Windows, secret-tool on Linux) + a small wrapper script that reads the key and execs agentaoOPENAI_API_KEY in their shell profile, and let the IDE inherit the environmentExample wrapper (macOS):
#!/usr/bin/env bash
# /usr/local/bin/agentao-wrapper
export OPENAI_API_KEY="$(security find-generic-password -ws openai-api-key)"
exec agentao "$@"
Point the IDE at agentao-wrapper instead of agentao.
When things go wrong in the IDE:
| Symptom | Where to look |
|---|---|
| Agent doesn’t appear in picker | IDE’s own log (e.g. Zed’s Help → Open Log) |
| Agent crashes on first message | <workspace>/agentao.log |
| Tool call hangs forever | IDE is probably not responding to session/request_permission — look at its permission UI code |
| All text comes as one big blob at end | IDE isn’t treating session/update as streamed; check its UI rendering |
| MCP server doesn’t show up | mcpCapabilities.http is false — use stdio or SSE only |
| Conversation disappears after restart | IDE isn’t calling session/load — feature may not be implemented yet |
Capture the wire trace by launching Agentao manually and piping JSON in:
# Send hand-crafted JSON via stdin
agentao --acp --stdio < trace.ndjson > output.ndjson 2> agentao.stderr.log
This bisects whether the issue is in your JSON, in Agentao’s handling, or in the IDE’s rendering.
When a user updates the agentao binary:
protocolVersion negotiation handles version mismatch: if the IDE sends version 2 and Agentao supports 1, Agentao returns 1 and the IDE either continues or disconnectsBreaking protocol changes are avoided. If you need to pin a specific version, ask users to install with uv tool install agentao==0.2.11.
protocolVersion: 1 as an integersession/update rendered as streaming UI (chunks appear immediately)session/request_permission responses within the timeout, never silently dropsession/cancel wired to a “Stop” buttonEnd of Part 3. Next: Part 4 · Event layer & UI integration.