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
Why Python is canonical: the harness is consumed by the FastAPI server
(deeppath-api), the sidecar runtime, and the optional in-process Tier 3
runtime. 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 also owns the production CoreLoop (loop.py) — 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 a binary
that any UI shell can spawn. Wire format documented at
Sidecar spec. Used today by the Electron desktop app
(deeppath-agent) so it can share 100% of its agent business logic with
the FastAPI backend without forking.
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¶
This was a project pivot decision; see Migration guide § all-py-sidecar. Short version:
- 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 Python CoreLoop, cross-language conformance holds by
construction; a test gates that the TS API surface covers every method
the sidecar registers.