diff --git a/persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-5-complete.md b/persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-5-complete.md new file mode 100644 index 0000000..39eed18 --- /dev/null +++ b/persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-5-complete.md @@ -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). diff --git a/persistent-memory.md b/persistent-memory.md index 0dcbb53..ba4bf91 100644 --- a/persistent-memory.md +++ b/persistent-memory.md @@ -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-1–4 COMPLETE, slice-5 next.** +**🔨 ACTIVE MIGRATION — worldtree-sdk cutover (issue #20): SLICE-1–5 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-1–3 (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-1–3 (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 via `ss -ltnp | grep `). `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._