Skip to content

Steerable

The model-quality layer that makes local, quantized, and cheap models behave.

Recovers and executes malformed tool calls · vetoes bad completion drafts · catches fabricated data · self-calibrates token estimates. Plus the plumbing you'd otherwise rewrite: typed wire protocol · pluggable LLM runtime · embeddable Python sidecar · headless React chat UI. Pick any subset, skip the rest — every layer ships on its own.

License: Apache 2.0 CI npm: @steerable/agent-ui PyPI: steerable-agent-runtime

80.7% Steerable + GLM-5.3-Flash TB 2.1 · 6-run mean @max · this repo

+7.3 vs Pi on the same Flash model Pi + GLM-5.3-Flash · 73.4%

$0.146 per solved task six-run mean · ~$10.50 / catalog-89

Why Steerable

Every agent SDK assumes the model emits clean, structured tool_calls. Local, quantized, and cheap models don't. Steerable is the model-quality layer that closes that gap — plus the plumbing layers you'd otherwise rewrite, each shippable on its own.

The model-quality layer

Local, quantized, and cheap models break the structured-tool_calls assumptions every SDK makes. Steerable recovers and executes malformed calls (MiniMax XML, DeepSeek <function=>, markdown), vetoes completion drafts (accept / retry / narrate), judges grounding, and self-calibrates token estimates. Why this is the differentiator.

One wire protocol

One JSON Schema → generated TypeScript types + Pydantic models. content, tool_call, tool_result, error, done, budget_exhausted — all standardised, with a conformance suite keeping both SDKs byte-compatible. All 8 published packages share one lockstep X.Y.Z; npm tarballs ship sigstore provenance attestations.

Pure-function harness

Policy, budget, retry, completion, tracing, safety patterns. Zero I/O coupling — drop into FastAPI, Celery, or a notebook. Unit and golden tests in CI.

Pluggable runtime

One LLMProvider interface across four wire protocols — OpenAI-compatible chat/completions (Ollama, vLLM, DeepSeek, Groq, …), OpenAI Responses, Anthropic-native, Gemini-native — plus a live gateway model catalog, per-vendor sampling presets, @tool decorator, ToolRouter, SSE-over-HTTP and stdio JSON-RPC transports.

Embeddable sidecar

A portable, signed CPython binary speaking JSON-RPC over stdio (34 methods). Ship local LLMs inside Electron / Tauri / Wails — macOS notarised, Windows code-signed — with an OS sandbox, per-host egress proxy, and plugin lifecycle built in.

Headless React UI

7 components + 14 rich cards + 3 hooks + Tailwind preset. Every state covered by Storybook, axe a11y, and visual-regression baselines locked in CI.

Terminal-Bench 2.1

The score of record is still Steerable + GLM-5.3-Flash 80.7% at reasoning_effort=max (six-run mean, SD 2.9). The figures below are a separate n=1 protocol: GLM-5.3-Flash and DeepSeek-V4-Flash 0731 at high, Qwen3.8-27B at medium, five harnesses, one OpenRouter account, Harbor catalog-89. Timeout, error, and missing trials count as fail. Terminus is omitted. DSH is on both figures (SHA c576a88); its dollars are OpenRouter analytics tokens for the catalog window, scored with the same pinned-host list formula as the twelve Harbor cells. Protocol and run list: Evals.

Same protocol · three Flash models · five harnesses

Grouped bars are pass/89. Color is the harness. This is n=1 at high/medium, not the 80.7% @max six-run.

0 25 50 75 89 79.8 73.0 77.5 56.2 66.3 GLM @high 78.7 69.7 44.9 61.8 77.5 DS0731 @high 68.5 62.9 70.8 64.0 60.7 Qwen @medium Steerable Pi Claude Code Codex DSH

Mean = pass/89. GLM/DS @high pin z-ai / alibaba; Qwen @medium pin alibaba (Pi maps medium to --thinking high). DeepSeek is 0731 GA, not the 0423 preview. Steerable/Pi/CC/Codex SHA 6f70bf5 + fill 19213d7. DSH SHA c576a88 (Qwen missing winning-avg-corewars counts as fail).

Cost × score · fifteen cells · same list-price formula

Color is the harness. Shape is the model: circle GLM, square DeepSeek, diamond Qwen. Larger marker is Steerable. Dollars are OpenRouter list on the pinned host, not the published GLM @max $0.146 axis. Green DSH markers use OpenRouter analytics tokens for the catalog window (Harbor DSH trials still write no tokens).

