Architecture (Tier 1 → Tier 5)¶
Steerable is intentionally layered. Each tier has a single responsibility and a small surface, so consumers can swap or replace any layer without leaking concerns into the layers above and below.
Why five tiers, not "an SDK"¶
Most agent frameworks ship a monolithic SDK that bundles wire types, prompt construction, tool execution, storage, and React widgets into one package. That makes the smallest examples fit on a slide — but couples upgrades and forces you to take all-or-nothing dependencies. Steerable splits the five concerns that change at very different rates:
| Tier | Changes when… | Versioning impact |
|---|---|---|
| 1 | Wire protocol evolves (new event type, etc.) | Lock-step npm + PyPI bump |
| 2 | Harness rule changes (new policy mode, etc.) | PyPI bump (TS facade auto) |
| 3 | Runtime adapter added (new LLM provider) | Independent PyPI bump |
| 4 | UI components added / refactored | Independent npm bump |
| 5 | Host shell gains a product surface | Lock-step npm bump (public) |
Tier 1 — Protocol¶
Packages: @steerable/agent-protocol · steerable-agent-protocol
Pure data types. No I/O, no logic, no LLM client code. Defined in JSON
Schema under spec/, codegen produces:
- TypeScript:
interfaces + Zod validators - Python:
pydantic.BaseModelclasses
Drift between the two SDKs is detected by CI (scripts/check_ts_drift.mjs
+ scripts/check_drift.py) so a change to the schema can never produce
inconsistent types in production.
Lock-step version — both SDKs always release at the same version.
Tier 2 — Harness¶
Packages: steerable-agent-harness (Python — source of truth) ·
@steerable/agent-harness (TypeScript — thin parity facade)
Pure functions over Tier 1 types. No I/O, no I/O-adjacent state. Topics covered:
policy.decide_tool_mode(name)— read / safe_write / destructive / localbudget.consume_budget(...)— token / step / tool-call accountingretry.next_retry_delay_ms(...)— exponential-backoff helperscompletion.is_terminal_result(...)— should the loop stop?tracing.TraceSpan— span data structure (storage is Tier 3)safety_patterns.classify_shell_command(...)— shell command risk grading
Python is canonical because the harness is consumed by server and sidecar
runtimes. The TypeScript facade exists so cross-language conformance
tests can verify both SDKs answer identically against the
tests/conformance/cases/ golden inputs — but no production TS code
needs the harness directly today.
Tier 3 — Runtime¶
Package: steerable-agent-runtime (Python only)
Adapter interfaces and reference implementations:
| Interface | Reference implementations |
|---|---|
LLMProvider |
OpenAICompatProvider (covers OpenAI, Ollama, vLLM, DeepSeek, Groq, …), OpenAIResponsesProvider, AnthropicProvider, GoogleGenAIProvider |
ToolRouter |
In-process registry with @tool |
StorageAdapter |
InMemoryStorage, SqlAlchemyStorage |
TransportAdapter |
FastAPISseTransport, StdioJsonRpcTransport |
Tier 3 exposes the production CoreLoop facade backed by the pinned native engine — the
single-agent think → act → observe step loop with its structured
LoopEvent taxonomy (15 kinds), pseudo tool-call recovery, compaction,
approval/sandbox executor decorators, subagent pool, and MCP client.
Multi-agent planning, DAGs, and groupchat stay above the loop: the
framework provides the loop and the primitives; product-level
orchestration semantics remain your business logic. See the
CoreLoop spec.
Tier 3 — Sidecar (executable)¶
Package: steerable-sidecar (Python executable, packaged as portable
CPython via python-build-standalone)
A pre-wired JSON-RPC server that composes Tier 1 + 2 + 3 into an executable that any UI shell can spawn. Wire format documented at Sidecar spec. Desktop and headless hosts can use the same runtime without duplicating agent business logic.
Bundle size: < 300 MB per platform after stdlib stripping (CI enforced).
Tier 4 — UI¶
Package: @steerable/agent-ui
Headless React hooks + components, Tailwind preset. Designed so a single React tree can mount either:
- a transport that hits HTTP+SSE (web app)
- a transport that uses Electron IPC bridged to the sidecar (desktop)
without any component-level changes.
Tier 5 — Host Shell¶
Packages: @steerable/agent-shell · @steerable/agent-shell-web ·
@steerable/pack-sdk — versioned in lockstep and published to npm so
product repos can pin semantic versions. agent-shell ships its compiled
dist/; agent-shell-web ships its src/ (products compile it via the
createProductViteConfig factory and the @/ alias); pack-sdk ships pure
types/ (zero runtime). Products may still use source/link: during local
development.
The assemble-a-product tier: an Electron desktop shell (main process, IPC,
strict CSP, visible PTY) and a headless HTTP server (/api/v2/*, SSE) built
from the same HostRuntime; a local backend (chat/project/agent CRUD,
CoreLoop streaming, skill loader, subagent profiles, worktree service,
background tasks, usage/insights storage); sidecar supervision (boot, health,
egress proxy, exec sandbox, reverse approval/ask-user bridges); and a
product-neutral renderer SPA. Brand, telemetry endpoints, help links, and
data-directory names are injected by the consuming product's assembly root —
the shell:neutral CI gate fails on any product hardcoding in shell sources.
Tier 5 is how DeepPath ships its desktop app, and it
runs standalone for evaluation: pnpm agent-shell:web (headless server +
neutral web app) or pnpm agent-shell:client (Electron window).
Data flow at runtime¶
┌─────────────────┐
│ User input │
└────────┬────────┘
│
ChatMessage[user]
│
▼
┌──────────────────┐ ┌──────────────────────┐
│ useChatStream │ ◀────── │ agent-protocol │
│ (Tier 4) │ │ SSEEvent stream │
└──────┬───────────┘ └──────────────────────┘
│
│ fetch / IPC / sidecar JSON-RPC
│
▼
┌──────────────────┐
│ TransportAdapter│ ──── ToolCall ────► ToolRouter (Tier 3)
│ (Tier 3) │ ◀─── ToolResult ───
└──────┬───────────┘
│
│ consume_budget / decide_tool_mode (Tier 2)
│
▼
┌──────────────────┐
│ LLMProvider │ ──► Anthropic / OpenAI / Ollama / …
│ (Tier 3) │
└──────────────────┘
Why Python-only Tier 2 / 3¶
The split avoids maintaining duplicate business logic:
- The web app and the FastAPI server already shared the protocol types.
- The desktop app needed to share business logic, not just types.
- Maintaining two implementations of policy/budget/retry/tools/storage in TS+Py guaranteed drift.
- Embedding a portable Python sidecar in Electron lets the desktop app call the same harness + runtime the server uses, with zero drift.
The TS facade for Tier 2 stays only as a parity test surface — your production TS code should not import from it.
Official TS production entry: the embedded-sidecar runtime¶
Python is the only production implementation of Tiers 2 / 3. There is no
TS CoreLoop and there will not be one — a second loop implementation is
the drift trap this architecture exists to avoid. The supported way for a
pure-TypeScript product to run the framework in production is
@steerable/agent-runtime (packages/agent-runtime/ts): an official
runtime package that owns the sidecar process lifecycle (spawn,
lifecycle.ready handshake, health ping, bounded auto-restart, graceful
drain) and exposes CoreLoop-level API — chat stream/cancel/steer,
session create/resume/list/fork/branches, tool list/invoke, skills,
workspace edits, trace, config — over the same JSON-RPC method surface
documented in sidecar.md. Callers get the framework;
they never write subprocess management. Because the TS runtime drives
the same Rust CoreLoop through the Python sidecar facade, cross-language
conformance holds by construction; a test gates that the TS API surface
covers every method the sidecar registers.