Files
vh ec68b1f3a5 feat(#20): worldtree-sdk cutover teardown (slice-7) + v0.22.0
The last slice of the consumer-layer cutover. Teardown only — zero
runtime-logic change; the 494-green suite is the regression gate.

- Drop `httpx-sse` from pyproject + lockfile: slice-6 deleted its last
  user, nothing imports `httpx_sse`, the SDK owns SSE parsing now.
- Module boundary (operator decision): KEEP `sessions.py` + `sse_client.py`
  as pure caller-semantic type/exception homes (no rename, no fold —
  A3 was blocked by the `AgentNotAvailable` name collision + `wt.py`
  would mis-home `endpoint_for_plane`). Docstrings updated to stop
  claiming "client"; the `AdminEvent`/`SseId`/exception homes stay put
  (resolves the slice-6 deferred-home item).
- Retire wire contracts #2 (sessions) + #15 (tier3): DEC-1 phase-2 —
  normative authority already transferred to the cutover contract; the
  code they specified is gone, so the files are deleted. #1 (SSE event
  vocab) and `first_message` stay (ratatoskr-owned, not retired).
- Final coverage-map re-anchor: tools/list_sessions re-homed to `wt.py`;
  the Last-Event-ID SSE-resume sub-gap CLOSED (folded into `stream_turn`
  auto-resume); Surface-2 SSE parsing re-anchored to the SDK.
- Stale doc-rot fix: the cli.py transport comment no longer calls
  `seed_preset_first_message` "not-yet-migrated" (it rides `wt`).
- v0.22.0 (minor, DEC-6, operator-approved): publishes the full 6-slice
  cutover milestone.
2026-07-19 13:41:34 -07:00

24 KiB
Raw Permalink Blame History

Ratatoskr v1 coverage map

The v1 convergence-target ledger. Ratatoskr has no self-defined feature roadmap: v1 = consume all of Worldtree's I/O points, reached when Worldtree hits 1.0 (operator, 2026-06-19; auto-memory project-ratatoskr-v1-derived-from-worldtree-io-coverage). This file is that coverage map — every Worldtree v1-frozen I/O point × ratatoskr's coverage status, so "are we at v1?" is a ledger lookup, not a judgement call.

First authored: 2026-06-30 (the v1 coverage-audit kickoff).


Frozen target

