Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions acp/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions acp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace]

[package]
name = "iii-acp"
name = "acp"
version = "0.1.0"
edition = "2024"
description = "Agent Client Protocol worker for iii-engine"
Expand All @@ -15,11 +15,11 @@ categories = ["command-line-utilities"]
publish = false

[lib]
name = "iii_acp"
name = "acp"
path = "src/lib.rs"

[[bin]]
name = "iii-acp"
name = "acp"
path = "src/main.rs"

[dependencies]
Expand Down
46 changes: 23 additions & 23 deletions acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

**iii as a first-class agent in any ACP-speaking editor.**

`iii-acp` is a stdio JSON-RPC adapter that exposes the iii engine — and every
`acp` is a stdio JSON-RPC adapter that exposes the iii engine — and every
brain worker on it — through the [Agent Client Protocol](https://agentclientprotocol.com).
Editors and clients that already speak ACP launch `iii-acp` as a subprocess
Editors and clients that already speak ACP launch `acp` as a subprocess
and drive it through their native agent UI. No editor plugin, no fork, no
bespoke per-client integration.

Expand All @@ -27,14 +27,14 @@ others don't.
| **MCP server** (`iii-mcp`) | Exposes iii functions as **tools** to an external agent | You're already running Claude Code / Cursor / etc. and want to give it iii tools |
| **Skill bundles** | Curated prompts + tools loaded into an agent host | You're inside a skill-aware host (Claude Code, Cursor) and want a preset toolset |
| **Agent workers** (`turn-orchestrator`, `agent`, `coding`, …) | The brain itself — registers `run::start_and_wait`, runs LLM turns, executes tools | You're calling iii from your own code (`iii.trigger("run::start_and_wait", …)`) or backend automation |
| **`iii-acp` (this worker)** | Editor → iii. Translates ACP `session/*` JSON-RPC into the canonical iii brain contract; turns iii's `agent::events` stream into ACP `session/update` notifications | You want iii to **be** the agent in an editor users already opened today |
| **`acp` (this worker)** | Editor → iii. Translates ACP `session/*` JSON-RPC into the canonical iii brain contract; turns iii's `agent::events` stream into ACP `session/update` notifications | You want iii to **be** the agent in an editor users already opened today |

ACP is the **north** edge of the stack. MCP is the **south** edge. They
coexist:

```
Editor (Zed, VS Code, Neovim, …)
↓ ACP ─ session/prompt, session/update ← iii-acp
↓ ACP ─ session/prompt, session/update ← acp
iii engine + brain workers (turn-orchestrator,
provider-router, guardrails, llm-budget,
audit-log, dlp-scrubber, policy-denylist, …)
Expand All @@ -54,13 +54,13 @@ What iii brings to an editor session that a vanilla agent host doesn't:

## Supported clients

ACP is an open spec. Any client that speaks it works with `iii-acp`. As of
ACP is an open spec. Any client that speaks it works with `acp`. As of
this writing the public client list ([agentclientprotocol.com/get-started/clients](https://agentclientprotocol.com/get-started/clients))
includes:

**Editors / IDEs**

| Client | How to wire iii-acp |
| Client | How to wire acp |
|---|---|
| [Zed](https://zed.dev) | `agent_servers` block in `~/.config/zed/settings.json` (snippet below) |
| Visual Studio Code | ACP Client extension |
Expand All @@ -80,15 +80,15 @@ includes:
(via `sidequery/duckdb-acp`), `marimo`, `Agmente` (iOS), `Ferngeist` (Android),
`Happy`, `Mobvibe` (mobile), `OpenACP` (Telegram/Discord/Slack), and others.

Setup pattern is the same everywhere: point the client at the `iii-acp`
Setup pattern is the same everywhere: point the client at the `acp`
binary, set the `IIIACP_*` env vars below.

## Prerequisites

`iii-acp` needs an iii engine plus a brain. Minimum stack:
`acp` needs an iii engine plus a brain. Minimum stack:

```bash
# 1. Engine builtins iii-acp uses directly. iii-state holds session
# 1. Engine builtins acp uses directly. iii-state holds session
# records + history; iii-stream carries the agent::events tape;
# iii-queue backs durable cancel topics.
iii worker add iii-state iii-stream iii-queue
Expand Down Expand Up @@ -132,12 +132,12 @@ the stack is healthy.

## Spawn

`iii-acp` is a stdio agent. The client launches it as a subprocess and
`acp` is a stdio agent. The client launches it as a subprocess and
exchanges JSON-RPC frames over stdin/stdout. **stderr is reserved for
logs; stdout is reserved for ACP frames.**

```bash
iii-acp --use-canonical-brain --model claude-sonnet-4-5-20250929 --provider anthropic
acp --use-canonical-brain --model claude-sonnet-4-5-20250929 --provider anthropic
```

## Configuration
Expand All @@ -162,9 +162,9 @@ iii-acp --use-canonical-brain --model claude-sonnet-4-5-20250929 --provider anth
```jsonc
{
"agent_servers": {
"iii-acp": {
"acp": {
"type": "custom",
"command": "/path/to/iii-acp",
"command": "/path/to/acp",
"args": [],
"env": {
"IIIACP_ENGINE_URL": "ws://localhost:49134",
Expand All @@ -178,11 +178,11 @@ iii-acp --use-canonical-brain --model claude-sonnet-4-5-20250929 --provider anth
}
```

Restart Zed → Agent panel → `+` → pick **iii-acp** → type a prompt.
Restart Zed → Agent panel → `+` → pick **acp** → type a prompt.

### VS Code

Install the ACP Client extension. Add `iii-acp` as a custom agent in the
Install the ACP Client extension. Add `acp` as a custom agent in the
extension's settings, pointing `command` at the binary and replicating the
`env` block above.

Expand All @@ -195,12 +195,12 @@ the same env vars work.
### JetBrains / Emacs / Obsidian / Unity / Chrome

Same pattern: client config takes a command path and env map. Point at
`iii-acp` with the env vars above. The protocol is the same on all sides.
`acp` with the env vars above. The protocol is the same on all sides.

### CLIs (`acpx`, `Nori CLI`, …)

Most CLI ACP clients accept `--agent <command>` or a config file. Point them
at `iii-acp` directly.
at `acp` directly.

## Methods

Expand Down Expand Up @@ -232,11 +232,11 @@ include those slots in `SessionCapabilities` yet — clients that try them
get a successful response; clients that don't try are unaffected. Both
methods persist their values on the session record (`mode: Option<String>`
and `config_options: Map<String, Value>`). Brains opt in to honoring them
on the next `session/prompt` turn; iii-acp just stores.
on the next `session/prompt` turn; acp just stores.

## Brain contract

iii-acp talks to the canonical iii brain shape used by `turn-orchestrator`
acp talks to the canonical iii brain shape used by `turn-orchestrator`
and every provider worker. Any function with this input contract drops in
as a brain — no adapter required.

Expand Down Expand Up @@ -269,12 +269,12 @@ as a brain — no adapter required.
}
```

iii-acp picks the ACP `stopReason` from the final assistant message's
acp picks the ACP `stopReason` from the final assistant message's
`stop_reason` field (`end` → `end_turn`, `length` → `max_tokens`,
`aborted` → `cancelled`, `error` → `refusal`).

**Streaming.** The brain emits `AgentEvent` frames into `agent::events`
(group_id = session_id). iii-acp registers one stream subscriber per
(group_id = session_id). acp registers one stream subscriber per
connection at startup and translates each event:

| `AgentEvent` | ACP `sessionUpdate` |
Expand All @@ -287,7 +287,7 @@ connection at startup and translates each event:
| other | dropped |

This is the same stream `context-compaction` and every provider worker
already subscribe to. **No bespoke iii-acp publish protocol.**
already subscribe to. **No bespoke acp publish protocol.**

## State layout

Expand All @@ -307,7 +307,7 @@ Streaming wire: `agent::events` (per-session events), per-connection topic

```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{},"clientInfo":{"name":"demo","version":"0"}}}' \
| iii-acp --use-canonical-brain --model claude-sonnet-4-5-20250929 --provider anthropic
| acp --use-canonical-brain --model claude-sonnet-4-5-20250929 --provider anthropic
```

Replies stream on stdout, one JSON frame per line.
Expand Down
2 changes: 1 addition & 1 deletion acp/iii.worker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ name: acp
language: rust
deploy: binary
manifest: Cargo.toml
bin: iii-acp
bin: acp
description: Agent Client Protocol surface — stdio JSON-RPC, exposes iii agents as ACP sessions
12 changes: 6 additions & 6 deletions acp/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::types::{

// Canonical iii brain function. Any worker exposing this id with the
// turn-orchestrator wire shape (session_id, messages, model, ...) can
// drive iii-acp without an adapter.
// drive acp without an adapter.
pub const DEFAULT_BRAIN_FN: &str = "run::start_and_wait";
const BRAIN_TIMEOUT_MS: u64 = 600_000;

Expand Down Expand Up @@ -72,7 +72,7 @@ pub struct AcpHandler {
brain_system_prompt: Option<String>,
// Session ids owned by this connection. The agent::events stream
// subscriber filters by this set so we don't forward events for
// sessions another iii-acp subprocess owns. Also written by
// sessions another acp subprocess owns. Also written by
// session/new and session/close so close cleans up.
owned_sessions: Arc<DashSet<String>>,
// Trigger + function guards. Dropping them tears the registration
Expand Down Expand Up @@ -215,7 +215,7 @@ impl AcpHandler {
}
},
"agentInfo": {
"name": "iii-acp",
"name": "acp",
"title": "iii Agent",
"version": env!("CARGO_PKG_VERSION")
}
Expand Down Expand Up @@ -322,7 +322,7 @@ impl AcpHandler {
if self.brain_fn.is_some() && !self.event_subscriber_healthy {
return Err((
INTERNAL_ERROR,
"iii-acp: agent::events stream subscriber failed to register at startup; \
"acp: agent::events stream subscriber failed to register at startup; \
external brain updates would not reach the editor. Check engine logs and \
ensure `iii-stream` worker is active before retrying."
.to_string(),
Expand Down Expand Up @@ -663,9 +663,9 @@ async fn write_notification(outbound: &Outbound, seq: &AtomicU64, method: &str,
// hold them; dropping either tears the registration down.
//
// The same stream is used by `turn-orchestrator`, every provider worker,
// `context-compaction`, etc. iii-acp filters frames by group_id (the
// `context-compaction`, etc. acp filters frames by group_id (the
// session_id) against the per-process owned_sessions set so multiple
// iii-acp subprocesses don't fight over the same events.
// acp subprocesses don't fight over the same events.
fn register_event_subscriber(
iii: &III,
conn_id: &str,
Expand Down
14 changes: 7 additions & 7 deletions acp/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::sync::Arc;

use clap::Parser;
use iii_acp::handler::{AcpHandler, BrainConfig, DEFAULT_BRAIN_FN};
use iii_acp::transport;
use acp::handler::{AcpHandler, BrainConfig, DEFAULT_BRAIN_FN};
use acp::transport;
use iii_sdk::{InitOptions, register_worker};
use tracing_subscriber::{EnvFilter, fmt, prelude::*};

#[derive(Parser, Debug)]
#[command(name = "iii-acp")]
#[command(name = "acp")]
#[command(version)]
#[command(about = "Agent Client Protocol worker for iii-engine")]
struct Args {
Expand Down Expand Up @@ -37,7 +37,7 @@ struct Args {
#[arg(
long,
env = "IIIACP_USE_CANONICAL_BRAIN",
help = "Shortcut for --brain-fn run::start_and_wait. Wires iii-acp \
help = "Shortcut for --brain-fn run::start_and_wait. Wires acp \
straight to turn-orchestrator. Ignored if --brain-fn is \
already set."
)]
Expand Down Expand Up @@ -84,9 +84,9 @@ async fn main() -> anyhow::Result<()> {
// Honor RUST_LOG when set (gives operators per-module control); fall
// back to the --debug switch otherwise.
let fallback = if args.debug {
"iii_acp=debug,iii_sdk=debug"
"acp=debug,iii_sdk=debug"
} else {
"iii_acp=info,iii_sdk=warn"
"acp=info,iii_sdk=warn"
};
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(fallback));

Expand All @@ -95,7 +95,7 @@ async fn main() -> anyhow::Result<()> {
.with(filter)
.init();

tracing::info!(version = env!("CARGO_PKG_VERSION"), "starting iii-acp");
tracing::info!(version = env!("CARGO_PKG_VERSION"), "starting acp");

let mut init_opts = InitOptions::default();
if let Some(tag) = args.rbac_tag.as_ref() {
Expand Down
2 changes: 1 addition & 1 deletion acp/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub fn session_history_key(session_id: &str) -> String {
// Streaming wire = the iii ecosystem's `agent::events` stream. No
// per-connection topic exists. Brains (turn-orchestrator and any
// drop-in replacement) emit AgentEvent frames into that stream with
// group_id = session_id; iii-acp subscribes once and routes by group.
// group_id = session_id; acp subscribes once and routes by group.
pub const AGENT_EVENTS_STREAM: &str = "agent::events";

pub fn cancel_topic(conn_id: &str, session_id: &str) -> String {
Expand Down
Loading
Loading