$0.10 $0.25 $0.50 $1 $2 $5 45 55 65 75 80 $ per solved task (log) · OpenRouter list on pinned host TB 2.1 score (n=1 pass / 89) Pi · GLM 73.0% · $0.12 Claude Code · GLM 77.5% · $0.23 Pi · DS 69.7% · $0.28 Steerable · GLM 79.8% · $0.61 Claude Code · DS 44.9% · $0.85 Codex · GLM 56.2% · $1.06 Claude Code · Qwen 70.8% · $1.68 Steerable · DS 78.7% · $2.18 Codex · Qwen 64.0% · $2.60 Pi · Qwen 62.9% · $2.87 Steerable · Qwen 68.5% · $4.12 Codex · DS 61.8% · $4.42 DSH · GLM 66.3% · $0.12 DSH · DS 77.5% · $3.48 DSH · Qwen 60.7% · $3.36 Steerable Pi Claude Code Codex DSH circle GLM · square DeepSeek · diamond Qwen

List dollars from per-trial Harbor result.json tokens on the pinned host (GLM Z.AI $0.15/$0.50/cache $0.03 per 1M; DeepSeek Alibaba $0.352/$1.056; Qwen Alibaba $0.425/$2.55). Cache billed at cache-read when n_cache_tokens ≤ n_input_tokens (DeepSeek/Qwen cache-read uses the input rate, matching the other twelve cells). Codex GLM/Qwen and Pi Qwen include GitHub 360-minute unfinished snapshots scored as fail. DSH tokens are OpenRouter analytics for 2026-09-14 13:14–20:00 UTC (catalog window; cheap-12 that morning excluded); Harbor DSH trials still write no tokens. Not mixed with the published GLM @max $0.146.

Same GLM-5.3-Flash, same Harbor catalog-89 protocol: Claude Code 83.1% at $0.162 per solved task, Steerable 80.7% at $0.146, Pi 73.4% at $0.061. About $10.50 per full 89-task run — usable as a coding agent, not a demo loop.

Quickstart — pick your path

uv add steerable-agent-protocol steerable-agent-harness steerable-agent-runtime
from steerable_agent_runtime import ToolRouter, tool
from steerable_agent_protocol import ToolCall

router = ToolRouter()

@tool(router=router, description="Read a file by path")
async def read_file(path: str) -> dict:
    return {"path": path, "content": open(path).read()}

result = await router.dispatch(
    ToolCall(id="c1", name="read_file", arguments={"path": "README.md"})
)
# result.success, result.data, result.error — all typed.
pnpm add @steerable/agent-protocol @steerable/agent-ui
import { ChatPanel, useChatStream } from '@steerable/agent-ui';

export function Chat() {
  const { messages, send, isStreaming } = useChatStream({
    endpoint: '/api/chats/123/send',
  });
  return <ChatPanel messages={messages} onSubmit={send} isStreaming={isStreaming} />;
}
# Bundle the sidecar binary into resources/python-runtime/<platform>/
# TS hosts: link:../steerable-framework/packages/agent-runtime/ts
import { AgentRuntime } from '@steerable/agent-runtime';

const runtime = new AgentRuntime({ sidecarPath });
await runtime.start();   // spawn + lifecycle.ready handshake

const stream = await runtime.chatStream({
  provider: 'openai_compat',
  model: 'glm-5.3-flash',
  baseUrl: process.env.GATEWAY_BASE_URL!,
  apiKey: process.env.GATEWAY_API_KEY!,
  messages: [{ role: 'user', content: 'hi' }],
});
for await (const event of stream.events) { /* typed SSEEvent stream */ }

Architecture

Five tiers, strict no-upward-imports rule. Tier N never imports Tier N+1 — adopting any layer means inheriting only the layers below it.

