Language:
- English (default)
- 简体中文
This page documents the current public SDK surface of acp-runtime.
It describes only host-facing runtime concepts, not raw ACP protocol messages.
Recommended reading order:
- Runtime SDK By Scenario
- Runtime SDK Observability
- Runtime SDK Read Models
- Runtime SDK API Coverage
- this page for grouped semantics and type notes
AcpRuntimeAcpRuntimeSession
The package root intentionally keeps a narrow value-export surface:
- runtime classes and runtime-facing error types
- ACP agent launch helpers such as
createClaudeCodeAcpAgent() - stdio transport construction via
createStdioAcpConnectionFactory() - protocol alignment metadata, registry helpers, and default path helpers
Internal implementation details such as AcpSessionDriver, session-service construction,
and stdio process internals are not part of the package-root public API.
The ./internal/* subpaths are advanced escape hatches for tooling and diagnostics, not the normal host integration surface.
Internal runtime implementation is currently organized around:
AcpRuntimeAcpRuntimeSessionAcpSessionDriveracp/session-service.tsacp/profiles/acp/driver.ts
acp-runtime is responsible for hiding ACP agent implementation differences
from host integrations. Once an agent type is known, compatibility behavior
belongs in the SDK/runtime profile or adapter layer, not in every host.
Examples of differences that should be absorbed by the runtime:
- registry id and short-alias launch resolution
- mode id/name/URI normalization
- auth method quirks, including terminal auth that is only a setup entrypoint
- agent-specific system prompt delivery
- config option aliases and value aliases
- protocol shape drift and benign agent-specific errors
Demos and harnesses may expose these behaviors for testing, but should call runtime APIs and profiles instead of duplicating per-agent workarounds.
AcpRuntime is the top-level host SDK object.
Public host surface:
runtime.sessions.start(options)runtime.sessions.load(options)runtime.sessions.resume(options)runtime.sessions.list(options?)
Registry-backed helper:
resolveRuntimeAgentFromRegistry(agentId)selectRuntimeAuthenticationMethod(methods)runtimeAuthenticationTerminalSuccessPatterns(method)resolveRuntimeTerminalAuthenticationRequest({ agent, method })resolveRuntimeHomePath(...segments)resolveRuntimeCachePath(...segments)
Default runtime-owned state now lives under ~/.acp-runtime/.
resolveRuntimeHomePath(...)resolves paths under the runtime home rootresolveRuntimeCachePath(...)resolves cache paths under~/.acp-runtime/cache/ACP_RUNTIME_HOME_DIRoverrides the home rootACP_RUNTIME_CACHE_DIRoverrides the cache root only
For most host integrations, runtime.sessions.start({ agent: "claude-acp", ... }) should be the default path.
Passing an agent id keeps launch resolution inside the runtime instead of repeating command / args rules in every host.
Any id listed in the ACP registry should work through this path even when this
package does not export a dedicated createXxxAcpAgent(...) helper for that
agent. Dedicated helpers are optional convenience APIs for callers that want to
override launch mode, package version, environment, or arguments explicitly.
Common short aliases are normalized by the same registry resolver, so
claude, codex, pi, copilot, sim, and simulator can be used anywhere
an agent id is accepted.
Runtime-owned local state is enabled by default and stored at ~/.acp-runtime/state/runtime-session-registry.json.
Use new AcpRuntime(factory, { state: { sessionRegistryPath } }) to override that path, or { state: false } to disable local state.
The runtime-owned session registry records the session snapshot needed to reopen an ACP session later. Snapshots are written when a managed session is registered and refreshed when the driver reports snapshot changes. They include the ACP session id, resolved agent launch config, cwd, MCP servers, current mode/config state, and session title metadata.
When a stored snapshot exists, runtime.sessions.resume({ sessionId, handlers })
is enough for the runtime to recover the previous agent/cwd/MCP setup. The host
does not need to keep a parallel copy of those launch options:
const resumed = await runtime.sessions.resume({
sessionId,
handlers: {
permission: decidePermission,
filesystem,
terminal,
authentication,
},
});If no stored snapshot exists, opening by id requires the caller to provide at
least agent and cwd; otherwise the runtime raises AcpLoadError. Hosts that
need crash recovery should treat the registry as the durable recovery source and
persist only host-owned lifecycle facts such as which product sessions are still
open. Live authority callbacks must still be passed on every start, load, or
resume call because they are process-local closures and cannot be restored from
the snapshot.
Use initialConfig when a host wants a preferred mode/model/reasoning preset immediately after the ACP session is opened:
const session = await runtime.sessions.start({
agent: "codex-acp",
cwd: process.cwd(),
initialConfig: {
mode: "full-access",
model: "gpt-5.4",
effort: "high",
},
});ACP config is discovered only after session/new, session/load, or session/resume. For that reason initialConfig is best-effort by default: unsupported or renamed options are skipped and recorded in session.initialConfigReport, but the session still opens.
mode, model, and effort are runtime-level names. The runtime
maps them to the current agent's exposed config option IDs/categories and
profile-specific value aliases, so hosts do not need separate CLI flags for
Codex vs. Claude.
Use strict: true or per-item required: true only when failing startup is better than running with a different agent config:
await runtime.sessions.start({
agent: "claude-acp",
cwd,
initialConfig: {
model: { value: "opus", required: true },
effort: { value: "xhigh", aliases: ["max"] },
},
});Use systemPrompt when a host wants to apply session-level instructions before the agent starts work:
const session = await runtime.sessions.start({
agent: "claude-acp",
cwd,
systemPrompt: "Answer in terse, implementation-focused language.",
});systemPrompt is only applied when creating a new runtime session with
sessions.start(). Loading or resuming an existing session keeps the session's
original instructions; sessions.load() and sessions.resume() do not accept
a systemPrompt option and reject it with AcpSystemPromptError.
systemPrompt is not an ACP-standard field, so the runtime applies it through agent profiles:
- Claude ACP receives
_meta.systemPromptonsession/new. - Codex ACP is launched with
-c developer_instructions=<prompt>. - Unsupported agents reject startup with
AcpSystemPromptErrorinstead of silently ignoring the prompt.
AcpRuntimeSession exposes:
capabilitiesmetadatainitialConfigReportdiagnosticsstatussession.agent.*listModes()listConfigOptions()setMode()setConfigOption()
session.turn.*cancel(turnId)start()run()send()stream()queue.clear()/sendNow()/get()/list()/remove()
session.queue.*policy()setPolicy({ delivery })
session.state.*history.drain()thread.entries()diffs.keys()/get()/list()/watch()terminals.ids()/get()/list()/watch()/refresh()/wait()/kill()/release()toolCalls.ids()/get()/list()/bundle()/bundles()/diffs()/terminals()/watch()/watchObjects()operations.ids()/get()/list()/bundle()/bundles()/permissions()/watch()/watchBundle()permissions.ids()/get()/list()/watch()metadata()usage()watch()
session.snapshot()session.close()
Turn submission is queue-first. start(), send(), run(), and stream() create a queued turn and mark it ready automatically. Hosts that need explicit scheduling should keep the returned turnId, then call session.turn.queue.sendNow(turnId) to move that not-yet-started queued turn to the front and cancel the active turn, remove(turnId) to withdraw one queued turn before it starts, or clear() to withdraw all queued turns before they start.
AcpRuntimeQueuedTurn.status is queued before dispatch and ready after dispatch while waiting for execution.
Queue drain policy is session-level. Pass queue: { delivery: "sequential" | "coalesce" } to runtime.sessions.start/load/resume, or update future drains with session.queue.setPolicy(...).
sequential sends ready queued turns one by one. coalesce drains all ready queued prompts into one agent prompt; the first turn becomes the actual turn, and later merged turns receive a terminal coalesced event with intoTurnId.
AcpRuntimeSession is a host-facing handle over an underlying runtime-managed session driver.
Current handle rules:
- repeated
runtime.sessions.load()calls for the samesessionIdshare one underlying driver while returning distinct handles - repeated
runtime.sessions.resume()calls for the samesessionIdshare one underlying driver while returning distinct handles - closing one handle does not close sibling handles that still reference the same underlying session
- the underlying driver closes only after the final live handle closes
- once a specific handle is closed, that handle rejects
session.turn.start,session.turn.run,session.turn.send,session.turn.stream,session.turn.cancel(turnId),session.agent.setMode, andsession.agent.setConfigOption - snapshot and read-model getters remain readable from a closed handle, but they no longer represent an active control surface
The runtime exposes state watchers at two granularities:
- broad state watchers such as
session.state.watch, which receive read-model and projection updates - targeted watchers such as
session.state.diffs.watch,session.state.terminals.watch,session.state.toolCalls.watch,session.state.toolCalls.watchObjects,session.state.operations.watch,session.state.operations.watchBundle, andsession.state.permissions.watch
Current tool_call read-model behavior is incremental rather than batch-coalesced:
- when a tool call update contains derived objects like diffs or terminals, the runtime emits those derived object updates first
- the corresponding
tool_callthread entry is emitted after the derived object updates for that same write session.state.toolCalls.watch(toolCallId, watcher)can therefore fire multiple times for one ACPtool_callortool_call_update- each callback receives the latest bundle snapshot visible at that step, not a deferred final-only bundle
Hosts should treat these watchers as live incremental state updates rather than assuming one callback per ACP update.
Public discriminant strings remain wire-stable, but callers should prefer the exported constants instead of hard-coded string literals:
import {
AcpRuntimeOperationKind,
AcpRuntimeReadModelUpdateType,
AcpRuntimeThreadEntryKind,
AcpRuntimeTurnEventType,
} from "@saaskit-dev/acp-runtime";
if (event.type === AcpRuntimeTurnEventType.UsageUpdated) {
console.log(event.usage.totalTokens);
}
if (entry.kind === AcpRuntimeThreadEntryKind.AssistantMessage) {
console.log(entry.text);
}The same pattern exists for operation kinds/phases, projection update types, read-model update types, content part types, prompt roles, permission kinds/scopes, queue delivery, session status, terminal status, and observability redaction kinds.
AcpRuntimeAgent may include:
commandargsenvtype
type is the stable runtime-facing agent family identifier used for profile selection and host-side filtering.
For registry-id startup, hosts can skip manual AcpRuntimeAgent construction by passing a registry agent id:
const runtime = new AcpRuntime(createStdioAcpConnectionFactory());
const session = await runtime.sessions.start({
agent: "claude-acp",
cwd: process.cwd(),
});Manual runtime.sessions.start({ agent }) is the override path for callers that need explicit launch control.
Runtime session control is now centered on the agent's native mode and config options.
That means:
- callers should treat
currentModeIdandconfigas the primary recovery state - hosts can inspect supported raw controls through
session.agent.listModes()andsession.agent.listConfigOptions() - hosts can update raw state through
session.agent.setMode()andsession.agent.setConfigOption()
runtime.sessions.list({ source: "remote", agent, cwd }) is agent-scoped, not global.
It asks one concrete ACP agent process to return the sessions it knows about. It does not aggregate across multiple agents.
For a local recent-session view, use runtime.sessions.list({ source: "local" }).
For a merged view, use runtime.sessions.list({ source: "all", agent, cwd }).
Returned references include source: "local" | "remote" | "both" when the source is known.
Current ACP agent-backed session management coverage includes:
session/listsession/loadsession/resumesession/close- unstable
session/fork, exposed asruntime.sessions.fork(...)
Local runtime session-history management also exposes:
runtime.sessions.watch(...)runtime.sessions.delete(...)runtime.sessions.refresh()
Those local registry APIs operate on the runtime-owned recent-session index. They do not pretend to delete or refresh remote agent history unless the ACP agent exposes such protocol methods.
AcpRuntimePrompt supports:
- plain string input
- structured content parts
- structured role-based messages
Content parts currently support:
textfileimageaudioresourcejson
Turn completion returns:
outputTextoutputturnId
output is the structured runtime output channel and should be preferred for rich host rendering.
Host-provided authority is modeled through:
authenticationfilesystempermissionterminal
These are runtime abstractions over client-side capability delegation.
Authentication handlers should use SDK policy helpers rather than hard-coding
agent ids. selectRuntimeAuthenticationMethod(...) applies runtime/profile
metadata such as acp-runtime/default-auth-method and safely auto-selects the
only available method. runtimeAuthenticationTerminalSuccessPatterns(...) reads
profile-provided terminal completion hints, and
resolveRuntimeTerminalAuthenticationRequest(...) resolves generic terminal
execution data from the selected method.
If no authentication handler is provided, the runtime may automatically select
and authenticate a safe protocol-only agent method. Terminal and env-var auth
still require a host handler because they need UI or local process execution.
AcpRuntimeCapabilities includes:
agentagentInfoauthMethodsclient
authMethods describe agent-advertised or runtime-normalized login options.
Agent-specific login quirks are normalized by profiles into runtime metadata.
Host-side UI policy, such as whether to prompt before selecting a method,
belongs in the host or adapter layer rather than the runtime core model.
For the full compatibility boundary, see Runtime Agent Compatibility.
AcpRuntimeSessionMetadata includes:
idtitlecurrentModeIdconfigavailableCommands
runtime.sessions.list(options) returns:
sessionsnextCursor
Each session reference currently includes:
agentTypeidcwdtitleupdatedAt
The local session index is runtime-owned implementation detail.
Hosts interact with it through runtime.sessions.list({ source: "local" }),
runtime.sessions.load({ sessionId, ... }), and runtime.sessions.resume({ sessionId, ... }).
session.state.thread.entries() exposes the runtime's thread-first read model.
This is additive. It does not replace:
AcpRuntimeTurnEventAcpRuntimeOperation
session.state.diffs.get(path) and session.state.terminals.get(terminalId) expose direct lookup helpers
for runtime-owned tool objects.
session.state.diffs.list() and session.state.terminals.list() expose runtime-owned object views derived
from that same thread-first model.
session.state.diffs.keys() and session.state.terminals.ids() expose the current object-store indexes.
session.state.toolCalls.diffs(toolCallId) and session.state.toolCalls.terminals(toolCallId)
expose the object-store view grouped by source tool call.
session.state.toolCalls.ids(), session.state.toolCalls.list(), session.state.toolCalls.bundles(),
session.state.toolCalls.get(toolCallId), and session.state.toolCalls.bundle(toolCallId)
expose the tool-call-level inspection view.
Those objects now carry basic lifecycle metadata such as:
revisioncreatedAtupdatedAtcompletedAtfor completed terminalsstopRequestedAtfor kill requestsreleasedAtfor released terminals
They also expose derived inspection metrics:
- terminal:
outputLength,outputLineCount - diff:
newLineCount,oldLineCount
session.state.watch(watcher) lets hosts subscribe to read-model changes:
thread_entry_addedthread_entry_updateddiff_updatedterminal_updated
It also exposes the runtime-owned projection layer for:
operation_projection_updatedpermission_projection_updatedmetadata_projection_updatedusage_projection_updated
That projection layer exists so hosts can consume stable live operation/permission/session-summary state
without treating raw AcpRuntimeTurnEvent delivery as the source of truth.
Operation and permission state now also expose narrower runtime-owned inspection views:
session.state.operations.get(operationId)session.state.operations.list()session.state.operations.permissions(operationId)session.state.operations.bundle(operationId)session.state.operations.bundles()session.state.permissions.get(requestId)session.state.permissions.list()
And targeted watchers:
session.state.operations.watch(operationId, watcher)session.state.operations.watchBundle(operationId, watcher)session.state.permissions.watch(requestId, watcher)
session.state.diffs.watch(path, watcher) and session.state.terminals.watch(terminalId, watcher)
provide targeted subscriptions for one diff or one terminal object.
session.state.toolCalls.watchObjects(toolCallId, watcher) provides a targeted subscription
for all diff/terminal objects associated with one tool call.
session.state.toolCalls.watch(toolCallId, watcher) provides a targeted subscription for the
full tool-call bundle, including the tool call entry plus grouped diff/terminal objects.
The event layer remains the stable host-facing streaming abstraction.
session.state.thread.entries() is the richer structured view used for history, tool-call inspection, and future thread-oriented UIs.
Current thread entry families:
user_messageassistant_messageassistant_thoughtplantool_call
tool_call entries may also include:
locations
Current tool_call.content families:
contentdiffterminal
diff content currently includes:
patholdTextnewTextchangeType
terminal content currently includes:
terminalIdstatuscommandcwdoutputtruncatedexitCode
These fields are best-effort snapshots derived from ACP tool-call content and local terminal handlers when available.
Two narrower runtime-side object views are also available:
session.state.diffs.list()session.state.terminals.list()
These are derived stores built from the same underlying thread-first model.
They exist so hosts can consume richer diff/terminal state without re-scanning session.state.thread.entries().
AcpRuntimeDiagnostics currently includes:
lastUsagelastError
AcpRuntimeOperation is the public abstraction over external actions.
Key fields:
idturnIdkindphasetitletargetprogressresultfailureReasonpermission
operation.permission is the normalized runtime evidence for permission-sensitive actions.
For denied operations, the current families are:
permission_request_cancelledpermission_request_end_turnmode_denied
AcpRuntimePermissionRequest links permission to action through:
idturnIdoperationId
Top-level turn control flow still stays normalized.
Even when vendors differ between cancelled, end_turn + failed tool update, or mode-based refusal,
permission-denied turns still surface as AcpPermissionDeniedError.
AcpRuntimeSnapshot is the minimal recovery model.
It includes:
agentconfigcurrentModeIdcwdmcpServerssession.idversion
Snapshot intentionally stores agent.type inside agent.
There is no separate runtime agentId field in the snapshot model.
Current public turn event families:
queuedstartedthinkingtextplan_updatedmetadata_updatedusage_updatedoperation_startedoperation_updatedpermission_requestedpermission_resolvedoperation_completedoperation_failedcompletedcancelledcoalescedwithdrawnfailed
Top-level typed runtime errors:
AcpCreateErrorAcpLoadErrorAcpResumeErrorAcpAuthenticationErrorAcpPermissionDeniedErrorAcpTurnCancelledErrorAcpTurnTimeoutErrorAcpProtocolErrorAcpProcessError