Language:
- English (default)
- 简体中文
acp-runtime is a product-facing, product-agnostic ACP runtime and simulator workspace.
The main goal of this repository is to build acp-runtime as the host-side ACP runtime layer for products, so future integrations with real ACP agents can share one consistent model for sessions, turns, permissions, recovery, and observability.
simulator-agent-acp exists to occupy the ACP agent position during development and testing. It is a deterministic stand-in for real agents, used to validate the runtime, host integration flows, and regression coverage without depending on live agent behavior.
This repository currently centers on:
simulator-agent-acpas a deterministic ACP agent- harness-driven protocol and scenario validation
- RFCs that define the runtime model for sessions, turns, permissions, recovery, and host integration
Protocol alignment for the simulator is tracked with explicit metadata:
- ACP protocol version:
1 - ACP source repo:
https://github.com/agentclientprotocol/agent-client-protocol - ACP source ref:
v0.11.4 - Last verified against upstream docs:
2026-04-08
- Client Integration Guide
- Runtime SDK By Scenario
- Runtime SDK Read Models
- Runtime SDK API Coverage
- RFC-0001: Runtime Public Abstraction
- RFC-0002: Runtime Execution and Authority
- RFC-0003: Runtime Snapshot, Policy, and Recovery
- RFC-0004: Runtime Diagnostics and Host Integration
- RFC-0005: Simulator Agent ACP
- Protocol Coverage Matrix
- Documentation Index
If you are integrating acp-runtime into a product host, start here:
import {
AcpRuntime,
createStdioAcpConnectionFactory,
} from "@saaskit-dev/acp-runtime";
const runtime = new AcpRuntime(createStdioAcpConnectionFactory());
const session = await runtime.sessions.start({
agent: "claude-acp",
cwd: process.cwd(),
handlers: {
permission: () => ({ decision: "allow", scope: "session" }),
},
});
const text = await session.turn.run("Summarize the current workspace.");
const snapshot = session.snapshot();
await session.close();Runtime-owned state is enabled by default and stored under
~/.acp-runtime/state/runtime-session-registry.json. Override it with
new AcpRuntime(factory, { state: { sessionRegistryPath } }), or disable local
state with { state: false }.
The session registry is the runtime's recovery source of truth. After a session
has been started, loaded, or resumed, the runtime stores a snapshot containing
the ACP session id, resolved agent launch config, cwd, MCP servers, mode/config
state, and title metadata. Later calls to runtime.sessions.resume({ sessionId, handlers }) can reopen the ACP session from that stored snapshot without the
host duplicating agent, cwd, or mcpServers. Hosts still provide fresh
authority handlers on every open because permission, filesystem, terminal, and
authentication callbacks are live process closures, not persisted state.
Then read in this order:
This repository now follows a cleaner open source documentation layout:
- English is the default language for entry-point documents
- Simplified Chinese lives in dedicated translation files
- package and install instructions use the published scoped package name
- contributor and security policy files live at the repository root
src/: runtime library source onlyexamples/: runnable smoke and demo entry pointsharness/: repository-level validation tooling and case definitionspackages/simulator-agent/: independently published simulator agent package
Run from source:
git clone <repo>
cd acp-runtime
pnpm install
pnpm build
pnpm simulator-agent-acpInstall from npm:
npm install -g @saaskit-dev/simulator-agent-acp
simulator-agent-acpOr run directly with npx:
npx @saaskit-dev/simulator-agent-acp@latest- stdio ACP agent process
- deterministic slash-command surface
- permission, file, terminal, and plan flows
- MCP server config validation for
stdio/http/sse - local harness baseline via
simulator-agent-acp-local
pnpm clean
pnpm build
pnpm test
pnpm demo:client-sdk
pnpm harness:check-admission -- --type codex-acp
pnpm harness:run-agent -- --type codex-acpGenerated runtime state now defaults to ~/.acp-runtime/.
You can override the home root with ACP_RUNTIME_HOME_DIR, and override cache-only paths with ACP_RUNTIME_CACHE_DIR.
The current runtime surface is organized around three public concepts:
AcpRuntime: host-facing entry point forruntime.sessions.*AcpRuntimeSession: unified object model forsession.agent.*,session.turn.*,session.state.*,session.queue.*, andsession.snapshot()/session.close()AcpSessionDriver: internal driver boundary used to normalize ACP agent differences behind the runtime session API
Internally, ACP-specific behavior is split into:
acp/session-service.ts: ACP session creation/load/resume/list orchestrationacp/profiles/: agent-specific normalization strategy selected byagent.typeacp/driver.ts: ACP SDK-backed session driver
The repository now documents a stricter compatibility boundary:
- runtime core keeps semantic normalization and protocol-shape correction
- host / demo adapters keep login execution strategy and UX policy
- explicit profile fallbacks must be marked as policy, not generic ACP behavior
See Runtime Agent Compatibility. For the recommended learning order, start with Runtime SDK By Scenario. For a method-by-method lookup, use Runtime SDK API Coverage.
This is intentionally not a generic multi-protocol abstraction. The runtime is ACP-focused, but still normalizes behavioral differences across ACP agents.
For registry-id startup, pass a registry agent id as agent.
The runtime resolves launch config from the ACP registry instead of forcing each host to hard-code command / args:
const runtime = new AcpRuntime(createStdioAcpConnectionFactory());
const session = await runtime.sessions.start({
agent: "claude-acp",
cwd: process.cwd(),
});If you need the resolved launch config before creating a session, use resolveRuntimeAgentFromRegistry(agentId).
The repository now has runtime-level validation for the currently integrated ACP agents:
simulator-agent-acp- validated by src/runtime/runtime-simulator.test.ts
- covers
create,send,configure,snapshot, andresume
Claude Code ACP- validated by src/runtime/runtime-claude-code.test.ts
- covers real stdio startup, session creation, and prompt execution against
claude-agent-acp
Codex ACP- validated by src/runtime/runtime-codex.test.ts
- covers real stdio startup, session creation, and prompt execution against
codex-acp
The Claude Code contract test now resolves launch config from the ACP registry by default.
If claude-agent-acp is already on PATH, it uses the local binary.
If the local binary is not installed, the default test suite now skips that real-environment contract test rather than forcing a package download through npx.
If you want to force the legacy direct npx path without consulting the registry first, set:
ACP_RUNTIME_RUN_CLAUDE_CODE_TEST=1 pnpm test -- --run src/runtime/runtime-claude-code.test.tsIf you want to skip the real-environment contract test even when the binary is installed, set:
ACP_RUNTIME_SKIP_CLAUDE_CODE_TEST=1 pnpm testThe Codex contract test also resolves launch config from the ACP registry by default.
If codex-acp is already on PATH, it uses the local binary.
If the local binary is not installed, the default test suite now skips that real-environment contract test rather than forcing a package download or registry binary fetch.
If you want to force the direct npx path for Codex, set:
ACP_RUNTIME_RUN_CODEX_TEST=1 pnpm test -- --run src/runtime/runtime-codex.test.tsIf you want to skip the real-environment Codex contract test, set:
ACP_RUNTIME_SKIP_CODEX_TEST=1 pnpm testIf the registry launch cannot be resolved, the default test suite skips that contract test and still runs the deterministic simulator-backed runtime integration test.
Runtime session handles are ref-counted wrappers over an underlying runtime-managed session driver.
- repeated
runtime.sessions.load()calls for the samesessionIdshare one underlying driver - repeated
runtime.sessions.resume()calls for the samesessionIdshare one underlying driver - closing one handle does not invalidate sibling handles for the same runtime session
- the underlying driver closes only after the final live handle closes
- once a specific handle is closed, that handle rejects new turn and mutation calls