Skip to content

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.BaseModel classes

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 / local
  • budget.consume_budget(...) — token / step / tool-call accounting
  • retry.next_retry_delay_ms(...) — exponential-backoff helpers
  • completion.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:

  1. The web app and the FastAPI server already shared the protocol types.
  2. The desktop app needed to share business logic, not just types.
  3. Maintaining two implementations of policy/budget/retry/tools/storage in TS+Py guaranteed drift.
  4. 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.