graph BT
  T5["<b>Tier 5 · Host Shell</b> (TypeScript, private)<br/>@steerable/agent-shell · agent-shell-web · pack-sdk<br/>Electron main + headless HTTP server · local backend ·<br/>sidecar supervision · product-neutral renderer SPA"]

  T4["<b>Tier 4 · UI</b> (TypeScript / React)<br/>@steerable/agent-ui<br/>Hooks: useChatStream · useToolCallStatus · useAgentSession<br/>Components: ChatPanel · MessageList · AgentSelector · ModelSelector ·<br/>OrchestrationPlanCard · ToolCallRenderer · SSEStreamView + 14 cards<br/>Tailwind preset (dark-mode aware)"]

  T3S["<b>Tier 3 · Sidecar</b> (portable CPython binary)<br/>steerable-sidecar<br/>JSON-RPC over stdio · 34 methods · graceful shutdown<br/>agent.chat.stream · tool.invoke · agent.session.* · plugin.* · models.list"]

  T3R["<b>Tier 3 · Runtime</b> (Python only)<br/>steerable-agent-runtime<br/>CoreLoop · LLMProvider (OpenAI-compat / Responses / Anthropic / Gemini)<br/>ToolRouter · StorageAdapter · TransportAdapter (FastAPI SSE)"]

  T2["<b>Tier 2 · Harness</b> (Python — single source of truth)<br/>steerable-agent-harness<br/>Policy · Budget · Retry · Completion · Tracing · Safety<br/><i>thin TS facade @steerable/agent-harness exists for parity tests</i>"]

  T1["<b>Tier 1 · Protocol</b> (TypeScript + Python, lock-step versions)<br/>@steerable/agent-protocol · steerable-agent-protocol<br/>SSEEvent · ChatMessage · ToolCall · ToolResult · AgentSession<br/>HarnessTrace · TraceSpan · SidecarRequest/Response/Notification"]

  T5 -->|"spawns + supervises"| T3S
  T5 -.->|"renders with"| T4
  T4 -.->|"shared wire types"| T1
  T4 -->|"spawns + JSON-RPC stdio (Electron)"| T3S
  T3S -->|"embeds"| T2
  T3S -->|"validates against"| T1
  T3R -->|"embeds"| T2
  T3R -->|"validates against"| T1
  T2 -->|"imports types"| T1

  classDef tier fill:#f4f4f5,stroke:#3f3f46,color:#18181b,rx:6,ry:6,padding:12;
  class T1,T2,T3R,T3S,T4,T5 tier;

What's in the box

Package Tier What you get
@steerable/agent-protocol · steerable-agent-protocol 1 SSEEvent envelope, ToolCall / ToolResult, ChatMessage, sidecar JSON-RPC types — codegen from spec/, drift-checked in CI
@steerable/agent-harness · steerable-agent-harness 2 decide_tool_mode, consume_budget, next_retry_delay_ms, is_terminal_result, command-safety patterns
steerable-agent-runtime · @steerable/agent-runtime (TS, source-only) 3 CoreLoop + LLMProvider adapters (OpenAI-compat / Responses / Anthropic / Gemini), ToolRouter + @tool, storage & transport adapters (FastAPI SSE, stdio JSON-RPC); the TS package owns the sidecar process lifecycle for pure-TypeScript hosts
steerable-sidecar · steerable-egress-proxy 3 Portable CPython binary — boots in <1s, macOS notarised, Windows signed — plus the bundled per-host CONNECT allow-list egress proxy
@steerable/agent-ui 4 ChatPanel, MessageList, AgentSelector, ModelSelector, OrchestrationPlanCard, ToolCallRenderer, SSEStreamView + 14-card /cards subpath + hooks + Tailwind preset
@steerable/agent-shell · agent-shell-web · pack-sdk 5 Electron + headless host shell, local backend, sidecar supervision, product-neutral renderer — published to npm (dist / source / pure-types respectively)

How it compares

Steerable isn't the only way to ship an agent. The short, honest version against the frameworks you're probably also evaluating:

Dimension Steerable OpenAI Codex DeepSeek Harness LangGraph OpenAI Agents SDK Claude Code / Agent SDK
Form factor Layered library — the same loop in a desktop sidecar and a server Product family: CLI, IDE, desktop, cloud — Rust core Plugin-based harness (TS) on Cordis Orchestration library — you write the graph Lightweight framework over the Responses API One closed-source bundle behind both the CLI and the Agent SDK
Loop / events CoreLoop + structured LoopEvent taxonomy; mid-turn agent.chat.steer Session-owned turn loop; interrupt/abort; 12-event hooks Inbox-driven ReactLoop; steer / inject / followup Interrupts at node boundaries Handoffs + guardrails; no mid-run steer Mid-turn fold queue; hooks can veto continuation
Sandbox Seatbelt + bwrap/Landlock, fail-closed; command classifier; per-host egress proxy Approvals + ExecPolicy + Seatbelt/Landlock + Guardian sandbox.confine, fail-closed None built-in Guardrails; no sandbox Permission modes; Seatbelt/bwrap opt-in, fail-open
Model quality Recovers malformed tool calls; completion veto; grounding; token calibration Assumes structured tool_calls Assumes structured tool_calls Substrate — you own the loop Built for frontier models Built for frontier models
Maturity 0.6.x; one production consumer Massive real-world usage Pre-release (0.1.x); internal use Widely adopted in production Production, OpenAI-tied Production; Anthropic models via first-party, Bedrock, Vertex, Foundry, Gateway

Where Steerable genuinely differs, where it lags, and how to choose: Full comparison.

Who's using it

DeepPath — web (agent-protocol + agent-ui), API (all three Python packages), Electron desktop (sidecar + UI). The framework was extracted from this codebase and is dogfooded back into it on every release.

Explore