Worldtree is at v1.0.0b2 — approaching 1.0, with its wire surfaces now FROZEN (Worldtree docs/v1-schema-freeze-manifest.md, #326). The audit anchors against the frozen machine-readable artifacts, NOT the prose markdown:

Worldtree v1 surface Frozen anchor Ratatoskr role
Conversation REST API OpenAPI info.version 2.3.0 (Worldtree/docs/conversation-api-openapi.json, sha 36148179…) — 41 path×method groups (2.3.0 added POST /sessions/{id}/history, #347) client (debug TUI / web)
Conversation SSE events conversation-api-sse-events.schema.json (sha 9deeebf4…) — 11 discriminated event types client
Bifrost wire (consumer protocol) wire v0.6 STABLE/FROZEN (bifrost==1.0.0) — memory + affect planes provider (Worldtree dispatches into us)

External ratification — worldtree-sdk v1.0.0 (wtsdk-dev, 2026-07-18, althing thread 01KXVF24WQD2T5ZCS49KKFCCMH). The TypeScript v0 consumer SDK wires all 41 ops as typed methods from the same OpenAPI 2.3.0 (sha 36148179601453a0 — matches our vendored pin exactly), so it is the independent parity authority for the surface this ledger tracks. Ratatoskr does not adopt the SDK — we are the parallel Python/httpx reference-consumer of the identical surface (no TS/npm surface exists to pin it into). Cross-check corroboration: the SDK's W-4 (send config.role, response echoes model unremapped) matches what ratatoskr shipped in v0.21.2 for ephemeral-Echo create.

Future consumable — worldtree-sdk Python spine (wtsdk-dev, next milestone). The TS v1 surface is our parity checklist; the forthcoming Python spine (same behavioral-spec + fixture corpus, TS decisions kept Python-portable per their surface-design §6) is what ratatoskr would actually consume when it lands. wtsdk-dev slots ratatoskr as THE reference consumer whose coverage column drives that spine — a future repin candidate, not yet available.

Pin drift (finding P-1) — REMEDIATED 2026-06-30. Ratatoskr formerly vendored only the prose markdown (docs/conversation-api-spec.md), which is byte-identical to live Worldtree's but frozen at v0.35.16-era content (last WT edit 2026-05-31) and does not document the b2 surface (7 endpoints below, the 409/503 on messages-POST #331, the unified error envelope #328, the SSE schema). Remediated: the OpenAPI 2.2.0 + SSE-schema JSON are now vendored and pinned (.corviduo-canonicals.tomlcanonical_drift.py gate); the prose markdown is the tolerate_drift reference. Spec pin advanced to 5810a26 (v1.0.0b2). See § Pin remediation.

7 endpoints new in b2 OpenAPI, absent from our vendored markdown: /admin/keys/bulk, /admin/persona/archive, /admin/persona/erase, /admin/usage, /embed, /judgments, /me/usage.


Scorecard

Scope mandate: A (ledger-mandate), locked by operator 2026-06-30. v1 "done" = every frozen I/O point is classified (covered or excluded-with-rationale), zero unaccounted. NOT "feature-complete client." All scope-pending rows are now resolved (§ Surface 1, scope-resolution table).

Surface Points covered-live gap (in-scope) 🚫 excluded-by-design
REST (OpenAPI 2.3.0, path groups) 41 19 0 22
SSE events 11 11 0 0
Bifrost provider planes 8 verbs 8 0 (10 gated verbs deferred)

Legend. consumed in code AND live-proven against real Worldtree · a debug-observability I/O point we should cover but don't yet (the convergence frontier) · 🚫 deliberate non-goal per the design-brief negative clauses + the A mandate. Counts are at the path-group level; mixed-method groups are footnoted (e.g. /sessions POST is but its GET picker is an unwired sub-gap).


Surface 1 — Conversation REST API (OpenAPI 2.3.0)

Covered — client path (ratatoskr's core identity)

Endpoint Status Where consumed Note
POST /sessions wt.py create_session (SDK sessions.create) → cli.py,web/server.py wt-adapter re-anchored (slice-2, #20) — + end_user_id, bifrost binding (consumer-key via SDK per-request auth), config passthrough; 404→AgentNotFound, bound-502→BifrostHandshakeFailed. Ephemeral-template (Echo) create (#19) carried through the adapter. Depth enhancement to an already-covered route — count unchanged
POST /sessions/{id}/messages (turn stream, SSE) wt.py stream_turn (SDK resilient sessions.stream_turn, auto-resume) → cli/web wt-adapter re-anchored (slice-2, #20) — the primary surface; 409→AgentNotAvailable, 503→TurnLaunchUnavailable, drop→SseConnectionDropped, protocol→same-named; absorbs the old reconnect_turn
POST /sessions/{id}/history (authored-history-write, #347) wt.py write_authored_history (SDK sessions.write_history) → cli.py --seed-first-message, first_message.py seed_preset_first_message (create-path seed) wt-adapter re-anchored (slice-3, #20) — SDK owns the entry shape; v1 author=assistant; 404→AuthoredHistoryUnavailable (hide-existence, route is the discriminator, never probe); 409/422→SessionApiFailed default. LIVE-SMOKE 2026-07-19 on personal :8081 (b128): --seed-first-message on a sindra session → 201 (seq=0, phase=seeded, turn_id=2294) → read-back verbatim; create-path preset seed observed via --new. (Prior 2026-07-06 hand-rolled proof superseded.)
GET /sessions/{id}/messages (history) wt.py get_session_messages (SDK sessions.messages) → cli.py --seed-first-message read-back, web/server.py messages proxy wt-adapter re-anchored (slice-3, #20) — the #347 seed read-back; open-world passthrough. LIVE-SMOKE 2026-07-19: read-back rendered the seeded seq-0 turn as a plain role=assistant message (model-invisible provenance confirmed)
POST /sessions/{id}/turns/{turn_id}/cancel wt.py cancel_turn (SDK sessions.cancel_turn) → cli/web wt-adapter re-anchored (slice-2, #20) — two-stage Ctrl-C; 404→CancelTurnNotFound, 409→CancelAlreadyCompleted, late-cancel 200 (cancelled=False) is a benign result, not an error
GET /agents wt.py list_agents (SDK agents.list) → web/server.py _agents_endpoint wt-adapter re-anchored (slice-4, #20) — open-world array verbatim (no AgentInfo normalization), merged with the local tier3 index (remote-wins); error→SessionApiFailed default, transport→ConnectFailed→502. LIVE-SMOKE 2026-07-19 on personal :8081 (b128): 6 agents returned (forseti/lofn/mask/mimir/vili/…)
GET /agents/{id}/persona_state wt.py get_persona_state (SDK agents.persona_state) → web/server.py _persona_state_endpoint wt-adapter re-anchored (slice-4, #20) — open-world snapshot; dual-key (status,error_code) map: 404 persona_not_configured→PersonaNotConfigured, 404 agent_not_available→AgentNotAvailable, 403 auth_scope_denied→AuthScopeDenied, else default. LIVE-SMOKE 2026-07-19: a tier3 agent → correctly mapped PersonaNotConfigured (route+code adapter proven)
POST /agents/define wt.py define_agent (SDK agents.define) → tier3.py _run_define wt-adapter re-anchored (slice-4, #20) — sends AgentDefineInput {agent_name,role,system_prompt}, returns open-world DefinedAgent (echoes role, b128); slug pre-validated; 429→Tier3QuotaExceeded(retry_after=0, header-less floor), 403→Tier3UserIdUnsupported, 422 layer_deferred→Tier3LayerDeferred. LIVE-SMOKE 2026-07-19: define --role thoughtful-characterdefined ratatoskr:slice4-smoke (thoughtful-character)
PATCH /agents/{id} wt.py patch_agent (SDK agents.patch) → tier3.py _run_patch wt-adapter re-anchored (slice-4, #20) — Tier-3 mutate (system_prompt/role, model→role folded in); 404→Tier3AgentNotFound, 422 field_not_mutable→Tier3FieldNotMutable. LIVE-SMOKE 2026-07-19: patched ratatoskr:slice4-smoke; a non-existent id via python -m[agent_not_found] (exit 20, class-identity fix proven)
DELETE /agents/{id} wt.py delete_agent (SDK agents.delete) → tier3.py _run_delete wt-adapter re-anchored (slice-4, #20) — 204→None; 404→Tier3AgentNotFound (route-discriminated, NOT hide-existence). LIVE-SMOKE 2026-07-19: deleted ratatoskr:slice4-smoke + local index → []
GET /me wt.py get_me (SDK me.get) → cli.py --whoami wt-adapter re-anchored (slice-5, #20) — open-world identity dict verbatim; any error→SessionApiFailed default (401 on a bad/absent key), transport→ConnectFailed→exit 21. LIVE-SMOKE 2026-07-19 on personal :8081 (b128): identity rendered (user_id ratatoskr, tier user, scopes incl. character.*, key_id c990f0be)
GET /capabilities wt.py get_capabilities (SDK capabilities.get) → cli.py --whoami wt-adapter re-anchored (slice-5, #20) — open-world advertisement verbatim; _format_whoami reads allowed_roles/default_role and degrades on a null/non-mapping template (slice-4 hardening); matches conversation-api-spec v1.1 (b4a278c). LIVE-SMOKE 2026-07-19: ephemeral_template echo: default=echo max_bytes=32768 roles=[echo]
GET /sessions/{id}/tools wt.py get_session_tools (SDK sessions.tools) → web/server.py _session_tools_endpoint wt-adapter re-anchored (slice-7 teardown, #20) — owner-scoped tool inventory (#183); consumer bearer (no admin scope), open-world dict verbatim, any error→SessionApiFailed default. (Consumer is web/server.py; the old sessions.pytui.py row was stale — the TUI is deleted.)
GET /admin/sessions/{id}/bifrost wt.py get_session_bifrost (SDK admin.sessions.bifrost) → web/server.py _session_bifrost_endpoint wt-adapter re-anchored (slice-6, #20) — admin-scoped BifrostState (#176); admin_auth rides on the wt client (_wt_client(admin_key=…)), NOT a per-call header; open-world dict verbatim, any error→SessionApiFailed default. LIVE-SMOKE 2026-07-19 on :8081 (readonly-admin key): admin-authed end-to-end (404 session_not_bifrost_bound clean envelope — auth + route + mapping proven). (Consumer is web/server.py, not tui.py — the old row was stale.)
GET /admin/events (SSE) wt.py stream_admin_events (SDK admin.stream_events) → web/server.py _admin_events_endpoint wt-adapter re-anchored (slice-6, #20) — admin lifecycle SSE (#11), session-filtered; admin_auth on the wt client; the adapter re-wraps the SDK's AdminEvent→ratatoskr's (nan admin_id→id 0, None type/data→""/{}), non-200 open ApiError→SseConnectFailed, ConnectionDropped→SseConnectionDropped. LIVE-SMOKE 2026-07-19: a real session.created event (id=32) re-wrapped cleanly on live wire. (Consumer is web/server.py, not tui.py — stale row corrected.)
GET /models/available-for-characters wt.py list_character_models (SDK models.available_for_characters) → cli.py --characters wt-adapter re-anchored (slice-5, #20) — open-world catalog verbatim; the probe reads items null-safe (or []); any error→SessionApiFailed default. LIVE-SMOKE 2026-07-19: character models: char-rp
POST /characters wt.py create_character (SDK characters.create) → cli.py --characters wt-adapter re-anchored (slice-5, #20) — body {character} (+state only when set — SDK-idiomatic, drops the redundant explicit null); open-world create ACK verbatim; the probe degrades on a missing character_id (no hard-index). LIVE-SMOKE 2026-07-19: created char_8c00006e…
GET /characters/{id}/state wt.py get_character_state (SDK characters.state) → cli.py --characters wt-adapter re-anchored (slice-5, #20) — open-world live PAD/emotions verbatim; TTL-refreshing read. LIVE-SMOKE 2026-07-19: state pad=[0.234, -0.136, 0.065] read back
DELETE /characters/{id} wt.py delete_character (SDK characters.delete) → cli.py --characters wt-adapter re-anchored (slice-5, #20) — returns the SDK's open ACK verbatim (`-> Mapping
POST /sessions/{id}/persona_state wt.py set_persona_state (SDK sessions.set_persona_state, PadState) → cli.py --set-persona-pad wt-adapter re-anchored (slice-3, #20) — SDK owns the canonical {"pad": {...}} wire (#317); CLI passes the 3 PAD axes (finiteness pre-validated); 204→None, else SessionApiFailed default. LIVE-SMOKE 2026-07-19 on personal :8081: --set-persona-pad 0.4,0.1,-0.2204

Sub-gaps inside path groups (the method we use is live; a sibling method on the same path is an unwired frontier item — see frontier Tier 1):

  • GET /sessionswt.py:234 list_sessions (SDK sessions.list) exists, no caller: the startup session-picker (design-brief §4 v1) was a TUI feature and the TUI is now deleted, so the frontier is moot unless a web picker is wired.
  • POST /sessions/{id}/messages + Last-Event-ID (SSE-resume) — CLOSED (slice-2 teardown): the old hand-rolled sse_client.reconnect_turn is deleted; resume is now folded into wt.py:280 stream_turn (the SDK's resilient auto-resume), which IS the wired presenter default. No longer an unwired sub-gap.
  • GET /agents/{id} — consumer-agent lookup (GET /agents/<owner>:<name> with the owner key) is manual-curl-only, not in code.

In-scope gaps — CONVERGED (re-closed 2026-07-06 after the #347 re-open)

Every in-scope REST I/O point is covered. The audit first converged 2026-07-01; Worldtree's #347 (authored-history-write, OpenAPI 2.3.0) then added one new in-scope path-group, re-opening the audit with a single gap — now closed (v0.19.6). The original frontier (design-brief §5 observability panes + presenter-wiring sub-gaps + Tier-2 tail) remains fully closed:

  • Session picker + SSE-resume — wired (v0.18.5.7).
  • Persona · Tools · BifrostState · AdminEvents panes — all built + live (v0.18.xv0.19.0).
  • Transient-characters CRUD + persona-state write — consumed via --characters / --set-persona-pad (v0.19.1).
  • Authored-history-write (#347) + messages read-back — write_authored_history + get_session_messages via --seed-first-message (v0.19.6; live-proof pending the session.history.write grant).

The only remaining not-consumed in-scope method is GET /agents/{id} (consumer- agent lookup, manual-curl-only) — a sub-method on an already- path group, not a path-group gap. Everything else is covered or excluded-by-design below.

Excluded by design — the design-brief negative clauses

Endpoint(s) Status Rationale (design-brief / memory)
PATCH /sessions/{id} · DELETE /sessions/{id} 🚫 §4: rename/delete happen outside the tool (sessions_cli.py)
GET /sessions/{id} 🚫 session detail — identity is footer-visible, no detail view
GET /sessions/{id}/tool-events 🚫 §5: tool calls observed inline from SSE tool_start/tool_result; persisted-events endpoint is opt-in only
GET /admin/sessions/{id}/tools 🚫 covered-by-alternative — the owner-scoped GET /sessions/{id}/tools () serves the Tools inventory; this admin variant is only for cross-user operator debug, out of the single-session focus (§6)
GET/POST /admin/keys · DELETE/POST /admin/keys/{id} · POST /admin/keys/{id}/rotate · DELETE/POST /admin/keys/bulk · POST /admin/keys/bulk/rotate 🚫 §6: NOT a Worldtree-admin tool (key mgmt)
POST /admin/sessions/{id}/retire 🚫 admin session mutation
POST /admin/persona/{archive,erase} 🚫 admin persona GDPR ops (new in b2)
POST /admin/users/{id}/tier 🚫 admin user mgmt
GET /me/usage · GET /admin/usage 🚫 usage metering — not turn-flow observability (new in b2)
GET /healthz · GET /readyz 🚫 liveness probes — low debug value (could become a connect preflight; park)
GET /search 🚫 §5: consumer-product feature, not turn-flow (was "defer to v2")
GET/POST /uploads · DELETE/GET /uploads/{id} 🚫 §6: no uploads — consumer-product feature
GET /pending · GET /sessions/{id}/pending 🚫 §5: poll-only, no turn-flow signal (was "optional, skip")
POST /embed 🚫 embedding utility — no turn flows through it
POST /judgments 🚫 LLM-as-judge A/B eval (response_a/response_b/rubric) — standalone eval utility, not turn-flow

Scope-resolution record (the 11 rows, resolved under A)

The 2026-05-20 design-brief deferred several surfaces; the 2026-06-19 reframe ("v1 = full I/O coverage") put them back in tension. Resolved 2026-06-30 under mandate A (debug-observability identity intact; classify, don't build-all):

Endpoint(s) Resolution
GET /search · uploads (×2) · pending (×2) · POST /embed · POST /judgments 🚫 excluded — consumer-product / eval / poll utilities, not turn-flow observability
characters (×4) · POST /sessions/{id}/persona_state in-scope (frontier Tier 2) — session-routing + affect-injection debug paths

Nothing remains . The -vs-🚫 line follows the debug-observability test: does a turn flow through it / is it a layer worth watching live?


Surface 2 — SSE events (11/11 )

Every frozen SSE event type is now parsed by worldtree-sdk (sessions.stream_turn, yielding TurnEvents — the hand-rolled sse_client._envelope_for_type is deleted) and rendered by both presenters (cli/web; the TUI is deleted). Full coverage.

text · worker_phase · thinking · text_boundary · tool_start · tool_result · done · error · cancelled · awaiting_llm_first_token · affect_update

Caveat (not a gap): affect_update is wire-verified to emit zero events for consumer (Tier-3) agents — the persona-strip SSE path never populates for them (memory 2026-06-18). The handler is correct; the upstream emitter is silent. PAD for consumer agents is surfaced via our own provider read route (GET /affect/state/{id}, #18 D2), not this event.


Surface 3 — Bifrost provider planes (8/8 , live-proven)

Ratatoskr implements the provider side; Worldtree dispatches into it. Live-proven end-to-end through real WT turns (#17/#18 smokes; combined :8392 WT-driven smoke 2026-06-20).

Memory plane — covers the entire bifrost.memory.MemoryDataStore protocol (required: describe_store, get, get_many, search, upsert_many) plus delete_many:

Verb Where
describe_store memory_store.py:140 advertises caps (sync)
search memory_store.py:224 vector recall; scope_all AND / scope_any OR
get / get_many memory_store.py:293 / :305 point reads
upsert_many memory_store.py:150 idempotent batch write; optimistic lock
delete_many memory_store.py:314 transactional delete

Affect plane — covers bifrost…InMemoryAffectStore (emit, fetch):

Verb Where
emit affect_store.py:47 conduit-opaque snapshot upsert (LWW)
fetch affect_store.py:116 {found, snapshot}; mandatory since bifrost 0.10.0 strong-or-absent gate

Plus the non-wire PAD read route GET /affect/state/{agent_id} (affect_store.py:189) and the combined :8392 endpoint advertising both caps by store-presence (combined.py:46).

Deferred-gated (advertised-unsupported, correctly out-of-scope for the basic plane): scan, get_edges_for, upsert_edges, mark_invalid, mark_superseded, patch_many, commit_checkpoint, lease_job, read_checkpoint, health. These live only in the bifrost reference extended store, not the MemoryDataStore protocol; deferred per the #294 re-scope (memory 2026-06-15). Re-evaluate only if Worldtree's Tier-3 path starts exercising them.


Convergence frontier (the v1 to-do) — CLOSED 2026-07-01, re-closed 2026-07-06 (#347)

Every in-scope I/O point is covered. The frontier is empty: REST 19/41 with zero in-scope gaps (the other 22 REST path-groups are excluded-by-design), SSE 11/11, Bifrost provider planes 8/8. v1 convergence (per scope A: "every frozen I/O point classified, zero unaccounted") is met — ratatoskr cuts v1 when Worldtree tags 1.0. The arc, for the record:

Tier 1 — debug-observability core:

  1. DONE — Session picker (v0.18.7) + SSE-resume (v0.18.5/.6).
  2. DONEGET /capabilities + GET /me (v0.18.8, --whoami).
  3. DONE — BifrostState pane (v0.18.10, GET /admin/sessions/{id}/bifrost, admin-key; live-auth-proven). The Tools half was already covered by the owner-scoped GET /sessions/{id}/tools (item 5).
  4. DONE — AdminEvents pane (v0.18.11, GET /admin/events SSE, session-filtered; admin-key; live-auth-proven). #11's blocker was already satisfied (admin key carries admin.events.read). Tier 1 complete — the admin/debug-observability core (Persona · Tools · BifrostState · AdminEvents) is fully built.

Tier 2 — rounds out coverage (all that remains):

  1. DONEGET /sessions/{id}/tools (v0.18.9, owner-scoped tool inventory in the TUI Tools pane).
  2. DONE — Transient-characters CRUD (4 endpoints) + POST /sessions/{id}/persona_state (v0.19.1, --characters + --set-persona-pad one-shot probes). The last in-scope client I/O points.

Pin remediation (finding P-1) — DONE 2026-06-30

Re-pinned to the frozen machine-readable artifacts (the chosen option): conversation-api-openapi.json (2.2.0) + conversation-api-sse-events.schema.json are vendored under docs/ and pinned in .corviduo-canonicals.toml (worldtree-conversation-api-openapi-v2, -sse-events-v1), drift-gated by canonical_drift.py. The prose markdown stays as a tolerate_drift reference (-spec-v1). pyproject.toml spec pin advanced f1b59f8 → 5810a26 (v1.0.0b2); docs/SPEC-PIN.md records the bump. This map now audits a frozen, diffable target — re-running the audit is a canonical_drift.py check away.


Decisions

  1. Scope mandate — RESOLVED 2026-06-30: A (ledger-mandate). v1 "done" = every frozen I/O point classified, zero unaccounted; NOT feature-complete client. The -vs-🚫 line for the 11 formerly-pending rows is locked above (§ Scope-resolution record).
  2. Pin remediation (P-1) — RESOLVED 2026-06-30 (re-pin to machine-readable). Vendored conversation-api-openapi.json (2.2.0) + conversation-api-sse-events.schema.json as the authoritative drift gates (pinned in .corviduo-canonicals.toml, canonical_drift.py green); prose markdown kept as tolerate_drift reference. Spec pin advanced f1b59f8 (v0.35.16) → 5810a26 (v1.0.0b2). This map now audits against a frozen, diffable target.