Slice-6 of the worldtree-sdk cutover: migrate the two admin routes off the
hand-rolled paths onto the `ratatoskr.wt` adapter over `client.admin.*`, and delete
the retired code. Both are web-only (the coverage-map's `tui.py` rows were stale —
corrected to `web/server.py`).
Adapter (`wt.py`): `get_session_bifrost` → `client.admin.sessions.bifrost` (open-world
dict verbatim, any error → SessionApiFailed default); `stream_admin_events` →
`client.admin.stream_events`, re-wrapping the SDK's `AdminEvent` → ratatoskr's at the
boundary.
Decisions (contract § slice-6 notes):
- Admin auth moves from a per-call `Authorization` header override to the client's
`admin_auth` (`_wt_client(admin_key=…)`, extended this slice) — the SDK's admin.*
routes use the provider, not a header.
- `AdminEvent` re-wrap (chosen over yield-through): the SDK's `admin_id`(nan)/None-able
`type`/`data` diverge from ratatoskr's `id`/`type`/`data` that the web filter reads;
re-wrapping (nan→0, None→""/{}) degrades the open-world None/nan ONCE at the adapter
and keeps the web endpoint + `_admin_event_matches_web` + the `AdminEvent` domain type
unchanged (preserves the web surface). Rejected: yield SDK events + rewire the web
filter (heavier churn, scattered hardening).
- Admin-stream error map: a NON-200 open raises `ApiError("admin_stream_failed")`
(NOT `ConnectFailed`) → SseConnectFailed; `ConnectionDropped` (connect-time OR
mid-stream/resumable-EOF) → SseConnectionDropped. The web integration test caught the
ApiError-not-ConnectFailed gotcha the unit fake couldn't.
Web (`web/server.py`): both admin endpoints build the wt client with admin_key and call
`wt.*`; the bifrost endpoint gains ConnectFailed→502 handling (cutover foot-gun); the
admin-events endpoint closes the injected transport (INV-CUT-1), never the wt client.
Deleted the hand-rolled `sessions.get_session_bifrost` + `sse_client.stream_admin_events`
(+ orphaned httpx/httpx_sse/json/AsyncIterator imports); the ratatoskr `AdminEvent`
dataclass stays in `sse_client.py` (re-wrap target, imported by wt + web) until slice-7.
Retired `test_sse_client.py` entirely (its last test was the admin stream) and the
`test_sessions.py` `TestGetSessionBifrost`; added the slice-6 adapter tests.
LIVE SMOKE (:8081, readonly-admin key) — INV-CUT-5 / DEC-4 cleared: the web bifrost
endpoint returned an admin-authed clean 404 envelope (auth + route + mapping proven);
a real `session.created` admin event (id=32) re-wrapped cleanly on live wire (driven by
a session-create, throwaway session cleaned up).
Suite 490 green; ruff clean; mypy net-improved on web/server.py (16→12 pre-existing, no
new). Patch bump 0.21.18 → 0.21.19 (the cutover MINOR is deferred to slice-7, DEC-6).
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)