Skip to main content

langgraph-acp

Use ACP-compatible agents — Codex, Claude, and anything else that speaks the protocol — as first-class LangGraph nodes.

LangGraph owns orchestration and workflow durability.
ACP owns the agent conversation and agent-side session state.
langgraph-acp owns the binding between the two.

The design and the ticket sequence live in docs/langgraph-acp Architecture and Implementation Plan.md.

Status

Tickets 1 to 4 of that plan: the core types, a connection to a real agent, the store that remembers which conversation belongs to which node, and the first working ACPNode end-to-end path.

from langgraph_acp import ACPNode

result = await ACPNode(agent="codex")("Review this change")
print(result.message)

Available now:

ACPNode a LangGraph-compatible async callable that runs one ACP turn
ACPAgentProvider, ACPAgentRegistry, default_registry names resolve to agents
StdioACPProvider, CodexACPProvider how an agent is launched
ACPClient, ACPCapabilities one live connection, and what it can do
ACPSession one conversation, and the turns run in it
ACPSessionSpec, ACPSessionStrategy which conversation a node speaks in
ACPSessionStore, InMemoryACPSessionStore which ACP session a node's identity resolves to
ACPWorkspace the filesystem context a session is given
ACPConfig, ACPRequirements settings requested, capabilities demanded
ACPEvent, ACPEventType streamed activity, normalized
ACPResult, ACPUsage what a turn returns, and what it cost
ACPError and its subclasses typed failures

Registering another ACP agent is a command line rather than a class:

default_registry().register(
    StdioACPProvider(name="gemini", command=["gemini", "--experimental-acp"])
)

The minimal node takes its invocation input as the prompt and starts a fresh session each time. Runtime prompt resolvers, session reuse, MCP injection, and LangGraph event streaming arrive in the following tickets.

Session bindings

ACPSessionStore maps a node's logical identity to the ACP session it speaks in, so that a workflow can pick a conversation back up later:

store = InMemoryACPSessionStore()

await store.put("pr-918", "reviewer", "sess_abc123")
await store.get("pr-918", "reviewer")     # -> "sess_abc123"
await store.delete("pr-918", "reviewer")

The identity is a pair, not a thread id, because one LangGraph thread routinely runs several agents — an implementer and three reviewers on the same pull request — and none of them may resume another's conversation.

A store holds ACP session identifiers and nothing else. No conversation history, no agent messages, no tool history, no model context, no agent memory: the ACP agent owns all of that and restores it itself when asked to load sess_abc123. That is what makes a reply arriving on a webhook days later resume a live conversation without reconstructing a transcript — and why the store is the piece that has to be durable. InMemoryACPSessionStore is not; it lives as long as its process, which is right for tests, examples, and single-process graphs. The durable implementations arrive with Ticket 17 and change nothing about the interface.

Two behaviours are placeholders that later tickets replace. Permission requests are streamed as acp.permission.requested and then declined, because the policy that could approve them does not exist yet; and the history an agent replays while loading a session is dropped, because no turn is streaming when it arrives.

Development

A standalone distribution, deliberately outside the uv workspace at the root of this repository: it depends on nothing OpenEngine ships, targets a newer Python than the rest of the tree, and is intended to be published on its own.

cd langgraph-acp
uv run pytest
uv run mypy    # strict, over src/ and tests/

The package ships py.typed, so its annotations are a promise to a downstream type checker; mypy --strict runs in CI on both supported interpreters to keep it. Each field is annotated with what the constructor accepts, since __post_init__ normalizes — a Path becomes a str, "reuse" becomes ACPSessionStrategy.REUSE.

The dependency list is empty and stays empty: ACP is JSON-RPC over a pipe, so the client is stdlib asyncio written here rather than a second protocol library in every application that installs this one. The client tests launch a real child process — tests/fake_agent.py, an ACP agent that does nothing, correctly — because the thing under test is a process boundary.

CI runs both only when something under langgraph-acp/ changes.

Release files for langgraph-acp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for langgraph-acp 0.1.0
File Size Uploaded
langgraph_acp-0.1.0.tar.gz 167.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for langgraph-acp 0.1.0
File Interpreter ABI Platform
langgraph_acp-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 217.2 kB

Release files / langgraph_acp-0.1.0.tar.gz

Download URL langgraph_acp-0.1.0.tar.gz
Size 167.4 kB
Tags Source
SHA-256 checksum
How to use checksums
b6586fe8580fc41cb97e2fa9faae1ae22565016ec9c700c8d4ad01128f8fd99a
BLAKE2b-256 checksum
How to use checksums
d6c375b076ea2232af29c67bccdd426e6a031f297f332e50e86295b42e9d6019
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / langgraph_acp-0.1.0-py3-none-any.whl

Download URL langgraph_acp-0.1.0-py3-none-any.whl
Size 49.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7aaaeef13013f1f7269016ef3a1404aae9a3d2d945395300b7ff0896e710ada7
BLAKE2b-256 checksum
How to use checksums
5311689fd626485fedb3e1fbf8385fd70956f261f7c76cb1ec216823d5ab8ee5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page