Motivation
Braid currently uses @anthropic-ai/claude-agent-sdk directly for all AI agent communication. The Agent Client Protocol (ACP) is emerging as an industry standard for editor-to-agent communication, with 17+ agents adopting it (Gemini CLI, Codex CLI, Cursor, Copilot, Cline, Goose, etc.) and IDE support from Zed, JetBrains, Neovim, and others.
Adding ACP as a second backend enables Braid to work with any ACP-compatible agent, starting with Gemini CLI which already has full ACP support. Claude continues to use the SDK directly (more feature-rich), while ACP becomes the gateway for everything else. Both coexist per-session - one session can run Claude SDK, another can run Gemini via ACP.
Architecture
The abstraction boundary is at the worker process layer. Both backends emit the same WorkerEvent types, so the coordinator and renderer need minimal changes.
Renderer -> IPC -> AgentCoordinator -> UtilityProcess -> agentProcess.ts -> AgentWorker (Claude SDK)
\-> UtilityProcess -> acpProcess.ts -> AcpWorker (ACP agents)
|
spawns agent subprocess (stdio)
ClientSideConnection from @agentclientprotocol/sdk
Key design decisions:
- Claude SDK stays primary - no regression, full feature set (slash commands, plan mode, thinking, resume, elicitation)
- ACP worker synthesizes
WorkerEvents matching the format the renderer already handles - zero changes to eventHandler.ts or message rendering
- Per-session backend - each session independently chooses Claude SDK or an ACP agent
- Ephemeral ops (commit message gen, session title gen) always use Claude SDK regardless of session backend
Implementation Plan
Phase 1: Types and Configuration
New types in src/main/services/agentTypes.ts:
export type AgentBackend =
| { type: 'claude-sdk' }
| { type: 'acp'; agentId: string }
export interface AcpAgentConfig {
id: string // e.g. 'gemini-cli'
name: string // e.g. 'Gemini CLI'
command: string // e.g. 'gemini'
args: string[] // e.g. ['--experimental-acp']
env?: Record<string, string>
}
New file src/main/services/acpConfig.ts (~60 lines):
loadAcpAgents() / saveAcpAgents() / getAcpAgent() - CRUD for agent configs
- Storage via existing
storage.ts patterns
Renderer types in src/renderer/types/session.ts:
- Add
SessionBackend type, backend?: SessionBackend field on AgentSession
Phase 2: ACP Worker
New directory src/main/services/acpWorker/:
| File |
~Lines |
Purpose |
core.ts |
250 |
AcpWorker class - spawns agent subprocess, manages ClientSideConnection, session lifecycle |
eventMapper.ts |
150 |
Maps ACP session/update notifications to Braid's WorkerEvent format |
clientHandlers.ts |
120 |
Implements ACP Client callbacks (file ops, terminal, permissions) |
index.ts |
5 |
Barrel export |
Event mapping (ACP -> WorkerEvent):
| ACP Update |
WorkerEvent |
agent_message_chunk (text) |
sdk_message with synthetic assistant message |
agent_thought_chunk |
sdk_message with synthetic thinking block |
tool_call start |
sdk_message with tool_use content block |
tool_call update/complete |
sdk_message with tool result |
plan |
sdk_message with synthetic system message |
cost_update |
sdk_message with token usage |
Client callbacks:
readTextFile / writeTextFile - direct fs operations from worker process
requestPermission - emit waiting_input with reason: 'tool_permission', wait for answerToolInput
createTerminal / terminalOutput / killTerminal - delegate to coordinator via braid_action data_request (same pattern as braidMcp.ts)
New file src/main/services/acpProcess.ts (~50 lines):
- Parallel to
agentProcess.ts - UtilityProcess entry point for ACP sessions
Dependency: yarn add @agentclientprotocol/sdk
Phase 3: Coordinator Changes
File src/main/services/agent.ts - minimal changes:
- Add
ACP_ENTRY_PATH alongside existing ENTRY_PATH
spawnSessionProcess() picks entry point based on backend.type
startSession() accepts optional backend?: AgentBackend parameter
handleWorkerEvent() needs zero changes - both backends emit the same types
Phase 4: IPC Threading
Thread backend through the 3-layer IPC:
src/main/ipc.ts - add backend param, add agent:getAcpAgents / agent:saveAcpAgents handlers
src/preload/index.ts - expose new channels
src/renderer/lib/ipc.ts - typed wrappers
Phase 5: Renderer Integration
communicationActions.ts - pass backend to startSession, skip Claude-only features for ACP
ModelSelector.tsx - extend dropdown with "ACP Agents" section alongside Claude models
- Conditional feature hiding for ACP sessions: hide slash commands, thinking toggle, plan mode toggle
- New
SettingsAgents.tsx (~200 lines) - add/edit/remove ACP agents, test connectivity
Phase 6: Session Persistence
- Add
backend field to PersistedSession in sessionStorage.ts
- ACP sessions reconnect via
session/load if the agent supports it
Feature Degradation Matrix
| Feature |
Claude SDK |
ACP |
Behavior |
| Slash commands |
Full |
None |
Hidden for ACP sessions |
| Plan mode |
Full |
Agent-dependent |
Hidden |
| Thinking toggle |
Full |
Agent-dependent |
Hidden |
| Resume |
SDK resume |
ACP session/load |
Maps to ACP equivalent |
| Token display |
Anthropic format |
cost_update |
Mapped in eventMapper |
| Commit/title gen |
Ephemeral |
N/A |
Always uses Claude SDK |
| Images |
SDK blocks |
ACP ImageContent |
Direct mapping |
| Braid MCP tools |
In-process |
Via ACP fs/terminal |
Handled by clientHandlers |
| Elicitation (OAuth) |
Full |
Not in ACP |
N/A for ACP sessions |
Files Summary
New files (7)
| File |
Lines |
Purpose |
src/main/services/acpConfig.ts |
~60 |
Agent registry/storage |
src/main/services/acpWorker/core.ts |
~250 |
AcpWorker class |
src/main/services/acpWorker/eventMapper.ts |
~150 |
ACP -> WorkerEvent translation |
src/main/services/acpWorker/clientHandlers.ts |
~120 |
File, terminal, permission callbacks |
src/main/services/acpWorker/index.ts |
~5 |
Barrel export |
src/main/services/acpProcess.ts |
~50 |
UtilityProcess entry point |
src/renderer/components/Settings/SettingsAgents.tsx |
~200 |
ACP agent management UI |
Modified files (14)
| File |
Change |
src/main/services/agentTypes.ts |
Add AgentBackend, AcpAgentConfig types |
src/main/services/agentProcessTypes.ts |
Add backend to startSession WorkerCommand |
src/main/services/agent.ts |
Dual entry path, backend param on startSession |
src/main/ipc.ts |
Thread backend, add ACP config IPC handlers |
src/preload/index.ts |
Expose new IPC channels |
src/renderer/lib/ipc.ts |
Typed wrappers for new IPC |
src/renderer/types/session.ts |
Add SessionBackend, backend field on AgentSession |
src/renderer/store/sessions/handlers/communicationActions.ts |
Pass backend, skip Claude-only features |
src/renderer/components/Center/ModelSelector.tsx |
Add ACP agent section |
src/renderer/components/Center/ChatInput.tsx |
Hide slash commands for ACP |
src/renderer/components/Settings/SettingsOverlay.tsx |
Register agents page |
src/renderer/components/Settings/SettingsNav.tsx |
Add nav entry |
src/renderer/locales/{en,ja,id}/settings.json |
Agent settings translations |
src/main/services/sessionStorage.ts |
Persist backend field |
Verification
References
Motivation
Braid currently uses
@anthropic-ai/claude-agent-sdkdirectly for all AI agent communication. The Agent Client Protocol (ACP) is emerging as an industry standard for editor-to-agent communication, with 17+ agents adopting it (Gemini CLI, Codex CLI, Cursor, Copilot, Cline, Goose, etc.) and IDE support from Zed, JetBrains, Neovim, and others.Adding ACP as a second backend enables Braid to work with any ACP-compatible agent, starting with Gemini CLI which already has full ACP support. Claude continues to use the SDK directly (more feature-rich), while ACP becomes the gateway for everything else. Both coexist per-session - one session can run Claude SDK, another can run Gemini via ACP.
Architecture
The abstraction boundary is at the worker process layer. Both backends emit the same
WorkerEventtypes, so the coordinator and renderer need minimal changes.Key design decisions:
WorkerEvents matching the format the renderer already handles - zero changes toeventHandler.tsor message renderingImplementation Plan
Phase 1: Types and Configuration
New types in
src/main/services/agentTypes.ts:New file
src/main/services/acpConfig.ts(~60 lines):loadAcpAgents()/saveAcpAgents()/getAcpAgent()- CRUD for agent configsstorage.tspatternsRenderer types in
src/renderer/types/session.ts:SessionBackendtype,backend?: SessionBackendfield onAgentSessionPhase 2: ACP Worker
New directory
src/main/services/acpWorker/:core.tsAcpWorkerclass - spawns agent subprocess, managesClientSideConnection, session lifecycleeventMapper.tssession/updatenotifications to Braid'sWorkerEventformatclientHandlers.tsindex.tsEvent mapping (ACP -> WorkerEvent):
agent_message_chunk(text)sdk_messagewith synthetic assistant messageagent_thought_chunksdk_messagewith synthetic thinking blocktool_callstartsdk_messagewith tool_use content blocktool_callupdate/completesdk_messagewith tool resultplansdk_messagewith synthetic system messagecost_updatesdk_messagewith token usageClient callbacks:
readTextFile/writeTextFile- direct fs operations from worker processrequestPermission- emitwaiting_inputwithreason: 'tool_permission', wait foranswerToolInputcreateTerminal/terminalOutput/killTerminal- delegate to coordinator viabraid_actiondata_request (same pattern asbraidMcp.ts)New file
src/main/services/acpProcess.ts(~50 lines):agentProcess.ts- UtilityProcess entry point for ACP sessionsDependency:
yarn add @agentclientprotocol/sdkPhase 3: Coordinator Changes
File
src/main/services/agent.ts- minimal changes:ACP_ENTRY_PATHalongside existingENTRY_PATHspawnSessionProcess()picks entry point based onbackend.typestartSession()accepts optionalbackend?: AgentBackendparameterhandleWorkerEvent()needs zero changes - both backends emit the same typesPhase 4: IPC Threading
Thread
backendthrough the 3-layer IPC:src/main/ipc.ts- addbackendparam, addagent:getAcpAgents/agent:saveAcpAgentshandlerssrc/preload/index.ts- expose new channelssrc/renderer/lib/ipc.ts- typed wrappersPhase 5: Renderer Integration
communicationActions.ts- passbackendtostartSession, skip Claude-only features for ACPModelSelector.tsx- extend dropdown with "ACP Agents" section alongside Claude modelsSettingsAgents.tsx(~200 lines) - add/edit/remove ACP agents, test connectivityPhase 6: Session Persistence
backendfield toPersistedSessioninsessionStorage.tssession/loadif the agent supports itFeature Degradation Matrix
session/loadcost_updateImageContentFiles Summary
New files (7)
src/main/services/acpConfig.tssrc/main/services/acpWorker/core.tssrc/main/services/acpWorker/eventMapper.tssrc/main/services/acpWorker/clientHandlers.tssrc/main/services/acpWorker/index.tssrc/main/services/acpProcess.tssrc/renderer/components/Settings/SettingsAgents.tsxModified files (14)
src/main/services/agentTypes.tsAgentBackend,AcpAgentConfigtypessrc/main/services/agentProcessTypes.tsbackendtostartSessionWorkerCommandsrc/main/services/agent.tsbackendparam onstartSessionsrc/main/ipc.tsbackend, add ACP config IPC handlerssrc/preload/index.tssrc/renderer/lib/ipc.tssrc/renderer/types/session.tsSessionBackend,backendfield onAgentSessionsrc/renderer/store/sessions/handlers/communicationActions.tssrc/renderer/components/Center/ModelSelector.tsxsrc/renderer/components/Center/ChatInput.tsxsrc/renderer/components/Settings/SettingsOverlay.tsxsrc/renderer/components/Settings/SettingsNav.tsxsrc/renderer/locales/{en,ja,id}/settings.jsonsrc/main/services/sessionStorage.tsVerification
yarn typecheckpassesReferences