@claxedo/agent-sdk-runtime
v0.8.0
Published
AgentRuntime facade over native SDK harnesses and configured agent connections
Maintainers
Readme
Agent SDK Runtime
@claxedo/agent-sdk-runtime is the embeddable SDK for running agent harnesses
behind one host-owned runtime API. Register harnesses, choose a store, create a
session, start a turn, and subscribe to events.
Install
npm install @claxedo/agent-sdk-runtimeimport { createAgentRuntime } from "@claxedo/agent-sdk-runtime"
import { claude } from "@claxedo/agent-sdk-runtime/harnesses/claude"
import { pi } from "@claxedo/agent-sdk-runtime/harnesses/pi"
import { createSqliteRuntimeStore } from "@claxedo/agent-sdk-runtime/stores/sqlite"
const runtime = createAgentRuntime({
store: createSqliteRuntimeStore({ root: ".agent-runtime" }),
harnesses: [
claude({ access: "native" }),
pi(),
],
})
const session = await runtime.sessions.create({
directory: "/repo",
harness: { id: "claude", access: "native" },
model: { providerID: "anthropic", modelID: "claude-sonnet-4-6" },
title: "Review",
})
const events = runtime.events.subscribe({ sessionId: session.id })
await runtime.turns.start({
sessionId: session.id,
text: "review this repo",
})The package owns harness process/session orchestration, adapter lifecycle, runtime configuration, model catalogs, MCP resolution, event fan-out, and first-party stores. Hosts still own auth policy, workspace routing, project inventory, HTTP route shape, and UI state.
Agent-First Public Docs
Coding agents and host integrators should start with docs/agent.md, then read docs/concepts.md and docs/architecture.md. Together they define the package job, mental model, blessed imports, stability labels, boundaries, recipes, and public API surface.
Flow
host app
-> AgentRuntime
-> harness factory
-> adapter driver
-> harness access (ACP / native)
-> runtime events
-> runtime.events.subscribe()The canonical event contract comes from @claxedo/agent-event-runtime.
Adapters either emit canonical AgentRuntimeEvent values directly or emit
Claxedo client-presentation CompatEvent values when bridging an existing OpenCode
surface.
Session Liveness And Turn Outcome
session.status is the current runtime liveness state. It is busy while a
turn is running, recovering when a harness is being rebound, error when the
session is in an error state, and null or absent when it is idle. It is never
done.
The durable result of the most recent turn is session.lastTurn:
session.lastTurn?.status // "completed" | "failed" | "cancelled"
session.lastTurn?.assistantMessageId // assistant row for that completed turnRender turn-level "Done", "Failed", or "Cancelled" from lastTurn, not from
message.time.completed. Message completion is row-level metadata; turn
outcome is recorded by the runtime after the harness stream settles. When a UI
needs to reconcile an in-flight prompt, compare lastTurn.assistantMessageId
with the assistant message id for that prompt; lastTurn.status alone only
describes the most recent settled turn. If the host has a higher-level lifecycle
such as a Goal or long-running task, use that lifecycle for session-level
"working" and "done" badges. Claude SDK, Codex app-server, ACP, OpenCode, and Pi
terminal signals all normalize through this runtime/store path. Shell hook
integrations remain useful for terminal presence, but they are not the canonical
SDK turn outcome source.
Goals
A Goal is a session-level completion condition that may span multiple harness
turns. It is a dedicated runtime resource, not a prompt mode: callers use
runtime.goals, and adapters must never repair missing support by sending the
objective through runtime.turns.start().
const capabilities = await runtime.goals.capabilities(session.id, directory)
if (!capabilities.available) throw new Error(capabilities.unavailableReason)
const started = await runtime.goals.start({
sessionId: session.id,
objective: "Ship when verification passes",
}, directory)
const current = await runtime.goals.read(session.id, directory)HarnessCapabilities.goals is only coarse runtime availability. Always read
the detailed Goal capabilities for the materialized session before rendering
actions. Pause and Resume form one reversible pair; do not expose either unless
both are advertised. Delete is independent. Stop is part of the common Goal
lifecycle and disables continuation before interrupting active work.
| Harness | Start authority | Actions | Recovery |
| --- | --- | --- | --- |
| Codex native | structured app-server Goal RPC | Pause, Resume, Delete | reconcile durable thread state |
| Claude native | SDK /goal plus active_goal messages | none | blocked if provider state is lost |
| Cursor native | Agent.send("/goal …") plus durable Run state | none | blocked if provider state is lost |
| OpenCode native | first-party Session Goal aggregate | Pause, Resume, Delete | reconcile; orphaned execution becomes blocked |
| Pi native | owned evaluator and follow-up controller | Pause, Resume, Delete | blocked after non-durable process loss |
| ACP | negotiated _meta.goal extension | negotiated subset | negotiated reconciliation |
Goal snapshots report only provider- or controller-owned fields. Optional
budgets, usage, iteration, and diagnostic reason fields are not synthesized.
After reconnect, refetch runtime.goals.read(); recovery: "blocked" means an
active persisted snapshot must become visibly blocked rather than complete.
Layers
Core Runtime
src/index.ts defines the public runtime surface.
createAgentRuntime()is the main public entrypoint.runtime.sessions,runtime.turns,runtime.goals,runtime.events,runtime.permissions,runtime.questions,runtime.todos,runtime.commands,runtime.config, andruntime.healthare the user-facing resource namespaces.PromptInputis the normalized message submission shape used across transports internally.SessionConfigandSessionConfigUpdatedescribe harness/model/agent selection as host-visible state.
Adapter classes are implementation plumbing behind harness factories. Public host code should use the runtime facade first.
Stores
First-party stores live on explicit subpaths:
@claxedo/agent-sdk-runtime/stores/memory@claxedo/agent-sdk-runtime/stores/sqlite
The root import does not load SQLite.
Harness Factories
Harness factories have individual entries and a convenience aggregate:
@claxedo/agent-sdk-runtime/harnesses/acp@claxedo/agent-sdk-runtime/harnesses/claude@claxedo/agent-sdk-runtime/harnesses/codex@claxedo/agent-sdk-runtime/harnesses/cursor@claxedo/agent-sdk-runtime/harnesses/pi
The browser-safe Pi provider/model catalog is available separately at
@claxedo/agent-sdk-runtime/pi-catalog; it does not load a harness runtime.
import { acp, claude, codex, cursor, pi } from "@claxedo/agent-sdk-runtime/harnesses"
// Smaller single-harness graph:
import { claude as nativeClaude } from "@claxedo/agent-sdk-runtime/harnesses/claude"Factories hide adapter class construction and let the runtime inject store and event plumbing.
Harness Metadata
src/harness-types.ts is the source of truth for supported harness ids and
access modes.
- ACP connections:
acp(id, { binary, ... })withaccess: "acp". - Native harnesses:
claude,codex,cursor,opencode, andpiwithaccess: "native". - Configured providers, including ACP, use an opaque connection id with
access: "connection"; they are not added to the native id catalog.
harnessDefinition(), harnessKey(), isAgentHarnessId(), and
isAgentHarnessAccess() centralize harness classification so callers do not
infer behavior from strings.
Runtime model options come from live, directory-scoped harness queries. Query errors are returned to the caller and an empty result remains empty. The exported SDK model catalog is an explicit reference/validation API; it is not substituted for a failed live query.
Subagent support matrix
Subagent discovery is normalized into durable, parent-scoped host rows. A spawn
tool is associated with its row by an explicit toolCallId edge. The host mints
childSessionId when it can materialize a readable child Session; provider ids
and transcript refs stay opaque and are never treated as Session ids or file
paths by the UI.
| Harness | Discovery | Transcript | Child Session |
| --- | --- | --- | --- |
| Claude native | Agent tool lifecycle | messages | openable after host materialization |
| Codex native | collab thread lifecycle | live | openable |
| Cursor native | Task result carrying a valid transcript reference | file | conditionally openable after host materialization |
| Configured ACP | negotiated ACP lifecycle | provider-dependent | capability-dependent |
| Pi model-backed | foreground or background child lifecycle | live | openable |
| Pi bare adapter | no subagent capability | none | no subagent row |
Foreground children participate in parent interruption; background children continue independently and remain visible as background work. Child transcripts are read-only conversation surfaces: opening one preserves the parent Session, and repeated activation focuses the existing child surface. Rows with no supported transcript render an explicit unavailable state with no navigation control.
Runtime event streams that expose these rows are scoped and authorized by parent
Session. A denied parent subscription returns 403 before replay or live events
are attached, so one parent cannot observe another parent's child lifecycle.
Capabilities
src/capabilities.ts describes runtime feature support.
HarnessCapabilitiesis the per-harness session feature matrix returned to hosts and UI.AdapterCapabilitymarks implementation abilities that are not part of the normal session contract, currentlyruntime-config.hasAdapterCapability()is the safe narrowing helper for optional adapter extensions.
Capability data is explicit. Unsupported operations fail rather than returning synthetic data or events.
Events And SSE
src/runtime-event-hub.ts and src/sse.ts provide in-process fan-out and SSE
helpers.
RuntimeEventHubis a lightweight pub/sub boundary for runtime events.createAgentRuntime({ subscriberBufferSize, eventDelivery })bounds each subscriber while allowing the host to authorize every delivered event.createSseReplayBuffer()keeps bounded replay for reconnecting clients.attachSseFanout()bridges a subscribe function into a streaming response with lifecycle cleanup.
These primitives are production-appropriate when each session is pinned to one owner machine and that machine's durable session/message log is the source of truth. Hosts only need external pub/sub when they serve the same session live from multiple processes or machines.
Native Pi
Pi runs as a child process in the session's machine directory, using the pinned 0.85.1 RPC protocol. The shared adapter owns product session lifecycle; Pi owns its native session file, tools, extensions, compaction and context. Each active session has a separate process. Idle processes resume from the same native file. A missing native file is an error, never a replacement conversation.
Use pi({ binary, agentDir }) to select the executable and isolated Pi profile.
PI_EXECUTABLE also selects the executable. The host projects credentials into
that profile and removes its auth file on disposal. It does not modify the
user's own Pi profile. Model options come from get_available_models in Pi.
Every session requires a real directory and workspace identity.
Pi's extension questions use the shared question API. Extensions and native tools execute with the machine process's permissions, and project extensions remain subject to Pi's project trust policy. Pi does not provide a permission prompt or native subagent API. Host-provided MCP servers are not automatically loaded by upstream Pi; use a trusted Pi extension when that capability is needed.
MCP Resolution
src/mcp-resolver.ts merges managed MCP state with user-defined MCP servers
and converts the result into harness-specific shapes.
resolveEffectiveMcp()returns the effective server map plus managed status.toOpencodeConfig()writes the OpenCode config shape.toAcpMcpServers()writes ACPMcpServervalues.
Managed MCP defaults are intentionally empty today. User MCP config still resolves normally, while managed loops remain explicit no-ops until real managed servers exist.
Command Discovery
src/command-discovery.ts reads command files from configured directories and
normalizes them into AgentCommandRow values. Adapters and hosts use it to
serve slash-command surfaces without making command parsing part of the core
runtime facade.
Adapters
ACP
src/harnesses/acp owns Agent Client Protocol process integration.
index.tsimplements the ACP harness driver.process.tsmanages ACP process lifecycle, prompts, config option probing, permissions, and restart behavior.session.tstranslates session load/resume state, mode/model sync, and prompt parts into ACP request content.transport.tsabstracts stdio/http process creation.recovery.tsandtitle.tshandle recovery metadata and title generation.
ACP adapters should prefer live session config options and only call ACP session-mode APIs when the advertised mode list supports the requested value.
Shared SDK Adapter
src/harnesses/shared contains reusable lifecycle and projection code for
SDK-backed transports.
sdk-runtime-adapter.tsimplements common session storage and adapter behavior.turn-lifecycle.tsowns active-turn cancellation and cleanup rules.turn-projection.tsbridges harness events into runtime/compat output.
SDK adapters should put harness-specific code in their driver and keep shared session semantics in this layer.
Claude SDK, Codex App Server, Cursor SDK
src/harnesses/claude, src/harnesses/codex, and
src/harnesses/cursor adapt native SDK or app-server streams into the
shared runtime contract through the shared SDK adapter.
These transports query their native SDK or app-server for model options and cache successful results per workspace directory.
Configured connections
External agents are installed through ConnectionProvider implementations.
The runtime resolves an explicit connection identity to a trusted descriptor,
materializes host-owned secret leases at execution time, and persists only the
full AgentExecutionBinding needed to address the session. Unknown, disabled,
or stale connections fail closed; there is no built-in external-server default.
ACP is one connection provider. The external OpenCode server protocol lives in
the separate @claxedo/opencode-server-adapter package and is composed by host
applications like any other installed provider.
Pi
src/harnesses/pi is a built-in native harness. It is useful for placeholder or
host-controlled flows where no external agent process owns a real model/config
surface.
Host Boundaries
Host packages are responsible for:
- choosing and constructing adapters
- resolving directories and workspace ids
- enforcing auth and operation guards
- owning HTTP/RPC route shape and status codes
- persisting sessions, config, and event history
- publishing global/session events beyond the current process
- exposing product-specific HTTP routes
- materializing Agent Plugins, skills, and MCP config outside this package
This package should stay focused on runtime contracts and transport execution.
Adding A Harness
- Add the harness to
AGENT_HARNESS_DEFINITIONSinsrc/harness-types.ts. - Decide whether it supports
access: "acp",access: "native", or both. - Implement an adapter under
src/harnesses/<harness>. - Expose explicit
HarnessCapabilities; unsupported operations must be errors. - Implement live model discovery when the harness exposes configurable models.
- Add focused adapter tests around session lifecycle, submit, abort, config, and event projection.
- Add or update the public factory in
src/harnesses/index.tswhen the harness should be user-selectable.
Public Entry Points
The package ships public docs under docs/. Use
docs/recipes.md for import examples and
docs/api.md for the stable root API.
Entry point status:
- Stable:
@claxedo/agent-sdk-runtime,@claxedo/agent-sdk-runtime/capabilities - Stable:
@claxedo/agent-sdk-runtime/harnesses,@claxedo/agent-sdk-runtime/stores/memory,@claxedo/agent-sdk-runtime/stores/sqlite - Advanced:
@claxedo/agent-sdk-runtime/adapters,@claxedo/agent-sdk-runtime/subagent-admission,@claxedo/agent-sdk-runtime/message-page@claxedo/agent-sdk-runtime/runtime-event-hub,@claxedo/agent-sdk-runtime/sse,@claxedo/agent-sdk-runtime/mcp-resolver - Compatibility:
@claxedo/agent-sdk-runtime/compat-events,@claxedo/agent-sdk-runtime/status
@claxedo/agent-sdk-runtime/adapters is public but advanced. Most hosts should
use createAgentRuntime() plus harness factories. Use the adapter subpath only
when building a workspace host, custom HTTP compatibility layer, or harness
integration that needs direct driver lifecycle control.
Handoff-capable custom adapters return an AgentPreparedHandoffSession with an
idempotent rollback() and may implement releaseHandoffSource to clean up the
old native session only after the target binding commits.
The package publishes built ESM and declaration files from dist. Publish
verification hashes the complete reachable declaration closure for every
export-map entrypoint, so reviewed type-contract changes must update the API
manifest explicitly.
Verification
Run package checks from the package directory:
cd packages/agent-sdk-runtime
bun test src
bun typecheck
bun run buildDo not run tests from the repository root. This workspace guards against root test execution.
