This guide covers building IDE integrations for tinycode using the Agent Client Protocol (ACP).
The Agent Client Protocol is a standardized JSON-RPC 2.0 protocol for communication between IDEs and AI coding agents. Originally developed by Zed Industries, ACP defines a bidirectional stdio-based protocol for:
- Session management (create, list, load, fork, close)
- Prompt turns with streamed
session/updatenotifications - Tool execution with
session/request_permissionrequests - User questions via
elicitation/create(form mode) when the client advertises elicitation - Multi-turn conversations with context preservation
ACP enables tinycode to integrate with any editor that supports spawning a child process and communicating over stdin/stdout.
Run tinycode in ACP mode:
tinycode acp --cwd /path/to/projectThe process will:
- Boot an embedded tinycode server (ephemeral localhost port) with providers, tools, permissions, agents, plugins, and MCP
- Listen on stdin for JSON-RPC requests using official ACP wire methods (
session/new,session/prompt, …) - Stream LLM responses and tool events as
session/updatenotifications on stdout - Ask the IDE for tool permissions via
session/request_permissionrequests - Ask the user questions via
elicitation/createwhen the client supports form elicitation
Flags:
| Flag | Description |
|---|---|
--cwd <dir> |
Working directory for new sessions (also accepts -cwd) |
--model / -m |
Default model (provider/model); required if discovery finds none |
--title |
Default title for sessions created via session/new |
--safe-mode |
Skip plugins, MCP, and user agents |
| Prompt knobs | --append-system-prompt, --max-tokens, etc. |
--continue / --resume are TUI/run flags; ACP clients use session/load or session/resume.
┌─────────────────┐
│ IDE Extension │
│ (VS Code, │
│ Zed, etc.) │
└────────┬────────┘
│ stdio (NDJSON)
│ JSON-RPC 2.0
│
┌────────▼──────────────────────────────┐
│ tinycode acp │
│ ┌────────────┐ ┌─────────────────┐ │
│ │ ACP Service│──│ SessionManager │ │
│ │ + EventRelay│ │ (in-process) │ │
│ └────────────┘ └────────┬────────┘ │
│ │ │
│ embedded HTTP server │
│ (port 0 / ephemeral) │
└───────────────────────────────────────┘
Go tinycode acp boots the same core stack as TUI/serve (providers, tools, permissions, SessionManager, plugins, MCP) and speaks ACP over stdio. Session operations prefer in-process SessionManager calls; the ephemeral HTTP listener exists so the full server lifecycle (hooks, discovery, etc.) runs even when the IDE never opens an HTTP client.
Note: The legacy TypeScript
packages/tinycodeACP path started HTTP + SDK separately. The Go binary is the supported ACP entrypoint.
npm install @agentclientprotocol/sdkimport { ClientSideConnection, ndJsonStream } from '@agentclientprotocol/sdk'
import { spawn } from 'child_process'
const proc = spawn('tinycode', ['acp', '--cwd', workspaceRoot], {
stdio: ['pipe', 'pipe', 'pipe']
})
const stream = ndJsonStream(proc.stdout, proc.stdin)
const conn = new ClientSideConnection((agent) => ({
async sessionUpdate(params) {
// Handle streamed chunks / tool calls
const update = params.update
if (update.sessionUpdate === 'agent_message_chunk') {
console.log(update.content?.text)
}
},
async requestPermission(params) {
// Present options to the user; return selected optionId
return {
outcome: { outcome: 'selected', optionId: 'allow_once' }
}
}
}), stream)
const initResult = await conn.initialize({
protocolVersion: 1,
clientCapabilities: {
// Advertise form elicitation so the question tool can prompt the user.
elicitation: { form: {} },
},
clientInfo: { name: 'my-editor', version: '1.0.0' }
})
console.log('Connected to:', initResult.agentInfo)
const session = await conn.newSession({ cwd: workspaceRoot })
console.log('Session ID:', session.sessionId)
const result = await conn.prompt({
sessionId: session.sessionId,
prompt: [{ type: 'text', text: 'Explain this codebase' }]
})
console.log('stopReason:', result.stopReason) // "end_turn" | "cancelled" | "refusal"Official ACP notifications use method session/update:
{
"jsonrpc": "2.0",
"method": "session/update",
"params": {
"sessionId": "ses_…",
"update": {
"sessionUpdate": "agent_message_chunk",
"content": { "type": "text", "text": "Hello…" }
}
}
}Common sessionUpdate values:
| Value | Meaning |
|---|---|
agent_message_chunk |
Assistant text (including streamed deltas) |
agent_thought_chunk |
Reasoning/thought text (including streamed deltas) |
user_message_chunk |
Replayed user text (loadSession) |
tool_call |
Tool invocation started (pending) |
tool_call_update |
Tool status: in_progress after args stream; completed/failed after execution with rawOutput when available. File-modifying tools (edit/write/apply_patch) include locations and diff/summary content. |
When a tool needs approval, the agent sends a request (not a notification):
- Method:
session/request_permission - Includes
toolCallIdwhen the ask is tied to a tool call (IDE sheets can correlate to the tool card) - Client must reply with
{ outcome: { outcome: "selected", optionId: "allow_once" | "allow_always" | "reject_once" } } - On timeout (120s) or disconnect, tinycode auto-denies
session/cancelcancels in-flight permission and elicitation RPCs (clears orphaned IDE modals)
When the agent uses the question tool:
- Method:
elicitation/createwithmode: "form"and a flatrequestedSchema(answerstring, optionally enum options) - Requires
clientCapabilities.elicitation.formduringinitialize - Client replies with
{ action: "accept", content: { answer: "…" } }ordecline/cancel - Without form elicitation support, the question tool fails immediately (no hang)
| Wire method | CamelCase alias | Status |
|---|---|---|
initialize |
— | Supported |
authenticate |
— | Supported (no-op) |
session/new |
newSession |
Supported |
session/load |
loadSession |
Supported (replays text + tool history) |
session/list |
listSessions |
Supported |
session/resume |
resumeSession |
Supported (no history replay) |
session/close |
closeSession |
Supported |
session/fork |
forkSession |
Supported (copies messages) |
session/prompt |
prompt |
Supported — waits until idle, returns stopReason |
session/cancel |
cancel |
Supported — aborts active run |
session/set_mode |
setSessionMode |
Supported — maps to agent name |
session/set_model |
setSessionModel |
Supported — provider/model |
session/set_config_option |
setSessionConfigOption |
Supported for model; unknown IDs error |
During initialize, tinycode advertises:
loadSession: truesessionCapabilities:close,fork,list,resumepromptCapabilities.image: false(not yet supported)promptCapabilities.embeddedContext: false(not yet supported)
session/prompt blocks until the SessionManager reports idle (or cancel), then returns:
{ "stopReason": "end_turn" }or { "stopReason": "cancelled" } if the client sent session/cancel, or { "stopReason": "refusal" } if the turn failed with a session.error (the error text is also streamed as an agent_message_chunk).
Prompt params accept either official prompt: ContentBlock[] or legacy content: ContentBlock[].
const session = await conn.newSession({
cwd: '/path/to/project'
})New sessions use agent build and the default model from config/discovery (same heuristics as TUI). session/new returns modes (primary agents) and a model configOptions select. Creation fails closed with an actionable error if no model is available after a short discovery wait — run tinycode doctor or pass --model provider/model.
const { sessions } = await conn.listSessions({ cwd: workspaceRoot })
await conn.loadSession({ sessionId, cwd: workspaceRoot }) // replays history via session/update
const forked = await conn.unstable_forkSession({ sessionId })session/resume (resumeSession) re-attaches the agent to an existing session id and returns setup metadata (modes / model config). It does not replay conversation history to the client as session/update notifications.
Use session/load (loadSession) when the IDE needs prior messages, tool calls, and tool results streamed back for UI restoration. After resume alone, the client only sees new turns.
All ACP methods return promises that reject on error. Handle JSON-RPC error codes (-32602 invalid params, -32603 internal, -32601 method not found).
| Editor | Status |
|---|---|
| VS Code | Spawn tinycode acp --cwd … from the editor. There is no VS Code extension in this repository. |
| Zed | Use official ACP agent spawn pointing at tinycode acp --cwd … |
| JetBrains | Not packaged yet; same stdio protocol applies |
Manual checklist when verifying ACP clients. Not automated.
| Client | Spawn | Permission UI | Form elicitation | Notes |
|---|---|---|---|---|
| Zed | Official ACP agent → tinycode acp --cwd <proj> |
session/request_permission shows Allow once / Always / Reject; toolCallId correlates with tool card |
Advertise clientCapabilities.elicitation.form; answer elicitation/create |
Confirm cancel dismisses modal without 120s hang |
| VS Code | Spawn tinycode acp --cwd … from the editor (no in-repo extension) |
Same three options; Always scope is tool-side | Same capability + handler | Confirm session/cancel clears pending permission modal |
| JetBrains | Same stdio protocol; not packaged here yet | Same | Same | Track client variance; reject without form elicitation must not hang the turn |
Smoke steps (each client):
- Tool that needs permission → modal appears with non-empty
toolCallIdwhen the call has an id - Allow once → tool continues; Always → matching asks auto-approve this turn
session/cancelwhile permission modal open → modal closes promptly (no AskTimeout orphan)questiontool with form elicitation → prompt appears; decline/cancel rejects without hanging- Client without form elicitation → question tool fails immediately
session/loadreplays history;session/resumedoes not
- Verify
which tinycode(Go binary frommake build→dist/tinycode) - Ensure
--cwdpoints to a valid directory - Check stderr for bootstrap/discovery errors
- Confirm a model is available (
tinycode models) or pass--model provider/model - Read process stderr — missing providers surface there
session/promptshould eventually return astopReason; if it hangs, check permission UI
- Client must implement
requestPermissionand always respond (deny on cancel) - Auto-deny kicks in after 120s
- Advertise
clientCapabilities.elicitation.formand implementelicitation/create - Without that capability, the question tool rejects immediately so the turn can continue
ACP is implemented in Go under internal/acp/ (service.go, transport.go, adapter.go, event.go) — not a stub. Entry point: tinycode acp.
The full ACP specification is available at agentclientprotocol.com.
- Explore @agentclientprotocol/sdk )