Cut ratatoskr's consumer agent-lifecycle routes over to worldtree-sdk (issue #20 slice-4). Five routes now flow through `ratatoskr.wt` over the SDK's `client.agents.*`, returning open-world dicts and mapping the SDK's undiscriminated `ApiError` floor by route+(status,error_code) per INV-CUT-2: - `list_agents` → `agents.list` - `get_persona_state`→ `agents.persona_state` (404 persona_not_configured / 404 agent_not_available / 403 auth_scope_denied) - `define_agent` → `agents.define` (429→Tier3QuotaExceeded(retry_after=0), 403→Tier3UserIdUnsupported, 422 layer_deferred→…) - `patch_agent` → `agents.patch` (404→Tier3AgentNotFound, 422 field_not_mutable) - `delete_agent` → `agents.delete` (404→Tier3AgentNotFound; NOT hide-existence) Rewired call-sites: the `python -m ratatoskr.tier3` CLI (define/patch/delete) and the web `_agents_endpoint` / `_persona_state_endpoint`, both catching the SDK's `ConnectFailed` transport-failure normalization. Deleted the hand-rolled paths: `sessions.list_agents` / `get_persona_state` / `AgentInfo`, and `tier3.define/patch/delete_agent` / `Tier3AgentInfo` / parse+extract helpers. model→role fold (scope B): the define/patch response echoes `role` (spec 1.2 / b128), read off the open-world dict; `LocalAgentEntry.model`→`.role`, local-index schema v1→2 (old index discarded, no-backwards-compat). The Tier-3 caller-semantic exceptions move to `sessions.py`: running the CLI as `__main__` while `wt` imports `ratatoskr.tier3` bound two copies of each exception class, so a raised `Tier3AgentNotFound` escaped the CLI's `except` as an uncaught traceback. Homing them in `sessions` (never `__main__`) makes the class identity single. The live smoke — not the unit tests, which call `main()` in-process — caught this. Error-map rows + slice-4 notes added to the cutover contract; coverage-map re-anchored. LIVE-SMOKE on personal :8081 (b128): define(thoughtful-character) → patch → list(6 agents) → persona_state(→PersonaNotConfigured mapped) → delete → index empty; non-existent-id patch via `-m` → [agent_not_found] exit 20. Suite 465 green.
Ratatoskr
A Worldtree Conversation API debug console. Runs up and down Worldtree's API surface — sessions, turns, persona, tools, admin events, Bifrost state — carrying messages between layers. Like the squirrel.
The product is the observability surface; chat is the input mechanism.
Devs run Ratatoskr against a local Worldtree to watch a turn flow through
every layer of the system, side-by-side. The interactive surface is the
web console (ratatoskr-web); a headless --send CLI drives scripted smokes.
Status
v0 scaffold. Design locked; implementation hasn't started. The dev team owns the implementation pass.
Read in this order
docs/design-brief.md— the locked design. Read this first. Every architectural decision is recorded with its rationale, the alternatives considered, and (where relevant) the operator's lock-in moment.docs/SPEC-PIN.md— what Worldtree spec version Ratatoskr is built against, where the vendored snapshot lives, and how to bump the pin.docs/conversation-api-spec.md— the vendored Worldtree spec snapshot. Read this to understand the API surface Ratatoskr consumes. Do not import anything from a Worldtree checkout — the boundary is the spec, not the code. Seedocs/design-brief.md§2.CLAUDE.md— Claude Code conventions for this repo (mostly inherited from the Corviduo template).persistent-memory.md— durable intent across context resets. Update as decisions and state evolve.
Quickstart
# 1. Environment
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
# 2. Verify the spec pin
cat docs/SPEC-PIN.md # documented Worldtree SHA + bump procedure
# 3. Tests (none yet; scaffold only)
uv run pytest
# 4. Run against a local Worldtree (Worldtree must be running)
# Interactive web console:
ratatoskr-web --host 0.0.0.0 --port 8765
# Headless CLI (scripted smoke):
ratatoskr --send "hello" --new --agent mimir --api-key "$WORLDTREE_API_KEY"
What this repo is NOT
- NOT a polished consumer for Worldtree's end-users — that's the web app.
- NOT a Worldtree admin tool — admin CLI is separate (
sessions_cli.pylives in Worldtree). - NOT a featuretracking shadow of the web app — when the Conversation API surface grows, Ratatoskr does not necessarily grow with it.
- NOT a remote-Worldtree debug client — file-tail surfaces (persona log, server log) assume local-dev posture.
The full negative-clause list lives in docs/design-brief.md §6.
Boundary rule
Ratatoskr depends on a small, fixed surface:
httpx+httpx-sse(network layer)starlette+uvicorn(the web console; thewebextra)- Worldtree's published Conversation API spec at the pinned SHA
Hard rule: no imports from a Worldtree checkout. No core.* imports,
no from worldtree.*, no shared models, no submodule of Worldtree. The
spec is the entire surface. Boundary smoke test at tests/test_no_worldtree_imports.py
enforces this in CI.
Version-skew strategy
The dev team is decoupled from Worldtree's dev team. To detect drift when Worldtree changes the API:
- Spec-version pin in
pyproject.toml(worldtree-spec-rev). Bump explicitly; bumps are a tracked action. - Recorded-SSE snapshot tests at
tests/snapshots/. Captured against a live Worldtree; replayed in CI. Re-record after every pin bump. - Conformance smoke test — boots Worldtree via Docker compose in CI, runs a one-turn happy path. Catches integration-level drift.
See docs/SPEC-PIN.md for the bump procedure.
Consumer-side discoveries
If you find a spec gap, ambiguity, or missing-but-needed endpoint while working on Ratatoskr: route the discovery back to worldtree-dev via althing rather than via PR on Worldtree directly. The separate-team boundary is intentional and helps catch spec gaps that an in-tree consumer would paper over.
Related repos
- Worldtree — the API Ratatoskr consumes
- brokkr-smithy — authored this design brief
- Skaldsong — another Worldtree API consumer (Python; render-and-aggregate pattern)
- mead-hall — another Worldtree API consumer (TypeScript; server-side broker)