Compare commits

...
1 Commits
Author SHA1 Message Date
vh e643d38f58 fix: persona_state SET body → canonical {pad:{pleasure,arousal,dominance}} + re-vendor Tier-3 prose (v0.19.7)
worldtree-dev landed the Tier-3 persona/memory/persona_state prose docs
(c9e59ec) — shapes that serialize as freeform Any in the OpenAPI, so the
prose markdown is their source of truth. Re-vendored docs/conversation-api-spec.md
(tolerate_drift markdown pin; worldtree-spec-rev 879cefe→c9e59ec).

Consumer alignment: --set-persona-pad / _set_persona_probe was building
{pad:[list]}, but the canonical POST /sessions/{id}/persona_state body (#317)
is {pad:{pleasure,arousal,dominance}} (named dict). Aligned the probe to the
named dict + a len!=3 guard; updated contract #2's note, the set_persona_state
docstring, and the tests. The set_persona_state wrapper was already correct
(freeform pass-through) — only the CLI probe's body construction drifted.

Suite 602 green. (Also this session: heid-code-review on the #347 slice
returned unanimous zero drift across all three panel arms.)
2026-07-06 09:56:46 -07:00
11 changed files with 208 additions and 28 deletions
+2 -2
View File
@@ -120,8 +120,8 @@ id = "worldtree-conversation-api-spec-v1"
canonical_source = "Worldtree" canonical_source = "Worldtree"
canonical_path = "docs/conversation-api-spec.md" canonical_path = "docs/conversation-api-spec.md"
consumer_path = "docs/conversation-api-spec.md" consumer_path = "docs/conversation-api-spec.md"
pinned_sha256_16 = "2d111a3b8322b7d1" pinned_sha256_16 = "c656a789caceef14"
pinned_at = "2026-06-30T22:25:56+00:00" pinned_at = "2026-07-06T16:51:09+00:00"
tolerate_drift = true # prose reference; OpenAPI+SSE are the gates tolerate_drift = true # prose reference; OpenAPI+SSE are the gates
# Worldtree persona render canons (d2) — the deterministic affect->NL the agent is # Worldtree persona render canons (d2) — the deterministic affect->NL the agent is
+5 -4
View File
@@ -7,16 +7,17 @@ documents the pin, the vendored artifacts, and the bump procedure.
| Field | Value | | Field | Value |
|---|---| |---|---|
| Worldtree git SHA | `879cefef02bbf442c2d01ff4214299fac7c89809` | | Worldtree git SHA | `c9e59ec` |
| Worldtree HEAD message | `fix(#347): freeze the authored-history route into the OpenAPI schema (spec 2.3.0)` | | Worldtree HEAD message | `docs: document Tier-3 persona/memory schemas + persona_state SET body (OpenAPI 2.3.0)` |
| Pinned on | 2026-07-06 | | Pinned on | 2026-07-06 |
| Pinned by | ratatoskr-dev (re-vendor OpenAPI 2.2.0→2.3.0 — #347 authored-history-write route shipped) | | Pinned by | ratatoskr-dev (re-vendor prose markdown — Tier-3 persona/memory/persona_state consumer shapes) |
| Worldtree version at pin | `v1.0.0b22` | | Worldtree version at pin | `v1.0.0b22` |
## Pin history ## Pin history
| Date | SHA | Version | Notable deltas consumed | | Date | SHA | Version | Notable deltas consumed |
|---|---|---|---| |---|---|---|---|
| 2026-07-06 | `c9e59ec` | v1.0.0b22 | **Re-vendor the prose markdown — Tier-3 consumer shapes documented.** `c9e59ec` (docs-only, OpenAPI byte-unchanged vs `879cefe`) adds `docs/conversation-api-spec.md` § "Tier 3 — Consumer-defined agents": the persona / memory / persona_state SET-body shapes that serialize as freeform `Any` in the OpenAPI (so prose is their source of truth). Drove a consumer fix: `--set-persona-pad` now sends the canonical `{pad:{pleasure,arousal,dominance}}` named dict (was `{pad:[list]}`) — #317, `v0.19.7`. Foot-guns encoded: persona.ocean single-letter `{O,C,E,A,N}` on `/agents/define` (spelled-out → 422, the #348 mismatch) vs spelled-out on `POST /characters`; memory `{embedder_version, tier3_dreaming}`, stm_* deprecated, allows_world_scope removed→422; only `valence` still 422s. `pin:`-only for the markdown; the `v0.19.7` bump rode the persona_state code fix. |
| 2026-07-06 | `879cefe` | v1.0.0b22 | **Re-vendor OpenAPI 2.2.0→2.3.0 — Worldtree shipped #347 authored-history-write.** One new REST path-group: `POST /sessions/{session_id}/history` (the authored-history-write primitive) + the `AuthoredTurnResponse` schema (openapi path count 40→41). #347 is **OpenAPI-only** — the prose `conversation-api-spec.md` + server `conversation_api.contract.md` are byte-unchanged since the 5810a26 pin (empty `git log` delta), so those `tolerate_drift` pins stay clean; the SSE schema is unchanged (#347 is event-silent by design). **Consumer side NOT yet built** — `POST /sessions/{id}/history` is a fresh in-scope ⬜ gap in `docs/coverage-map.md` (re-opens the v1 coverage-audit with exactly one gap; Heimdall-gated hide-existence → consumer treats 404 as feature-absent). `pin:`-only, no version bump. | | 2026-07-06 | `879cefe` | v1.0.0b22 | **Re-vendor OpenAPI 2.2.0→2.3.0 — Worldtree shipped #347 authored-history-write.** One new REST path-group: `POST /sessions/{session_id}/history` (the authored-history-write primitive) + the `AuthoredTurnResponse` schema (openapi path count 40→41). #347 is **OpenAPI-only** — the prose `conversation-api-spec.md` + server `conversation_api.contract.md` are byte-unchanged since the 5810a26 pin (empty `git log` delta), so those `tolerate_drift` pins stay clean; the SSE schema is unchanged (#347 is event-silent by design). **Consumer side NOT yet built** — `POST /sessions/{id}/history` is a fresh in-scope ⬜ gap in `docs/coverage-map.md` (re-opens the v1 coverage-audit with exactly one gap; Heimdall-gated hide-existence → consumer treats 404 as feature-absent). `pin:`-only, no version bump. |
| 2026-06-30 | `5810a26` | v1.0.0b2 | **Re-pin to Worldtree's FROZEN v1 surface (#326), as part of the v1 coverage-audit.** Vendored the machine-readable artifacts — `conversation-api-openapi.json` (OpenAPI **2.2.0**, 40 path-groups) + `conversation-api-sse-events.schema.json` (11 events) — now the **authoritative drift gates** (pinned in `.corviduo-canonicals.toml`, CI-checked by `canonical_drift.py`). The prose `conversation-api-spec.md` is **byte-identical** to the v0.35.16 pin (last WT markdown edit 2026-05-31), kept as the human reference (`tolerate_drift`). b2 deltas already consumed in code: 409/503 eager turn-launch statuses (#331, v0.18.3/.4) + the unified error envelope (#328). 7 endpoints documented only in the OpenAPI, not the prose, all classified in `docs/coverage-map.md`: `admin/keys/bulk`, `admin/persona/{archive,erase}`, `admin/usage`, `embed`, `judgments`, `me/usage`. No client-breaking change — `pin:`-only, no version bump. | | 2026-06-30 | `5810a26` | v1.0.0b2 | **Re-pin to Worldtree's FROZEN v1 surface (#326), as part of the v1 coverage-audit.** Vendored the machine-readable artifacts — `conversation-api-openapi.json` (OpenAPI **2.2.0**, 40 path-groups) + `conversation-api-sse-events.schema.json` (11 events) — now the **authoritative drift gates** (pinned in `.corviduo-canonicals.toml`, CI-checked by `canonical_drift.py`). The prose `conversation-api-spec.md` is **byte-identical** to the v0.35.16 pin (last WT markdown edit 2026-05-31), kept as the human reference (`tolerate_drift`). b2 deltas already consumed in code: 409/503 eager turn-launch statuses (#331, v0.18.3/.4) + the unified error envelope (#328). 7 endpoints documented only in the OpenAPI, not the prose, all classified in `docs/coverage-map.md`: `admin/keys/bulk`, `admin/persona/{archive,erase}`, `admin/usage`, `embed`, `judgments`, `me/usage`. No client-breaking change — `pin:`-only, no version bump. |
| 2026-06-17 | `f1b59f8` | v0.35.16 | **#297 + #298/#299 — Worldtree adopts the bifrost v0.6 scope wire (emits `scope_any`/`scope_all`) + client-side per-scope-value union recall. With our v0.17.6 provider this closes cold cross-session recall end-to-end.** Catch-up bump (v0.29.0→v0.35.16). Intervening client-facing deltas reviewed, none break our consumer: #211 agent rename (`saga`→`echo`, `actor`→`mask` — slugs only); #245 `end_user_id` persistence + memory-scope resolver; #187/#188/#219 Tier-3 define/PATCH policy (additive); `bifrost` binding field + `ephemeral_does_not_accept_bifrost` 422 now documented (the #17 surface). Error codes stable; no ratatoskr code change required. | | 2026-06-17 | `f1b59f8` | v0.35.16 | **#297 + #298/#299 — Worldtree adopts the bifrost v0.6 scope wire (emits `scope_any`/`scope_all`) + client-side per-scope-value union recall. With our v0.17.6 provider this closes cold cross-session recall end-to-end.** Catch-up bump (v0.29.0→v0.35.16). Intervening client-facing deltas reviewed, none break our consumer: #211 agent rename (`saga`→`echo`, `actor`→`mask` — slugs only); #245 `end_user_id` persistence + memory-scope resolver; #187/#188/#219 Tier-3 define/PATCH policy (additive); `bifrost` binding field + `ephemeral_does_not_accept_bifrost` 422 now documented (the #17 surface). Error codes stable; no ratatoskr code change required. |
@@ -32,7 +33,7 @@ documents the pin, the vendored artifacts, and the bump procedure.
**Reference (prose; allowed to lag — `tolerate_drift`):** **Reference (prose; allowed to lag — `tolerate_drift`):**
- `docs/conversation-api-spec.md` — copy of `Worldtree/docs/conversation-api-spec.md` at the pinned SHA. The **client-facing prose narrative**. Byte-frozen at v0.35.16-era content (last WT edit 2026-05-31); the OpenAPI/SSE JSON above are the source of truth where they diverge. Pinned `worldtree-conversation-api-spec-v1` (tolerate_drift). - `docs/conversation-api-spec.md` — copy of `Worldtree/docs/conversation-api-spec.md` at the pinned SHA. The **client-facing prose narrative**. Re-vendored at `c9e59ec` (2026-07-06) to carry the § "Tier 3 — Consumer-defined agents" subsections (persona/memory/persona_state SET body) that serialize as freeform `Any` in the OpenAPI JSON — so the **prose is the source of truth for those consumer shapes** (e.g. persona.ocean single-letter `{O,C,E,A,N}` on `/agents/define`; `POST /sessions/{id}/persona_state` body `{pad:{pleasure,arousal,dominance}}`). Elsewhere the OpenAPI/SSE JSON above remain authoritative. Pinned `worldtree-conversation-api-spec-v1` (tolerate_drift).
- `docs/conversation_api.contract.md` — copy of `Worldtree/docs/contracts/conversation_api.contract.md` at the pinned SHA (byte-identical at b2 — server contract unchanged since the v0.35.16 pin). The **server-side contract** including INV-001..INV-052 and amendments. Useful for understanding load-bearing server invariants (e.g., INV-014 turn-id-public, INV-046 admin-events-envelope-stable, INV-049 admin-events-pii-discipline) when designing client behavior against them. Not in the canonical manifest (reference-only). - `docs/conversation_api.contract.md` — copy of `Worldtree/docs/contracts/conversation_api.contract.md` at the pinned SHA (byte-identical at b2 — server contract unchanged since the v0.35.16 pin). The **server-side contract** including INV-001..INV-052 and amendments. Useful for understanding load-bearing server invariants (e.g., INV-014 turn-id-public, INV-046 admin-events-envelope-stable, INV-049 admin-events-pii-discipline) when designing client behavior against them. Not in the canonical manifest (reference-only).
Both files are vendored — they reflect Worldtree at the pinned SHA, not Both files are vendored — they reflect Worldtree at the pinned SHA, not
+4 -2
View File
@@ -322,7 +322,9 @@ via a `--characters` one-shot lifecycle probe; persona-state write surfaced via
wrappers: parsed dict verbatim (or None on 204), any off-status → SessionApiFailed. wrappers: parsed dict verbatim (or None on 204), any off-status → SessionApiFailed.
**Note:** `set_persona_state`'s request body is FREEFORM — the frozen OpenAPI 2.2.0 **Note:** `set_persona_state`'s request body is FREEFORM — the frozen OpenAPI 2.2.0
declares no request schema and the prose spec documents only the GET counterpart, declares no request schema and the prose spec documents only the GET counterpart,
so the caller supplies the snapshot shape (`--set-persona-pad` sends `{pad:[…]}`). so the caller supplies the snapshot shape. **Canonical (worldtree-dev prose #317,
`c9e59ec`): `{pad:{pleasure,arousal,dominance}}` — a named-key dict, NOT a list;
`--set-persona-pad` builds + sends the named dict (each float in [-1,1]).**
```contract ```contract
FN list_character_models(client) -> dict[str, Any] FN list_character_models(client) -> dict[str, Any]
@@ -369,7 +371,7 @@ POST: [POST-001 return_value] on 204 returns None; [POST-002 side_effect] outbou
STEPS: STEPS:
1. [sequential, prescriptive] resp = await client.post(f"/sessions/{session_id}/persona_state", json=snapshot); IF 204 RETURN None; ELSE RAISE SessionApiFailed 1. [sequential, prescriptive] resp = await client.post(f"/sessions/{session_id}/persona_state", json=snapshot); IF 204 RETURN None; ELSE RAISE SessionApiFailed
TESTS: TESTS:
happy [happy]: 204 → None; body == {"pad":[...]} verbatim happy [happy]: 204 → None; body == {"pad":{"pleasure","arousal","dominance"}} verbatim (canonical named-key dict, #317)
non_204 [error]: 422 → SessionApiFailed(422) non_204 [error]: 422 → SessionApiFailed(422)
``` ```
+158 -9
View File
@@ -2699,10 +2699,13 @@ The `turn.started` event always carries `bifrost_override_applied: bool` (True/F
Tier 3 agents are consumer-owned, Worldtree-hosted agents whose Tier 3 agents are consumer-owned, Worldtree-hosted agents whose
identity lives at `<user_id>:<agent_name>`. They share the persistent identity lives at `<user_id>:<agent_name>`. They share the persistent
session infrastructure with Tier 1 / Tier 2 but layer-specific session infrastructure with Tier 1 / Tier 2. The layer-specific
machinery (persona, motivational, memory, valence) is reserved for machinery is now largely active: **`persona` (Phase 2.1, #186),
later phases — Phase 2.0 ships baseline addressing + ownership + `memory` (Phase 2.1, #197), and `motivational` (Phase 2.2, #187) are
lifecycle only. shipped and consumer-settable at define-time.** Only **`valence` remains
deferred** (non-null → 422 `layer_deferred`). Phase 2.0 shipped the
baseline addressing + ownership + lifecycle substrate; the subsections
below document the active layers and their exact validated shapes.
### Endpoints ### Endpoints
@@ -2720,11 +2723,13 @@ lifecycle only.
{ {
"agent_name": "wizard", "agent_name": "wizard",
"system_prompt": "You are a guided-elicitation wizard...", "system_prompt": "You are a guided-elicitation wizard...",
"model": "glm5-turbo", "role": "gen-reasoning", // REQUIRED — a configured model-role (#344), not a raw model id
"persona": null, // schema-reserved; non-null → 422 layer_deferred "persona": { // active (Phase 2.1) — single-letter OCEAN keys; see "Persona layer"
"motivational": null, "ocean": {"O": 0.4, "C": 0.6, "E": -0.3, "A": 0.2, "N": 0.5}
"valence": null, },
"memory": null "motivational": null, // active (Phase 2.2) — see "Motivational layer"
"memory": null, // active (Phase 2.1) — see "Memory layer"
"valence": null // still deferred — non-null → 422 layer_deferred
} }
``` ```
@@ -2756,6 +2761,150 @@ after definition.
The 201 response includes an advisory `warnings` array (#219) — see The 201 response includes an advisory `warnings` array (#219) — see
"Model-assignment warnings" under `PATCH` below. "Model-assignment warnings" under `PATCH` below.
> **Vendoring note (OpenAPI 2.3.0).** In the frozen OpenAPI 2.3.0 document
> the `persona` / `motivational` / `memory` / `valence` request fields
> serialize as **untyped/freeform** — the `POST /agents/define` request
> model types them as `Any` so the layers can activate without a
> schema-breaking change. The shapes documented in the subsections below
> are the **authoritative, validator-enforced** schemas; generate client
> types from this section, not from the freeform OpenAPI fields.
##### Persona layer (Phase 2.1, #186)
`persona` is **active** as of Phase 2.1. It carries the agent's OCEAN
personality vector — the durable trait profile from which Worldtree
derives the mood setpoint (`baseline_pad`) and the mood dynamics
(gain + relaxation time-constants). Shape:
```json
"persona": {
"ocean": { // REQUIRED — exactly these 5 keys, no more, no fewer
"O": 0.4, // Openness — float in [-1.0, 1.0]
"C": 0.6, // Conscientiousness
"E": -0.3, // Extraversion
"A": 0.2, // Agreeableness
"N": 0.5 // Neuroticism
},
"behavioral_notes": "...", // optional, ≤ 4096 chars
"temperament_notes": "..." // optional, ≤ 4096 chars
}
```
**⚠ OCEAN key format — single-letter, uppercase.** The `/agents/define`
persona validator requires the `ocean` map to contain **exactly** the five
uppercase single-letter keys `O, C, E, A, N`. This is a deliberate,
load-bearing contrast with the transient-character primitive
(`POST /characters`), whose `ocean` block uses the **spelled-out**
lowercase keys (`openness`, `conscientiousness`, …). Sending spelled-out
keys to `/agents/define` returns 422 `persona_ocean_required` ("must
contain exactly the 5 keys O, C, E, A, N").
> **Fixed in v1.0.0b21 (#348).** Before that build a correctly
> single-letter-keyed persona was accepted and stored, but resolved to a
> **neutral** mood, because Worldtree's internal mood-derivation read the
> spelled-out key form. On v1.0.0b21+ an API-declared persona correctly
> drives the derived mood setpoint. If you observe neutral mood on a
> persona-defined agent, confirm the deployment is ≥ v1.0.0b21.
**Range.** Each value is a float in `[-1.0, 1.0]` **signed** — `0.0` is the
population mean, NOT `[0.0, 1.0]`. Booleans are rejected. Out-of-range → 422
`persona_ocean_out_of_range`. See [`docs/ocean-traits.md`](ocean-traits.md)
for the SOTA-grounded 5-band behavioural mapping.
Semantics:
- **Per-agent identity trait** — identical for every end-user and session;
immutable post-define (`PATCH {"persona": …}` → 422 `field_not_mutable`).
To change the OCEAN profile, delete and re-define the agent.
- **`extensions` is reserved** — the field exists but must be empty at v0.1;
a non-empty `extensions` returns 422 `layer_deferred`.
- **Sets the mood SETPOINT, not the current mood.** The OCEAN vector fixes
`baseline_pad` (the PAD point the mood relaxes toward over time); the
*current* per-session mood point is seeded separately via
`POST /sessions/{id}/persona_state` (below).
Validation 422 codes: `persona_ocean_required` (missing `ocean`, or keys
≠ {O,C,E,A,N}), `persona_ocean_out_of_range` (a value outside [-1.0, 1.0], or
a boolean), `persona_notes_too_large` (a note > 4096 chars), `layer_deferred`
(non-empty `extensions`), `validation_failed` (unknown top-level field).
##### `POST /sessions/{session_id}/persona_state` — seed the session mood point (Phase 2.1, #186/#189)
Session-scoped mood seed. Sets the *current* PAD mood point for one
session's bound agent — the starting emotional state, distinct from the
OCEAN-derived setpoint the mood relaxes toward. Works on any
persona-enabled session (Tier 1 or Tier 3); most useful for a Tier 3
durable-agent session that wants to start a conversation from a specific
mood.
Request:
```json
{
"pad": {
"pleasure": 0.42, // float in [-1.0, 1.0]
"arousal": 0.25,
"dominance": 0.33
}
}
```
Response: **`204 No Content`** — no body, no audit event (a session-scoped
runtime overlay, not a security-relevant event).
Semantics:
- **PAD-only** (#317 Option A). The body accepts exactly one key, `pad`,
which must carry all three of `pleasure` / `arousal` / `dominance`, each a
float in `[-1.0, 1.0]`. Any other top-level key → 422 `validation_failed`;
a missing or malformed `pad` → 422 `persona_seed_invalid`.
- **Seeds the current mood POINT, not the setpoint.** The OCEAN persona
(above) fixes the setpoint the mood relaxes toward; this endpoint sets
where the mood *starts*. It does not alter the persona.
- **Cross-owner sessions return 404** (existence-hiding — a session that
isn't yours is indistinguishable from one that doesn't exist).
- **Pull-over-push precedence (#289).** Once a session's baseline has been
rehydrated from an `affect.fetch` (the authoritative cross-session
source), a later SET seed is silently ignored — the fetched baseline wins.
There is **no** `POST /agents/{id}/persona_state` — mood is per-session, not
a durable agent property. `GET /agents/{agent_id}/persona_state`
short-circuits to 404 for Tier-3 colon-ids: Tier-3 mood is observable only
over the Bifrost `affect.emit` egress (ADR-0009), never read back through
the HTTP API.
##### Memory layer (Phase 2.1, #197)
`memory` is **active** as of Phase 2.1 but exposes a deliberately minimal
surface — the short-term-memory (STM) tier was removed (#197), so the
historically-present `stm_*` knobs are accept-and-ignore no-ops. Shape:
```json
"memory": {
"embedder_version": "<pinned>", // optional; MUST equal the library-pinned version
"tier3_dreaming": false // optional bool, default false
}
```
Semantics:
- **`embedder_version`** — optional. If supplied it MUST equal the library's
currently-pinned embedder version; a mismatch → 422
`embedder_version_mismatch` (with `expected` / `received` in the detail).
Omit it to accept the pin. Fixed at define-time and library-pinned
thereafter.
- **`tier3_dreaming`** — optional bool (default `false`); opt-in flag for the
Tier-3 dreaming / consolidation path.
- **`stm_capacity` / `stm_token_budget`** — **deprecated no-ops.** Accepted at
define (201) with a `DeprecationWarning`; they carry no runtime effect since
the STM tier was removed, and are slated for rejection at the next schema
break. Do not send them in new integrations.
- **`allows_world_scope` — removed.** Sending it → 422 `validation_failed`
("world-shared knowledge belongs in the KB/Mimir plane").
- **Wholesale-immutable post-define.** `PATCH {"memory": …}` → 422
`field_not_mutable` (even for the deprecated `stm_*` fields) — see the
PATCH table above.
##### Motivational layer (Phase 2.2, #187) ##### Motivational layer (Phase 2.2, #187)
`motivational` is **active** as of Phase 2.2 (persona + memory activated in `motivational` is **active** as of Phase 2.2 (persona + memory activated in
+2
View File
@@ -159,6 +159,8 @@ decision. Captures rationale that won't be obvious from code alone.
- `[2026-07-06]` **OpenAPI re-vendored 2.2.0->2.3.0 (`75da676`, pin-only no bump).** worldtree-dev shipped #347 as spec 2.3.0 (`879cefe`, = the deployed personal b22 image); the SessionStart drift-check flagged our openapi pin STALE. `canonical_sync` pulled 2.3.0; updated the 4 pin-tracking files (`.corviduo-canonicals.toml`, vendored openapi.json, SPEC-PIN.md, pyproject `worldtree-spec-rev`->879cefe). #347 is OpenAPI-only (prose + server contract byte-unchanged, SSE unchanged=event-silent). The re-vendor re-opened the coverage-audit with one new in-scope path-group (the #347 route). - `[2026-07-06]` **OpenAPI re-vendored 2.2.0->2.3.0 (`75da676`, pin-only no bump).** worldtree-dev shipped #347 as spec 2.3.0 (`879cefe`, = the deployed personal b22 image); the SessionStart drift-check flagged our openapi pin STALE. `canonical_sync` pulled 2.3.0; updated the 4 pin-tracking files (`.corviduo-canonicals.toml`, vendored openapi.json, SPEC-PIN.md, pyproject `worldtree-spec-rev`->879cefe). #347 is OpenAPI-only (prose + server contract byte-unchanged, SSE unchanged=event-silent). The re-vendor re-opened the coverage-audit with one new in-scope path-group (the #347 route).
- `[2026-07-06]` **#347 authored-history-write CONSUMER SIDE SHIPPED (`v0.19.6`) — direct in-session TDD.** `write_authored_history(client, session_id, *, content, idempotency_key, author="assistant", effects=None, claimed_original_at=None) -> dict` (POST /sessions/{id}/history; body server-pinned `AuthoredWriteRequest` extra="forbid" so omit null effects/claimed_original_at; 200-replay/201-fresh both -> ack dict; **404 -> `AuthoredHistoryUnavailable`** NOT SessionApiFailed = the hide-existence "feature-absent, never probe" contract; 409/422->SessionApiFailed) + `get_session_messages` (un-deferred GET /sessions/{id}/messages, the seed read-back proving model-invisible provenance) + a `--seed-first-message "<c>" --agent <id>` one-shot probe (create session -> seed -> read-back; 404->benign feature-absent exit 0). Contract #2 amended (2 FNs, validated OK) + 19 tests (12 wrapper + 7 cli). Suite **601 green** (clean env; the 2 "fails" under `source env.sh` are the RATATOSKR_ADMIN_API_KEY env-leak into TestParseArgs, not a regression). Coverage: **REST 19/41** (`docs/coverage-map.md` re-converged). Patch bump (coverage tail; consistent w/ the Tier-2 v0.19.1 cadence). **Live-proof pending** the `session.history.write` grant (infra-ops `01KWW3KQEY`). heid-code-review NOT run (offered). - `[2026-07-06]` **#347 authored-history-write CONSUMER SIDE SHIPPED (`v0.19.6`) — direct in-session TDD.** `write_authored_history(client, session_id, *, content, idempotency_key, author="assistant", effects=None, claimed_original_at=None) -> dict` (POST /sessions/{id}/history; body server-pinned `AuthoredWriteRequest` extra="forbid" so omit null effects/claimed_original_at; 200-replay/201-fresh both -> ack dict; **404 -> `AuthoredHistoryUnavailable`** NOT SessionApiFailed = the hide-existence "feature-absent, never probe" contract; 409/422->SessionApiFailed) + `get_session_messages` (un-deferred GET /sessions/{id}/messages, the seed read-back proving model-invisible provenance) + a `--seed-first-message "<c>" --agent <id>` one-shot probe (create session -> seed -> read-back; 404->benign feature-absent exit 0). Contract #2 amended (2 FNs, validated OK) + 19 tests (12 wrapper + 7 cli). Suite **601 green** (clean env; the 2 "fails" under `source env.sh` are the RATATOSKR_ADMIN_API_KEY env-leak into TestParseArgs, not a regression). Coverage: **REST 19/41** (`docs/coverage-map.md` re-converged). Patch bump (coverage tail; consistent w/ the Tier-2 v0.19.1 cadence). **Live-proof pending** the `session.history.write` grant (infra-ops `01KWW3KQEY`). heid-code-review NOT run (offered).
- `[2026-07-06]` **Tail-2 SHIPPED (`v0.19.7`) — Tier-3 prose docs re-vendored + persona_state body-shape aligned.** worldtree-dev landed the Tier-3 persona/memory/persona_state PROSE docs (`c9e59ec`, on origin) — they serialize as freeform `Any` in the OpenAPI JSON, so the **prose is their source of truth** (my earlier "2.3.0 = #347-only, tail-2 collapsed" was half-wrong: the JSON was #347-only but the prose is separate). Re-vendored `docs/conversation-api-spec.md` (markdown pin, tolerate_drift; `worldtree-spec-rev` 879cefe->c9e59ec, SPEC-PIN history row added). **Consumer fix:** `--set-persona-pad`/`_set_persona_probe` was sending `{pad:[list]}` but the canonical SET body (#317) is `{pad:{pleasure,arousal,dominance}}` (named dict) — aligned it + added a len!=3 guard; updated contract #2 note + set_persona_state docstring + tests. The `set_persona_state` WRAPPER was already correct (freeform pass-through); only the CLI probe drifted. TDD (probe test asserts the dict; +1 wrong-count test). Suite **602 green**, ruff clean. **heid-code-review on #347 (dispatched + returned this session): UNANIMOUS ZERO DRIFT** (Gróa/Hulda/Regin all confirmed the hide-existence 404->`AuthoredHistoryUnavailable` routing holds at wrapper/probe/test layers + the extra="forbid" body-omission + the deliberate write-vs-read 404 asymmetry — confirmation-not-discovery for a well-TDD'd slice against a prescriptive contract). worldtree-dev foot-guns banked in SPEC-PIN + [[reference_worldtree_affect_surface_map]]: ocean single-letter `{O,C,E,A,N}` on /agents/define (#348) vs spelled-out on /characters; memory `{embedder_version, tier3_dreaming}`, stm_* deprecated, allows_world_scope removed->422; only `valence` still 422s.
_41 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._ _41 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._
_For per-issue TDD implementation notes, Volva findings, and contract amendments, see the git log — every per-issue commit carries a structured message capturing the trail._ _For per-issue TDD implementation notes, Volva findings, and contract amendments, see the git log — every per-issue commit carries a structured message capturing the trail._
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "ratatoskr" name = "ratatoskr"
version = "0.19.6" version = "0.19.7"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard" description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
@@ -60,7 +60,7 @@ Repository = "https://gitea.phasefinal.com/vh/ratatoskr"
# Ratatoskr is built against Worldtree at this commit; the vendored # Ratatoskr is built against Worldtree at this commit; the vendored
# spec snapshot in docs/ reflects that SHA. # spec snapshot in docs/ reflects that SHA.
[tool.ratatoskr.spec-pin] [tool.ratatoskr.spec-pin]
worldtree-spec-rev = "879cefef02bbf442c2d01ff4214299fac7c89809" worldtree-spec-rev = "c9e59ec"
worldtree-version = "v1.0.0b22" worldtree-version = "v1.0.0b22"
pinned-on = "2026-07-06" pinned-on = "2026-07-06"
+10 -1
View File
@@ -740,9 +740,18 @@ async def _set_persona_probe(args: ParsedArgs) -> int:
"(e.g. '0.4,0.1,-0.2')\n" "(e.g. '0.4,0.1,-0.2')\n"
) )
return 10 return 10
if len(pad) != 3:
sys.stderr.write(
"[usage_error] --set-persona-pad needs exactly 3 floats "
"(pleasure,arousal,dominance), e.g. '0.4,0.1,-0.2'\n"
)
return 10
# Canonical POST /sessions/{id}/persona_state body (#317): a named-key dict,
# NOT a bare list — {"pad": {"pleasure", "arousal", "dominance"}}.
snapshot = {"pad": {"pleasure": pad[0], "arousal": pad[1], "dominance": pad[2]}}
async with _probe_client(args) as client: async with _probe_client(args) as client:
try: try:
await set_persona_state(client, args.session_id, {"pad": pad}) await set_persona_state(client, args.session_id, snapshot)
except SessionApiFailed as exc: except SessionApiFailed as exc:
sys.stderr.write(f"[session_api_failed] status={exc.status} body={exc.body!r}\n") sys.stderr.write(f"[session_api_failed] status={exc.status} body={exc.body!r}\n")
return 20 return 20
+6 -4
View File
@@ -508,10 +508,12 @@ async def set_persona_state(
) -> None: ) -> None:
"""POST /sessions/{session_id}/persona_state — set a session's persona state (affect injection). """POST /sessions/{session_id}/persona_state — set a session's persona state (affect injection).
The request body is FREEFORM: the frozen OpenAPI 2.2.0 declares no request The request body is FREEFORM on the wire (the OpenAPI declares no request
schema and the prose spec documents only the GET counterpart — so the caller schema), but worldtree-dev's prose now pins the canonical shape (#317):
supplies the snapshot shape (e.g. `{pad: [p, a, d]}`, mirroring the GET `{"pad": {"pleasure": p, "arousal": a, "dominance": d}}` — a named-key dict
`snapshot`). 204 No Content → None; any other status → SessionApiFailed. (each in [-1, 1]), NOT a bare list; PAD-only, session-scoped, pull-over-push
(#289). The caller supplies the snapshot. 204 No Content → None; any other
status → SessionApiFailed.
""" """
assert client is not None assert client is not None
assert session_id and isinstance(session_id, str) assert session_id and isinstance(session_id, str)
+12 -1
View File
@@ -1692,7 +1692,18 @@ class TestTier2Probes:
) )
assert rc == 0 assert rc == 0
assert "persona_state set" in capsys.readouterr().out assert "persona_state set" in capsys.readouterr().out
assert _json.loads(route.calls[0].request.content) == {"pad": [0.4, 0.1, -0.2]} # canonical POST /sessions/{id}/persona_state body: named-key dict, NOT a list
assert _json.loads(route.calls[0].request.content) == {
"pad": {"pleasure": 0.4, "arousal": 0.1, "dominance": -0.2}
}
def test_set_persona_wrong_count(self) -> None:
"""set_persona_wrong_count [adversarial]: not exactly 3 floats → exit 10, no HTTP."""
rc = main(
["--set-persona-pad", "0.4,0.1", "--session", "s1",
"--api-key", "k", "--server", "https://w.example"]
)
assert rc == 10
class TestSeedFirstMessageProbe: class TestSeedFirstMessageProbe:
+6 -2
View File
@@ -1182,9 +1182,13 @@ class TestSetPersonaState:
return_value=httpx.Response(204) return_value=httpx.Response(204)
) )
async with httpx.AsyncClient(base_url="https://w.example") as client: async with httpx.AsyncClient(base_url="https://w.example") as client:
result = await set_persona_state(client, "s1", {"pad": [0.4, 0.1, -0.2]}) result = await set_persona_state(
client, "s1", {"pad": {"pleasure": 0.4, "arousal": 0.1, "dominance": -0.2}}
)
assert result is None assert result is None
assert _json.loads(route.calls[0].request.content) == {"pad": [0.4, 0.1, -0.2]} assert _json.loads(route.calls[0].request.content) == {
"pad": {"pleasure": 0.4, "arousal": 0.1, "dominance": -0.2}
}
@respx.mock @respx.mock
async def test_non_204_raises(self) -> None: async def test_non_204_raises(self) -> None:
Generated
+1 -1
View File
@@ -1052,7 +1052,7 @@ wheels = [
[[package]] [[package]]
name = "ratatoskr" name = "ratatoskr"
version = "0.19.6" version = "0.19.7"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "httpx" }, { name = "httpx" },