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.

Metadata

Release files for langgraph-acp 1.4.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 1.4.0
File Size Uploaded
langgraph_acp-1.4.0.tar.gz 169.0 kB Details

Built distribution (wheel)

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

Total release size: 220.8 kB

Release files / langgraph_acp-1.4.0.tar.gz

Download URL langgraph_acp-1.4.0.tar.gz
Size 169.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a5d94e3c9955ba51fc23bb5b7181ba3967976215f7bfed6088711bea27797fb5
BLAKE2b-256 checksum
How to use checksums
3852fb84ab83e006c8c03a6c93a5487aaa2090db9bcdd29c659e4cb9b2eec015
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-1.4.0-py3-none-any.whl

Download URL langgraph_acp-1.4.0-py3-none-any.whl
Size 51.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f5dbb5be48ec6a9460ce71041e79a594f870135204edf1c312673378d58bb0d4
BLAKE2b-256 checksum
How to use checksums
f77616dcf454c831047f5120610a45a61940c9c6e59e1405df545d1855238964
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

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

This release

1.4.0 This release

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

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