Runtimes
Runtime adapters, AgentProvider internals, and runtime-specific behavior.
Each runtime is implemented as a class behind a common
AgentProvider interface. Adapters live in
core/providers/{claude-code,codex,opencode,grok,cursor}.ts.
ACP-speaking runtimes (Grok Build, Cursor) share core/providers/acp/base.ts.
Supported runtimes
SNA is designed to let a product choose the runtime that fits the workflow instead of baking in one CLI forever. The table below is a product-level overview; for exact feature support, see the Feature × runtime matrix.
| Runtime | Best fit | Strengths | Tradeoffs |
|---|---|---|---|
| Claude Code | Reliable local coding sessions with Anthropic models and rich permission behavior. | Mature CLI behavior, good tool-use UX, detailed token/cost metadata, strong streaming for thinking and tool input deltas. | Stateless per call/session, so there is no daemon pooling. Permission approval is hook-based rather than bidirectional over SNA. |
| Codex | Low-latency product integrations that need many sessions, one-shot jobs, or frequent runtime reuse. | RuntimePool reuses a daemon, supports cwd changes per thread, emits rich item lifecycle events and shell output deltas. | Codex-specific configuration has its own native shape; tool input deltas are not exposed like Claude Code's stream-json path. |
| OpenCode | Apps that already use OpenCode or want an HTTP daemon with OpenCode model/provider configuration. | Pooled daemon, SDK-backed HTTP path, good fit for repeated prompts in one cwd. | Daemon is single-cwd, some runtime mutations require respawn, and event detail depends on what OpenCode's stream exposes. |
| Grok Build | ACP-based sessions with Grok Build and a public protocol surface. | Shares the SNA ACP adapter, has bidirectional permission requests, straightforward stateless session lifecycle. | No runtime pooling, model/cwd changes require respawn, and metadata is thinner than Claude/Codex for token and tool detail. |
| Cursor | Users who already live in Cursor and want SNA to drive the Cursor subscription-backed headless CLI. | Uses cursor-agent with existing Cursor login, ACP permissions, Cursor model selection, and model-id reasoning variants. | No daemon pooling, model/cwd changes require respawn, and setup depends on the user's Cursor CLI authentication state. |
The rest of this page describes the adapter internals behind that product-facing choice.
AgentProvider
interface AgentProvider {
name: string;
isAvailable(): Promise<boolean>;
spawn(opts: SpawnOptions): AgentProcess;
complete(opts: CompleteOptions): Promise<CompletionResult>;
// ...
}
interface AgentProcess {
send(message: string, opts?): void;
interrupt(): void;
setModel(model: string): void;
setPermissionMode(mode: PermissionMode): void;
applyPatch(patch: SessionPatch): PatchResult;
respondToPermission(approved: boolean): void;
kill(): void;
on(event: string, handler): void; // emits AgentEvent
}applyPatch() is the per-field dispatch hook used by
PATCH /agent/session. Each runtime adapter decides which fields it can
apply in-place vs which require a respawn-with-history-replay.
Runtime strategy
| Runtime | Transport | State | Pool? |
|---|---|---|---|
| claude-code | claude -p (one-shot) plus claude --resume (replay) | stateless per call | no |
| codex | codex app-server daemon, JSON-RPC over stdio | persistent daemon, multi-thread | yes |
| opencode | opencode serve daemon plus SDK HTTP | persistent daemon, single-cwd | yes |
| grok | grok agent stdio (Agent Client Protocol over stdio — Grok Build CLI) | stateless per session | no |
| cursor | cursor-agent acp (Agent Client Protocol over stdio — Cursor CLI) | stateless per session | no |
The Codex and OpenCode runtime adapters share daemons through RuntimePool.
When a session opens, the pool is queried by (provider, cwd, config)
and an existing daemon is reused if compatible, sparing the cold-start
cost (≈2s for Codex, ≈3s for OpenCode).
Grok Build was the first SNA runtime whose wire protocol is a public
standard (ACP). With Cursor added as
the second ACP-speaking runtime, the shared logic now lives in
core/providers/acp/base.ts — a JSON-RPC pump, the
initialize/session/new handshake, the session/update →
AgentEvent translation, bidirectional session/request_permission
(with bypassPermissions auto-approve), and the cross-runtime
resource-block history injection. Each subclass keeps only what
genuinely differs: CLI path resolution, spawn args, optional auth step
(Cursor's authenticate({methodId:"cursor_login"})), vendor extension
notification prefixes (_x.ai/* for Grok, cursor/* for Cursor), and
runtime-specific tool-call unwraps. Cross-runtime resume serialises
SNA's canonical history into a single ACP resource block on the first
session/prompt — see
Cross-runtime history replay
on the feature matrix page.
Cursor uses the user's existing cursor-agent login (macOS Keychain entries
cursor-access-token / cursor-refresh-token) — and therefore the
Cursor subscription. No CURSOR_API_KEY is required; SNA passes the
spawn's environment through unchanged, and the child process picks up
the same credentials the user would use interactively. Reasoning level
is encoded into the model id itself (gpt-5.3-codex → gpt-5.3-codex-high
for level 4); applyCursorReasoning in reasoning-level.ts does that
rewrite, and models outside the effort family (composer-*, auto, etc.)
are left unchanged.
RuntimePool
prepare(opts, provider): eagerly warm up a daemon for a known cwd.findExisting(spawnOpts): strict match for session spawns (config must align).findCompatible(provider, cwd): loose match for one-shotcompletion()/runOnce()(ignores MCP, hooks, permission mode).shutdown(handle): graceful teardown when refcount hits zero.
Claude Code does not pool. Its CLI is stateless, so per-call spawns are competitive with daemon-resume for typical session granularity.
For why SNA keeps high-fidelity native runtime adapters instead of treating ACP as the only runtime layer, see SNA and ACP.
Cross-runtime knobs
Two latency knobs flow through every runtime:
reasoningLevel: 0..5: runtime-agnostic, lightest to heaviest. Translates to Claude Code's--effort, Codex'smodel_reasoning_effort, Grok Build's--effort, and Cursor's model-id suffix (e.g.gpt-5.3-codex-high) via reasoning-level.ts. OpenCode currently ignores this field.providerOptions.serviceTier: Codex-only. Mirrors Codex's/fastslash command (values:"priority","flex","batch"). Intentionally not auto-mapped to Claude Code because Claude Code's/fastis a different MODEL variant with separate billing.
For a per-feature compatibility view across all runtimes, see Feature × runtime matrix.