memory: /snapshot — worldtree-sdk cutover slice-5 complete (deab762→4e20030)

Slice-5 (characters + me/capabilities/models) done through the full House Code
Discipline, tags v0.21.16–.18, suite 488 green, live-smoke-proven on :8081/b128,
both heid gates cleared. Current state / in-flight advanced to slice-6 (admin) next;
Recent-decisions index entry + detail file added; substrate at v0.21.18.

persistent-memory.md stays ~333 lines (over the ~300 soft cap): the length is
dominated by the non-archivable Current state / in-flight block plus <30-day July
entries (guarded), so archival can't reach the 250 target — left as-is per the
stop-where-the-guards-stop rule.
This commit is contained in:
2026-07-19 11:34:10 -07:00
parent 4e20030229
commit 5bc39a092e
2 changed files with 148 additions and 25 deletions
@@ -0,0 +1,115 @@
# worldtree-sdk cutover — SLICE-5 COMPLETE (characters + me/capabilities/models)
`[2026-07-19]` Slice 5 of 7 of the worldtree-sdk consumer cutover (issue #20;
contract `docs/contracts/worldtree_sdk_cutover.contract.md`). Full House Code
Discipline end-to-end: contract slice-notes → TDD → LIVE smoke → heid-code-review →
fixup → heid-bug-hunt → fixup. Both heid panels cleared. Suite **488 green**.
## Commits (tags v0.21.16.18, on `main`, not-yet-pushed)
- **`deab762`** feat — the six routes onto `ratatoskr.wt`, hand-rolled deleted.
- **`d86d6df`** fix — heid-code-review fixups (CLI presenter degrade-not-crash).
- **`4e20030`** fix — heid-bug-hunt fixups (CLI open-world container-type hardening).
## What migrated
`get_me` / `get_capabilities` / `list_character_models` / `create_character` /
`get_character_state` / `delete_character` moved off the hand-rolled httpx wrappers
onto `client.me.get()` / `client.capabilities.get()` /
`client.models.available_for_characters()` / `client.characters.create|state|delete`.
All six are **open-world reads/acks returned verbatim**; none carries a discriminated
SDK error, so each maps any `ApiError` → the `SessionApiFailed` default —
**NO new § Error map rows** (exact parity with the retired path, which never
discriminated a status/code on these routes).
**CLI-only rewire**`--whoami` (me + capabilities) and `--characters`
(models → create → state → delete) build a `wt.build_client` over the injected
`_probe_client` transport and catch `wt.SessionApiFailed` + `ConnectFailed`. **No
web-server caller** for any of these six routes.
Deleted the six hand-rolled `sessions.py` wrappers (net **5 mypy `no-any-return`**
errors); `endpoint_for_plane` + `get_session_bifrost` (slice-6) + the exception
classes stay. Retired the matching `test_sessions.py` classes (`TestGetMe`,
`TestGetCapabilities`, `TestTransientCharacters`); kept `TestEndpointForPlane` +
`TestGetSessionBifrost`.
## Decisions made at TDD (contract § slice-5 notes)
- **`create_character` omits `state` when None** — SDK-idiomatic inline literal
(per branch, to type-check against the SDK's `CreateCharacterInput` TypedDict
without importing its private `_types`); server-equivalent to the retired explicit
`state: null` (Worldtree's field defaults None either way). The only wire-shape
change; the sole call-site never sets state.
- **`delete_character` returns the SDK's open ACK verbatim** (`-> Mapping | None`,
not normalized to the hand-rolled `None`; 204 → None). The CLI ignores it.
## LIVE SMOKE (:8081/b128, `WORLDTREE_API_KEY`, INV-CUT-5 / DEC-4 cleared)
Drove both probes end-to-end through the CLI (`python -c "from ratatoskr.cli import
main; main([...])"` — the `ratatoskr` console script isn't on PATH here; `python -m
ratatoskr.cli` imports without calling `main`, no `__main__` guard). `--whoami`
rendered real identity (user_id ratatoskr, tier user, scopes incl. `character.*`,
key_id c990f0be) + `ephemeral_template echo`. `--characters` drove the full
lifecycle: `char-rp` catalog → `created char_…``state pad=[0.234,0.136,0.065]`
read-back → `deleted`. Observed real success, not merely non-crash.
## heid-code-review (thread 01KXXRN50K…) — 2 fixups
Panel: **Gróa + Regin zero** (adapter/route-map/error-map faithful); **Hulda** flagged
2 source-confirmed CLI open-world-presenter crash holes + a live-smoke test-gap. Both
holes fixed (the null/element layer):
- `_format_whoami` `scopes`: `', '.join(me.get('scopes', []))` crashes on a
present-null `scopes` (`.get(k, [])` returns None, not the default) or a non-string
element. The contract names `_format_whoami` the degrade-not-crash exemplar — the
cited exemplar had an un-hardened line (`allowed_roles` was hardened in slice-4,
`scopes` was not).
- `_characters_probe` model `items`: the slice-5 `or []` guarded the list-level null
but not each entry (`[None]`/`["x"]`/`[{"name":123}]`).
Test-gap (live-smoke not in the file set) → accept (it WAS run + recorded).
## heid-bug-hunt (thread 01KXXS9S45…) — 3 fixups + 1 accept + 1 dismiss
Cold spec-free diff-scoped panel over the post-code-review-fixup diff. Adapter +
route-map + `ConnectFailed`-at-call-sites **sound against the declared invariants
(all arms agree)**. 4 real findings, all CLI open-world paths — the **container-type
layer BELOW** the null/element holes the code-review had just fixed (the code
comments cite the CR; the two consults were firewalled from each other and converged
independently):
- **[bug, fixed] non-iterable `scopes`/`allowed_roles`** — `{"scopes": 123}`
`123 or [] == 123``for s in 123` TypeError. New `_display_seq(value)` helper
degrades any non-list (scalar / bare string / null / absent) to empty; applied to
both.
- **[bug, fixed] non-iterable `items`** — `{"items": 123}`, same class. Guard `models`
is a Mapping AND `items` is a list before iterating.
- **[robustness, fixed] non-mapping top-level `created`/`state`** — a non-mapping SDK
passthrough (`created=[...]`) → `.get` AttributeError. `isinstance(_, Mapping)`
guard → clean exit-20 abort / `pad=None`.
- **[robustness, ACCEPTED] the probe leaks its transient character on a mid-lifecycle
failure** — create → state → delete linear, no `finally`. PRE-EXISTING (retired
probe had the identical structure — "cutover did not worsen it"), TTL-bounded,
one-shot diagnostic; a `try/finally` would swallow a happy-path delete-failure
(delete is both teardown and a tested step). Gróa + Heid concur accept is
defensible. Documented in contract § slice-5 notes.
- **[DISMISSED] `sessions.py` dropped `get_me`/etc.** (Hulda, caller-contract) — the
intended DEC-3 no-backwards-compat migration (all in-repo callers rewired
same-diff); Heid labels it intended-surface-change, not a defect.
**Regin BH calibration note:** Regin (glm-5.2 non-reasoning) found only the leak,
missed the 3 crash paths, and self-graded "0 confirmed" on a control-flow-guaranteed
finding — consistent with the crystallized **Regin-unreliable-on-bug-hunts** pattern
(its code-review work this session was reliable). Gróa was the BH standout.
## The cumulative lesson (foot-gun for slices 6-7)
Open-world SDK reads need degrade-not-crash guarding at **THREE levels**, and the two
heid lenses caught different ones: the CODE-REVIEW (conformance) caught the
null/element layer; the cold BUG-HUNT (robustness) caught the container-type layer
below it. Run BOTH — they are complementary, not redundant. The three levels:
1. **container-type** — the field value may be a truthy non-iterable scalar (`123`) or
a bare string; `or []` only catches null/absent. Guard `isinstance(_, (list, tuple))`.
2. **element-type** — each entry may be a non-mapping; guard `isinstance(m, dict)`.
3. **top-level-mapping** — the whole read may be a non-mapping passthrough; guard
`isinstance(_, Mapping)` before any `.get`.
See also [[2026-07-19-worldtree-sdk-cutover-slice-4-complete]] (the slice-4 arc + the
`-m` double-module class-identity foot-gun).
+33 -25
View File
@@ -46,30 +46,35 @@ upstream API key stays server-side (INV-003).
_As of 2026-07-19:_
**🔨 ACTIVE MIGRATION — worldtree-sdk cutover (issue #20): SLICE-14 COMPLETE, slice-5 next.**
**🔨 ACTIVE MIGRATION — worldtree-sdk cutover (issue #20): SLICE-15 COMPLETE, slice-6 (admin) next.**
Operator ruled ADOPT (2026-07-18): ratatoskr cuts its CONSUMER client layer over to **worldtree-sdk (Python)
1.0.0**, retiring the hand-rolled httpx wrappers behind a thin `ratatoskr.wt` adapter. Design locked (6 DECs,
vor-cross'd, heid-panel-reviewed); contract `docs/contracts/worldtree_sdk_cutover.contract.md`. **SLICE-1+2
(foundation + sessions/turn) ✅ PUSHED** origin `aba1730`. **SLICE-3 (persona + authored-history + first-message)
✅ DONE** `ca9a339`+`fc256bb`. **SLICE-4 (agents/Tier-3 + `model`→`role` fold) ✅ DONE** `c62b4ee` (feat) +
`aed9429` (heid-code-review fixups) + `477d98f` (heid-bug-hunt fixups), tags v0.21.13.15; full House Code
Discipline, both heid panels cleared. Suite **475 green**. Slice-4 migrated
list_agents/get_persona_state/define/patch/delete onto `client.agents.*` (open-world dicts, errors mapped by
route+(status,error_code)), folded `model``role` (`LocalAgentEntry.role`, index schema v2), and DELETED the
hand-rolled `sessions.list_agents`/`get_persona_state`/`AgentInfo` + `tier3.define/patch/delete_agent`. Full arc
+ the two slice-4 foot-guns → `persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-4-complete.md`.
**KEY ADAPTER FACTS (foot-guns, cumulative for slices 5-7):** SDK reads = **open-world dicts**
presenters MUST degrade not crash (slice-4 bug-hunt: hard-indexing `info["x"]` crashes on partial wire; use
`.get`/type-guards; `system_prompt: null``None.splitlines()`); the SDK **normalizes ANY transport failure to
`ConnectFailed(status=0)`** (NOT raw httpx) — every adapter caller must `except ConnectFailed`;
**caller-semantic exceptions the adapter raises + a `-m` CLI catches must NOT live in the `-m` module** (the
`python -m ratatoskr.tier3` double-module split gave two `Tier3*` class identities → uncaught traceback; fixed
by homing them in `sessions.py`; unit tests can't catch this, the live smoke did); `TurnEvent` `turn_id` ABSENT
on text/thinking frames; `consumer_key` is BOUND-create-only; envelope parser prefers nested `detail`.
**NEXT = slice-5** (characters + me/capabilities/models — remaining consumer reads); then slice-6 (admin:
`stream_admin_events` + `get_session_bifrost`, admin_auth), slice-7 (teardown: retire contracts #2/#15, drop
`httpx-sse`, MINOR bump per DEC-6 w/ operator approval). Scope: consumer layer ONLY; Bifrost provider untouched.
Full design → auto-memory `project_worldtree_sdk_cutover`.
✅ DONE** `ca9a339`+`fc256bb`. **SLICE-4 (agents/Tier-3 + `model`→`role` fold) ✅ DONE** `c62b4ee``477d98f`
(v0.21.13.15). **SLICE-5 (characters + me/capabilities/models) ✅ DONE**`deab762` (feat) + `d86d6df`
(heid-code-review fixups) + `4e20030` (heid-bug-hunt fixups), tags v0.21.16.18; full House Code Discipline,
both heid panels cleared. Suite **488 green**; **LIVE SMOKE on :8081/b128** drove `--whoami` (identity+caps) +
`--characters` (models→create→PAD read-back→delete) end-to-end. Slice-5 migrated
`get_me`/`get_capabilities`/`list_character_models`/`create_character`/`get_character_state`/`delete_character`
onto `client.me`/`.capabilities`/`.models`/`.characters.*` (all open-world reads → `SessionApiFailed` default,
**NO new Error-map rows**), rewired `--whoami`/`--characters` (**CLI-only; no web caller**), and DELETED the 6
hand-rolled `sessions.py` wrappers (`endpoint_for_plane`+`get_session_bifrost` [slice-6]+exceptions stay). Full
arc → `persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-5-complete.md`.
**KEY ADAPTER FACTS (foot-guns, cumulative for slices 6-7):** SDK reads = **open-world dicts** — presenters
MUST degrade not crash, guarded at THREE levels (slice-5 needed all three): **container-type** (a scalar `123`
is non-iterable → `for x in 123` TypeError; the `or []` idiom catches null/absent but NOT a truthy non-iterable
— the heid CODE-REVIEW caught null/element, the cold BUG-HUNT caught the container layer below it, run BOTH),
**element-type** (`isinstance(m, dict)`), **top-level-mapping** (`isinstance(_, Mapping)` before any `.get`; a
non-mapping passthrough → AttributeError); never hard-index `info["x"]`. The SDK **normalizes ANY transport
failure to `ConnectFailed(status=0)`** (NOT raw httpx) — every adapter caller `except ConnectFailed`.
**caller-semantic exceptions the adapter raises + a `-m` CLI catches must NOT live in the `-m` module** (double-
module class-identity split → uncaught traceback; live smoke catches it, unit tests can't); `TurnEvent` `turn_id`
ABSENT on text/thinking frames; `consumer_key` is BOUND-create-only; envelope parser prefers nested `detail`.
**NEXT = slice-6** (admin: `admin.sessions.bifrost` + `admin.stream_events`, admin_auth — `get_session_bifrost`
in `sessions.py` + `stream_admin_events` in `sse_client.py`); then slice-7 (teardown: retire contracts #2/#15,
drop `httpx-sse`, MINOR bump per DEC-6 w/ operator approval). Scope: consumer layer ONLY; Bifrost provider
untouched. Full design → auto-memory `project_worldtree_sdk_cutover`.
**✅ RESOLVED — tier3 agents `model``role` (scope B) folded into cutover slice-4** (`c62b4ee`, v0.21.13). The
deferred deploy-gated scope-B work (response `model``role` per spec 1.2 / b128, `LocalAgentEntry`, index schema
@@ -117,13 +122,14 @@ Full record → `persistent-memory.d/2026-07-18-368-silo-test-passed.md`. Siblin
(2) R39 Phase-2 **matched-quartets rebuild** (confirmatory, "whenever"); (3) bifrost **snapshot-cursor
adoption** (ruled normative, not blocking → `persistent-memory.d/2026-07-16-bifrost-cursor-conformance.md`).
**Substrate / environment:** branch `main` at **v0.21.15** — slice-4 arc `c62b4ee``477d98f` + this snapshot
**COMMITTED, not-yet-pushed** (tags v0.21.13.15; push is the operator's call); slice-13 (v0.21.3.12,
`b1fbadd``4f92a21`) PUSHED to origin earlier. origin `git@gitea.phasefinal.com:vh/ratatoskr.git`. **NEW core dep:
**Substrate / environment:** branch `main` at **v0.21.18** — slice-4 arc `c62b4ee``477d98f` + slice-5 arc
`deab762``4e20030` + this snapshot **COMMITTED, not-yet-pushed** (tags v0.21.13.18; push is the operator's
call); slice-13 (v0.21.3.12, `b1fbadd``4f92a21`) PUSHED to origin earlier. origin
`git@gitea.phasefinal.com:vh/ratatoskr.git`. **NEW core dep:
`worldtree-sdk==1.0.0`** (gitea PyPI, `[tool.uv.sources]`; `httpx-sse` retires at slice-7). bifrost
**`==1.1.4`** / wire v0.7; WT openapi vendored 2.3.0, **conversation-api-spec re-synced to v1.1** (`b4a278c`);
**suite 475 green** (slice-4 added the agents-family adapter tests + heid-gate fixup tests, ~offset by the
deleted hand-rolled agent/persona tests). Personal WT on **b128**
**suite 488 green** (slice-5 added the characters/me/caps adapter tests + heid code-review/bug-hunt fixup
tests, ~offset by the deleted hand-rolled character/me/caps tests). Personal WT on **b128**
(`http://10.250.50.152:8081`; #368 silo + #364 promotion-hygiene live both instances). The combined
**:8392** provider (memory+affect) + **:8765** web are THE surfaces, dev-box BACKGROUND SHELLS —
restart via `scratchpad/relaunch_by_pid.py <pid>` (pid via `ss -ltnp | grep <port>`). `env.sh` sets
@@ -276,6 +282,8 @@ decision. Captures rationale that won't be obvious from code alone.
- `[2026-07-19]` **worldtree-sdk cutover SLICE-4 COMPLETE (agents/Tier-3 + `model`→`role` fold, `c62b4ee`+`aed9429`+`477d98f`, v0.21.13.15, 475 green).** Full House Code Discipline; the LIVE SMOKE caught a `python -m` double-module exception-class-identity bug unit tests structurally can't; both heid panels cleared (code-review zero-drift + 3 fixups; bug-hunt 5/5/5 → open-world-presenter degrade-not-crash fixes). Resolves the deferred scope-B model→role. Slice-5 next. → `persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-4-complete.md`
- `[2026-07-19]` **worldtree-sdk cutover SLICE-5 COMPLETE (characters + me/capabilities/models, `deab762`+`d86d6df`+`4e20030`, v0.21.16.18, 488 green).** Last consumer reads + transient-character CRUD onto the wt adapter (CLI-only rewire, NO new Error-map rows — all six routes → SessionApiFailed default); live-smoke-proven on :8081/b128; both heid gates cleared — code-review caught the null/element open-world-presenter degrade holes, the cold bug-hunt caught the **container-type layer below** them (guard container-type + element-type + top-level-mapping). Slice-6 (admin) next. → `persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-5-complete.md`
- `[2026-07-19]` **wyrd-dev #368 silo-enforcement consult delivered — ratatoskr's store-side silo is CONVENTIONAL (query-time filter); wyrd going STRUCTURAL off the framing.** Answered artifact-only from the provider memory-store code; wyrd folded my foot-guns into their unit-4 contract + committed to one-DB-file-per-campaign + first-class partition columns. OPEN LOOP: I'll eyeball their data model once the contract's cut (they'll ping). → `persistent-memory.d/2026-07-19-wyrd-368-silo-consult-delivered.md`
_67 older entries (2026-05-* debug-TUI/web era + the 2026-06-14 → 06-18 Bifrost-provider build / #17+#18 / #295-296 era) archived to archival-memory.md._