Skip to content
Open
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
37 changes: 8 additions & 29 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ repository = "https://github.com/danielkov/agentkit"
rust-version = "1.92"
version = "0.10.5"

# Intentionally unpublishable until the upstream SDK releases session injection.
[patch.crates-io]
agent-client-protocol = { git = "https://github.com/danielkov/rust-sdk", rev = "2f039993d1d6ed8da35b38c31f54a7cbb7338c70" }

[workspace.dependencies]
async-trait = "0.1.89"
dotenvy = "0.15.7"
Expand Down
36 changes: 24 additions & 12 deletions book/src/acp.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,28 @@

The [Agent Client Protocol (ACP)](https://agentclientprotocol.com) standardizes communication between clients (code editors, IDEs, desktop apps) and coding agents. Where MCP connects an agent to external tools, ACP connects a client to the agent itself: session lifecycle, prompt turns, streamed updates, tool call reporting, and permission prompts all travel over JSON-RPC — usually with the agent running as an editor child process on stdio. This chapter covers [`agentkit-acp`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-acp): how an agentkit host becomes ACP-addressable, and how a standalone agent serves ACP directly.

## Built on the official SDK
## Built on the ACP Rust SDK

Like `agentkit-mcp`, this crate does not define a parallel protocol vocabulary. It builds on the official Rust SDK, [`agent-client-protocol`](https://crates.io/crates/agent-client-protocol), and re-exports the stable v1 wire types (`SessionId`, `ContentBlock`, `SessionUpdate`, `ToolCallUpdate`, `StopReason`, …) at the crate root and under `agentkit_acp::wire`. The full upstream SDK is available as `agentkit_acp::sdk`. Agentkit owns only the host-facing glue: session binding, observer routing, prompt conversion, cancellation handles, and approval resolution.
Like `agentkit-mcp`, this crate does not define a parallel protocol vocabulary. It currently builds on a commit-pinned [`agent-client-protocol` fork](https://github.com/danielkov/rust-sdk), needed for experimental session injection support, and re-exports the stable v1 wire types (`SessionId`, `ContentBlock`, `SessionUpdate`, `ToolCallUpdate`, `StopReason`, …) at the crate root and under `agentkit_acp::wire`. The full SDK is available as `agentkit_acp::sdk`. Agentkit owns only the host-facing glue: session binding, observer routing, prompt conversion, cancellation handles, and approval resolution.

- **Protocol docs:** [agentclientprotocol.com](https://agentclientprotocol.com/protocol/v1/overview)
- **Rust SDK:** [`agent-client-protocol` on crates.io](https://crates.io/crates/agent-client-protocol)
- **Rust SDK fork:** [`danielkov/rust-sdk`](https://github.com/danielkov/rust-sdk), pinned in `Cargo.toml` and `Cargo.lock`

## Opt-in ACP v2 runtime

ACP v2 support is additive and disabled by default. Enable it explicitly:

```toml
agentkit-acp = { version = "0.10.8", features = ["protocol-v2"] }
agentkit-acp = { version = "0.10.9", features = ["protocol-v2"] }
```

`protocol-v2` enables the official upstream
`protocol-v2` enables the fork's
`agent-client-protocol/unstable_protocol_v2` feature. The root API and
`agentkit_acp::wire` continue to expose stable v1 behavior. Experimental v2
runtime APIs and official v2 wire types are isolated under
runtime APIs and v2 wire types are isolated under
`agentkit_acp::v2` and `agentkit_acp::v2::wire`; v1 wire types are not part of
that namespace.
that namespace. The additive `unstable-inject` feature implies `protocol-v2`
and enables the unstable session-injection methods.

Build a v2 server with `agentkit_acp::v2::AcpHeadlessRuntime`. Its factory is
called once for each `session/new` and receives a v2 session ID, an agentkit
Expand All @@ -47,11 +48,22 @@ progress concurrently, while a second prompt for a running session is rejected.
work and drops the session worker. `session/list` and `session/resume` cover
active in-memory sessions; replay is not supported.

Session injection is steer-only and finishes an in-flight model stream rather
than interrupting it. Acceptance follows response-frame enqueue, including for
JSON-RPC batches; the full `ContentBlock` list is emitted unchanged at the next
safe model/tool boundary. Request or session cancellation cannot remove a
response-committed steer, so it carries to the next valid boundary when the
current turn is cancelled. Close may discard it. Revoke is serialized with
acknowledged user-message forwarding. Queueing and replacement are not
supported. Delivered IDs retain `already_delivered` classification for the
session lifetime; a 4,096-accept lifetime cap bounds that history.

The initial v2 foundation routes text, reasoning, and tool lifecycle updates.
ACP v2 permission callbacks are intentionally deferred; an unsupported approval
interrupt retains the transcript and ends the prompt with the custom `_error`
stop reason rather than `refusal`. Upstream labels the v2 protocol unstable, so
opt-in callers should expect the `v2` namespace to track official SDK changes.
ACP v2 permission callbacks are intentionally deferred; unsupported approval
requests are denied while accepted steers remain pending for the next safe
boundary. The SDK labels v2 unstable, so opt-in callers should expect the `v2`
namespace to track the pinned fork. The workspace patch is intentionally
unpublishable until the required APIs are released upstream.

## Two integration shapes

Expand Down Expand Up @@ -228,4 +240,4 @@ The `agentkit-acp` crate itself has a default `stdio` feature that gates `serve_

> **Example:** [`openrouter-acp-trio`](https://github.com/danielkov/agentkit/tree/main/examples/openrouter-acp-trio) runs three OpenRouter-backed agents (orchestrator, worker, reviewer) that call each other over in-memory ACP endpoints while a REPL drives the orchestrator through a persistent ACP session — session binding, streamed updates, tool call reporting, and agent-to-agent handoffs in one program.
>
> **Crate:** [`agentkit-acp`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-acp) — depends on [`agentkit-core`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-core), [`agentkit-loop`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-loop), [`agentkit-tools-core`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-tools-core), and [`agent-client-protocol`](https://crates.io/crates/agent-client-protocol). Design notes: [`docs/acp.md`](https://github.com/danielkov/agentkit/blob/main/docs/acp.md).
> **Crate:** [`agentkit-acp`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-acp) — depends on [`agentkit-core`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-core), [`agentkit-loop`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-loop), [`agentkit-tools-core`](https://github.com/danielkov/agentkit/tree/main/crates/agentkit-tools-core), and a commit-pinned [`agent-client-protocol` fork](https://github.com/danielkov/rust-sdk). Design notes: [`docs/acp.md`](https://github.com/danielkov/agentkit/blob/main/docs/acp.md).
7 changes: 6 additions & 1 deletion crates/agentkit-acp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ homepage.workspace = true
name = "agentkit-acp"
readme = "README.md"
repository.workspace = true
version = "0.10.8"
version = "0.10.9"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
Expand All @@ -14,6 +14,10 @@ default = ["stdio"]
stdio = []
unstable-acp = ["agent-client-protocol/unstable"]
protocol-v2 = ["agent-client-protocol/unstable_protocol_v2"]
unstable-inject = [
"protocol-v2",
"agent-client-protocol/unstable_session_inject",
]

[dependencies]
agent-client-protocol = "=2.0.0"
Expand All @@ -28,5 +32,6 @@ tokio = { workspace = true, features = ["sync", "time"] }
tracing.workspace = true

[dev-dependencies]
futures-util.workspace = true
agentkit-integration-tests = { path = "../agentkit-integration-tests" }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
35 changes: 26 additions & 9 deletions crates/agentkit-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Agent Client Protocol integration for agentkit hosts.

This crate re-exports upstream ACP wire types from `agent-client-protocol` and
This crate re-exports ACP wire types from a pinned `agent-client-protocol` fork and
adds only agentkit-specific glue: session binding, observer routing, prompt
conversion, cancellation handles, and approval resolver abstractions.

Expand All @@ -21,13 +21,14 @@ The crate root, default features, and `wire` module remain ACP v1. To use the
experimental upstream ACP v2 protocol, enable the additive feature:

```toml
agentkit-acp = { version = "0.10.8", features = ["protocol-v2"] }
agentkit-acp = { version = "0.10.9", features = ["protocol-v2"] }
```

The feature maps directly to the official
`agent-client-protocol/unstable_protocol_v2` feature. V2 APIs and official v2
wire types live only under `agentkit_acp::v2` (and
`agentkit_acp::v2::wire`).
The feature maps directly to the pinned fork's
`agent-client-protocol/unstable_protocol_v2` feature. V2 APIs and v2 wire
types live only under `agentkit_acp::v2` (and
`agentkit_acp::v2::wire`). Enable `unstable-inject` instead to add the unstable
ACP v2 session-injection surface; it implies `protocol-v2`.

`v2::AcpHeadlessRuntime` supports ACP v2 initialize, new/list/resume
session, prompt, cancel, session updates, and close. Listing and resume cover
Expand All @@ -38,11 +39,27 @@ the runtime emits ordered `UserMessage`, `Running`, streamed output, and `Idle`
updates. User, visible-agent, and thought message IDs are distinct and stable
for the lifetime of a prompt.

With `unstable-inject`, the runtime advertises only `steer` delivery with
finish-current-stream behavior. `session/inject` returns an agent-owned message
ID after its response frame is enqueued, preserves every `ContentBlock`, and
delivers at the next safe model/tool boundary. Single and batch requests use the
same receipt-backed acceptance path. Once response commitment succeeds, request
or session cancellation cannot remove the reservation; a committed steer that
misses the cancelled turn carries into the next prompt. Closing the session may
discard it. `session/revoke_inject` is mandatory and is serialized with
acknowledged `UserMessage` forwarding. Queueing, stream interruption, and
replacement are not supported. Delivered IDs retain their `already_delivered`
classification for the session lifetime. To bound that history safely, each
session accepts at most 4,096 injections and rejects later accepts with a
lifetime-limit error.

This first v2 foundation streams text, reasoning, and tool lifecycle updates.
The v1 permission bridge is not exposed through v2 wire types. Unsupported
approval interrupts retain the transcript and therefore end with the custom
`_error` stop reason rather than `Refusal`. Because upstream marks protocol v2
unstable, all APIs in the `v2` namespace can evolve with the official SDK.
approval requests are resolved as denials; already accepted steers remain
pending and are delivered at the next safe boundary. Because the SDK marks
protocol v2 unstable, all APIs in the `v2` namespace can evolve with the pinned
SDK fork. The workspace patch is intentionally unpublishable until these SDK
APIs are available in an upstream release.

Run the stable v1 in-memory end-to-end example with:

Expand Down
Loading