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.2.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.2.0
File Size Uploaded
langgraph_acp-0.2.0.tar.gz 167.4 kB Details

Built distribution (wheel)

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

Total release size: 217.2 kB

Release files / langgraph_acp-0.2.0.tar.gz

Download URL langgraph_acp-0.2.0.tar.gz
Size 167.4 kB
Tags Source
SHA-256 checksum
How to use checksums
3c9e01b505e0ab3b79345a378e61d1a12658ed056b9a062f4f353a279337ae56
BLAKE2b-256 checksum
How to use checksums
5c9654cd521d2856d2510f0235a1c6c8d58fb46b68b47ecc62e8d5b542ec54dc
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.2.0-py3-none-any.whl

Download URL langgraph_acp-0.2.0-py3-none-any.whl
Size 49.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
23070064b490460109aa7c4b35a2de1e10bfcfbae8e1c9489ca95589df8d4c87
BLAKE2b-256 checksum
How to use checksums
a7bf8dfc25912bdfe53569188d0c67051fbe4cc2be1f4c2196901e5c1eb06a68
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.2.0 This release

2 release files

0.1.0

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