Migrating from DeepPath internals¶
This page is for engineers migrating an existing DeepPath-internal agent
(in deeppath/, deeppath-api/, or deeppath-agent/) onto the
extracted steerable-framework. New users — read Getting Started instead.
TL;DR¶
- Wire types: drop your local copies → import from
@steerable/agent-protocol(TS) orsteerable-agent-protocol(Py). - Harness primitives (policy / budget / retry / completion / safety): drop your local helpers → import from
steerable-agent-harness(Py canonical) or@steerable/agent-harness(TS facade). - Runtime (LLM provider / tool router / storage / transport): adopt
steerable-agent-runtimeinterfaces; in-process implementations stay in your repo. - Desktop business logic: stop maintaining a TS twin — embed
steerable-sidecarin Electron and call the Python harness/runtime over JSON-RPC. The TS UI indeeppath-agentkeeps working unchanged. - UI: opt in incrementally via
@steerable/agent-ui. The reference cutover is the dev-only/dev/framework-previewpage indeeppath/apps/web.
Architectural pivot you should know about¶
The original extraction plan dual-implemented every tier in TS+Py. We pivoted to Python-only Tier 2/3 + TS-only Tier 4 because:
- The web app (Next.js) and the FastAPI server already shared protocol types — the TS Tier 2 logic was just duplicated effort.
- The Electron desktop app needed to share business logic (not just types) with the FastAPI server.
- Maintaining two implementations of policy/budget/retry/tools/storage in TS+Py guaranteed drift over time.
- Embedding a portable Python sidecar in Electron lets the desktop app call the same harness + runtime the server uses, with zero drift.
You'll see this reflected throughout the migration — the TS facade for the harness still exists, but it's a parity-test surface, not a production code path.
Package mapping¶
| What you had (DeepPath-internal) | What you import now |
|---|---|
deeppath/apps/web/src/types/{sse,tool,chat}.ts |
@steerable/agent-protocol types |
deeppath-api/app/services/agentic/protocol/* |
steerable_agent_protocol |
deeppath-api/app/services/agentic/harness/{policy,budget,retry,...}.py |
steerable_agent_harness |
deeppath-api/app/services/agentic/runtime/* |
steerable_agent_runtime |
deeppath-agent/src/harness/* (TS) |
Replaced — call into the sidecar via STEERABLE_USE_SIDECAR=1 |
deeppath-agent/src/llm/* (TS) |
Replaced — SidecarLlmProvider proxies to Python |
deeppath/apps/web/src/components/chat/ChatPanel.tsx |
Future — @steerable/agent-ui ChatPanel. Not yet swapped in production. |
Step-by-step migration order¶
We did this in 8 phases (P1 → P8) and recommend the same shape for any downstream consumer.
1. Imports only — protocol (1 PR per repo)¶
Replace every local definition of SSEEvent, ToolCall, ToolResult,
ChatMessage, ChatAgent, CommandSafetyPattern, AgentSession,
HarnessTrace, TraceSpan, TraceEvent with imports from the
respective protocol package.
// before
import type { SSEEvent } from '@/types/sse';
// after
import type { SSEEvent } from '@steerable/agent-protocol';
# before
from app.services.agentic.protocol.sse import SSEEvent
# after
from steerable_agent_protocol import SSEEvent
2. Imports only — harness (Python first)¶
In deeppath-api, replace local policy.py, budget.py, retry.py,
completion.py, safety_patterns.py imports with the framework's:
# before
from app.services.agentic.harness.policy import decide_tool_mode
# after
from steerable_agent_harness import decide_tool_mode
Keep the local file for one release as a re-export shim if you have many call sites.
3. Runtime adapters¶
steerable-agent-runtime ships interfaces (LLMProvider,
ToolRouter, StorageAdapter, TransportAdapter) and reference
implementations. Your repo can:
- Use the references as-is for green-field code (e.g.
OpenAICompatProvider). - Subclass / replace them where you need product-specific behavior (e.g. your custom
SqlAlchemyStorageschema). The interface is atyping.Protocolso duck-typing works.
The harness loop (run_agent_step etc.) doesn't live in the runtime —
that's intentionally left to your product. You compose protocol +
harness + runtime however your orchestration needs.
4. Desktop: enable the sidecar¶
In deeppath-agent, the cutover is gated behind an env var so we can
bake the change before flipping the default:
When enabled, Electron's main process spawns
python -m steerable_sidecar (or the bundled portable CPython binary in
production builds) and routes:
LLMProvider→SidecarLlmProvider→ JSON-RPCagent.chat.streamToolRouter→ bridge to sidecartool.invoke- (Future)
StorageAdapter→ sidecaragent.session.*
The legacy in-process TypeScript OllamaProvider /
OpenAICompatProvider / LocalToolRouter remain wired as fallbacks
during the rollout. Remove them once the sidecar has shipped to your
canary users.
5. UI: incremental, not big-bang¶
deeppath/apps/web/src/components/chat/ChatPanel.tsx is a heavy
production surface (multi-agent, variants, automation hooks, loader
hints). Don't try to swap it in one PR. The pattern that worked:
- Add
@steerable/agent-uias alink:dependency. - Add the framework's CSS variables to
globals.cssso its components inherit the existing Tailwind v4 theme. - Implement
framework-sse-bridge.tsto convert your existing wire format to the framework'sSSEEvent. - Ship a dev-only
/dev/framework-previewpage that mounts the framework'sChatPanelagainst the real backend SSE. - Iterate on the framework's UI surface using the preview as the canary.
- Plan the production swap for v0.2.x once the framework
ChatPanelcovers the variants/multi-agent feature set.
Naming differences (TS vs Py)¶
- TypeScript fields are camelCase (
maxToolCalls,needsFollowup); Python pydantic models keep the same wire field names (no aliasing). - Python helpers expose snake_case function names (
decide_tool_mode); TypeScript wrappers use camelCase (decideToolMode). Both implementations answer identically againsttests/conformance/cases/.
Validation flow after migration¶
# Wire-level: regenerate codegen + drift check
pnpm gen
pnpm check:drift
uv run python scripts/generate_py.py
uv run python scripts/check_drift.py
# Tier 1+2 conformance against golden cases
pnpm -r test
uv run pytest tests/conformance/py
# Sidecar smoke test
uv run --package steerable-example-sidecar-roundtrip \
python -m steerable_example_sidecar_roundtrip.main
# (Desktop) end-to-end against the embedded sidecar
cd ../deeppath-agent
pnpm test --filter sidecar
Common gotchas¶
additionalPropertiesmismatches. The protocol envelopes intentionally leaveadditionalProperties: trueso old clients can ignore new fields. Don't add apassthroughplugin in your validators — it'll mask spec violations.- Don't import the TS harness facade in production. It exists for parity tests only; production TS code should stay on the protocol layer or call the sidecar.
- Sidecar boot timing.
__SIDECAR_READY__:is sent on stderr. Don't gate readiness on stdout activity — the first stdout byte is the response to your first JSON-RPC frame, which can deadlock against a parent waiting for a stdout signal. - Bundle size. The Electron-bundled CPython is ~270 MB after
pruning (CI enforces < 300 MB per platform). If you add Python deps,
re-run
scripts/sidecar/measure_size.pyand verify the prune unit-test still passes.
Reference deltas¶
The DeepPath repo's own framework adoption commits are tagged
framework/p1-protocol, framework/p2-harness, …, framework/p7-ui.
They're the canonical worked example for every step above.