Aleph
Concepts

ACP Protocol

ACP (Agent Client Protocol) bridge that manages external CLI agents (Claude Code, Codex, Gemini) as Aleph subagents.

The acp module implements the Agent Client Protocol (ACP) bridge — Aleph acts as an ACP client that spawns and drives external ACP-capable CLI tools (Claude Code, Codex, Gemini CLI) as subagents. The bridge runs each external agent as a child process and talks to it over NDJSON-encoded JSON-RPC 2.0 on stdin/stdout.

Design Philosophy

ACP integration follows three principles:

  1. Subprocess isolation — Each ACP harness runs in its own OS process, owned by Aleph. A misbehaving agent cannot affect peers.
  2. Two execution modesNativeAcp for agents that speak full persistent ACP (Gemini CLI --acp); Oneshot for agents that complete in a single CLI invocation (Claude Code --print, Codex exec).
  3. Crash-recoverable sessions — Active sessions are snapshotted to ~/.aleph/data/acp_sessions.json and restored at boot via wire_persistence.

Protocol Overview

ACP is JSON-RPC 2.0 framed as newline-delimited JSON. Aleph sends requests on the child's stdin and reads responses on stdout:

┌─────────────┐           NDJSON / JSON-RPC 2.0           ┌─────────────┐
│   Aleph     │  ──────────────────────────────────────▶  │  External   │
│  ACP client │  initialize → session/new → session/...  │  CLI agent  │
│             │  ◀──────────────────────────────────────  │  (claude,   │
│             │  responses + session/update notifications │   codex,    │
│             │                                          │   gemini)   │
└─────────────┘                                          └─────────────┘

Core methods Aleph sends

MethodPurpose
initializeNegotiate protocol version + capabilities (Aleph advertises fs.readTextFile, fs.writeTextFile, session.requestPermission).
session/newOpen a new ACP session bound to a cwd.
session/promptSend a prompt; streams chunks via AcpChunkCallback.
session/cancelCooperative cancel; written to stdin via CancelHandle so it does not block on the per-session mutex held by an in-flight prompt.
fs/read_text_file, fs/write_text_fileBidirectional — the agent can ask Aleph to read/write files under the project root.
session/request_permissionBidirectional — the agent can ask the user for permission before a sensitive action.

The transport (src/acp/transport.rs) wraps the child's stdin/stdout in a background reader task that demultiplexes responses and notifications onto an mpsc channel, and services inbound fs/* + session/request_permission requests through IncomingHandler (src/acp/incoming.rs).


Execution Modes

pub enum AdapterMode {
    /// Persistent stdio ACP subprocess (e.g. Gemini CLI `--acp`).
    NativeAcp,
    /// One CLI invocation per prompt (e.g. Claude `--print`, Codex `exec`).
    Oneshot,
}
  • NativeAcp — Aleph keeps the child alive across prompts in a slot keyed by (harness_id, cwd) (SessionKey). Lazy-start, auto-respawn on death. Persistent stdio means a long session can stream incremental chunks and surface fs/* requests mid-prompt.
  • Oneshot — Each prompt spawns a fresh process; Aleph reads its full stdout and tears down. Cheaper setup, but no incremental streaming and no mid-prompt inbound requests.

A TieredAuthenticator-style tier system does not apply at the Aleph side; authentication to the external CLI is whatever the harness itself enforces.


Preset Adapters

The acp module ships built-in presets for the most common external CLIs (src/acp/adapters/):

Preset idDisplay nameDefault modeNotes
claude-codeClaude CodeOneshotUses claude --print ....
codexCodexOneshotUses codex exec ....
geminiGemini CLINativeAcpUses gemini --acp.

Additional custom harnesses can be registered through acp.create and persist alongside the presets in the same config store.


Harness Lifecycle RPCs

The gateway exposes a full CRUD + ops surface for harnesses and live sessions (src/gateway/handlers/acp_config.rs):

Harness configuration

MethodDescription
acp.listList all ACP harnesses (presets + custom).
acp.getGet a single harness by id.
acp.createRegister a new custom harness.
acp.updateUpdate an existing harness configuration.
acp.deleteRemove a custom harness (presets cannot be deleted).
acp.testProbe the harness executable (executable --version, 5s timeout) and report success + duration.
acp.set_enabledEnable or disable a harness.
acp.presetsList all built-in harness presets.
acp.presets_metaStatic metadata for the preset catalogue (display name, executable, supported modes).

Live session pool

MethodDescription
acp.sessions.listEnumerate live NativeAcp sessions.
acp.sessions.cancelSend session/cancel to a live session without blocking on the per-session prompt mutex.
acp.sessions.shutdownShut a session down and remove it from the pool.

Mutations publish acp.sessions.changed on the gateway event bus so the panel drops polling.


Process-Isolation Kernel

The Aleph server (the aleph-server binary) is the spawner for ACP subprocesses. When a child harness is launched, the spawn path runs through the same process-isolation kernel the daemon uses for its own children — restricted tokens / job objects / AppContainer / integrity level / SID·ACL syscalls are issued in place at spawn. This is a deliberate carve-out from R1 (Brain / Limb separation): the ACP subprocess is a separate OS process from the moment of Command::spawn, so the kernel-level isolation replaces the harness-level isolation that the rest of the system enforces. See Architectural Redlines R1 Process-Isolation Kernel carve-out.


Integration Points

ACP integrates with:

  • Gatewayextensions.* is not ACP; ACP harness CRUD is acp.* (acp.list, acp.create, acp.update, acp.delete, acp.test, acp.set_enabled, acp.presets, acp.presets_meta, acp.sessions.list, acp.sessions.cancel, acp.sessions.shutdown).
  • Spawner / SubAgent — The acp_member builtin tool wraps a configured harness as a team member; the acp_delegate tool dispatches one-shot tasks to a named harness.
  • Event busacp.sessions.changed is published on every pool mutation.
  • Persistence~/.aleph/data/acp_sessions.json snapshots live sessions and is replayed at boot.

Code Location

  • src/acp/mod.rs — Module entry point, AcpSessionEvent, PersistenceHook, GatewayChangeHook, AcpChunkCallback
  • src/acp/adapter.rsAcpAdapter trait, AdapterMode (NativeAcp / Oneshot)
  • src/acp/adapters/ — Built-in presets: claude_code, codex, gemini, custom, generic
  • src/acp/manager/AcpAdapterManager (harness_admin, lifecycle, persistence, session_key)
  • src/acp/protocol.rs — JSON-RPC request/response/notification types
  • src/acp/transport.rsStdioTransport (NDJSON framing, background reader, CancelHandle)
  • src/acp/session.rsAcpSession, AdapterConfig, PersistedAcpSession
  • src/acp/incoming.rsIncomingHandler (services fs/* + session/request_permission from the agent)
  • src/acp/output_format.rsOutputFormat (text/markdown/json shaping for the model's view)
  • src/gateway/handlers/acp_config.rsacp.* RPC handlers

See Also

  • Agent Runtime — How subagents are spawned and managed
  • Extensions Store — Unified catalog for Skills / Plugins / MCP (distinct from ACP, which is for external CLI harnesses)
  • Multi-Agent System — How ACP harnesses fit as team members

On this page