Framework Comparison¶
How Steerable compares to other agent frameworks and products — including
where we lag. Steerable is an early-stage project (0.6.x); this page is
written to help you decide, not to declare a winner.
Honesty policy
Every claim below links to a spec page or names a shipped artifact. Where Steerable is behind, the table says so. If you find a stale row, open an issue — this page is reviewed on every release.
How the Claude Code column was sourced
Claude Code is closed source, so its column is not quoted from marketing
copy: it was read out of the shipped artifact. The 2.1.263 npm package is
an installer stub whose real payload is eight platform-specific native
binaries; the darwin-arm64 one embeds its full application bundle as
plain-text JavaScript, and the package also publishes sdk-tools.d.ts —
unminified TypeScript declarations for 40 built-in tool input/output types.
Identifiers are minified, but string literals, environment variable names,
error messages, tool schemas and system-prompt text are intact. Facts below
are taken from that bundle at version 2.1.263. The same bundle serves the
CLI and the Agent SDK — it carries three system-prompt variants, one of
which reads "running within the Claude Agent SDK" — so they share a
column rather than pretending to be separate products.
The short version¶
Most agent frameworks answer one of two questions: "how do I orchestrate agent logic?" (LangGraph, OpenAI Agents SDK) or "how do I ship a coding agent product?" (Claude Code, Codex, DeepSeek Harness, Pi). Steerable answers a third: "how do I ship the same agent loop into a desktop app and a server, over one typed protocol, without rewriting the plumbing twice?" It is a layered library — protocol, harness, runtime/sidecar, UI — where each tier is independently adoptable.
At a glance¶
| Dimension | Steerable | OpenAI Codex | DeepSeek Harness | Pi | LangGraph | OpenAI Agents SDK | Claude Code / Agent SDK |
|---|---|---|---|---|---|---|---|
| What it is | Layered library (4 tiers, independently published) | Product family: CLI/TUI, IDE, desktop, cloud — Rust core | Plugin-based harness (TS) on vendored Cordis; everything is a plugin | Minimal-core coding agent CLI (TS, 11 lockstep packages); omits MCP, subagents, and a permission system by design — extensions add them | Low-level orchestration library (Py/JS): state graphs | Lightweight agent framework (Py/TS) over the Responses API | Closed-source product shipped as one bundle behind both the CLI and the Agent SDK |
| Loop control / steering | CoreLoop single-agent step loop; structured LoopEvent taxonomy (15 kinds); agent.chat.steer RPC mid-turn; fork |
Session-owned turn loop; interrupt/abort; 12-event hook engine | Inbox-driven ReactLoop; steer / inject / followup delivery | agent-loop.ts turn loop; steering and follow-up queues polled between turns; tool calls parallel by default, per-tool sequential opt-out |
You design the graph; interrupts at node boundaries | Handoffs + guardrails; no mid-run steer | Mid-turn "fold" queue absorbs messages between tool rounds, with a separate follow-up queue when folding is suspended; Stop/PostToolUse/PreToolUse hooks can return preventContinuation to veto turn completion, capped at 8 consecutive blocks; --max-turns |
| Tool execution | ToolRouter + @tool; host reverse channel — desktop tools run in the host process (visible terminal); native stdio MCP client wired on sidecar (chat.stream mcp), headless (--mcp), and ACP paths. Programmatic tool calling ships two ways: run_code (single-shot Python function body, sandboxed child, nested tools over stdio) and a session-style JS PTC (run_js / wait_js — a long-lived Node worker with per-session store/load KV, yield/wait cells, and a Promise tool bridge). The desktop adds two product families on top: cross-turn background Tasks (task_run / task_status / task_result on a host task table, with a task panel UI) and git-worktree isolation (worktree_create / worktree_list / worktree_remove, composable with tasks and mergeable/discarded from the UI) |
Unified exec (PTY), MCP, parallel gating | Concurrency-safe tool pool, MCP client | 8 built-in tools; extensions register tools at runtime with no reload; no MCP | ToolNode inside your graph |
Function tools, MCP, hosted tools | 40 built-in tool types (sdk-tools.d.ts), lazy exposure via ToolSearch + defer_loading, tool concurrency capped at 10; MCP over stdio/SSE/HTTP with user/project/org scoping, OAuth, and a 25k-token output cap |
| Safety model | Two layers: OS sandbox for the sidecar (macOS Seatbelt with a deny-by-default write whitelist; Linux bwrap, falling back to Landlock; no Windows rewriter) + a command classifier with dozens of rules, consent gate, plan-mode hard block. Egress is per-host by default: a bundled CONNECT allow-list proxy (steerable-egress-proxy) starts on boot (auto-degrading to port-only Seatbelt when a system/ambient proxy is present), the sidecar and shell tools route HTTP(S) through it, and the web tools' domain allow-list and the proxy's CONNECT list are one source. With the proxy live, shell egress pins to the localhost proxy endpoint, so Seatbelt reports full enforcement and requireFull defaults on |
Approval policies + ExecPolicy + platform sandbox (Seatbelt/Landlock) + Guardian second-pass review | sandbox.confine (bwrap/Landlock/Seatbelt), fail-closed |
None built-in — tools run with host user permissions; project trust gate only; containerization documented externally | None built-in — your infrastructure | Guardrails; no sandbox | Six permission modes (default/plan/acceptEdits/auto/dontAsk/bypassPermissions), allow/deny/ask rules from eight sources, defer as a fourth per-call state, headless fail-closed deny; real Seatbelt/bwrap confinement with a domain allowlist, but opt-in and fail-open — failIfUnavailable defaults to false, so a missing backend runs commands unconfined |
| Protocol surface | One JSON Schema → codegen TS types + Pydantic models, lockstep-released; sidecar JSON-RPC (34 methods); conformance suite keeps both SDKs byte-compatible | app-server JSON-RPC (v2) with generated TS types; single-language (Rust) core | JSON-RPC SDK + ACP server; typed session-event map | CBOR-framed pi-protocol (experimental server/client) plus --mode rpc JSONL over stdio; no cross-language codegen |
LangGraph Platform REST/SDK | OpenAI Responses / Realtime APIs | --print --input-format/--output-format stream-json plus ~25 control-request subtypes, so a host can answer permission prompts (can_use_tool), interrupt, swap the model mid-session (set_model), and hot-reload plugins; typed via the published sdk-tools.d.ts (TypeScript only) |
| Skills ecosystem | Layered disclosure: eager base skills in the system prompt, catalog skills loaded on demand via a skill tool; SKILL.md-compatible frontmatter (disable-model-invocation interop) |
Skill files (.codex/skills) |
Skill provider registry + catalog/loader tool | Agent Skills (SKILL.md) from ~/.pi/agent/skills/ and .pi/skills/, exposed as /skill:name |
None built-in | None built-in | Agent Skills plus a plugin runtime: a plugin contributes commands, skills, agents, hooks, MCP and LSP servers, output styles, themes, workflows and background monitors, from six install sources, hot-reloadable via reload_plugins, with a marketplace schema, a blocklist and an impersonation check |
| Persistence / sessions | Append-only JSONL record per session via TraceRecorder + resume projection; fork with seed provenance and cycle-guarded lineage walking (fork_record / resolve_fork_seq — regenerate forks at the last user turn, the old tail stays intact); agent.session.tree returns the full family tree (cousins and all, depth/node-capped) and the desktop renders it as a /tree-style modal branch view with single-hop switching to any node; CompactionBoundary carries pre/post token counts across compactions; cancelled turns still persist traces |
Rollout files as source of truth; resume + fork | Event-sourced session log (SQLite); fork | JSONL session tree keyed by cwd; -c / -r / --fork; in-session /tree branch UI; optional SQLite backend on the library path |
Checkpointers (SQLite/Postgres/…) | Sessions (memory) | JSONL transcript per session under ~/.claude/projects/<cwd>/, parentUuid chain with isSidechain branches, --fork-session, --resume-session-at, and a logical_parent_uuid that survives compaction |
| Deployment form | Dual form: embeddable signed sidecar binary (desktop: Electron/Tauri/Wails) + in-process FastAPI (server) | Local CLI/desktop + hosted cloud | Library + headless/ACP binaries | npm packages + Bun standalone binaries; library SDK via createAgentSession |
Self-host or LangGraph Platform | Your infra + OpenAI platform | Eight platform-specific native binaries (~200 MB each) behind an installer stub; no user-visible runtime to install |
| Maturity | 0.6.x; one production consumer (DeepPath); small traffic |
Massive real-world usage | Pre-release (0.1.x RC); internal use |
Lockstep 0.85.1 across 11 packages; patch = fixes/additions, minor = breaking, no majors |
Widely adopted in production | Production, OpenAI-tied | Production; Anthropic models only, routed across first-party, Bedrock, Vertex, Foundry and Gateway. Much of the surface sits behind server-side flags, so reading the binary tells you the default, not necessarily what is live for a given user |
Where Steerable is genuinely different¶
- Dual-form deployment, one loop. The same
CoreLoopruns embedded in a desktop app (signed, notarized sidecar binary, OS-sandboxed) and in a server process. The wire protocol is identical in both — a desktop frontend and a FastAPI backend consume the same event stream. - The loop yields structured events, not bytes. The
LoopEventtaxonomy (15 kinds) was derived from a production server's ~114 emission sites, then adopted by the desktop. Transports render wire formats — including byte-compatible rendering onto an existing frontend contract — instead of the loop printing SSE. See CoreLoop spec and the API SSE drift survey. - Cross-language contract as codegen, not parallel implementation.
spec/*.schema.jsonis the single source of truth; TypeScript types and Pydantic models are generated and drift-checked in CI. The conformance suite replays the same event fixtures against both SDKs. - The sidecar is a distribution unit. A portable, signed CPython binary that speaks JSON-RPC over stdio — your users never install Python. On macOS it spawns under a Seatbelt profile with a deny-by-default write whitelist. See Sidecar spec and Safety spec.
- Confinement is fail-closed. When a sandbox backend is unavailable the
sidecar refuses to run the command rather than running it unconfined, and
the degradation is surfaced in the UI with guidance. This is the opposite
default from Claude Code, whose sandbox is opt-in and whose
failIfUnavailablesetting defaults to false — a missing backend there prints a warning and proceeds. Our layer-1 confinement is narrower than theirs in platform reach; the difference here is which way it fails. See Safety spec.
Where Steerable lags — honestly¶
- Production volume. Codex serves massive daily traffic; LangGraph is deployed across the industry. Steerable has one production consumer and a fraction of the mileage.
- Ecosystem. LangGraph's integration catalog and community dwarf ours.
The framework-native stdio MCP client (
McpStdioClient) is now wired on the sidecar (chat.streammcpparam), headless (--mcp), and ACP paths, so MCP tools reach theToolRouterdirectly; desktop hosts may still prefer the reverse channel. What we lack is LangGraph's breadth of prebuilt integrations, not the wiring. - Extension runtime: lifecycle + hot reload, two install sources. A
PluginRegistrytracks which tools each plugin registered (via a recording router proxy), and supportsenable/disable/unload/reloadper plugin — reload re-executes the module in place and swaps its tool registrations without a sidecar restart. Two install sources ship: thesteerable.toolsimportlib.metadataentry point group and a local directory source (STEERABLE_PLUGIN_DIR, each.pya plugin with a top-levelregister(router)); thePluginSourceprotocol leaves room for a remote/marketplace source. Name collisions fail closed (first registration wins, the offender is named). What we still lack versus Claude Code is the marketplace, blocklist, and impersonation check — the runtime is there, the distribution trust layer is not. - Context compaction now ships three default paths plus one opt-in,
and both breakers — pressure-triggered, overflow-reactive, and
manual (
compact_now, the host-command path) are on by default; periodic micro-compaction (tool-result pruning) is opt-in viamicro_compact_interval_rounds(default 0, no per-chat switch). Two circuit breakers match Claude Code's pair: the failure breaker stops the pressure path after three consecutive ineffective compactions, and the rapid-refill breaker stops it after three consecutive compactions whose freed space refills within three rounds — the tripping round appends an actionable thrashing reminder (model- and UI-visible) telling the model to converge instead of re-reading folded output.pre_tokens/post_tokensestimates are recorded on everyCompactionBoundary(thecompact_boundaryobservability pattern), and a hysteresis margin (which CC does not have) keeps a borderline transcript from re-compacting every round. What we still lack is Claude Code's partial-compaction variant that preserves named conversation sections. - Structured questions reach the model end-to-end, at Claude Code's
constraints. The
ask_usertool registers sidecar-side per request, the desktop answers over the reverse channel with a rendered question card (Electron and browser-server modes alike), and answers land back in the transcript as the tool result. The schema is tightened to Claude Code'sAskUserQuestionspec: 1–4 questions, aheaderchip label (≤12 chars, derived from the question text when the model omits it), 2–4 options per select question, an explicitmultiSelect(defaulted to single-select when omitted), and an automatic "Other" free-text escape the host appends. Out-of-range payloads fail closed at the tool boundary. - Write-conflict detection is now a default-on hard gate. Both the
framework file tools and the desktop local executor refuse a write or
edit to a file the model has not read this session (opt-out env var for
legacy flows), reject full-file writes when the model only saw a clipped
view (
edit_filestays allowed for targeted changes), and detect external modification between read and write by content hash — a stronger check than Claude Code's mtime compare, in the spirit of DeepSeek Harness's versioned-handle CAS. - Web tools carry the deployment policy knobs, with four search
backends.
web_search/web_fetchship with domain allow/block lists (passed to Tavily's nativeinclude_domains/exclude_domainsand enforced post-hoc for every provider, so the policy is provider-independent) and per-session call caps (search defaults to 200, Claude Code's per-session WebSearch limit parity). Search backends: Tavily (key), Brave Search (STEERABLE_WEB_SEARCH_PROVIDER=brave+BRAVE_SEARCH_API_KEY), host-delegated OpenAI hosted search, and an explicit no-key DuckDuckGo lite backend (STEERABLE_WEB_SEARCH_PROVIDER=ddg; not a silent fallback). What we still lack is a self-hosted usage-accounting service, and Harbor evals run--no-web-toolsregardless. - Provider compatibility is data, now including per-model optimal
parameters. Four wire protocols ship: OpenAI-compatible
chat/completions (
OpenAICompatProvider— covers OpenAI, Ollama, vLLM, DeepSeek, Groq, Mistral, Moonshot, Together and the rest of the chat/completions ecosystem), OpenAI Responses (OpenAIResponsesProvider— the item-based wire where the o-series / gpt-5 reasoning models expose their full capability set, withstore: false+ encrypted-reasoning round-trips; also serves xAI), Anthropic-native (AnthropicProvider), and Gemini-native (GoogleGenAIProvider—generateContent/streamGenerateContent, where thinking config and cached content are first-class). Vendor wire divergences are flag entries (PROVIDER_COMPAT_HOSTS), and a preset table (llm.presets) fills vendor-documented sampling optima for the open-weight families — DeepSeek (0.0 for coding, nothing for the fixed-1.0 reasoner), Qwen3 (0.6 / 0.95 /top_k20), GLM (1.0 / 0.95), Llama (0.6 / 0.9), gpt-oss (1.0 / 1.0 / effortmedium), MiniMax (1.0 / 0.95 /top_k40) — keyed by base-URL host and model leaf, applied only where the caller left the field unset, with compat flags still gating what may be sent. What we still lack is Bedrock Converse (the enterprise AWS path; boto3-weighted, and Bedrock's OpenAI-compatible proxy covers the interim) and a remote model catalog with ETag-cached updates like Codex's. - Sandbox coverage. Layer-1 OS confinement covers macOS (Seatbelt) and
Linux (bwrap, falling back to Landlock). Windows has no rewriter and relies
on the layer-2 classifier plus consent. Egress control is productized: the
bundled
steerable-egress-proxy(a CONNECT allow-list proxy) is on by default and holds the per-host allow-list outside the sandbox, so per-host enforcement survives sbpl's port-only limitation on macOS. On Linux the layer-1 backends have no per-host pinning (bwrap'sallowed_hostsis interface-compatible only, Landlock has none), so per-host egress there is enforced by the proxy plus the app-layer domain list, not the namespace — the UI says so honestly. When the proxy is live, shell egress pins to its localhost endpoint, Seatbelt reportsfull, andrequireFulldefaults on. A denied CONNECT is no longer a dead end: the proxy's 403 names the target, and the web tools offer a host approval round-trip — an allow lands as a session-scoped allow-list addition via the proxy's loopback control endpoint (bearer-token'd; the token never reaches sandboxed children, so a confined process cannot widen its own egress) and the fetch retries once. The approval UI defaults to deny for these prompts and hides the durable variants (the list is process-lifetime; durable grants belong to the configured domain list). The ambient-proxy fallback is disclosed in the security settings panel with its reason, not just in the main-process log. - No hosted offering. No cloud, no managed platform, no live observability stream (post-hoc OTLP export only).
- Multi-agent: one delegate tool on a shared pool. The model-facing
surface is a single
delegate_subagenttool — depth-1 by construction, namedsubagent_typeprofiles with per-profile tool domains that fail closed (tool_not_delegated), per-profile models (via the host's provider factory), per-profile system prompts seeded into the child loop, and profile descriptions advertised in the tool schema so the model picks a profile by purpose (CC.claude/agentsparity), plus opt-in concurrency. Underneath, delegations run on the framework'sAgentPool, so concurrent profiles execute in parallel under one budget and child lifecycle lands asagent.childevents hosts can render live. A six-tool orchestration family (agent_spawn/agent_send/agent_wait/agent_close/agent_list/agent_interrupt) remains available as an opt-in advanced mode for explicit coordination, sharing the same pool. The desktop additionally ships a cross-turn background Task family (task_run/task_send/task_status/task_resulton a host-side task table, with a task panel UI) — tasks run on their own sidecar stream so they outlive the parent turn, compose with git-worktree isolation, and orchestrate:dependsOnschedules a task to auto-start once its dependencies complete (fail-fast on a failed dependency), andtask_sendsteers a running task mid-flight (agent-to-agent messaging). What stays out of scope by design in the framework: planning, DAGs, and groupchat. If you want batteries-included orchestration, LangGraph or the Agents SDK will get you there faster.
Terminal-Bench 2.1¶
The score of record is Steerable + GLM-5.3-Flash = 80.7% on the 89-task catalog (six-run mean at tag tb-8e260de; see Evals), at $0.146 per solved task (~$10.50 per catalog run).
On the same model, gateway account, and Harbor protocol, Claude Code scores 83.1% at $0.162 per solved task and Pi scores 73.4% at $0.061. Z.AI's own Claude Code run of GLM-5.3-Flash is 84.3% under a 6-hour timeout — we wrap at 170 minutes. Pass rate and cost per solved task are tracked as co-equal metrics because they can move in opposite directions. Read both numbers with the six-run spread in mind: our sample standard deviation is 2.9 points, wide enough to contain the 2.4-point gap versus Claude Code.
Choosing¶
Reach for Steerable when you need a typed cross-language contract (TS + Python held byte-compatible by codegen), plan to ship to desktop / on-prem / air-gapped environments, want the same loop in your Electron app and your FastAPI backend, or want a headless React chat UI you can theme without fighting markup opinions.
Don't reach for Steerable when your agent lives entirely inside one Python process with no frontend (LangGraph or the Agents SDK are more direct), you want a hosted platform with managed tracing and evals, you need a large integration ecosystem today, or you want a finished product rather than plumbing — Claude Code and Codex ship the product.
Related¶
- CoreLoop spec — the loop and its event taxonomy
- Safety spec — the two-layer safety model
- Sidecar spec — the JSON-RPC method catalog
- Architecture — the four-tier layering rule