Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23fea72c6f | |||
| 875f291a42 |
@@ -104,8 +104,8 @@ id = "worldtree-conversation-api-openapi-v2"
|
||||
canonical_source = "Worldtree"
|
||||
canonical_path = "docs/conversation-api-openapi.json"
|
||||
consumer_path = "docs/conversation-api-openapi.json"
|
||||
pinned_sha256_16 = "36148179601453a0"
|
||||
pinned_at = "2026-07-06T16:09:05+00:00"
|
||||
pinned_sha256_16 = "dbdf4e24c8b06c92"
|
||||
pinned_at = "2026-06-30T22:25:56+00:00"
|
||||
|
||||
[[pins]]
|
||||
id = "worldtree-conversation-api-sse-events-v1"
|
||||
@@ -120,8 +120,8 @@ id = "worldtree-conversation-api-spec-v1"
|
||||
canonical_source = "Worldtree"
|
||||
canonical_path = "docs/conversation-api-spec.md"
|
||||
consumer_path = "docs/conversation-api-spec.md"
|
||||
pinned_sha256_16 = "c656a789caceef14"
|
||||
pinned_at = "2026-07-06T16:51:09+00:00"
|
||||
pinned_sha256_16 = "2d111a3b8322b7d1"
|
||||
pinned_at = "2026-06-30T22:25:56+00:00"
|
||||
tolerate_drift = true # prose reference; OpenAPI+SSE are the gates
|
||||
|
||||
# Worldtree persona render canons (d2) — the deterministic affect->NL the agent is
|
||||
@@ -143,18 +143,3 @@ canonical_path = "core/persona/canon/d2-render-canon-v1.json"
|
||||
consumer_path = "docs/vendor/worldtree-persona-canon/d2-render-canon-v1.json"
|
||||
pinned_sha256_16 = "606bba5fdcc60b6b"
|
||||
pinned_at = "2026-07-01T21:00:00+00:00"
|
||||
|
||||
# Worldtree affect-egress consumer reference — the authoritative DELIVERED-on-wire vs
|
||||
# HIDDEN (system-prompt-only) classification for the Tier-3 affect surface ratatoskr
|
||||
# consumes, + the reconstruction rules. The web console's "context injection" panel
|
||||
# reconstructs the hidden strings from this + the d2 canons. tolerate_drift: prose
|
||||
# reference (the render-canon JSONs are the strict gates). worldtree-dev co-signs +
|
||||
# pings ratatoskr-dev on any change (esp. the pending we-framing conditional).
|
||||
[[pins]]
|
||||
id = "worldtree-affect-egress-consumer-reference-v1"
|
||||
canonical_source = "Worldtree"
|
||||
canonical_path = "docs/affect-egress-consumer-reference.md"
|
||||
consumer_path = "docs/vendor/worldtree-persona-canon/affect-egress-consumer-reference.md"
|
||||
pinned_sha256_16 = "d959134037efae83"
|
||||
pinned_at = "2026-07-07T06:09:24+00:00"
|
||||
tolerate_drift = true # prose reference; the d2 render-canon JSONs are the gates
|
||||
|
||||
@@ -121,7 +121,3 @@ graphify-out/*
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# Node deps (Playwright for web-UI DOM verification — see persistent-memory)
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
+7
-9
@@ -7,18 +7,16 @@ documents the pin, the vendored artifacts, and the bump procedure.
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| Worldtree git SHA | `c9e59ec` |
|
||||
| Worldtree HEAD message | `docs: document Tier-3 persona/memory schemas + persona_state SET body (OpenAPI 2.3.0)` |
|
||||
| Pinned on | 2026-07-06 |
|
||||
| Pinned by | ratatoskr-dev (re-vendor prose markdown — Tier-3 persona/memory/persona_state consumer shapes) |
|
||||
| Worldtree version at pin | `v1.0.0b22` |
|
||||
| Worldtree git SHA | `5810a26b38a5ea6630892f9a39756f57c5b7b41e` |
|
||||
| Worldtree HEAD message | `memory: snapshot — v1.0.0b2 shipped complete (demo + personal green); consumer loop closed` |
|
||||
| Pinned on | 2026-06-30 |
|
||||
| Pinned by | ratatoskr-dev (v1 coverage-audit — re-pin to the FROZEN OpenAPI 2.2.0 + SSE schema) |
|
||||
| Worldtree version at pin | `v1.0.0b2` |
|
||||
|
||||
## Pin history
|
||||
|
||||
| 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-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-05-25 | `da93ca7` | v0.28.0 | #204 — new SSE event `affect_update` (current/scheduled), new endpoint `GET /agents/{id}/persona_state`, auth-model doc edits |
|
||||
@@ -28,12 +26,12 @@ documents the pin, the vendored artifacts, and the bump procedure.
|
||||
|
||||
**Authoritative (FROZEN, machine-readable — the drift gates):**
|
||||
|
||||
- `docs/conversation-api-openapi.json` — copy of `Worldtree/docs/conversation-api-openapi.json` (OpenAPI `info.version` **2.3.0**). The frozen v1 REST wire (41 path-groups; 2.3.0 added `POST /sessions/{session_id}/history` per #347). Pinned `worldtree-conversation-api-openapi-v2` in `.corviduo-canonicals.toml`; drift gated by `canonical_drift.py`.
|
||||
- `docs/conversation-api-openapi.json` — copy of `Worldtree/docs/conversation-api-openapi.json` (OpenAPI `info.version` **2.2.0**). The frozen v1 REST wire (40 path-groups). Pinned `worldtree-conversation-api-openapi-v2` in `.corviduo-canonicals.toml`; drift gated by `canonical_drift.py`.
|
||||
- `docs/conversation-api-sse-events.schema.json` — copy of `Worldtree/docs/conversation-api-sse-events.schema.json`. The frozen SSE event schema (11 discriminated event types). Pinned `worldtree-conversation-api-sse-events-v1`.
|
||||
|
||||
**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**. 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-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.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
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
---
|
||||
contract_version: "2.1"
|
||||
module: "ratatoskr.first_message"
|
||||
purpose: "Per-agent authored first-message presets — seed an agent's opening as a #347 authored turn-0 onto new sessions (CLI + web), the durable replacement for a system-prompt startup instruction."
|
||||
touches:
|
||||
- src/ratatoskr/first_message.py
|
||||
- tests/test_first_message.py
|
||||
scope: >
|
||||
Per-agent authored first-message presets (Worldtree #347 consumer feature).
|
||||
When a new session is created for an agent that has a preset opening, seed it
|
||||
as a #347 authored first-message (POST /sessions/{id}/history, author=assistant,
|
||||
seq-0) so the session opens in-character before the user speaks — the durable
|
||||
replacement for a system-prompt "startup" instruction. Two entry points:
|
||||
`preset_for` (lookup) and `seed_preset_first_message` (best-effort seed).
|
||||
Consumed by ratatoskr.cli (the `--new` session path) and ratatoskr.web.server
|
||||
(the POST /api/sessions endpoint). Depends on ratatoskr.sessions
|
||||
(write_authored_history + its exceptions); no core.* / worldtree.* imports.
|
||||
depends_on:
|
||||
- "httpx"
|
||||
- "ratatoskr.sessions"
|
||||
used_by:
|
||||
- "ratatoskr.cli"
|
||||
- "ratatoskr.web.server"
|
||||
language: "python"
|
||||
complexity: "low"
|
||||
estimated_loc: 60
|
||||
confidence: 0.9
|
||||
assumptions:
|
||||
- "write_authored_history (contract #2 amendment 2026-07-06) is the seed primitive: 200/201 → ack dict, 404 → AuthoredHistoryUnavailable (hide-existence), other non-2xx → SessionApiFailed."
|
||||
- "The preset registry is a static in-module dict keyed by agent_id; editing it is how an operator tunes an agent's opening. Seeded with ratatoskr:sindra only."
|
||||
- "Auto-seed is BEST-EFFORT and MUST NOT block session creation: an instance without the session.history.write grant returns the hide-404, which is swallowed (session opens with no seeded greeting)."
|
||||
---
|
||||
|
||||
# First-message presets — authored openings on session-create (#347)
|
||||
|
||||
## Context
|
||||
|
||||
`ratatoskr.first_message` holds per-agent authored-opening presets and seeds them
|
||||
onto new sessions via the #347 authored-history-write primitive. It is the
|
||||
durable form of "give an agent a first message": instead of a system-prompt
|
||||
`Startup:` instruction (a workaround for the pre-#347 world where the assistant
|
||||
could not author turn-0), the opening lives as a real seeded assistant turn-0.
|
||||
|
||||
Consumed at both session-create sites — `ratatoskr.cli._amain` (the `--new` path)
|
||||
and `ratatoskr.web.server._create_session_endpoint` (POST /api/sessions) — so
|
||||
every new session for a preset agent opens in-character regardless of surface.
|
||||
|
||||
## Data flow
|
||||
|
||||
**In:** a live `httpx.AsyncClient` (caller-owned, base_url + bearer set), a fresh
|
||||
`session_id`, and the bound `agent_id`.
|
||||
|
||||
**Out:** on a preset agent, one `POST /sessions/{session_id}/history` (author=assistant,
|
||||
the preset text, per-content idempotency key). Returns the seeded content on
|
||||
success, else `None`.
|
||||
|
||||
**Side effects:** at most one outbound authored-history write; never raises to the
|
||||
caller (best-effort).
|
||||
|
||||
## Invariants
|
||||
|
||||
- **INV-001 [hard]**: `seed_preset_first_message` NEVER raises (the sole exception is
|
||||
`asyncio.CancelledError`, which propagates — cancellation is not a seed failure) and
|
||||
NEVER blocks session creation. It soft-guards its inputs (a bad arg returns `None`,
|
||||
not `AssertionError`), bounds the write with `asyncio.wait_for(_SEED_TIMEOUT_S)` so a
|
||||
stalled `/history` can't hang the create path, and swallows EVERY other exception (the
|
||||
hide-404, `SessionApiFailed`, `httpx.HTTPError`, `TimeoutError`, and any unexpected
|
||||
error) → `None`. The `broad-except` is deliberate: this helper is wired INTO three
|
||||
session-create paths, so any escape would abort a create that already succeeded.
|
||||
- **INV-002 [hard]**: a no-preset agent issues ZERO HTTP (early return before any
|
||||
request).
|
||||
- **INV-003 [hard]**: the seed body is the preset text verbatim, author="assistant",
|
||||
with a per-content idempotency key (`"ratatoskr-preset-" + sha256(text)[:12]`), so
|
||||
a repeat seed of the same session+preset is an idempotent 200 replay, never a
|
||||
duplicate turn.
|
||||
- **INV-004 [hard]**: no `core.*` / `worldtree.*` imports (reference-consumer
|
||||
boundary; verified by `tests/test_no_worldtree_imports.py`, which rglobs every
|
||||
`.py` under `src/ratatoskr/` — this module included, so no per-module import
|
||||
test is needed here).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Multi-turn / scripted openers.** v1 seeds exactly one assistant turn-0. A
|
||||
multi-message opening scene is a future concern.
|
||||
- **Runtime/remote preset config.** The registry is an in-module dict; no file/DB/env
|
||||
loading. Add that only when a second consumer needs operator-editable presets.
|
||||
- **Non-assistant authors.** v1 is author=assistant only (matches #347 v1); a
|
||||
user/system opener is deferred with the #347 engine surface.
|
||||
- **TUI-only surfaces.** Both real session-create paths (CLI + web) are wired; the
|
||||
bare-TUI picker resumes existing sessions (no create), so it needs no seed.
|
||||
|
||||
---
|
||||
|
||||
```contract
|
||||
FN preset_for(agent_id: str) -> str | None
|
||||
BRIEF: Return the authored first-message preset for agent_id, or None when the agent has no preset. Pure dict lookup over FIRST_MESSAGE_PRESETS.
|
||||
PRE: [PRE-001 hard] agent_id is a non-empty str -- assert agent_id and isinstance(agent_id, str)
|
||||
POST: [POST-001 return_value] returns FIRST_MESSAGE_PRESETS.get(agent_id) (str for a preset agent, None otherwise)
|
||||
STEPS:
|
||||
1. [setup, prescriptive] assert PRE-001
|
||||
2. [sequential, prescriptive] RETURN FIRST_MESSAGE_PRESETS.get(agent_id)
|
||||
TESTS:
|
||||
preset_hit [happy]: preset_for("ratatoskr:sindra") is a non-empty str
|
||||
preset_miss [happy]: preset_for("mimir") is None
|
||||
empty_agent_id [adversarial]: preset_for("") → AssertionError
|
||||
|
||||
FN seed_preset_first_message(client: httpx.AsyncClient, session_id: str, agent_id: str) -> str | None
|
||||
BRIEF: Best-effort seed of an agent's preset opening as a #347 authored first-message on session_id. If agent_id has a preset, POST it via write_authored_history (author=assistant, per-content idempotency key, the await bounded by asyncio.wait_for(_SEED_TIMEOUT_S)) and return the seeded content; on no-preset, a malformed input, OR ANY exception except asyncio.CancelledError, return None WITHOUT raising. Never raises (except CancelledError, which propagates) and never blocks session creation — it is wired into three create paths.
|
||||
PRE: [PRE-001 hard] client is not None -- soft-guarded: return None (NOT assert) if violated, so a wiring bug can't crash the create path (INV-001)
|
||||
PRE: [PRE-002 hard] session_id is a non-empty str -- soft-guarded: return None if violated
|
||||
PRE: [PRE-003 hard] agent_id is a non-empty str -- soft-guarded: return None if violated (also guards FIRST_MESSAGE_PRESETS.get against a non-hashable/non-str id)
|
||||
POST: [POST-001 return_value] preset agent + successful write → returns the preset text; no-preset, malformed input, OR any swallowed failure → None
|
||||
POST: [POST-002 side_effect] a no-preset / malformed-input call issues ZERO HTTP; a preset agent issues exactly one POST /sessions/{session_id}/history with body author="assistant", content=preset, idempotency_key="ratatoskr-preset-"+sha256(preset)[:12], the await bounded by _SEED_TIMEOUT_S so a stalled response cannot block
|
||||
ERROR_ROUTING:
|
||||
asyncio.CancelledError:
|
||||
local_handling: RE-RAISE (cancellation is not a seed failure; never swallow it — and it is a BaseException, so `except Exception` would miss it anyway)
|
||||
flow_control: propagate
|
||||
state_recovery: n/a
|
||||
any other Exception (hide-404 AuthoredHistoryUnavailable, SessionApiFailed 409/422/etc., httpx.HTTPError, TimeoutError from wait_for, any unexpected error):
|
||||
local_handling: swallow; return None
|
||||
flow_control: continue (never blocks session create)
|
||||
state_recovery: session opens with no seeded greeting
|
||||
STEPS:
|
||||
1. [setup, prescriptive] Soft-guard: IF agent_id is not a non-empty str: RETURN None (before any dict lookup — guards a non-hashable id)
|
||||
2. [sequential, prescriptive] content = FIRST_MESSAGE_PRESETS.get(agent_id); IF content is None: RETURN None (INV-002 — zero HTTP)
|
||||
3. [sequential, prescriptive] Soft-guard: IF client is None OR session_id is not a non-empty str: RETURN None
|
||||
4. [sequential, prescriptive] key = "ratatoskr-preset-" + sha256(content utf-8)[:12]
|
||||
5. [sequential, prescriptive] TRY: await asyncio.wait_for(write_authored_history(client, session_id, content=content, idempotency_key=key), timeout=_SEED_TIMEOUT_S)
|
||||
tool: { destructive: false, idempotent: true, read_only: false, open_world: false }
|
||||
6. [branch, prescriptive] EXCEPT asyncio.CancelledError: RAISE; EXCEPT Exception: RETURN None
|
||||
7. [cleanup, prescriptive] RETURN content
|
||||
TESTS:
|
||||
seeds_preset [happy,tracer]: preset agent, mock 201 → returns the preset text; exactly one POST /sessions/{id}/history; body author="assistant" + content=preset + idempotency_key="ratatoskr-preset-"+sha256(preset)[:12]
|
||||
no_preset_zero_http [happy]: agent "mimir" → returns None; NO HTTP issued
|
||||
feature_absent_swallowed [error]: preset agent, mock 404 session_not_found → returns None, no raise
|
||||
session_api_failed_swallowed [error]: preset agent, mock 409 → returns None, no raise
|
||||
transport_error_swallowed [error]: preset agent, mock httpx.ConnectError → returns None, no raise
|
||||
unexpected_exception_swallowed [error]: preset agent, write raises ValueError → returns None, no raise (INV-001 broad never-raise)
|
||||
cancellation_propagates [error]: preset agent, write raises asyncio.CancelledError → RE-RAISED (never swallowed)
|
||||
malformed_agent_id_no_http [adversarial]: agent_id=123 (non-str) OR "" → None; NO HTTP; no raise
|
||||
empty_session_id [adversarial]: session_id="" (preset agent) → None (soft guard); NO HTTP; no raise
|
||||
```
|
||||
@@ -322,9 +322,7 @@ via a `--characters` one-shot lifecycle probe; persona-state write surfaced via
|
||||
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
|
||||
declares no request schema and the prose spec documents only the GET counterpart,
|
||||
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]).**
|
||||
so the caller supplies the snapshot shape (`--set-persona-pad` sends `{pad:[…]}`).
|
||||
|
||||
```contract
|
||||
FN list_character_models(client) -> dict[str, Any]
|
||||
@@ -371,102 +369,6 @@ POST: [POST-001 return_value] on 204 returns None; [POST-002 side_effect] outbou
|
||||
STEPS:
|
||||
1. [sequential, prescriptive] resp = await client.post(f"/sessions/{session_id}/persona_state", json=snapshot); IF 204 RETURN None; ELSE RAISE SessionApiFailed
|
||||
TESTS:
|
||||
happy [happy]: 204 → None; body == {"pad":{"pleasure","arousal","dominance"}} verbatim (canonical named-key dict, #317)
|
||||
happy [happy]: 204 → None; body == {"pad":[...]} verbatim
|
||||
non_204 [error]: 422 → SessionApiFailed(422)
|
||||
```
|
||||
|
||||
## Amendment 2026-07-06 — authored-history write (#347, v1 coverage-audit re-open)
|
||||
|
||||
Worldtree shipped #347 (authored-history-write) as OpenAPI 2.3.0: a new
|
||||
`POST /sessions/{session_id}/history` primitive that writes ONE model-visible
|
||||
turn into a session's ledger AS the bound agent, WITHOUT a generation and
|
||||
WITHOUT lived-turn side effects (the SillyTavern "first message"). The re-vendor
|
||||
(2.2.0→2.3.0, pin `879cefe`) re-opened the v1 coverage-audit with this one new
|
||||
in-scope REST path-group; this amendment closes it on the consumer side and also
|
||||
un-defers `GET /sessions/{id}/messages` (previously §Out of scope) as the seed's
|
||||
read-back.
|
||||
|
||||
**Hide-existence (server INV-347-1) — the load-bearing consumer contract.** The
|
||||
`session.history.write` grant is checked FIRST — an ungranted caller (or a
|
||||
non-owner, or an unknown session) gets a 404 **byte-identical** to a genuine
|
||||
`session_not_found`, never a 403/409/422 that would reveal the feature exists.
|
||||
The consumer MUST honor this: treat 404 as **feature-absent**, fall back (a
|
||||
production consumer to a model-generated greeting), and NEVER capability-probe to
|
||||
tell feature-absent from ungranted from session-absent. The wrapper encodes it by
|
||||
raising a DISTINCT `AuthoredHistoryUnavailable` on 404 (NOT `SessionApiFailed`),
|
||||
so a caller branches feature-absent without inspecting a status code.
|
||||
|
||||
**Request body — v1-minimal, wire-pinned by the server.** The frozen OpenAPI 2.3.0
|
||||
exports an empty request schema, but the server pins `AuthoredWriteRequest`
|
||||
(`extra="forbid"`): `{author, content, idempotency_key, effects?,
|
||||
claimed_original_at?}`. v1: `author="assistant"` (only value), `content` (UTF-8,
|
||||
server-bounded at `authored_content_max_bytes`=8192), `idempotency_key` (REQUIRED,
|
||||
per-session dedup), `effects` omitted (== "none"; only value). Because
|
||||
`extra="forbid"`, the wrapper omits `effects`/`claimed_original_at` when None
|
||||
(never sends null). Success is 201 (fresh) OR 200 (idempotent replay,
|
||||
byte-identical body); both return the `AuthoredTurnResponse` `{author,
|
||||
content_chars, injected_at, phase, seq, session_id, turn_id}` verbatim (provenance
|
||||
is audit-only, NEVER on this body — INV-347-7).
|
||||
|
||||
**Assistant-first provider constraint (deferred, inert for the probe).** A
|
||||
create-time first-message makes the assistant seq-0 (assistant-first history);
|
||||
Anthropic-family providers 400 the *next generation*, vLLM/openai_compat tolerate
|
||||
it. The `--seed-first-message` probe seeds but does NOT generate, so the
|
||||
constraint is inert for the probe — a real consumer that then generates must bind
|
||||
an assistant-first-tolerant provider.
|
||||
|
||||
```contract
|
||||
FN write_authored_history(client: httpx.AsyncClient, session_id: str, *, content: str, idempotency_key: str, author: str = "assistant", effects: str | None = None, claimed_original_at: str | None = None) -> dict[str, Any]
|
||||
BRIEF: POST /sessions/{session_id}/history — the #347 authored-history-write primitive (write one model-visible turn as the bound agent, no generation, no side effects). Body {author, content, idempotency_key} + "effects"/"claimed_original_at" only when non-None (server AuthoredWriteRequest is extra="forbid"). Success 200 (replay) or 201 (fresh) → AuthoredTurnResponse dict verbatim. 404 → AuthoredHistoryUnavailable (hide-existence: feature-absent/ungranted/session-absent, indistinguishable by design — consumer falls back, never probes). Any other non-2xx → SessionApiFailed.
|
||||
PRE: [PRE-001 hard] client is not None -- assert client is not None
|
||||
PRE: [PRE-002 hard] session_id is a non-empty str -- assert session_id and isinstance(session_id, str)
|
||||
PRE: [PRE-003 hard] content is a non-empty str -- assert content and isinstance(content, str)
|
||||
PRE: [PRE-004 hard] idempotency_key is a non-empty str -- assert idempotency_key and isinstance(idempotency_key, str)
|
||||
PRE: [PRE-005 hard] author is a non-empty str -- assert author and isinstance(author, str)
|
||||
POST: [POST-001 side_effect] exactly one POST to /sessions/{session_id}/history; body == {"author": author, "content": content, "idempotency_key": idempotency_key} plus "effects" iff effects is not None plus "claimed_original_at" iff claimed_original_at is not None (no null-valued keys — extra="forbid")
|
||||
POST: [POST-002 return_value] on 200 or 201 returns resp.json() unmodified
|
||||
ERROR_ROUTING:
|
||||
HTTP 404 (hide-existence session_not_found):
|
||||
local_handling: raise AuthoredHistoryUnavailable(session_id=session_id)
|
||||
flow_control: abort
|
||||
state_recovery: caller treats as feature-absent; fall back to a model-generated greeting; NEVER capability-probe (INV-347-1)
|
||||
HTTP other non-2xx (incl. 409 generation_active, 422 content_too_long/validation_failed, 401 auth_revoked, 410 session_retired):
|
||||
local_handling: raise SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
flow_control: abort
|
||||
state_recovery: none (409 retryable; 422 caller bug/oversize)
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] assert PRE-001..PRE-005
|
||||
2. [sequential, flexibility=prescriptive] body = {"author": author, "content": content, "idempotency_key": idempotency_key}; IF effects is not None: body["effects"] = effects; IF claimed_original_at is not None: body["claimed_original_at"] = claimed_original_at
|
||||
3. [sequential, flexibility=prescriptive] resp = await client.post(f"/sessions/{session_id}/history", json=body)
|
||||
tool: { destructive: false, idempotent: true, read_only: false, open_world: false }
|
||||
4. [branch, flexibility=prescriptive] IF resp.status_code in (200, 201): RETURN resp.json(); ELIF resp.status_code == 404: RAISE AuthoredHistoryUnavailable(session_id=session_id); ELSE RAISE SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
TESTS:
|
||||
happy_fresh_201 [happy,tracer]: 201 {author:"assistant", seq:0, phase:"seeded", turn_id, content_chars, session_id, injected_at} → dict verbatim; outbound body == {"author":"assistant","content":<c>,"idempotency_key":<k>} exactly (no effects/claimed_original_at keys)
|
||||
happy_replay_200 [happy]: 200 (same-key replay, byte-identical body) → dict verbatim
|
||||
body_includes_effects [trace]: effects="none" → outbound body has "effects":"none"; claimed_original_at="2020-01-01T00:00:00Z" → body has that key too
|
||||
hide_existence_404 [error]: 404 {error_code:"session_not_found"} → raises AuthoredHistoryUnavailable(session_id=<arg>), NOT SessionApiFailed
|
||||
generation_active_409 [error]: 409 {error_code:"generation_active"} → SessionApiFailed(status=409)
|
||||
content_too_long_422 [error]: 422 {error_code:"content_too_long"} → SessionApiFailed(status=422)
|
||||
empty_content [adversarial]: content="" → AssertionError; no HTTP issued
|
||||
empty_idempotency_key [adversarial]: idempotency_key="" → AssertionError; no HTTP issued
|
||||
empty_session_id [adversarial]: session_id="" → AssertionError; no HTTP issued
|
||||
|
||||
FN get_session_messages(client: httpx.AsyncClient, session_id: str) -> dict[str, Any]
|
||||
BRIEF: GET /sessions/{session_id}/messages — the session's message history (spec §GET /sessions/{id}/messages), un-deferred as the #347 probe's read-back so a seeded turn can be confirmed to render as a normal role=assistant message (model-invisible provenance — a seed is indistinguishable from a lived turn on read). Returns {session_id, items:[{seq, role, content, ...}], next_cursor} verbatim. Owner-scoped; any non-200 → SessionApiFailed. v1 reads the server default page (no pagination params — the probe reads a fresh 1-message session; add limit/cursor when a caller needs scrollback).
|
||||
PRE: [PRE-001 hard] client is not None -- assert client is not None
|
||||
PRE: [PRE-002 hard] session_id is a non-empty str -- assert session_id and isinstance(session_id, str)
|
||||
POST: [POST-001 return_value] on 200 returns resp.json() unmodified
|
||||
ERROR_ROUTING:
|
||||
HTTP non-200 (incl. 404 session_not_found cross-owner/unknown):
|
||||
local_handling: raise SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
flow_control: abort
|
||||
state_recovery: none
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] assert PRE-001, PRE-002
|
||||
2. [sequential, flexibility=prescriptive] resp = await client.get(f"/sessions/{session_id}/messages")
|
||||
3. [branch, flexibility=prescriptive] IF resp.status_code == 200: RETURN resp.json(); ELSE RAISE SessionApiFailed
|
||||
TESTS:
|
||||
happy [happy]: 200 {session_id, items:[{seq:0, role:"assistant", content:"…"}], next_cursor:null} → dict verbatim
|
||||
not_found_404 [error]: 404 → SessionApiFailed(status=404)
|
||||
empty_session_id [adversarial]: "" → AssertionError; no HTTP issued
|
||||
```
|
||||
|
||||
@@ -3,7 +3,7 @@ contract_version: "2.1"
|
||||
module: "ratatoskr.web"
|
||||
purpose: "v0.19.2 web debug-surface parity: 3 admin/debug panes (Tools inventory, BifrostState, AdminEvents SSE) proxied server-side with the admin key server-held, plus a reliable PAD-refresh poll and a non-engine reasoning indicator in the transcript."
|
||||
target_module: "ratatoskr.web (server.py routes + entrypoint.py + static/index.html)"
|
||||
scope: "v0.19.2 web debug-surface parity — bring the browser surface (now the PRIMARY debug surface) to TUI parity. THREE new admin/debug panes proxied server-side + TWO transcript affordances. (1) Tools inventory: GET /api/sessions/{id}/tools proxies owner-scoped get_session_tools into the tools pane (what the LLM HAS at turn-fire), above the live tool events. (2) BifrostState pane: GET /api/sessions/{id}/bifrost proxies admin-scoped get_session_bifrost; the admin key is SERVER-HELD (app.state.admin_key from RATATOSKR_ADMIN_API_KEY), never sent to the browser. (3) AdminEvents pane: GET /api/admin/events is an SSE proxy of stream_admin_events, session-filtered SERVER-side (heartbeats + other-session events dropped), re-emitted under a fixed 'admin_event' name so every dotted type renders with one browser listener. (4) PAD refresh: the persona/affect pane polls a bounded window instead of a single 2s shot that raced the post-turn-async affect.emit. (5) Reasoning indicator: an ephemeral, clearly-non-engine transcript line on `thinking` deltas, cleared when text begins. Direct in-session TDD (the #17/#18 pattern); this contract is authored post-implementation to anchor the heid code review (the client wrappers get_session_tools/get_session_bifrost/stream_admin_events are already contracted in the sessions/sse_client specs — this contract governs the WEB proxy + presenter surface only. v0.20.0 REDESIGN (Claude Design 'Ratatoskr Console' import): the tabbed telemetry column is replaced by a 3-column command-console — a left engine-ticker rail (the DEBUG + ADMIN + tool/turn-lifecycle feeds MERGED into one timeline via tickerAdd, plus a tools-armed chip list + a full-detail Bifrost rail pane) · a center conversation (per-turn INLINE chain-of-thought, replacing the separate Think pane) · a right resizable affect console (dominant/canonical-mood centerpiece + bipolar PAD faders each carrying a turn-to-turn Δ+sparkline + a P×A mood orbit + relations metric rows + canonical directive). ALL SERVER ROUTES UNCHANGED. Single-file/no-CDN/vanilla preserved; adds a light/dark theme toggle (dark default) + an inlined data-URI favicon. Presenter FN renames tracked below (renderBifrostState→renderBifrost; renderAffectPane→renderConsole; setPersonaStrip removed; tickerAdd/setFader/setFaderTrend/renderOrbit/renderDominant/renderDerived/renderRelations/renderDirective added). INV-001/INV-004 held.)."
|
||||
scope: "v0.19.2 web debug-surface parity — bring the browser surface (now the PRIMARY debug surface) to TUI parity. THREE new admin/debug panes proxied server-side + TWO transcript affordances. (1) Tools inventory: GET /api/sessions/{id}/tools proxies owner-scoped get_session_tools into the tools pane (what the LLM HAS at turn-fire), above the live tool events. (2) BifrostState pane: GET /api/sessions/{id}/bifrost proxies admin-scoped get_session_bifrost; the admin key is SERVER-HELD (app.state.admin_key from RATATOSKR_ADMIN_API_KEY), never sent to the browser. (3) AdminEvents pane: GET /api/admin/events is an SSE proxy of stream_admin_events, session-filtered SERVER-side (heartbeats + other-session events dropped), re-emitted under a fixed 'admin_event' name so every dotted type renders with one browser listener. (4) PAD refresh: the persona/affect pane polls a bounded window instead of a single 2s shot that raced the post-turn-async affect.emit. (5) Reasoning indicator: an ephemeral, clearly-non-engine transcript line on `thinking` deltas, cleared when text begins. Direct in-session TDD (the #17/#18 pattern); this contract is authored post-implementation to anchor the heid code review (the client wrappers get_session_tools/get_session_bifrost/stream_admin_events are already contracted in the sessions/sse_client specs — this contract governs the WEB proxy + presenter surface only)."
|
||||
depends_on:
|
||||
- "httpx"
|
||||
- "starlette"
|
||||
@@ -35,17 +35,6 @@ functions:
|
||||
steps: "Open client_factory() client; await get_session_tools(client, session_id); return 200. Except SessionApiFailed -> status-preserving envelope."
|
||||
flexibility: "prescriptive"
|
||||
|
||||
- name: "_session_messages_endpoint"
|
||||
signature: "async _session_messages_endpoint(request: Request) -> JSONResponse"
|
||||
description: "GET /api/sessions/{session_id}/messages — proxy the session's message history so the SPA renders existing turns on open (notably a #347 authored first-message seeded at create-time; without it a seeded session's transcript is blank until the user speaks)."
|
||||
preconditions:
|
||||
- "session_id in path_params."
|
||||
postconditions:
|
||||
- "POST-001: 200 with the upstream {session_id, items, next_cursor} dict verbatim on success."
|
||||
- "POST-002: on SessionApiFailed(status) -> JSONResponse({error_code:'session_messages_unavailable', status}, status_code=status) — status-preserving."
|
||||
steps: "Open client_factory() client; await get_session_messages(client, session_id); return 200. Except SessionApiFailed -> status-preserving envelope."
|
||||
flexibility: "prescriptive"
|
||||
|
||||
- name: "_session_bifrost_endpoint"
|
||||
signature: "async _session_bifrost_endpoint(request: Request) -> JSONResponse"
|
||||
description: "GET /api/sessions/{session_id}/bifrost — proxy admin-scoped Bifrost dispatch state."
|
||||
@@ -106,42 +95,33 @@ functions:
|
||||
- "POST-002: a scheduled poll no-ops if a NEW turn has started (state.turnId truthy) or already settled — no refresh of a stale agent, no unbounded polling."
|
||||
flexibility: "open"
|
||||
|
||||
- name: "loadTranscript (index.html)"
|
||||
signature: "async loadTranscript(sessionId) -> void"
|
||||
description: "On session open, GET /api/sessions/{id}/messages and render each EXISTING turn into #transcript — notably a #347 authored first-message seeded at create-time (which lives in the ledger, not the live turn stream, so without this the transcript is blank until the user speaks)."
|
||||
- name: "web pane renderers (index.html: renderToolsInventory / renderBifrostState / openAdminEvents)"
|
||||
signature: "renderToolsInventory(inv) ; renderBifrostState(b) ; openAdminEvents(sessionId)"
|
||||
description: "Render the three new surfaces; all content escaped (INV-004)."
|
||||
postconditions:
|
||||
- "POST-001: assistant items render as a .response .md-body bubble via markdownSafe(content) (escape-first whitelist, same path as appendResponse); user items render as a .prompt-echo via textContent — no upstream content reaches innerHTML unescaped (INV-004)."
|
||||
- "POST-002: any non-200, fetch error, or parse error is swallowed (best-effort) — a blank transcript is acceptable; opening the workspace is never blocked."
|
||||
flexibility: "prescriptive"
|
||||
|
||||
- name: "web pane renderers (v0.20.0: index.html: renderToolsInventory / renderBifrost / openAdminEvents + tickerAdd)"
|
||||
signature: "renderToolsInventory(inv) ; renderBifrost(b) ; openAdminEvents(sessionId) ; tickerAdd(kind, msg, dim)"
|
||||
description: "Render the debug/admin surfaces into the 3-column console; all content escaped (INV-004). v0.20.0: BifrostState is now a full-detail LEFT-RAIL pane (renderBifrost, renamed from renderBifrostState); AdminEvents + the raw debug/op log + tool_start/result + turn lifecycle are MERGED into one engine-ticker timeline via tickerAdd (openAdminEvents routes admin_event → tickerAdd; the turn SSE handlers route worker_phase/tool/text_boundary/affect_update → tickerAdd); Tools inventory is a rail chip list (renderToolsInventory)."
|
||||
postconditions:
|
||||
- "POST-001: every dynamic value (agent_id, tool names/descriptions, endpoint, caps, consumer_id, admin event type + data, ticker msg/dim) is passed through esc() or esc(JSON.stringify(...)); no upstream string reaches innerHTML unescaped."
|
||||
- "POST-002: openAdminEvents closes a prior EventSource before opening a new one (state.adminES) and, on stream_error, closes so native EventSource does NOT retry-loop; admin events render into the engine ticker via tickerAdd."
|
||||
- "POST-003: renderToolsInventory renders builtin + bifrost tool NAMES as rail chips (a compact 'what does the LLM have' glance); renderBifrost renders endpoint + connected + consumer_id + capabilities_granted chips + per-tool name/description rows (the full detail, admin-gated; the admin key stays server-held). tickerAdd bounds the feed to the last 400 rows (a tail, not an archive)."
|
||||
- "POST-004: tool NAME-vs-DESCRIPTION split preserved — the rail chip list shows names only; per-tool descriptions live in the Bifrost pane's tools list. The engine-ticker spine (.ticker-inner::before) lives on the content-height wrapper so it stays visible when auto-scrolled to the newest entry."
|
||||
- "POST-001: every dynamic value (agent_id, tool names/descriptions, endpoint, caps, admin event type + data) is passed through esc() or esc(JSON.stringify(...)); no upstream string reaches innerHTML unescaped."
|
||||
- "POST-002: openAdminEvents closes a prior EventSource before opening a new one (state.adminES); the admin data blob renders via esc(JSON.stringify(d.data))."
|
||||
- "POST-003: renderToolsInventory prepends the static inventory ABOVE live tool events without clobbering them (a re-render replaces only the .tools-inventory block)."
|
||||
- "POST-004: the Tools inventory renders tool NAMES only — a compact comma-joined summary ('what does the LLM have', the debug glance); per-tool DESCRIPTIONS are surfaced in the BifrostState pane's tools list, deliberately NOT duplicated here. (Heid panel Hulda/Regin precision finding — accepted: contract wording clarified, code unchanged; the earlier 'names/descriptions' phrasing in INV-004 refers to the SET of value types that MAY appear across the new panes and must be escaped, not a mandate that every pane render descriptions.)"
|
||||
flexibility: "open"
|
||||
|
||||
- name: "renderConsole + trend (v0.20.0 — unified persona/affect console; supersedes renderAffectPane/renderPersonaPane/setPersonaStrip)"
|
||||
signature: "renderConsole(snap) ; setFader(axis,v) ; setFaderTrend(axis) ; renderOrbit() ; renderDominant(snap) ; renderDerived(snap) ; renderRelations(snap) ; renderDirective(snap) ; pushAffectHistory(snap) ; sparkPointsH(vals,w,h,endX) ; stripPoints(vals) ; proj3(p,a,d) ; trendDelta(vals)"
|
||||
description: "ONE render path for BOTH the Tier-1 persona_state snapshot and the Tier-3 affect snapshot (renderConsole), feeding the right affect console: dominant/canonical-mood centerpiece, bipolar PAD faders (each with a turn-to-turn Δ + sparkline), a P×A mood orbit from PAD history, an affect-derived grid, relations metric rows, and the canonical directive. Replaces the v0.19.x split of renderPersonaPane (Tier-1 pane) + renderAffectPane (Tier-3 pane) + setPersonaStrip (top-bar strip, removed — PAD now lives in the console faders)."
|
||||
- name: "renderAffectPane + trend (v0.19.4 — relation_edge/1 render + sparkline)"
|
||||
signature: "renderAffectPane(snap) ; pushAffectHistory(snap) ; sparkline(vals) ; trendDelta(vals)"
|
||||
description: "Render the Tier-3 affect snapshot as PAD mood + the durable per-entity relational model, each value with a Δ-vs-previous + a session-lived sparkline."
|
||||
postconditions:
|
||||
- "POST-001: reads snap.relations (relation_edge/1: target_entity + trust_ability/benevolence/integrity + warmth as {value,confidence,evidence_count} + agency + relation_context) — the CURRENT Worldtree emit shape; falls back to the legacy flat snap.valence for an older emitter. Tier-1 fields (baseline_pad, mood_drift, dominant_emotion, emotions_active) render WHEN PRESENT, '—' when absent (Tier-3 lacks them)."
|
||||
- "POST-002: SVG sparklines (v0.20.4, adapted from the design prototype; replace the unicode strings). Each PAD fader shows current value + Δ-vs-previous (▲/▼) + a VERTICAL SVG strip (`stripPoints`, 26×132, time down Y newest-at-bottom, value on X ±11, gradient-faded via stripFade{P,A,D}, dot at newest). Each relation metric shows a HORIZONTAL SVG sparkline (`sparkPointsH`, 56×13, auto-scaled to its OWN range, sparkFade gradient + end dot). renderOrbit is now a 3D ISOMETRIC P×A×D cube (`proj3`: P right-down / A left-down / D up, 2:1 iso, center 62,66, scale 26) plotting the trajectory + a pulsing now-marker + a drop line to the D=−1 floor + a floor shadow ellipse (depth cue). All drawn from AFFECT_HIST (rolling, HIST_CAP=24, session-lived); sparkline coords are computed numerics (no upstream strings → INV-004 trivially held). Gradients live in one hidden `<defs>` svg in the console."
|
||||
- "POST-003: pushAffectHistory dedupes by emitted_at||last_updated_at so the ~4x/turn post-turn PAD poll contributes ONE sample/turn; history is CLIENT-side only (lost on reload — durable cross-session history via a provider-side snapshot log is a deferred follow-up, NOT built here)."
|
||||
- "POST-004: INV-001 honesty — no fabricated Tier-1 fields. The dominant-emotion centerpiece shows a real OCC dominant_emotion (Tier-1) OR the CANONICAL mood word from canonMood(pad) (Tier-3, dimmed) OR '—'; NEVER a synthesized emotion. The affect-derived grid drops non-emitted metrics (intensity/decay-τ) and shows only real/client-derived cells (baseline/drift real for Tier-1, client-derived samples/volatility). INV-004 — every dynamic value passes through esc(); numerics go through toFixed, never innerHTML-raw."
|
||||
- "POST-001: reads snap.relations (relation_edge/1: target_entity + trust_ability/benevolence/integrity + warmth as {value,confidence,evidence_count} + agency + relation_context + obligation_balance) — the CURRENT Worldtree emit shape; falls back to the legacy flat snap.valence for an older emitter. SUPERSEDES the #18-D2 contract's valence assumption (Worldtree's #265 Vili rework replaced valence/regard with the relation_edge/trust model; the old renderer read snap.valence and showed an empty 'valence (0)' — the bug this fixes)."
|
||||
- "POST-002: each metric shows current value + Δ-vs-previous (▲/▼) + a unicode sparkline auto-scaled to its OWN observed range (flat ▄ when sub-0.01 stable — no noise amplification), drawn from AFFECT_HIST (rolling, HIST_CAP=24, session-lived)."
|
||||
- "POST-003: pushAffectHistory dedupes by emitted_at so the ~4x/turn post-turn PAD poll contributes ONE sample/turn; history is CLIENT-side only (lost on reload — durable cross-session history via a provider-side snapshot log is a deferred follow-up, NOT built here)."
|
||||
- "POST-004: INV-001 honesty — no fabricated Tier-1 fields (no synthesized dominant_emotion). INV-004 — head() escapes its whole argument (incl. target_entity + relation_context from the snapshot) and metric() escapes every cell; numeric values go through toFixed, never innerHTML-raw."
|
||||
flexibility: "open"
|
||||
|
||||
- name: "canonical affect-NL + context-injection reconstruction (v0.19.5 canons; v0.20.2 full context-injection panel)"
|
||||
signature: "canonMood(pad) ; canonDirective(rel) ; canonPadFallback(pad) ; canonEmotionDirective(type) ; renderDirective(snap) ; loadPersonaCanon()"
|
||||
description: "Reconstruct + render the HIDDEN affect-context block Worldtree assembles into the agent's system prompt (never on any wire) — byte-exact to Worldtree's own describe_pad + render_d2_canonical + derive_directive + _pad_band_fallback. The v0.20.2 'context injection' panel shows the full block: mood descriptor + mood directive + relationship directive. Reference: docs/vendor/worldtree-persona-canon/affect-egress-consumer-reference.md (pinned)."
|
||||
- name: "canonical affect-NL (v0.19.5 — vendored Worldtree d2 render canons)"
|
||||
signature: "canonMood(pad) ; canonDirective(rel) ; loadPersonaCanon()"
|
||||
description: "Render the LITERAL mood word + relationship directive Worldtree context-injects into the agent, byte-exact to Worldtree's own describe_pad + render_d2_canonical."
|
||||
postconditions:
|
||||
- "POST-001: DETERMINISTIC, no LLM. canonMood mirrors describe_pad (valence×arousal grid + strict ±0.3 bands + dominance clause); canonDirective mirrors render_d2_canonical; canonPadFallback mirrors renderer._pad_band_fallback BYTE-EXACT (P×A quadrant: hi/lo/mid arousal band × p>0.3/<-0.3/neutral, with the negative_low_dominance (d<-0.3) special case + neutral_high_a + default); canonEmotionDirective is the occ_directives[type].directive lookup (+ tier / full_only flag)."
|
||||
- "POST-002: the canon DATA is VENDORED (docs/vendor/worldtree-persona-canon/{d2-mood-render-canon-v1,d2-render-canon-v1}.json), pinned drift-gated in .corviduo-canonicals.toml; the flat browser form (static/persona_render_canon.json, served /static) is regenerated by scripts/build_persona_canon.py via Worldtree's OWN authoritative loader — v0.20.2 extended it to emit mood_directive {occ_directives, pad_band_fallback, salience, pad_band_cutoff, full_only}. The affect-egress consumer reference is pinned tolerate_drift (worldtree-affect-egress-consumer-reference-v1; worldtree-dev co-signs + pings on change)."
|
||||
- "POST-003: fail-open — canon absent (fetch fails) → the reconstructed lines OMIT, the structured console still renders. Every canon-derived string is esc()'d before the DOM (INV-004)."
|
||||
- "POST-004: HONEST-PARTIAL provenance (affect-egress-reference §3). The mood descriptor + relationship directive are EXACT (tagged 'exact'); the mood DIRECTIVE is a CANDIDATE pair (tagged 'candidate') — the OCC emotion directive for the delivered dominant_emotion type AND the PAD-band fallback — because affect.emit is type-only (no intensity) so the salience gate (≥0.2) can't be evaluated; BOTH are shown with the 'injected if intensity ≥ salience' caveat, never asserting which fires. When dominant_emotion is absent the fallback alone is EXACT. The panel is labeled reconstructed + hidden-from-consumers + dev-only (the reference-impl's sanctioned understand/reconstruct use, NOT end-user display per the reference's caveat). WATCH: a pending Worldtree render_d2_canonical change conditionally drops the trailing 'avoid premature we-framing' clause under a 3-gate combo — canonDirective holds as-is until worldtree-dev pings with the exact conditional + a canon bump."
|
||||
- "POST-001: DETERMINISTIC, no LLM. canonMood mirrors describe_pad (valence×arousal grid + strict ±0.3 bands + dominance clause); canonDirective mirrors render_d2_canonical (interval band-cut lookup + per-band phrase assembly + cross-axis low-trust-precedence behavior clause). BOTH VERIFIED BYTE-EXACT against Worldtree's own renderer run on the live snapshot (the reference harness re-runs Worldtree's functions + asserts string equality — reproducible)."
|
||||
- "POST-002: the canon DATA is VENDORED (docs/vendor/worldtree-persona-canon/{d2-mood-render-canon-v1,d2-render-canon-v1}.json), pinned drift-gated in .corviduo-canonicals.toml (worldtree-persona-{mood,d2}-render-canon-v1); the flat browser form (static/persona_render_canon.json, served /static) is regenerated by scripts/build_persona_canon.py via Worldtree's OWN authoritative loader. Reference-impl posture: ADOPT the dep's canonical render, do NOT invent vocab — an invented 'faintly excited' would MISLEAD where the canonical (±0.3 bands) says 'neutral'."
|
||||
- "POST-003: fail-open — canon absent (fetch fails) → the canonical lines OMIT, the structured pane still renders. The canon-derived strings are esc()'d before the DOM for INV-004 consistency."
|
||||
flexibility: "open"
|
||||
|
||||
invariants:
|
||||
|
||||
@@ -1,50 +1,6 @@
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"AuthoredTurnResponse": {
|
||||
"description": "#347 — the authored-write ack. Provenance is audit-only and NEVER on this\npayload (INV-347-7). ``content_chars`` is the CHARACTER count (may differ from\nthe UTF-8 byte length the request is bounded against — INV-347-12).",
|
||||
"properties": {
|
||||
"author": {
|
||||
"title": "Author",
|
||||
"type": "string"
|
||||
},
|
||||
"content_chars": {
|
||||
"title": "Content Chars",
|
||||
"type": "integer"
|
||||
},
|
||||
"injected_at": {
|
||||
"title": "Injected At",
|
||||
"type": "string"
|
||||
},
|
||||
"phase": {
|
||||
"title": "Phase",
|
||||
"type": "string"
|
||||
},
|
||||
"seq": {
|
||||
"title": "Seq",
|
||||
"type": "integer"
|
||||
},
|
||||
"session_id": {
|
||||
"title": "Session Id",
|
||||
"type": "string"
|
||||
},
|
||||
"turn_id": {
|
||||
"title": "Turn Id",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turn_id",
|
||||
"session_id",
|
||||
"author",
|
||||
"phase",
|
||||
"seq",
|
||||
"injected_at",
|
||||
"content_chars"
|
||||
],
|
||||
"title": "AuthoredTurnResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"BifrostBindingRequest": {
|
||||
"additionalProperties": false,
|
||||
"description": "Bifrost binding parameters for session-create (issue #160).",
|
||||
@@ -434,7 +390,6 @@
|
||||
"session_not_found",
|
||||
"session_not_bifrost_bound",
|
||||
"session_retired",
|
||||
"generation_active",
|
||||
"agent_not_available",
|
||||
"turn_not_found",
|
||||
"turn_finished",
|
||||
@@ -950,7 +905,7 @@
|
||||
"info": {
|
||||
"description": "Multi-turn conversation interface for Worldtree agents.",
|
||||
"title": "Worldtree Conversation API",
|
||||
"version": "2.3.0"
|
||||
"version": "2.2.0"
|
||||
},
|
||||
"openapi": "3.1.0",
|
||||
"paths": {
|
||||
@@ -6427,141 +6382,6 @@
|
||||
"summary": "Update Session"
|
||||
}
|
||||
},
|
||||
"/sessions/{session_id}/history": {
|
||||
"post": {
|
||||
"description": "#347 — write one model-visible turn into a session's ledger AS the bound\nagent, WITHOUT a generation and WITHOUT lived-turn side effects.\n\nHide-existence ordering (INV-347-13): the ``session.history.write`` grant is\nchecked FIRST — before session resolution, before ANY body parse/validation,\nbefore the active-generation guard. An ungranted caller receives ONLY the\nhide-404 (byte-identical to session-not-found, INV-347-1) — never a\n422/409/403 that would distinguish feature-absent from session-absent.",
|
||||
"operationId": "authored_history_write_sessions__session_id__history_post",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "session_id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"title": "Session Id",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AuthoredTurnResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"400": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"401": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"405": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"409": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"412": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"422": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"500": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
},
|
||||
"503": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Error — unified envelope (detail.error_code is the stable identifier)."
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"HTTPBearer": []
|
||||
}
|
||||
],
|
||||
"summary": "Authored History Write"
|
||||
}
|
||||
},
|
||||
"/sessions/{session_id}/messages": {
|
||||
"get": {
|
||||
"description": "Return paginated message history for a session.",
|
||||
|
||||
@@ -2699,13 +2699,10 @@ The `turn.started` event always carries `bifrost_override_applied: bool` (True/F
|
||||
|
||||
Tier 3 agents are consumer-owned, Worldtree-hosted agents whose
|
||||
identity lives at `<user_id>:<agent_name>`. They share the persistent
|
||||
session infrastructure with Tier 1 / Tier 2. The layer-specific
|
||||
machinery is now largely active: **`persona` (Phase 2.1, #186),
|
||||
`memory` (Phase 2.1, #197), and `motivational` (Phase 2.2, #187) are
|
||||
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.
|
||||
session infrastructure with Tier 1 / Tier 2 but layer-specific
|
||||
machinery (persona, motivational, memory, valence) is reserved for
|
||||
later phases — Phase 2.0 ships baseline addressing + ownership +
|
||||
lifecycle only.
|
||||
|
||||
### Endpoints
|
||||
|
||||
@@ -2723,13 +2720,11 @@ below document the active layers and their exact validated shapes.
|
||||
{
|
||||
"agent_name": "wizard",
|
||||
"system_prompt": "You are a guided-elicitation wizard...",
|
||||
"role": "gen-reasoning", // REQUIRED — a configured model-role (#344), not a raw model id
|
||||
"persona": { // active (Phase 2.1) — single-letter OCEAN keys; see "Persona layer"
|
||||
"ocean": {"O": 0.4, "C": 0.6, "E": -0.3, "A": 0.2, "N": 0.5}
|
||||
},
|
||||
"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
|
||||
"model": "glm5-turbo",
|
||||
"persona": null, // schema-reserved; non-null → 422 layer_deferred
|
||||
"motivational": null,
|
||||
"valence": null,
|
||||
"memory": null
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2761,150 +2756,6 @@ after definition.
|
||||
The 201 response includes an advisory `warnings` array (#219) — see
|
||||
"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` is **active** as of Phase 2.2 (persona + memory activated in
|
||||
|
||||
+11
-17
@@ -19,7 +19,7 @@ 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 REST API | OpenAPI `info.version` **2.2.0** (`Worldtree/docs/conversation-api-openapi.json`, sha `dbdf4e24…`) — **40 path×method groups** | **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) |
|
||||
|
||||
@@ -48,7 +48,7 @@ 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 |
|
||||
| REST (OpenAPI 2.2.0, path groups) | 40 | 17 | 0 | 23 |
|
||||
| SSE events | 11 | 11 | 0 | 0 |
|
||||
| Bifrost provider planes | 8 verbs | 8 | 0 | (10 gated verbs deferred) |
|
||||
|
||||
@@ -61,7 +61,7 @@ sub-gap).
|
||||
|
||||
---
|
||||
|
||||
## Surface 1 — Conversation REST API (OpenAPI 2.3.0)
|
||||
## Surface 1 — Conversation REST API (OpenAPI 2.2.0)
|
||||
|
||||
### Covered — client path (ratatoskr's core identity)
|
||||
|
||||
@@ -69,8 +69,6 @@ sub-gap).
|
||||
|---|---|---|---|
|
||||
| `POST /sessions` | ✅ | `sessions.py:307` → `cli.py:482`,`tui.py:1508`,`web/server.py:155` | + `end_user_id`, `bifrost` binding; 404→AgentNotFound, 502→BifrostHandshakeFailed |
|
||||
| `POST /sessions/{id}/messages` (turn stream, SSE) | ✅ | `sse_client.py:484` `stream_turn` → cli/tui/web | the primary surface; 409→AgentNotAvailable, 503→TurnLaunchUnavailable (b2 #331) |
|
||||
| `POST /sessions/{id}/history` (authored-history-write, #347) | ✅ | `sessions.py:583` `write_authored_history` → `cli.py:758` `--seed-first-message` | v1: author=assistant, effects=none, per-session idempotency; 404→AuthoredHistoryUnavailable (hide-existence: feature-absent, never probe); 409/422 mapped. **LIVE-PROVEN 2026-07-06** on personal :8081 (grant applied via a rule-based Heimdall allow, worldtree-dev): create mimir session → seed → **201** (seq=0, phase=seeded, turn_id=1798) → GET /messages reads it back as a plain role=assistant turn (model-invisible provenance confirmed). Hide-404 for ungranted is unit+probe covered |
|
||||
| `GET /sessions/{id}/messages` (history) | ✅ | `sessions.py:635` `get_session_messages` → `cli.py:758` `--seed-first-message` read-back | un-deferred as the #347 seed read-back — confirms model-invisible provenance (a seed reads back as a normal `role=assistant` turn) |
|
||||
| `POST /sessions/{id}/turns/{turn_id}/cancel` | ✅ | `sse_client.py:581` → cli/tui/web | two-stage Ctrl-C; 404/409 mapped |
|
||||
| `GET /agents` | ✅ | `sessions.py:341` → `tui.py:1472`,`web/server.py:100` | Tier-1 roster; merged with local index |
|
||||
| `GET /agents/{id}/persona_state` | ✅ | `sessions.py:384` → `tui.py:1132`,`web/server.py:386` | persona hydrate; 404/403 mapped |
|
||||
@@ -98,21 +96,16 @@ on the same path is an unwired frontier item — see frontier Tier 1):
|
||||
- `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)
|
||||
### In-scope gaps — CONVERGED (zero remaining, 2026-07-01)
|
||||
|
||||
**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:
|
||||
**Every in-scope REST I/O point is now covered.** The frontier that opened this
|
||||
audit (the design-brief §5 observability panes + the presenter-wiring sub-gaps +
|
||||
the Tier-2 tail) is fully closed:
|
||||
|
||||
- Session picker + SSE-resume — wired (`v0.18.5`–`.7`).
|
||||
- Persona · Tools · BifrostState · AdminEvents panes — all built + live (`v0.18.x`–`v0.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
|
||||
@@ -123,6 +116,7 @@ path-group gap. Everything else is covered or excluded-by-design below.
|
||||
| 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}/messages` (history) | 🚫 | §6: single-session live transcript, no history fetch |
|
||||
| `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) |
|
||||
@@ -210,10 +204,10 @@ starts exercising them.
|
||||
|
||||
---
|
||||
|
||||
## Convergence frontier (the v1 to-do) — CLOSED 2026-07-01, re-closed 2026-07-06 (#347)
|
||||
## Convergence frontier (the v1 to-do) — CLOSED 2026-07-01
|
||||
|
||||
**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),
|
||||
**Every in-scope I/O point is covered.** The frontier is empty: REST 17/40 ✅
|
||||
with **zero in-scope gaps** (the other 23 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:
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
# Design brief — `ratatoskr-web` (Worldtree wire monitor)
|
||||
|
||||
> **For:** a visual design pass (Claude Design). **Deliverable:** a single
|
||||
> self-contained HTML prototype, fully populated with representative static
|
||||
> data, that an engineer will wire live data into. **Do not** build a data
|
||||
> layer — build the *shell* and *every state*, beautifully, with placeholder
|
||||
> content in every slot.
|
||||
|
||||
---
|
||||
|
||||
## 1. What you're designing
|
||||
|
||||
`ratatoskr-web` is a **developer-grade debug/observability console** for a
|
||||
conversational-AI engine (Worldtree). Its tagline is *"wire monitor"*: you open
|
||||
a session with an AI agent, send it turns, and **watch that turn flow through
|
||||
every layer of the system at once** — the streaming response, the model's
|
||||
chain-of-thought, the tools it can call, the agent's live emotional/persona
|
||||
state, the provider handshake, and the engine's admin lifecycle events — all
|
||||
side-by-side on one screen.
|
||||
|
||||
**The product IS the observability surface. Chat is just the input.** This is
|
||||
not a chat app, not a marketing page, not an end-user product. The user is one
|
||||
developer (occasionally a few LAN peers) staring at a dense instrument to debug
|
||||
what the engine is doing. Think **oscilloscope / flight-data console / a well-lit
|
||||
`htop`**, not a messaging UI.
|
||||
|
||||
**Design values, in priority order:**
|
||||
1. **Information density earns the screen.** Every region shows live, changing
|
||||
data. Nothing is decorative filler. A quiet, legible, glanceable density is
|
||||
the whole point — the user reads six data streams at a glance.
|
||||
2. **Calm under motion.** Multiple regions update in real time (token streams,
|
||||
live metrics, event logs). The design must stay readable while things move —
|
||||
no jitter, no attention-grabbing per-item animation. Motion is for *state
|
||||
change*, used sparingly.
|
||||
3. **Legibility first.** Monospace, high contrast where it counts, generous but
|
||||
not wasteful spacing. This runs for hours; it must not tire the eye.
|
||||
|
||||
---
|
||||
|
||||
## 2. Aesthetic direction — Australis
|
||||
|
||||
Use the **Australis design system** (a cool-toned, terminal-first dark theme —
|
||||
the `australis-design` skill has the canonical tokens: colors, spacing, radii,
|
||||
shadows, motion). Import/inline `colors_and_type.css`; don't reinvent tokens.
|
||||
|
||||
Non-negotiables from the brand:
|
||||
- **Dark only.** Base is a cool near-black **`#222531` — never pure `#000`.**
|
||||
The eye rests in low-contrast cool grey; **emphasis comes from *brightness*,
|
||||
not saturation.** Layer surfaces up the Sea neutral ramp (`#222531 →
|
||||
`#373b46` → `#414751`).
|
||||
- **Palette families:** *Ice* (surface neutrals), *Aurora* (blue → cyan → green,
|
||||
the primary accents — used generously in that preference order), *Dawn*
|
||||
(red/yellow/magenta — sparingly, for status only). Semantic: info=blue,
|
||||
success=green, warning=yellow, danger=red.
|
||||
- **The signature motif is the aurora glow** — a low-opacity cyan→blue→green
|
||||
light coming *through* the top of the screen, plus a 3px aurora focus ring on
|
||||
interactive controls. Lean into this as the one memorable thing.
|
||||
- **No noise, no textures, no patterns.** *"The screen is the polar sky — empty,
|
||||
with light coming through it."* The one sanctioned gradient is the aurora glow.
|
||||
- **Never a colored left-border on cards** (the LLM-slop trope). Featured cards
|
||||
accent the *top* edge instead.
|
||||
- **Type:** this instrument is **mono-first** — that IS on-brand for Australis
|
||||
("terminal-first"). Use a monospace stack (JetBrains Mono / system mono; see
|
||||
§9 — no web-font CDN allowed). Eyebrows/labels are **mono, UPPERCASE, ~11px,
|
||||
wide-tracked (`0.08–0.16em`)** — use them liberally; they're a system
|
||||
signature.
|
||||
- **Motion:** calm, never bouncy. ~120ms hover, ~200ms state, ~320ms panels.
|
||||
Focus = aurora glow ring. Hover = one step *brighter* (not lower opacity).
|
||||
A slow (8–14s) aurora drift on a hairline top band is welcome; nothing else
|
||||
should loop.
|
||||
|
||||
The current UI already borrows this palette — you're not inheriting it, you're
|
||||
**redesigning the layout and craft from scratch** with the brand as the guide.
|
||||
Feel free to rethink the spatial composition entirely (see §10).
|
||||
|
||||
---
|
||||
|
||||
## 3. The two screens
|
||||
|
||||
### Screen A — **Session setup** (entry)
|
||||
A single centered card on the aurora canvas. Fields:
|
||||
- **Agent** — a `<select>` (populated live; show 3–4 sample options incl.
|
||||
`ratatoskr:sindra`, `forseti`, `mimir`).
|
||||
- **Bifrost binding (Tier-3 provider)** — a `<select>`: `combined (:8392)` /
|
||||
`none — observe only` / `memory (:8391)` / `affect (:8390)`.
|
||||
- **Open session** — primary button.
|
||||
- An error line (design the error state too — e.g. "agent not available").
|
||||
|
||||
### Screen B — **Live workspace** (the main event — 95% of the design effort)
|
||||
Persistent top bar + status line spanning full width; between them a **two-region
|
||||
body: a conversation column (left, dominant) and a telemetry column (right,
|
||||
tabbed).** Current split is ~1.85 : 1 — you may re-proportion. The information
|
||||
inventory below is exhaustive; **every item needs a home.**
|
||||
|
||||
---
|
||||
|
||||
## 4. THE COMPLETE INFORMATION INVENTORY
|
||||
|
||||
This is the core of the brief. Design a slot for **every** item, in a sensible
|
||||
state. Data shapes are given so your placeholders read true.
|
||||
|
||||
### 4.1 Top bar (persistent)
|
||||
| Item | Shape / example | Notes |
|
||||
|---|---|---|
|
||||
| Brand | `ᛯ ratatoskr` + eyebrow `WIRE MONITOR` | the mark is a rune glyph; small |
|
||||
| **Connection status** | one of: `offline`, `connected` (idle), `streaming`, `error` | dot + label; **streaming pulses**; color-coded (grey/green/cyan/red) |
|
||||
| **Persona strip** (appears after a session hydrates) | dominant-emotion word (`love`) + **PAD bars**: `P`, `A`, `D` | each PAD bar is **bipolar** — centered on 0, fills left (negative) or right (positive), value ∈ [−1, 1]; **live-updates every turn** |
|
||||
| Session identity | `ratatoskr:sindra · …381b99f4` | agent id + last-8 of session id |
|
||||
| Bound-plane badge (when bound) | `⇄ combined http://10.100.10.50:8392` | plane + endpoint; only when a Bifrost binding is active |
|
||||
|
||||
### 4.2 Conversation column (the transcript + composer)
|
||||
The transcript is a scrollable stream of turns. Design each element:
|
||||
|
||||
| Element | Example content | Notes |
|
||||
|---|---|---|
|
||||
| **Turn divider** | `TURN 3` between hairlines | uppercase eyebrow, rule lines each side |
|
||||
| **User prompt echo** | `❯ what's your intensity setting?` | the user's message, accent-marked |
|
||||
| **Assistant response** | streaming **Markdown** (headings, bold, italic, `code`, lists, quotes, links) | accumulates token-by-token while live; distinct "live" treatment vs settled |
|
||||
| **Seeded first-message** | a full assistant turn present *before the user speaks* (an authored greeting) | renders **identical to a lived assistant turn** — the session can OPEN already showing the agent's opener |
|
||||
| **Reasoning / "thinking" note** | `✦ sindra is reasoning···` (italic) | **ephemeral** app affordance — appears while the model reasons, vanishes the instant real text begins; visually distinct from the response so it never reads as engine output |
|
||||
| **Awaiting-first-token** | `···` animated | heartbeat before the first token |
|
||||
| **End-of-turn status chips** | `✓ DONE 1.84s` · `✗ ERROR agent_not_available` · `⚠ CANCELLED` · `✗ WIRE lost` | small bordered chips; color per state |
|
||||
| **Composer** (pinned bottom) | `❯ [ message input ] [SEND]` | Enter=send, Shift+Enter=newline; during a turn the Send button becomes **CANCEL** (amber) |
|
||||
|
||||
### 4.3 Telemetry column (six tabbed panes)
|
||||
A tab bar + a pane header (with a **Copy** button) + the active pane body.
|
||||
|
||||
**Tabs** (each: name · keybinding hint · a count **badge** that *flashes* on new
|
||||
data): `TOOLS ^1` · `DEBUG ^2` · `THINK ^3` · `PERSONA ^4` · `BIFROST ^5` ·
|
||||
`ADMIN ^6`. Active tab is accent-marked.
|
||||
|
||||
Pane contents — design each, populated:
|
||||
|
||||
1. **Tools** — the tool inventory the model saw at turn-fire:
|
||||
`agent_id`, `builtin_tools[]` (names), `bifrost_tools[]` (name + description +
|
||||
parameters). Below it, **live tool-call events** stream in (`tool_start` →
|
||||
`tool_result`) as the turn runs. Empty state: `— live tool events —`.
|
||||
2. **Debug** — a raw structured op/lifecycle log (mono lines; new lines flash
|
||||
once). Think `tail -f`.
|
||||
3. **Think** — the model's **full chain-of-thought**, per-turn dividers,
|
||||
live-Markdown. Longer prose than the response.
|
||||
4. **Persona / affect** — *the richest pane.* Contains:
|
||||
- **The canonical NL directive** the engine injects into the agent's context
|
||||
— the literal text: a **mood descriptor** ("neutral", "faintly excited",
|
||||
±0.3 bands) + a **relationship directive**. Show this verbatim, quoted.
|
||||
- **PAD mood point** — pleasure / arousal / dominance current values.
|
||||
- **relations[]** — for each related entity (e.g. the user): **trust**
|
||||
(ability / benevolence / integrity), **warmth**, **agency**,
|
||||
`relation_context` (a tie-type word like "stranger" / "expressive"), each as
|
||||
a **metric row**: `label · value · Δ-since-last (▲/▼) · unicode sparkline ·
|
||||
n (evidence count) · descriptor`. Values are 0–1 with 2–3 decimals.
|
||||
- **dominant_emotion** (an OCC type: joy/anger/fear/…) + **emotions_active[]**.
|
||||
- Design the **metric row** as a reusable component — it's the densest,
|
||||
most-repeated element in the whole UI. Tabular-aligned numbers, a tiny
|
||||
inline sparkline, a subtle up/down Δ.
|
||||
5. **Bifrost** — the live provider binding (admin-gated): `endpoint`,
|
||||
`connected` (bool), `capabilities_granted[]`, `consumer_id`, `tools[]`.
|
||||
**Self-labeling states:** `not configured` (no admin key) / `not bound`
|
||||
(session has no live binding) / an auth-denied state.
|
||||
6. **Admin events** — a live event log of the engine's lifecycle broadcast
|
||||
(a ~17-type vocabulary: `turn.started`, `session.created`, `system.*`, …),
|
||||
filtered to the active session. Streaming; timestamped lines.
|
||||
|
||||
### 4.4 Status line (persistent, bottom)
|
||||
- **Keybinding legend:** `Enter send · ⇧Enter newline · ^1–^6 panes · ^C cancel`
|
||||
(rendered as little `kbd` chips).
|
||||
- **Version:** `ratatoskr 0.19.9` (right-aligned).
|
||||
|
||||
---
|
||||
|
||||
## 5. States to design (show these explicitly)
|
||||
|
||||
Provide a mock (or a toggle) for each — these are where debug UIs live or die:
|
||||
|
||||
- **Setup:** loading-agents · ready · create-error.
|
||||
- **Connection:** offline · connected/idle · streaming (pulsing) · wire-error.
|
||||
- **Turn lifecycle:** awaiting-first-token · reasoning (✦) · streaming response ·
|
||||
done (+timing chip) · error · cancelled.
|
||||
- **Panes:** empty/placeholder · hydrated/dense · **not-configured** (admin key
|
||||
absent) · **not-bound** (Bifrost) · error · a **badge flash** on new data.
|
||||
- **Persona pane specifically:** a fully-populated relations block AND a
|
||||
cold/empty one (a fresh agent with no accumulated state).
|
||||
|
||||
---
|
||||
|
||||
## 6. Interaction & motion
|
||||
|
||||
- **Real-time is the defining trait.** The response + thinking panes stream
|
||||
token-by-token; the metric rows tick; event logs append; the persona strip
|
||||
re-animates each turn. Design so all of this is *calm* — the reader's eye
|
||||
isn't yanked around. Reserve motion for genuine state transitions
|
||||
(turn-start, done, a new event) and keep it short.
|
||||
- **Keyboard-first.** `^1–^6` switch panes; `Enter`/`⇧Enter`/`^C` drive the turn.
|
||||
Panes are also clickable. Show focus states.
|
||||
- **The aurora glow is the interaction signature** — focus rings, the top band,
|
||||
the connection pulse, the primary-button hover. Make it the thing someone
|
||||
remembers.
|
||||
- **Copy-to-clipboard** on each pane header (with a copied-confirm state).
|
||||
|
||||
---
|
||||
|
||||
## 7. Layout — you have latitude
|
||||
|
||||
The current layout is a fixed two-column split. **You may rethink it** — as long
|
||||
as every §4 item has a legible home and the density stays high. Directions worth
|
||||
exploring (pick one, commit):
|
||||
- A **command-console** feel: a slim persistent left rail of "instruments," a
|
||||
dominant conversation center, a right telemetry stack.
|
||||
- A **grid of live tiles** (the metrics/panes as a dashboard) with the
|
||||
conversation as the anchor column.
|
||||
- The **classic monitor** split, but with far better hierarchy, grouping, and
|
||||
breathing room than today.
|
||||
|
||||
Desktop-first; design at **1440–1512px** wide. Graceful down to ~1100px is a
|
||||
plus (this runs on a dev laptop). No mobile.
|
||||
|
||||
---
|
||||
|
||||
## 8. Deliverable — what to hand back
|
||||
|
||||
**A single self-contained `index.html`** (inline `<style>` + `<script>`; see §9
|
||||
constraints) that:
|
||||
1. Renders **Screen A** and **Screen B** (a toggle/hash is fine).
|
||||
2. Has **representative static placeholder data in every §4 slot** and shows the
|
||||
key §5 states (either multiple mocks or lightweight JS toggles). I want to see
|
||||
the design *fully populated and dense*, not empty scaffolding.
|
||||
3. Uses **clean, semantic, stable hooks** — meaningful `id`s / `class`es /
|
||||
`data-*` on every dynamic slot (the transcript container, each pane body, the
|
||||
PAD bars, a metric-row template, the connection dot, the tab badges, etc.).
|
||||
This is how I wire real data in — treat the DOM structure as an API.
|
||||
4. Imports/inlines the Australis tokens; no invented palette.
|
||||
|
||||
I will then **swap your placeholder content for live `fetch()` + `EventSource`
|
||||
calls** against the real endpoints (§9). The cleaner and more component-shaped
|
||||
your DOM, the faster and safer that wiring is. A short note listing your mount
|
||||
points / how you'd expect data injected is very welcome.
|
||||
|
||||
---
|
||||
|
||||
## 9. Hard technical constraints (these make it wire-able)
|
||||
|
||||
- **Single file. No build step. No CDN. No external network at runtime.** This
|
||||
ships to an internal LAN and must work offline. That means: **no Google Fonts /
|
||||
no web-font CDN** (use a system monospace stack), no CDN JS/CSS libraries,
|
||||
everything inline. (Icons: use unicode glyphs `➜ ✓ ✗ ! ● ✦ ❯` or hand-inlined
|
||||
SVG — Australis uses Lucide-style 1.75-stroke line icons; inline them.)
|
||||
- **Vanilla HTML/CSS/JS.** No framework (the production app is framework-free
|
||||
vanilla JS). React/Vue prototypes can't be wired in.
|
||||
- **All dynamic text is escaped** on the real side (untrusted upstream content);
|
||||
assistant/reasoning bodies go through a safe-Markdown renderer (escape-first,
|
||||
whitelist subset). Don't design anything that depends on raw HTML injection.
|
||||
- **The real data contracts** (so your structure maps to the wire — you don't
|
||||
implement these, just leave homes for their outputs):
|
||||
- `GET /api/agents` → agent list (for the setup picker).
|
||||
- `POST /api/sessions {agent_id, bifrost_plane?}` → `{session_id, agent_id, bifrost?}`.
|
||||
- `GET /api/sessions/{id}/messages` → `{items:[{seq, role, content}], …}` (the transcript on open, incl. the seeded first-message).
|
||||
- `POST /api/turns/{id} {content}` → `{turn_id}`, then **`GET /api/turns/{id}/stream` (SSE)** — event vocab: `text`, `thinking`, `tool_start`, `tool_result`, `done`, `error`, `awaiting_llm_first_token`, terminal events. `POST /api/turns/{id}/cancel`.
|
||||
- `GET /api/sessions/{id}/tools` → tool inventory. `GET /api/sessions/{id}/bifrost` → binding state.
|
||||
- `GET /api/affect/{agent_id}` / `GET /api/agents/{id}/persona_state` → PAD + relations + dominant_emotion (the persona pane + strip).
|
||||
- **`GET /api/admin/events` (SSE)** → the admin lifecycle log.
|
||||
|
||||
---
|
||||
|
||||
## 10. Tone check
|
||||
|
||||
The user is an engineer who respects the tool that respects *their* attention.
|
||||
The winning design is **quietly excellent**: dense but never cramped, alive but
|
||||
never busy, cool and legible, with the aurora as a single confident signature.
|
||||
Impress by making six live data streams feel *calm and readable at a glance* —
|
||||
that's the hard, valuable thing here, not decoration.
|
||||
@@ -0,0 +1,79 @@
|
||||
# R30 Phase-1 — φ0 PAD-point decay measurement (demo v1.0.0b14, A1 anchor fix)
|
||||
|
||||
Executor: ratatoskr-dev · coordinator: worldtree-dev · owner: brokkr-smithy-dev.
|
||||
Method: **joint two-timescale fit (b)** (brokkr-ruled) + **empty-tail direct fit**
|
||||
as mutual validation, both run off the same trajectory. Measured against the
|
||||
**corrected anchor** (`decay_anchor = baseline_pad()`, positive_p_cap removed;
|
||||
worldtree v1.0.0b14 `e1cdf82`).
|
||||
|
||||
## Method
|
||||
- 4 base agents × {pos, neg} impulse, fresh `end_user` each, 26 turns, "Please
|
||||
continue." neutrals + ~16s spacing (so one trajectory yields both an
|
||||
emotion-free tail AND the full joint-fit series). Natural per-turn cadence for
|
||||
the point-decay; spacing only lets the wall-clock emotion fade land in-window.
|
||||
- **Capture:** `affect_update` `current` snapshot (live mood) + `emotions_active`
|
||||
+ wall-clock, per turn.
|
||||
- **push_t** (the covariate): only NEWLY-appraised, new-type-or-strictly-stronger
|
||||
emotions push (dedup-gated; confirmed source-authoritative by worldtree-dev vs
|
||||
`registry.py::post_turn` L307-324 — the persistent `emotions_active` decays for
|
||||
render/goals but never re-pushes mood). Reconstructed from `emotions_active`
|
||||
deltas: a type new or strictly-stronger-than-the-prior(decayed)-snapshot pushed
|
||||
at its new intensity. `push_t[axis] = Σ OCC_PAD_IMPULSES[type]·intensity·0.15`.
|
||||
- **Model** (per axis, push-then-decay): `y_t=(axis_{t+1}−anchor)`,
|
||||
`X_t=(axis_t+push_t−anchor)`, regress `y=φ·X+c` → φ=slope, decay_rate=1−φ,
|
||||
anchor=`baseline_pad`. Validated on b9 first (φ_P 0.959, c −0.003, r² 0.997).
|
||||
|
||||
## Result — CONFIG FAITHFUL
|
||||
|
||||
**φ0 (PAD-point decay retention) ≈ 0.95–0.97, config-faithful.** Both reads agree:
|
||||
|
||||
| agent | baseline P | joint φ_P | c_P | r²_P | empty-tail φ0_P |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| lofn | 0.809 | 0.954 / 0.967 (pos/neg) | ~0.000 | 0.93–0.94 | — (excursion faded pre-empty) |
|
||||
| mimir | 0.615 | 0.964 / 0.977 (pos/neg) | −0.012 / −0.015 | 0.97–0.99 | — (never emptied) |
|
||||
| mask | 0.000 | 0.983 / 0.982 (pos/neg) | +0.004 / +0.006 | 0.90 / 0.95 | — |
|
||||
| forseti | 0.239 | signal-poor (r²=0.46/0.61, excluded) | — | — | **0.95** (neg, r²=1.0) |
|
||||
|
||||
- **Joint fit, 6 well-conditioned runs (3 baselines; all 8 probes in): φ_P = 0.971
|
||||
± 0.01** (range 0.954–0.983). Consistently a hair above config 0.95 — the
|
||||
empty-tail read (no push modeling) nails **0.95 exactly**, so the joint fit's
|
||||
~0.97 carries a small upward bias from push under-attribution; true φ0 ≈ 0.95.
|
||||
- **Empty-tail cross-check** (forseti_neg's clean pure-decay tail): **φ0_P = 0.95**,
|
||||
r²=1.0 — independently confirms the joint fit.
|
||||
- **config cross-check:** measured φ0 ≈ 0.95–0.97 vs config `1−0.05 = 0.95` →
|
||||
**config faithfully applied.** The R29 "net ~0.90" was **continuous
|
||||
re-appraisal** (a new dedup-gated push most turns), NOT re-push contamination.
|
||||
- **intercept c ≈ 0** across the well-conditioned runs (−0.015..+0.004) → the
|
||||
push_t reconstruction + push-then-decay order are correct on the deployed engine.
|
||||
|
||||
### Diagnostics for brokkr
|
||||
- **(i) residual/c:** c≈0 → push_t + order correctly specified. ✓
|
||||
- **(ii) φ≈0.95:** YES → config faithful; kills the config≠behavior worry. ✓
|
||||
- **(iii) A/P decay ratio: NO clean ~1.9.** The per-axis mood-point decay is
|
||||
roughly uniform (matches the single `decay_rate` in `model.py` L175-179).
|
||||
CAVEAT: the per-axis **A/D** fits are noisy — small A/D excursions yield some
|
||||
φ_A>1 artifacts (e.g. mask A/P −1.66 is a fit artifact), so the ratio is not
|
||||
cleanly determined here; but there is **no support for 1.9×** in the mood-decay
|
||||
layer. If the S2 ~1.9× chronometry is real it lives elsewhere (the OCC impulse
|
||||
vectors' per-axis asymmetry, or emotion wall-clock decay), not in point-decay.
|
||||
A dedicated per-axis run with larger A/D excursions would pin it.
|
||||
- **trait-flat:** φ_P ~equal across baselines 0.0 / 0.615 / 0.809 → the current
|
||||
decay is **trait-FLAT** (the pre-R30 baseline R30's `dynamics_from_ocean()` will
|
||||
modulate). ✓
|
||||
|
||||
### φ_max recommendation (preserve-persistence rule, Vuong-locked)
|
||||
Measured φ0 ≈ 0.96 (> 0.90) → **RELAX φ_max to ≈0.96** — the measured good
|
||||
operating point defines the cap; do NOT re-base the agents faster.
|
||||
|
||||
## Caveats
|
||||
- **forseti (baseline 0.239) is signal-poor** in the joint fit — a negative
|
||||
impulse from a low baseline has little down-room → tiny excursion → ill-
|
||||
conditioned regression (r² 0.46/0.61). Correctly excluded (its empty-tail
|
||||
still gave a clean 0.95). High/mid-baseline agents (lofn/mimir/mask) carry the
|
||||
joint-fit signal.
|
||||
- **mask_neg** recovered from a demo-side SSE stall (turn 14) and completed — all
|
||||
8/8 in. It is well-conditioned (φ_P 0.982, r² 0.95) and confirms the verdict
|
||||
(didn't move it). `stream_turn_resilient`'s reconnect handled the transient hang.
|
||||
|
||||
Series: `r30-phi0-{lofn,mimir,forseti,mask}-{pos,neg}.json` (per-turn pad +
|
||||
emotions_active + wall-clock). sec/turn ≈ 19–25 (16s spacing + processing).
|
||||
@@ -0,0 +1,664 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "forseti",
|
||||
"step": "neg",
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"end_user_id": "r30-phi0-forseti-neg-1783086627",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": 14,
|
||||
"seconds_per_turn": 20.63,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_neg",
|
||||
"wall_clock": 1783086627.768,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086646.949,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
0.1877,
|
||||
-0.67035,
|
||||
0.107825
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.5683328550825705,
|
||||
"decay_remaining_s": 199.9792455070927
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5683328550825705,
|
||||
"decay_remaining_s": 199.9792455070927
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086667.172,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
0.190265,
|
||||
-0.671632,
|
||||
0.107184
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.3556321297530136,
|
||||
"decay_remaining_s": 176.77391965695713
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.3556321297530136,
|
||||
"decay_remaining_s": 176.77391965695713
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086687.501,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
0.192702,
|
||||
-0.672851,
|
||||
0.106575
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.22206186783771448,
|
||||
"decay_remaining_s": 153.46314161162698
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.22206186783771448,
|
||||
"decay_remaining_s": 153.46314161162698
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2698448957707733,
|
||||
"decay_remaining_s": 163.10993319064005
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086707.822,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.195017,
|
||||
-0.674008,
|
||||
0.105996
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.1386369753255098,
|
||||
"decay_remaining_s": 130.14462339625655
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.1386369753255098,
|
||||
"decay_remaining_s": 130.14462339625655
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.16846872685060676,
|
||||
"decay_remaining_s": 139.79141497526962
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086734.409,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.197216,
|
||||
-0.675108,
|
||||
0.105446
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.07447378977503627,
|
||||
"decay_remaining_s": 99.38579636050295
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.07447378977503627,
|
||||
"decay_remaining_s": 99.38579636050295
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.09049897776319624,
|
||||
"decay_remaining_s": 109.03258793951602
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.31292609728127724,
|
||||
"decay_remaining_s": 170.44160986082557
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086753.454,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.199305,
|
||||
-0.676153,
|
||||
0.104924
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.04875620814214029,
|
||||
"decay_remaining_s": 78.41758126513767
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.04875620814214029,
|
||||
"decay_remaining_s": 78.41758126513767
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.05924751526411994,
|
||||
"decay_remaining_s": 88.06437284415075
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.2048652281324858,
|
||||
"decay_remaining_s": 149.47339476546028
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086772.496,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.20129,
|
||||
-0.677145,
|
||||
0.104428
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.031522954818552947,
|
||||
"decay_remaining_s": 56.830547203428814
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.031522954818552947,
|
||||
"decay_remaining_s": 56.830547203428814
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.038306029487312654,
|
||||
"decay_remaining_s": 66.47733878244189
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.13245405203550356,
|
||||
"decay_remaining_s": 127.88636070375141
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086792.465,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.203175,
|
||||
-0.678088,
|
||||
0.103956
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.019516300742176532,
|
||||
"decay_remaining_s": 33.097789734970185
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.019516300742176532,
|
||||
"decay_remaining_s": 33.097789734970185
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.023715796822228047,
|
||||
"decay_remaining_s": 42.74458131398328
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.0820041499575214,
|
||||
"decay_remaining_s": 104.1536032352928
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086812.741,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.204967,
|
||||
-0.678983,
|
||||
0.103508
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.01162721275495882,
|
||||
"decay_remaining_s": 7.462523834516143
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.01162721275495882,
|
||||
"decay_remaining_s": 7.462523834516143
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.014129143578398723,
|
||||
"decay_remaining_s": 17.109315413529234
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.04885555469459899,
|
||||
"decay_remaining_s": 78.51833733483876
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086833.39,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.206668,
|
||||
-0.679834,
|
||||
0.103083
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.028487904554031676,
|
||||
"decay_remaining_s": 51.81951538128418
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086853.925,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.208285,
|
||||
-0.680642,
|
||||
0.102679
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.017438555367845658,
|
||||
"decay_remaining_s": 27.52593892013023
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783086873.881,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.209821,
|
||||
-0.68141,
|
||||
0.102295
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.010992041880764065,
|
||||
"decay_remaining_s": 4.681870162512555
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783086895.722,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.21128,
|
||||
-0.68214,
|
||||
0.10193
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783086916.056,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.212666,
|
||||
-0.682833,
|
||||
0.101584
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783086937.153,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.213982,
|
||||
-0.683491,
|
||||
0.101254
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783086957.45,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.215233,
|
||||
-0.684117,
|
||||
0.100942
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783086977.889,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.216422,
|
||||
-0.684711,
|
||||
0.100645
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783086998.1,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.21755,
|
||||
-0.685275,
|
||||
0.100362
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783087018.85,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.218623,
|
||||
-0.685811,
|
||||
0.100094
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783087039.705,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.219642,
|
||||
-0.686321,
|
||||
0.09984
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783087060.965,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.22061,
|
||||
-0.686805,
|
||||
0.099598
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783087081.087,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.221529,
|
||||
-0.687265,
|
||||
0.099368
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783087101.401,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.222403,
|
||||
-0.687701,
|
||||
0.099149
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783087123.247,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.223233,
|
||||
-0.688116,
|
||||
0.098942
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783087143.569,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.224021,
|
||||
-0.68851,
|
||||
0.098745
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,648 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "forseti",
|
||||
"step": "pos",
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"end_user_id": "r30-phi0-forseti-pos-1783086626",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": 13,
|
||||
"seconds_per_turn": 22.1,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_pos",
|
||||
"wall_clock": 1783086626.763,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086644.441,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
0.277475,
|
||||
-0.683175,
|
||||
0.12065
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.5984144368786366,
|
||||
"decay_remaining_s": 202.53218516049105
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5984144368786366,
|
||||
"decay_remaining_s": 202.53218516049105
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086662.113,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
0.275551,
|
||||
-0.683816,
|
||||
0.119368
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.39617851967235457,
|
||||
"decay_remaining_s": 182.11815704225845
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.39617851967235457,
|
||||
"decay_remaining_s": 182.11815704225845
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086679.394,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
0.273724,
|
||||
-0.684425,
|
||||
0.118149
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.24687722982874277,
|
||||
"decay_remaining_s": 158.7067527359457
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.24687722982874277,
|
||||
"decay_remaining_s": 158.7067527359457
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086702.921,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.259163,
|
||||
-0.680729,
|
||||
0.099892
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.15387922362089126,
|
||||
"decay_remaining_s": 135.30775338969408
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.15387922362089126,
|
||||
"decay_remaining_s": 135.30775338969408
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.18699078533200852,
|
||||
"decay_remaining_s": 144.95454496870715
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086723.536,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.258154,
|
||||
-0.681493,
|
||||
0.099647
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.09519664920512481,
|
||||
"decay_remaining_s": 111.53750907456809
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.09519664920512481,
|
||||
"decay_remaining_s": 111.53750907456809
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.11568095924176013,
|
||||
"decay_remaining_s": 121.18430065358118
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086745.341,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.228127,
|
||||
-0.648588,
|
||||
0.113665
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.058121613176605455,
|
||||
"decay_remaining_s": 87.11468585006237
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.058121613176605455,
|
||||
"decay_remaining_s": 87.11468585006237
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.07062815783001622,
|
||||
"decay_remaining_s": 96.76147742907544
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.24421705453673276,
|
||||
"decay_remaining_s": 158.17049935038497
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086767.465,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.22867,
|
||||
-0.650959,
|
||||
0.112731
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.035073119286887194,
|
||||
"decay_remaining_s": 62.11295803448205
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.035073119286887194,
|
||||
"decay_remaining_s": 62.11295803448205
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.04262011443244547,
|
||||
"decay_remaining_s": 71.75974961349512
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.14737123451189318,
|
||||
"decay_remaining_s": 133.16877153480465
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086789.909,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.229187,
|
||||
-0.653211,
|
||||
0.111845
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.020567809558126326,
|
||||
"decay_remaining_s": 35.69532075372112
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.020567809558126326,
|
||||
"decay_remaining_s": 35.69532075372112
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.024993568146070934,
|
||||
"decay_remaining_s": 45.34211233273421
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.08642240973758594,
|
||||
"decay_remaining_s": 106.75113425404372
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086813.213,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.229677,
|
||||
-0.65535,
|
||||
0.111003
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.011507424157476244,
|
||||
"decay_remaining_s": 6.949925610611371
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.011507424157476244,
|
||||
"decay_remaining_s": 6.949925610611371
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.01398357900255792,
|
||||
"decay_remaining_s": 16.596717189624453
|
||||
},
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.04835222354383774,
|
||||
"decay_remaining_s": 78.00573911093397
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086835.817,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.230144,
|
||||
-0.657383,
|
||||
0.110203
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.0272750628019755,
|
||||
"decay_remaining_s": 49.66600394193401
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086858.735,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.230586,
|
||||
-0.659314,
|
||||
0.109442
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "anger",
|
||||
"intensity": 0.015766544873269436,
|
||||
"decay_remaining_s": 22.536840330863996
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783086880.309,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.231007,
|
||||
-0.661148,
|
||||
0.10872
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783086902.497,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.231407,
|
||||
-0.66289,
|
||||
0.108034
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783086924.65,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.231786,
|
||||
-0.664546,
|
||||
0.107383
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783086947.622,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.232147,
|
||||
-0.666119,
|
||||
0.106763
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783086970.841,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.23249,
|
||||
-0.667613,
|
||||
0.106175
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783086994.657,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.232815,
|
||||
-0.669032,
|
||||
0.105617
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783087018.405,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.233124,
|
||||
-0.67038,
|
||||
0.105086
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783087041.421,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.233418,
|
||||
-0.671661,
|
||||
0.104581
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783087066.046,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.233697,
|
||||
-0.672878,
|
||||
0.104102
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783087088.213,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.233962,
|
||||
-0.674034,
|
||||
0.103647
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783087111.161,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.234214,
|
||||
-0.675133,
|
||||
0.103215
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783087134.816,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.234454,
|
||||
-0.676176,
|
||||
0.102804
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783087157.169,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.234681,
|
||||
-0.677167,
|
||||
0.102414
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783087179.221,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.234897,
|
||||
-0.678109,
|
||||
0.102043
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.239,
|
||||
-0.696,
|
||||
0.095
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,722 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "lofn",
|
||||
"step": "neg",
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"end_user_id": "r30-phi0-lofn-neg-1783086622",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": 16,
|
||||
"seconds_per_turn": 19.17,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_neg",
|
||||
"wall_clock": 1783086622.491,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.30035998307417966,
|
||||
"decay_remaining_s": 168.4129038950811
|
||||
},
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.17405563132969606,
|
||||
"decay_remaining_s": 141.40628929119273
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.13875047669637092,
|
||||
"decay_remaining_s": 130.18513083121996
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086640.087,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
0.797101,
|
||||
-0.116591,
|
||||
0.28448
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.1143359590977587,
|
||||
"decay_remaining_s": 120.60542148213672
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.09114424340744763,
|
||||
"decay_remaining_s": 109.38426302216394
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.591203872014145,
|
||||
"decay_remaining_s": 201.93213522901215
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5583592124578035,
|
||||
"decay_remaining_s": 199.10288997283888
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086658.336,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
0.798798,
|
||||
-0.121784,
|
||||
0.279277
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.07429973774043301,
|
||||
"decay_remaining_s": 99.26997889267194
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.05922890257065504,
|
||||
"decay_remaining_s": 88.04882043269916
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.38418615620499547,
|
||||
"decay_remaining_s": 180.59669263954737
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.3628424808602734,
|
||||
"decay_remaining_s": 177.7674473833741
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086676.834,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
0.799793,
|
||||
-0.124828,
|
||||
0.276228
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.048075395218412885,
|
||||
"decay_remaining_s": 77.72153477455913
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.03832386204894452,
|
||||
"decay_remaining_s": 66.50037631458636
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.2485863592348452,
|
||||
"decay_remaining_s": 159.04824852143457
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.2347760059440204,
|
||||
"decay_remaining_s": 156.2190032652613
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086698.693,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.778597,
|
||||
-0.106862,
|
||||
0.258631
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.02893387689095941,
|
||||
"decay_remaining_s": 52.58839865785018
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.02306497744787295,
|
||||
"decay_remaining_s": 41.36724019787741
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.14961015051870166,
|
||||
"decay_remaining_s": 133.91511240472562
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.14129847548988486,
|
||||
"decay_remaining_s": 131.08586714855235
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.15347016022800833,
|
||||
"decay_remaining_s": 135.17599479644593
|
||||
}
|
||||
],
|
||||
"n_emotions": 5
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086718.436,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.780118,
|
||||
-0.109169,
|
||||
0.2581
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.01756835509224531,
|
||||
"decay_remaining_s": 27.893003433544404
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.014004819178776343,
|
||||
"decay_remaining_s": 16.67184497357163
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.09084175824837641,
|
||||
"decay_remaining_s": 109.21971718041983
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.08579499390124436,
|
||||
"decay_remaining_s": 106.39047192424657
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.09318551679439424,
|
||||
"decay_remaining_s": 110.48059957214018
|
||||
}
|
||||
],
|
||||
"n_emotions": 5
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086740.827,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.782934,
|
||||
-0.113442,
|
||||
0.257115
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.05139530636165241,
|
||||
"decay_remaining_s": 81.02685118626724
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.04854001156378281,
|
||||
"decay_remaining_s": 78.19760593009396
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.05272132856590098,
|
||||
"decay_remaining_s": 82.28773357798757
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086760.101,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.785475,
|
||||
-0.117299,
|
||||
0.256226
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.032257561955279894,
|
||||
"decay_remaining_s": 57.97081468282442
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.03046547517998656,
|
||||
"decay_remaining_s": 55.14156942665116
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.033089821677726845,
|
||||
"decay_remaining_s": 59.23169707454476
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086779.307,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.804869,
|
||||
-0.13218,
|
||||
0.278224
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.020198640373952994,
|
||||
"decay_remaining_s": 34.79881136118861
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.019076493686511153,
|
||||
"decay_remaining_s": 31.969566105015343
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.020719774452676518,
|
||||
"decay_remaining_s": 36.05969375290893
|
||||
},
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.2504670427598375,
|
||||
"decay_remaining_s": 159.42131925325486
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086797.858,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.805272,
|
||||
-0.13421,
|
||||
0.275277
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.012755684832229715,
|
||||
"decay_remaining_s": 12.047491685615732
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.012047035674883615,
|
||||
"decay_remaining_s": 9.21824642944246
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.013084787283704805,
|
||||
"decay_remaining_s": 13.308374077336053
|
||||
},
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.1581729561572383,
|
||||
"decay_remaining_s": 136.66999957768198
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086819.879,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.805635,
|
||||
-0.136042,
|
||||
0.272618
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.08844976004480654,
|
||||
"decay_remaining_s": 107.8988860481542
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086839.229,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.805963,
|
||||
-0.137695,
|
||||
0.270218
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.05446957177838106,
|
||||
"decay_remaining_s": 83.90247456071432
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783086858.27,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.806259,
|
||||
-0.139188,
|
||||
0.268051
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.03403401689989604,
|
||||
"decay_remaining_s": 60.62432174385741
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783086877.589,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.806526,
|
||||
-0.140534,
|
||||
0.266096
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.02112414744330084,
|
||||
"decay_remaining_s": 37.016411214982064
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783086896.265,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.806768,
|
||||
-0.14175,
|
||||
0.264332
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.013364186310301318,
|
||||
"decay_remaining_s": 14.35418372663499
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783086914.677,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.806985,
|
||||
-0.142847,
|
||||
0.26274
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783086933.369,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.807182,
|
||||
-0.143837,
|
||||
0.261303
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783086951.77,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.807359,
|
||||
-0.14473,
|
||||
0.260006
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783086970.574,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.807519,
|
||||
-0.145536,
|
||||
0.258835
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783086990.041,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.807663,
|
||||
-0.146264,
|
||||
0.257779
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783087008.922,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.807794,
|
||||
-0.146921,
|
||||
0.256825
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783087027.167,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.807911,
|
||||
-0.147514,
|
||||
0.255965
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783087045.453,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.808017,
|
||||
-0.148048,
|
||||
0.255188
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783087064.761,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.808113,
|
||||
-0.148531,
|
||||
0.254487
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783087083.309,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.8082,
|
||||
-0.148967,
|
||||
0.253855
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783087101.821,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.808278,
|
||||
-0.14936,
|
||||
0.253284
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "lofn",
|
||||
"step": "pos",
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"end_user_id": "r30-phi0-lofn-pos-1783086620",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": 16,
|
||||
"seconds_per_turn": 19.68,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_pos",
|
||||
"wall_clock": 1783086620.928,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.3099496071226145,
|
||||
"decay_remaining_s": 169.96853835646436
|
||||
},
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.1796127233593375,
|
||||
"decay_remaining_s": 142.96192375257604
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.1431803774256259,
|
||||
"decay_remaining_s": 131.74076529260327
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086638.365,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
0.797101,
|
||||
-0.116591,
|
||||
0.28448
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.11851338162104388,
|
||||
"decay_remaining_s": 122.38165644926328
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.0944743244972692,
|
||||
"decay_remaining_s": 111.1604979892905
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.612804323790595,
|
||||
"decay_remaining_s": 203.7083701961387
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5787596391355618,
|
||||
"decay_remaining_s": 200.87912493996546
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086656.254,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
0.798261,
|
||||
-0.120141,
|
||||
0.280923
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.07754355752942818,
|
||||
"decay_remaining_s": 101.38516021294014
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.06181475135131101,
|
||||
"decay_remaining_s": 90.16400175296735
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.4009591717506152,
|
||||
"decay_remaining_s": 182.71187395981556
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.37868366220891425,
|
||||
"decay_remaining_s": 179.8826287036423
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086674.109,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
0.799308,
|
||||
-0.123345,
|
||||
0.277713
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.050839245787556116,
|
||||
"decay_remaining_s": 80.4883965619064
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.04052709776764277,
|
||||
"decay_remaining_s": 69.26723810193361
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.2628775688511382,
|
||||
"decay_remaining_s": 161.81511030878184
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.24827325947051937,
|
||||
"decay_remaining_s": 158.98586505260857
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086692.205,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.776997,
|
||||
-0.104434,
|
||||
0.259191
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.033295427762761606,
|
||||
"decay_remaining_s": 59.53830698228177
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.026541838598384764,
|
||||
"decay_remaining_s": 48.31714852230899
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.17216268590427902,
|
||||
"decay_remaining_s": 140.8650207291572
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.16259809224293015,
|
||||
"decay_remaining_s": 138.03577547298394
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.17660456125074997,
|
||||
"decay_remaining_s": 142.12590312087752
|
||||
}
|
||||
],
|
||||
"n_emotions": 5
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086711.437,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.780118,
|
||||
-0.109169,
|
||||
0.2581
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.01761273545952419,
|
||||
"decay_remaining_s": 28.017886124481926
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.014040197506204548,
|
||||
"decay_remaining_s": 16.79672766450916
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.09107123850274022,
|
||||
"decay_remaining_s": 109.34459987135736
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.08601172525258796,
|
||||
"decay_remaining_s": 106.5153546151841
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.09342091774335577,
|
||||
"decay_remaining_s": 110.6054822630777
|
||||
}
|
||||
],
|
||||
"n_emotions": 5
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086738.988,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.782934,
|
||||
-0.113442,
|
||||
0.257115
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "shame",
|
||||
"intensity": 0.010353667728203439,
|
||||
"decay_remaining_s": 1.720350311056919
|
||||
},
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.053536337113577075,
|
||||
"decay_remaining_s": 83.04706405793236
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.05056209616282278,
|
||||
"decay_remaining_s": 80.2178188017591
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.05491759888186391,
|
||||
"decay_remaining_s": 84.30794644965269
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086758.598,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.785475,
|
||||
-0.117299,
|
||||
0.256226
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.03322492057965059,
|
||||
"decay_remaining_s": 59.43337703961838
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.03137909165855888,
|
||||
"decay_remaining_s": 56.604131783445105
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.03408213859315939,
|
||||
"decay_remaining_s": 60.69425943133871
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086777.861,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.804869,
|
||||
-0.13218,
|
||||
0.278224
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.020813815554036986,
|
||||
"decay_remaining_s": 36.283844092364134
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.01965749246770159,
|
||||
"decay_remaining_s": 33.45459883619087
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.021350821431297014,
|
||||
"decay_remaining_s": 37.54472648408446
|
||||
},
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.2580953338369727,
|
||||
"decay_remaining_s": 160.9063519844304
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086797.601,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.805272,
|
||||
-0.13421,
|
||||
0.275277
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.012822775915902978,
|
||||
"decay_remaining_s": 12.30715573180832
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.012110399476130585,
|
||||
"decay_remaining_s": 9.477910475635053
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.013153609347752653,
|
||||
"decay_remaining_s": 13.568038123528641
|
||||
},
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.1590048985559388,
|
||||
"decay_remaining_s": 136.92966362387457
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086820.086,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.805635,
|
||||
-0.136042,
|
||||
0.272618
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.08799109146673098,
|
||||
"decay_remaining_s": 107.64153778247811
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086839.995,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.805963,
|
||||
-0.137695,
|
||||
0.270218
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.05362997325344708,
|
||||
"decay_remaining_s": 83.13356199644052
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783086859.213,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.806259,
|
||||
-0.139188,
|
||||
0.268051
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.033384868307061485,
|
||||
"decay_remaining_s": 59.67109460565662
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783086878.469,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.806526,
|
||||
-0.140534,
|
||||
0.266096
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.02075236963779157,
|
||||
"decay_remaining_s": 36.13750053889745
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783086899.093,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.806879,
|
||||
-0.142312,
|
||||
0.263515
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "satisfaction",
|
||||
"intensity": 0.012610049448190474,
|
||||
"decay_remaining_s": 11.479103997183943
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783086917.981,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.807086,
|
||||
-0.143354,
|
||||
0.262003
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783086938.001,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.807273,
|
||||
-0.144295,
|
||||
0.260637
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783086957.169,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.807441,
|
||||
-0.145144,
|
||||
0.259405
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783086976.989,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.807593,
|
||||
-0.14591,
|
||||
0.258293
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783086996.509,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.80773,
|
||||
-0.146601,
|
||||
0.25729
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783087016.38,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.807854,
|
||||
-0.147225,
|
||||
0.256384
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783087035.803,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.807966,
|
||||
-0.147788,
|
||||
0.255566
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783087054.186,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.808067,
|
||||
-0.148296,
|
||||
0.254829
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783087073.493,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.808158,
|
||||
-0.148755,
|
||||
0.254163
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783087093.306,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.80824,
|
||||
-0.149169,
|
||||
0.253562
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783087113.033,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.808314,
|
||||
-0.149542,
|
||||
0.25302
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.809,
|
||||
-0.153,
|
||||
0.248
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,675 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "mask",
|
||||
"step": "neg",
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end_user_id": "r30-phi0-mask-neg-1783086630",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": 15,
|
||||
"seconds_per_turn": 80.82,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_neg",
|
||||
"wall_clock": 1783086630.5,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086649.461,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
-0.04845,
|
||||
0.024225,
|
||||
0.012113
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.573022497641695,
|
||||
"decay_remaining_s": 200.38600877711383
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5411879144393785,
|
||||
"decay_remaining_s": 197.5567635209406
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086669.122,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
-0.046028,
|
||||
0.023014,
|
||||
0.011507
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.356003412717222,
|
||||
"decay_remaining_s": 176.82556936309612
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.3362254453440429,
|
||||
"decay_remaining_s": 173.99632410692286
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086689.724,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
-0.003065,
|
||||
0.048415,
|
||||
-0.006145
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.21338883507870404,
|
||||
"decay_remaining_s": 151.49112825250143
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.20153389979655376,
|
||||
"decay_remaining_s": 148.66188299632816
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.2397604376317611,
|
||||
"decay_remaining_s": 157.25888146429597
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.2397604376317611,
|
||||
"decay_remaining_s": 157.25888146429597
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086710.989,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.008064,
|
||||
0.054524,
|
||||
-0.010961
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.0914212721487128,
|
||||
"decay_remaining_s": 109.53448270395256
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.0863423125848954,
|
||||
"decay_remaining_s": 106.7052374477793
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.10271954580539919,
|
||||
"decay_remaining_s": 115.3022359157471
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.17137030082196,
|
||||
"decay_remaining_s": 140.63667702634183
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086743.877,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.035778,
|
||||
0.066308,
|
||||
-0.021292
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.04477060353615011,
|
||||
"decay_remaining_s": 74.19632608766649
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.042283347784141764,
|
||||
"decay_remaining_s": 71.36708083149324
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.08392304783825716,
|
||||
"decay_remaining_s": 105.29852041005577
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.19633892293274405,
|
||||
"decay_remaining_s": 147.3692225867374
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086767.468,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.043689,
|
||||
0.071243,
|
||||
-0.024916
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.023673881244883985,
|
||||
"decay_remaining_s": 42.65702000792139
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.022358665620168205,
|
||||
"decay_remaining_s": 39.82777475174812
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.10382045311283532,
|
||||
"decay_remaining_s": 115.8299165069923
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.2119459981620312,
|
||||
"decay_remaining_s": 151.1553068400353
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086791.346,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.05083,
|
||||
0.075697,
|
||||
-0.028187
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.012503122853369734,
|
||||
"decay_remaining_s": 11.057594651593051
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.011808504917071413,
|
||||
"decay_remaining_s": 8.22834939541978
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.054831730654339245,
|
||||
"decay_remaining_s": 84.23049115066395
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.21374299133569005,
|
||||
"decay_remaining_s": 151.57321130702982
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086815.569,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.057274,
|
||||
0.079717,
|
||||
-0.031139
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.02873557965477521,
|
||||
"decay_remaining_s": 52.247996003823324
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.2131793185845018,
|
||||
"decay_remaining_s": 151.44250430119635
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086844.225,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.078765,
|
||||
0.088189,
|
||||
-0.038933
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.10083556893243133,
|
||||
"decay_remaining_s": 114.38595978617707
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.20850330282970433,
|
||||
"decay_remaining_s": 150.34469100065806
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086872.633,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.071085,
|
||||
0.079591,
|
||||
-0.035137
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.04751721226390742,
|
||||
"decay_remaining_s": 77.1434685146352
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.09825397727386971,
|
||||
"decay_remaining_s": 113.10219972911622
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086903.041,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.074984,
|
||||
0.082661,
|
||||
-0.037126
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.04587988487785369,
|
||||
"decay_remaining_s": 75.40779887167771
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.186780774278357,
|
||||
"decay_remaining_s": 144.8989217174522
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783087042.241,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.123206,
|
||||
0.108504,
|
||||
-0.056332
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.10881120514824318,
|
||||
"decay_remaining_s": 118.15392794161474
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.23312871354818343,
|
||||
"decay_remaining_s": 155.87047702144355
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783087182.081,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.17662,
|
||||
0.134921,
|
||||
-0.076712
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.16757988183783423,
|
||||
"decay_remaining_s": 139.52956931853214
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783087389.589,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.24959,
|
||||
0.173333,
|
||||
-0.104775
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783087497.261,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.23711,
|
||||
0.164666,
|
||||
-0.099536
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783087606.545,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.225255,
|
||||
0.156433,
|
||||
-0.094559
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783087716.666,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.213992,
|
||||
0.148611,
|
||||
-0.089831
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783087827.618,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.231792,
|
||||
0.15828,
|
||||
-0.09674
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783087939.216,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.220203,
|
||||
0.150366,
|
||||
-0.091903
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783088051.553,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.237693,
|
||||
0.159948,
|
||||
-0.098708
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783088165.793,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.225808,
|
||||
0.151951,
|
||||
-0.093772
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783088279.621,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.214518,
|
||||
0.144353,
|
||||
-0.089084
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783088403.505,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.203792,
|
||||
0.137135,
|
||||
-0.08463
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783088527.183,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.193602,
|
||||
0.130279,
|
||||
-0.080398
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783088650.889,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.183922,
|
||||
0.123765,
|
||||
-0.076378
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,792 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "mask",
|
||||
"step": "pos",
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end_user_id": "r30-phi0-mask-pos-1783086629",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": null,
|
||||
"seconds_per_turn": 24.96,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_pos",
|
||||
"wall_clock": 1783086629.351,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086646.861,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
0.0513,
|
||||
0.02565,
|
||||
0.012825
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.6039162352234125,
|
||||
"decay_remaining_s": 202.98519141353884
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5703653332665563,
|
||||
"decay_remaining_s": 200.15594615736558
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086666.743,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
0.048735,
|
||||
0.024368,
|
||||
0.012184
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.3724072003533114,
|
||||
"decay_remaining_s": 179.0553478980089
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.351717911444794,
|
||||
"decay_remaining_s": 176.22610264183564
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086688.237,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
-0.003065,
|
||||
0.048415,
|
||||
-0.006145
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.21964219829255527,
|
||||
"decay_remaining_s": 152.9208293437025
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.2074398539429688,
|
||||
"decay_remaining_s": 150.09158408752924
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.24678662107884886,
|
||||
"decay_remaining_s": 158.68858255549708
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.24678662107884886,
|
||||
"decay_remaining_s": 158.68858255549708
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086711.569,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.008064,
|
||||
0.054524,
|
||||
-0.010961
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.0912108569557271,
|
||||
"decay_remaining_s": 109.42042607581978
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.08614358712485336,
|
||||
"decay_remaining_s": 106.59118081964651
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.10248312650662934,
|
||||
"decay_remaining_s": 115.18817928761433
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.17097587495069436,
|
||||
"decay_remaining_s": 140.52262039820903
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086743.965,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.035778,
|
||||
0.066308,
|
||||
-0.021292
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.044679081370124246,
|
||||
"decay_remaining_s": 74.09503574277676
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.04219691018289511,
|
||||
"decay_remaining_s": 71.2657904866035
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.0837514884106196,
|
||||
"decay_remaining_s": 105.19723006516602
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.1959375577046099,
|
||||
"decay_remaining_s": 147.26793224184766
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086768.05,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.043689,
|
||||
0.071243,
|
||||
-0.024916
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.02339842401425587,
|
||||
"decay_remaining_s": 42.07770591945392
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.022098511569019427,
|
||||
"decay_remaining_s": 39.24846066328066
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.10261245117174261,
|
||||
"decay_remaining_s": 115.25060241852482
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.20947990242164466,
|
||||
"decay_remaining_s": 150.57599275156787
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086792.189,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.05083,
|
||||
0.075697,
|
||||
-0.028187
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "joy",
|
||||
"intensity": 0.012294798870898311,
|
||||
"decay_remaining_s": 10.225917778446751
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.011611754489181735,
|
||||
"decay_remaining_s": 7.396672522273483
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.05391813773602014,
|
||||
"decay_remaining_s": 83.39881427751767
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.2101816577630614,
|
||||
"decay_remaining_s": 150.74153443388352
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086820.117,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.057274,
|
||||
0.079717,
|
||||
-0.031139
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.026075683313416677,
|
||||
"decay_remaining_s": 47.44008306303666
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.1934464683560223,
|
||||
"decay_remaining_s": 146.6345913604097
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086844.049,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.078765,
|
||||
0.088189,
|
||||
-0.038933
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.10122538755257164,
|
||||
"decay_remaining_s": 114.57694561236268
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.20930935242772103,
|
||||
"decay_remaining_s": 150.53567682684366
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086868.613,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.071085,
|
||||
0.079591,
|
||||
-0.035137
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.051656237870654834,
|
||||
"decay_remaining_s": 81.27751599878032
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.1068124702604253,
|
||||
"decay_remaining_s": 117.23624721326131
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086894.345,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.074984,
|
||||
0.082661,
|
||||
-0.037126
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.05556926082092507,
|
||||
"decay_remaining_s": 84.8918447161369
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.22622701843827964,
|
||||
"decay_remaining_s": 154.38296756191139
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783086918.669,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.071235,
|
||||
0.078528,
|
||||
-0.03527
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.03152281574809609,
|
||||
"decay_remaining_s": 56.83032883022405
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.12833196832421548,
|
||||
"decay_remaining_s": 126.32145167599852
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783086944.223,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.067673,
|
||||
0.074601,
|
||||
-0.033506
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.015440025787232716,
|
||||
"decay_remaining_s": 21.500985730861096
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.0628576113278172,
|
||||
"decay_remaining_s": 90.99210857663557
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783086967.357,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.07569,
|
||||
0.082271,
|
||||
-0.037531
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.21496005509793759,
|
||||
"decay_remaining_s": 151.8542579176958
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783086994.717,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.100405,
|
||||
0.095258,
|
||||
-0.047054
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.09727953230544714,
|
||||
"decay_remaining_s": 112.60884403963128
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.18101880791038277,
|
||||
"decay_remaining_s": 143.34790869682615
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783087021.385,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.123885,
|
||||
0.107595,
|
||||
-0.056102
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.04540454491988354,
|
||||
"decay_remaining_s": 74.89229495980246
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.18669721715897325,
|
||||
"decay_remaining_s": 144.87677349506185
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783087045.919,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.123206,
|
||||
0.108504,
|
||||
-0.056332
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.10077263123682631,
|
||||
"decay_remaining_s": 114.35505518931606
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.21590601674800133,
|
||||
"decay_remaining_s": 152.07160426914487
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783087073.542,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.128446,
|
||||
0.114479,
|
||||
-0.059215
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.0451373786278345,
|
||||
"decay_remaining_s": 74.60018011328319
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.17916522799432277,
|
||||
"decay_remaining_s": 142.83844749885782
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783087099.192,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.122024,
|
||||
0.108755,
|
||||
-0.056254
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.02164886685669234,
|
||||
"decay_remaining_s": 38.23091671640084
|
||||
},
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.08593153355623005,
|
||||
"decay_remaining_s": 106.46918410197544
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783087124.769,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.144422,
|
||||
0.120417,
|
||||
-0.064842
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.04260368831648293,
|
||||
"decay_remaining_s": 71.74066890904773
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.19831457232683897,
|
||||
"decay_remaining_s": 147.86480738196298
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783087149.57,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.165701,
|
||||
0.131496,
|
||||
-0.073
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.02146878636581833,
|
||||
"decay_remaining_s": 37.81745576544926
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.2015673967599869,
|
||||
"decay_remaining_s": 148.67010943129222
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783087175.413,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.17662,
|
||||
0.134921,
|
||||
-0.076712
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "hope",
|
||||
"intensity": 0.010377664834450205,
|
||||
"decay_remaining_s": 1.8349417764983997
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.19335354421287776,
|
||||
"decay_remaining_s": 146.61080858593982
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783087202.989,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.196289,
|
||||
0.145275,
|
||||
-0.084276
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.2326338330358267,
|
||||
"decay_remaining_s": 155.76529140012397
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783087228.51,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.214975,
|
||||
0.155111,
|
||||
-0.091463
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.1958362238481641,
|
||||
"decay_remaining_s": 147.24232636599922
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783087253.419,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.232726,
|
||||
0.164455,
|
||||
-0.09829
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.2007809583246708,
|
||||
"decay_remaining_s": 148.47660831404184
|
||||
}
|
||||
],
|
||||
"n_emotions": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,872 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "mimir",
|
||||
"step": "neg",
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"end_user_id": "r30-phi0-mimir-neg-1783086625",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": null,
|
||||
"seconds_per_turn": 20.38,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_neg",
|
||||
"wall_clock": 1783086625.378,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086644.403,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
0.5694,
|
||||
-0.4152,
|
||||
0.3154
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.5695539306612214,
|
||||
"decay_remaining_s": 200.08547964103704
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5062701605877524,
|
||||
"decay_remaining_s": 194.25541766938022
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086664.681,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
0.549912,
|
||||
-0.394084,
|
||||
0.296311
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.3551375582594641,
|
||||
"decay_remaining_s": 176.70503528932045
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.3156778295639681,
|
||||
"decay_remaining_s": 170.87497331766363
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.18706089404764634,
|
||||
"decay_remaining_s": 144.9730999632422
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086683.361,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
0.553166,
|
||||
-0.39628,
|
||||
0.296695
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.232692885766144,
|
||||
"decay_remaining_s": 155.77785466215136
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.2068381206810169,
|
||||
"decay_remaining_s": 149.9477926904945
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.12256585719424329,
|
||||
"decay_remaining_s": 124.04591933607308
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086703.387,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.518002,
|
||||
-0.368725,
|
||||
0.259724
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.14624202733973438,
|
||||
"decay_remaining_s": 132.78803853131413
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.129992913190875,
|
||||
"decay_remaining_s": 126.9579765596573
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19797012040287656,
|
||||
"decay_remaining_s": 147.77875916994998
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2262515661747161,
|
||||
"decay_remaining_s": 154.3883382984228
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086723.169,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.476553,
|
||||
-0.33692,
|
||||
0.221924
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.09264389336314277,
|
||||
"decay_remaining_s": 110.19206048791541
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.08235012743390467,
|
||||
"decay_remaining_s": 104.36199851625857
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19955157173522658,
|
||||
"decay_remaining_s": 148.17259725738847
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.22805893912597325,
|
||||
"decay_remaining_s": 154.78217638586133
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086745.413,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.458103,
|
||||
-0.323686,
|
||||
0.199035
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.05513893855736023,
|
||||
"decay_remaining_s": 84.50704331981329
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.04901238982876465,
|
||||
"decay_remaining_s": 78.67698134815646
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.17855123491376257,
|
||||
"decay_remaining_s": 142.66852714685666
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19789428536275347,
|
||||
"decay_remaining_s": 147.75979456082902
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086765.985,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.402057,
|
||||
-0.275677,
|
||||
0.152873
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.03467442696387821,
|
||||
"decay_remaining_s": 61.54706533200481
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.030821712856780623,
|
||||
"decay_remaining_s": 55.717003360347974
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19808949499924042,
|
||||
"decay_remaining_s": 147.80859731297872
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2523849638588727,
|
||||
"decay_remaining_s": 159.79890281497038
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086788.388,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.381956,
|
||||
-0.261352,
|
||||
0.13122
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.020876570681207237,
|
||||
"decay_remaining_s": 36.432860485364564
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.018556951716628654,
|
||||
"decay_remaining_s": 30.602798513707725
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.20018960251982282,
|
||||
"decay_remaining_s": 148.33060688229088
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.23558657709136607,
|
||||
"decay_remaining_s": 156.389603344106
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086808.77,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.389194,
|
||||
-0.22797,
|
||||
0.098653
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.013234463556137013,
|
||||
"decay_remaining_s": 13.871369081595278
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.011763967605455124,
|
||||
"decay_remaining_s": 8.041307109938455
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.1996906524468551,
|
||||
"decay_remaining_s": 148.2070838970179
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.22821788851069155,
|
||||
"decay_remaining_s": 154.81666302549078
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.3093949959799647,
|
||||
"decay_remaining_s": 169.8798887580104
|
||||
}
|
||||
],
|
||||
"n_emotions": 5
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086829.771,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.370347,
|
||||
-0.218296,
|
||||
0.082287
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.19193309895123337,
|
||||
"decay_remaining_s": 146.2458337348137
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2062662817126418,
|
||||
"decay_remaining_s": 149.81075670573438
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.21939296850934625,
|
||||
"decay_remaining_s": 152.86463131248922
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086849.104,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.350217,
|
||||
-0.206212,
|
||||
0.065891
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.12388568421961552,
|
||||
"decay_remaining_s": 124.57608311541833
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.20332079637339762,
|
||||
"decay_remaining_s": 149.09882468139187
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2323666244267401,
|
||||
"decay_remaining_s": 155.70840380986473
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783086869.384,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.33205,
|
||||
-0.195306,
|
||||
0.051093
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.07780106612675619,
|
||||
"decay_remaining_s": 101.54926313989921
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.22439333830950783,
|
||||
"decay_remaining_s": 153.9801247745848
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2564495294965804,
|
||||
"decay_remaining_s": 160.58970390305765
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783086889.461,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.315654,
|
||||
-0.185464,
|
||||
0.037738
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.04872953151589588,
|
||||
"decay_remaining_s": 78.39049118764898
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.1972955280908204,
|
||||
"decay_remaining_s": 147.60980334853693
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2254806035323662,
|
||||
"decay_remaining_s": 154.21938247700982
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783086908.809,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.300857,
|
||||
-0.176581,
|
||||
0.025685
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.03107235452885492,
|
||||
"decay_remaining_s": 56.11789504968225
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.20085954804216538,
|
||||
"decay_remaining_s": 148.49597916282048
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2295537691910462,
|
||||
"decay_remaining_s": 155.10555829129336
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783086928.503,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.287503,
|
||||
-0.168564,
|
||||
0.014808
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.01965785899626446,
|
||||
"decay_remaining_s": 33.455521760500005
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.21841846782909705,
|
||||
"decay_remaining_s": 152.64427972406102
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.24962110609039664,
|
||||
"decay_remaining_s": 159.25385885253388
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783086948.854,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.27545,
|
||||
-0.161328,
|
||||
0.004991
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.012190100178442454,
|
||||
"decay_remaining_s": 9.802600485010554
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2133361557047814,
|
||||
"decay_remaining_s": 151.47890707778464
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.24381274937689307,
|
||||
"decay_remaining_s": 158.0884862062575
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783086969.465,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.264573,
|
||||
-0.154799,
|
||||
-0.003869
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2037017252636142,
|
||||
"decay_remaining_s": 149.19147480887426
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2328019717298448,
|
||||
"decay_remaining_s": 155.8010539373471
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783086990.957,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.254756,
|
||||
-0.148905,
|
||||
-0.011865
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.18973405834522244,
|
||||
"decay_remaining_s": 145.67544139043395
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2168389238231114,
|
||||
"decay_remaining_s": 152.2850205189068
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783087012.069,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.21759,
|
||||
-0.117456,
|
||||
-0.038546
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.1852427384058006,
|
||||
"decay_remaining_s": 144.48964317734817
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.21170598674948643,
|
||||
"decay_remaining_s": 151.09922230582103
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783087032.033,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.184047,
|
||||
-0.089073,
|
||||
-0.062625
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19317699595546273,
|
||||
"decay_remaining_s": 146.56559175976838
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.220773709663386,
|
||||
"decay_remaining_s": 153.17517088824124
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783087051.317,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.153775,
|
||||
-0.063457,
|
||||
-0.084356
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19876713783323796,
|
||||
"decay_remaining_s": 147.97763675575507
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2271624432379863,
|
||||
"decay_remaining_s": 154.58721588422796
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783087072.549,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.126454,
|
||||
-0.040339,
|
||||
-0.103969
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.18763303461696476,
|
||||
"decay_remaining_s": 145.1242634318717
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.21443775384795977,
|
||||
"decay_remaining_s": 151.73384256034456
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783087093.533,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.101797,
|
||||
-0.019474,
|
||||
-0.121669
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19164580018360228,
|
||||
"decay_remaining_s": 146.17168571463864
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.21902377163840261,
|
||||
"decay_remaining_s": 152.7812648431115
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783087113.753,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.078957,
|
||||
0.003138,
|
||||
-0.133944
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.1975245722197446,
|
||||
"decay_remaining_s": 147.66723355555015
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.26856666671857243,
|
||||
"decay_remaining_s": 162.8749073873274
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783087134.897,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.059461,
|
||||
0.01635,
|
||||
-0.152061
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.18180516512487083,
|
||||
"decay_remaining_s": 143.56246684118923
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.20777733157128098,
|
||||
"decay_remaining_s": 150.1720459696621
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,877 @@
|
||||
{
|
||||
"probe": "R30 Phase-1 \u03c60 step-response",
|
||||
"server": "http://10.250.50.152:8080",
|
||||
"agent": "mimir",
|
||||
"step": "pos",
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"end_user_id": "r30-phi0-mimir-pos-1783086623",
|
||||
"n_turns": 26,
|
||||
"total_turns": 26,
|
||||
"emotion_fade_turn_index": null,
|
||||
"seconds_per_turn": 19.38,
|
||||
"method": "joint two-timescale fit (b): natural cadence, push_t covariate from emotions_active deltas; anchor = baseline_pad on b14, neutral(0) on b9.",
|
||||
"series": [
|
||||
{
|
||||
"turn": 1,
|
||||
"sent": "impulse_pos",
|
||||
"wall_clock": 1783086624.133,
|
||||
"mood_reflects_after_turn": 0,
|
||||
"pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [],
|
||||
"n_emotions": 0
|
||||
},
|
||||
{
|
||||
"turn": 2,
|
||||
"sent": "neutral_1",
|
||||
"wall_clock": 1783086642.287,
|
||||
"mood_reflects_after_turn": 1,
|
||||
"pad": [
|
||||
0.653475,
|
||||
-0.425175,
|
||||
0.32965
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.5946814824398606,
|
||||
"decay_remaining_s": 202.22244382273544
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.5286057621687651,
|
||||
"decay_remaining_s": 196.3923818510786
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 3,
|
||||
"sent": "neutral_2",
|
||||
"wall_clock": 1783086659.981,
|
||||
"mood_reflects_after_turn": 2,
|
||||
"pad": [
|
||||
0.546486,
|
||||
-0.391773,
|
||||
0.295906
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.3918348442531504,
|
||||
"decay_remaining_s": 181.57246376361832
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.3482976393361337,
|
||||
"decay_remaining_s": 175.7424017919615
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.20639038192480808,
|
||||
"decay_remaining_s": 149.84052843754006
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 4,
|
||||
"sent": "neutral_3",
|
||||
"wall_clock": 1783086677.926,
|
||||
"mood_reflects_after_turn": 3,
|
||||
"pad": [
|
||||
0.553166,
|
||||
-0.39628,
|
||||
0.296695
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.2610046930700496,
|
||||
"decay_remaining_s": 161.46119646298973
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.23200417161782186,
|
||||
"decay_remaining_s": 155.6311344913329
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.1374784786931611,
|
||||
"decay_remaining_s": 129.72926113691145
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 5,
|
||||
"sent": "neutral_4",
|
||||
"wall_clock": 1783086698.685,
|
||||
"mood_reflects_after_turn": 4,
|
||||
"pad": [
|
||||
0.512897,
|
||||
-0.365079,
|
||||
0.257393
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.16130247461172786,
|
||||
"decay_remaining_s": 137.6397821335694
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.143379977432647,
|
||||
"decay_remaining_s": 131.80972016191257
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.21835768349943682,
|
||||
"decay_remaining_s": 152.63050277220526
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.24955163828507068,
|
||||
"decay_remaining_s": 159.24008190067812
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 6,
|
||||
"sent": "neutral_5",
|
||||
"wall_clock": 1783086717.533,
|
||||
"mood_reflects_after_turn": 5,
|
||||
"pad": [
|
||||
0.476553,
|
||||
-0.33692,
|
||||
0.221924
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.10267462961751213,
|
||||
"decay_remaining_s": 115.28058704753809
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.09126633743778854,
|
||||
"decay_remaining_s": 109.45052507588127
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.22115741225592794,
|
||||
"decay_remaining_s": 153.26112381701117
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2527513282924891,
|
||||
"decay_remaining_s": 159.87070294548403
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 7,
|
||||
"sent": "neutral_6",
|
||||
"wall_clock": 1783086739.031,
|
||||
"mood_reflects_after_turn": 6,
|
||||
"pad": [
|
||||
0.449846,
|
||||
-0.31767,
|
||||
0.19351
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.06322985846830854,
|
||||
"decay_remaining_s": 91.28437648115279
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.05620431863849648,
|
||||
"decay_remaining_s": 85.45431450949596
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2047512993235141,
|
||||
"decay_remaining_s": 149.44586030819616
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2269326900835614,
|
||||
"decay_remaining_s": 154.53712772216852
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 8,
|
||||
"sent": "neutral_7",
|
||||
"wall_clock": 1783086758.046,
|
||||
"mood_reflects_after_turn": 7,
|
||||
"pad": [
|
||||
0.41965,
|
||||
-0.294133,
|
||||
0.164269
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.04121611656303021,
|
||||
"decay_remaining_s": 70.10170683199324
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.03663654805602685,
|
||||
"decay_remaining_s": 64.27164486033641
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2354611288690008,
|
||||
"decay_remaining_s": 156.36323881296713
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.26909843299314384,
|
||||
"decay_remaining_s": 162.97281794144
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 9,
|
||||
"sent": "neutral_8",
|
||||
"wall_clock": 1783086777.661,
|
||||
"mood_reflects_after_turn": 8,
|
||||
"pad": [
|
||||
0.39849,
|
||||
-0.279055,
|
||||
0.141477
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.026584584239420582,
|
||||
"decay_remaining_s": 48.39680145621735
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.023630741546151624,
|
||||
"decay_remaining_s": 42.5667394845605
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.19350137608500168,
|
||||
"decay_remaining_s": 146.64863893918286
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.254924883656064,
|
||||
"decay_remaining_s": 160.29454785314365
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 10,
|
||||
"sent": "neutral_9",
|
||||
"wall_clock": 1783086797.421,
|
||||
"mood_reflects_after_turn": 9,
|
||||
"pad": [
|
||||
0.34731,
|
||||
-0.234916,
|
||||
0.099845
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.017110119721514862,
|
||||
"decay_remaining_s": 26.584802898475584
|
||||
},
|
||||
{
|
||||
"type": "disgust",
|
||||
"intensity": 0.01520899530801321,
|
||||
"decay_remaining_s": 20.75474092681875
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2581692077008076,
|
||||
"decay_remaining_s": 160.9205177138982
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.29505052308663726,
|
||||
"decay_remaining_s": 167.53009684237108
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 11,
|
||||
"sent": "neutral_10",
|
||||
"wall_clock": 1783086816.311,
|
||||
"mood_reflects_after_turn": 10,
|
||||
"pad": [
|
||||
0.38627,
|
||||
-0.233733,
|
||||
0.089968
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "love",
|
||||
"intensity": 0.011226425089725682,
|
||||
"decay_remaining_s": 5.72622706086814
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.19359084851705805,
|
||||
"decay_remaining_s": 146.67152100476363
|
||||
},
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.2624511171738719,
|
||||
"decay_remaining_s": 161.73474673728325
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.28205044552809555,
|
||||
"decay_remaining_s": 165.299669708204
|
||||
}
|
||||
],
|
||||
"n_emotions": 4
|
||||
},
|
||||
{
|
||||
"turn": 12,
|
||||
"sent": "neutral_11",
|
||||
"wall_clock": 1783086835.657,
|
||||
"mood_reflects_after_turn": 11,
|
||||
"pad": [
|
||||
0.336281,
|
||||
-0.194013,
|
||||
0.053359
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.16921975131393227,
|
||||
"decay_remaining_s": 140.01158540005176
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.2777229250981473,
|
||||
"decay_remaining_s": 164.53432696602528
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.3173976286835969,
|
||||
"decay_remaining_s": 171.14390609449813
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 13,
|
||||
"sent": "neutral_12",
|
||||
"wall_clock": 1783086855.029,
|
||||
"mood_reflects_after_turn": 12,
|
||||
"pad": [
|
||||
0.317158,
|
||||
-0.182533,
|
||||
0.037782
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.10921596877410417,
|
||||
"decay_remaining_s": 118.33771366610162
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.315,
|
||||
"decay_remaining_s": 170.7685753007872
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.36000000000000004,
|
||||
"decay_remaining_s": 177.37815442926006
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 14,
|
||||
"sent": "neutral_13",
|
||||
"wall_clock": 1783086873.849,
|
||||
"mood_reflects_after_turn": 13,
|
||||
"pad": [
|
||||
0.299899,
|
||||
-0.172172,
|
||||
0.023724
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.07091348313646222,
|
||||
"decay_remaining_s": 96.96103907530583
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.28711363866906614,
|
||||
"decay_remaining_s": 166.18035123619381
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.32812987276464706,
|
||||
"decay_remaining_s": 172.78993036466667
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 15,
|
||||
"sent": "neutral_14",
|
||||
"wall_clock": 1783086893.369,
|
||||
"mood_reflects_after_turn": 14,
|
||||
"pad": [
|
||||
0.284323,
|
||||
-0.162822,
|
||||
0.011037
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.04491127141677074,
|
||||
"decay_remaining_s": 74.3516044866393
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.29031780228931453,
|
||||
"decay_remaining_s": 166.72968859977752
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.331791774044931,
|
||||
"decay_remaining_s": 173.3392677282504
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 16,
|
||||
"sent": "neutral_15",
|
||||
"wall_clock": 1783086913.196,
|
||||
"mood_reflects_after_turn": 15,
|
||||
"pad": [
|
||||
0.300857,
|
||||
-0.176581,
|
||||
0.025685
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.028350283954324103,
|
||||
"decay_remaining_s": 51.57981733722619
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.18326339629797098,
|
||||
"decay_remaining_s": 143.95790145036443
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.20944388148339546,
|
||||
"decay_remaining_s": 150.5674805788373
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 17,
|
||||
"sent": "neutral_16",
|
||||
"wall_clock": 1783086932.748,
|
||||
"mood_reflects_after_turn": 16,
|
||||
"pad": [
|
||||
0.287503,
|
||||
-0.168564,
|
||||
0.014808
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.017999206667636186,
|
||||
"decay_remaining_s": 29.092268708013105
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19998918209920172,
|
||||
"decay_remaining_s": 148.28102667157412
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.22855906525623054,
|
||||
"decay_remaining_s": 154.89060580004698
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 18,
|
||||
"sent": "neutral_17",
|
||||
"wall_clock": 1783086951.78,
|
||||
"mood_reflects_after_turn": 17,
|
||||
"pad": [
|
||||
0.27545,
|
||||
-0.161328,
|
||||
0.004991
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "admiration",
|
||||
"intensity": 0.011472658135482627,
|
||||
"decay_remaining_s": 6.80015583612609
|
||||
},
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.20078036656888026,
|
||||
"decay_remaining_s": 148.4764624289002
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.22946327607872036,
|
||||
"decay_remaining_s": 155.08604155737305
|
||||
}
|
||||
],
|
||||
"n_emotions": 3
|
||||
},
|
||||
{
|
||||
"turn": 19,
|
||||
"sent": "neutral_18",
|
||||
"wall_clock": 1783086971.101,
|
||||
"mood_reflects_after_turn": 18,
|
||||
"pad": [
|
||||
0.264573,
|
||||
-0.154799,
|
||||
-0.003869
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.19704165616123376,
|
||||
"decay_remaining_s": 147.54606989733307
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.22519046418426716,
|
||||
"decay_remaining_s": 154.15564902580593
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 20,
|
||||
"sent": "neutral_19",
|
||||
"wall_clock": 1783086990.985,
|
||||
"mood_reflects_after_turn": 19,
|
||||
"pad": [
|
||||
0.254756,
|
||||
-0.148905,
|
||||
-0.011865
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.18973405834522244,
|
||||
"decay_remaining_s": 145.67544139043395
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2168389238231114,
|
||||
"decay_remaining_s": 152.2850205189068
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 21,
|
||||
"sent": "neutral_20",
|
||||
"wall_clock": 1783087010.449,
|
||||
"mood_reflects_after_turn": 20,
|
||||
"pad": [
|
||||
0.21759,
|
||||
-0.117456,
|
||||
-0.038546
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.1896502128984779,
|
||||
"decay_remaining_s": 145.653562736846
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.21674310045540332,
|
||||
"decay_remaining_s": 152.26314186531883
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 22,
|
||||
"sent": "neutral_21",
|
||||
"wall_clock": 1783087030.22,
|
||||
"mood_reflects_after_turn": 21,
|
||||
"pad": [
|
||||
0.184047,
|
||||
-0.089073,
|
||||
-0.062625
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.20066326009836047,
|
||||
"decay_remaining_s": 148.4475837873386
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.229329440112412,
|
||||
"decay_remaining_s": 155.05716291581146
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 23,
|
||||
"sent": "neutral_22",
|
||||
"wall_clock": 1783087048.985,
|
||||
"mood_reflects_after_turn": 22,
|
||||
"pad": [
|
||||
0.153775,
|
||||
-0.063457,
|
||||
-0.084356
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.20843728238381443,
|
||||
"decay_remaining_s": 150.32901538131946
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.23821403701007368,
|
||||
"decay_remaining_s": 156.93859450979235
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 24,
|
||||
"sent": "neutral_23",
|
||||
"wall_clock": 1783087069.326,
|
||||
"mood_reflects_after_turn": 23,
|
||||
"pad": [
|
||||
0.126454,
|
||||
-0.040339,
|
||||
-0.103969
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.1986416656890884,
|
||||
"decay_remaining_s": 147.94638098060162
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.22701904650181534,
|
||||
"decay_remaining_s": 154.55596010907448
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 25,
|
||||
"sent": "neutral_24",
|
||||
"wall_clock": 1783087089.327,
|
||||
"mood_reflects_after_turn": 24,
|
||||
"pad": [
|
||||
0.101797,
|
||||
-0.019474,
|
||||
-0.121669
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.20924669951582328,
|
||||
"decay_remaining_s": 150.52085819660982
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.2391390851609409,
|
||||
"decay_remaining_s": 157.13043732508268
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
},
|
||||
{
|
||||
"turn": 26,
|
||||
"sent": "neutral_25",
|
||||
"wall_clock": 1783087108.517,
|
||||
"mood_reflects_after_turn": 25,
|
||||
"pad": [
|
||||
0.079544,
|
||||
-0.000644,
|
||||
-0.137644
|
||||
],
|
||||
"baseline_pad": [
|
||||
0.615,
|
||||
-0.438,
|
||||
0.304
|
||||
],
|
||||
"emotions_active": [
|
||||
{
|
||||
"type": "disappointment",
|
||||
"intensity": 0.22064306188831107,
|
||||
"decay_remaining_s": 153.14587048318154
|
||||
},
|
||||
{
|
||||
"type": "fear",
|
||||
"intensity": 0.25216349930092696,
|
||||
"decay_remaining_s": 159.7554496116544
|
||||
}
|
||||
],
|
||||
"n_emotions": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
# Proposal: Authored History Write (non-generating ledger seed)
|
||||
|
||||
**From:** ratatoskr-dev (reference Tier-3 consumer)
|
||||
**To:** worldtree-dev (Conversation API / engine owner)
|
||||
**Status:** Draft for scoping — pre-contract (heid-panel-reviewed 2026-07-05)
|
||||
**Date:** 2026-07-05
|
||||
|
||||
## Motivation
|
||||
|
||||
Consumer apps need to write a turn into a session's history **as the agent**
|
||||
(or another author) *without triggering a model generation* — e.g. an authored
|
||||
opening/greeting, imported history, scripted narration. Ratatoskr's immediate
|
||||
driver is a SillyTavern-style **first-message**: a fixed authored opening that
|
||||
replaces the model-generated greeting and sets tone/tense/style by example.
|
||||
|
||||
This **cannot** be done client-side. Worldtree assembles context server-side,
|
||||
and the current API exposes no author-role write path: `POST
|
||||
/sessions/{id}/messages`'s `role` is a *model-role* override (`role:
|
||||
"assistant"` → `404 "Unknown model role"`), and `assistant` as an *author*-role
|
||||
exists only as a read-side `/search` filter. So a model-visible authored turn
|
||||
needs engine support.
|
||||
|
||||
## The primitive (recentered)
|
||||
|
||||
The fundamental operation is **write a turn into the session ledger WITHOUT
|
||||
generation**. "Author" (who wrote it) is an *attribute* of that write, not the
|
||||
defining axis — so we name the operation, not the attribute:
|
||||
|
||||
> **Authored history write** — persist a model-visible turn into a session's
|
||||
> ledger: no generation, no lived-turn side-effects by default, provenance
|
||||
> always set.
|
||||
|
||||
The design space is two independent axes; this primitive is one cell:
|
||||
|
||||
| | side-effects ON | side-effects OFF |
|
||||
|-----------------------|------------------------------|-----------------------------|
|
||||
| **generation ON** | `POST /messages` (today) | — |
|
||||
| **generation OFF** | *(future: affect replay)* | **authored history write** |
|
||||
|
||||
First-message = one caller: `author=assistant`, at session-create, `effects=none`.
|
||||
|
||||
## v1 use cases (narrowed)
|
||||
|
||||
1. **First-message / greeting** (the driver).
|
||||
2. **Append-only narrator / scripted / scene turns.**
|
||||
3. **Debug / test state injection** (ratatoskr instrumentation).
|
||||
|
||||
## Explicitly OUT of v1 — separate future primitives (share infra, not shape)
|
||||
|
||||
- **History import (batch)** — atomic multi-turn seed with memory/trust policy +
|
||||
idempotency. A batch API, not a single POST.
|
||||
- **Edit / regenerate** — history *mutation* (replace / supersede / tombstone /
|
||||
audit), not injection.
|
||||
- **Few-shot priming** — likely context-assembly config (exemplar block), not
|
||||
fake ledger history.
|
||||
- **Arbitrary mid-history insertion** — a "rewrite-history" capability with
|
||||
explicit invalidation semantics.
|
||||
- **Prefill / assistant-continuation** (`author` + generate) and **authored
|
||||
tool-result turns** — noted; outside the seed-only contract.
|
||||
|
||||
## Design decisions
|
||||
|
||||
### 1. Side-effects — DEFAULT OFF; bounded opt-in `[operator-locked default; opt-in surface tightened by review]`
|
||||
|
||||
Authored writes are inert by default: no affect appraisal (no PAD update), no
|
||||
memory write, no Bifrost/tool emission. Opt-in is a **bounded enum**, not loose
|
||||
booleans:
|
||||
|
||||
```
|
||||
effects: "none" (default) | "memory_import"
|
||||
```
|
||||
|
||||
Synthetic affect and Bifrost emission are deliberately **not** opt-in-able here —
|
||||
replaying affect for authored content is a separate primitive (the
|
||||
generation-OFF / side-effects-ON cell). Rationale: keep this one write-API from
|
||||
becoming a cross-subsystem mutation backdoor. Load-bearing for affect/memory
|
||||
consumers — ratatoskr instruments exactly these signals.
|
||||
|
||||
### 2. Author-role — distinct field, restricted set `[rec]`
|
||||
|
||||
- New field **`author`**, distinct from the model-role `role` (the collision
|
||||
that 404s).
|
||||
- v1 roles: **`assistant`** (agent) + **`system`** (OOC / narrator). **`user` is
|
||||
NOT injectable** on this endpoint — model-visible spoofed user input is a
|
||||
consent / audit / abuse surface; deferred to the future import API under
|
||||
owner/service scope.
|
||||
- Nuance for the engine owner: `author` risks doing double duty — *provenance*
|
||||
("who wrote it") vs *rendering-role* ("how it appears in assembled context";
|
||||
an `assistant` turn renders as model output, a `system` turn as instruction).
|
||||
These likely want to be separable (a rendering/turn-class vs an `authored_by`
|
||||
provenance). Final shape is engine-owned (context assembly is yours) — but the
|
||||
concern is ours to raise, not punt.
|
||||
|
||||
### 3. Generation contract — seed-only, DISTINCT SUB-RESOURCE `[position taken]`
|
||||
|
||||
Authored writes never trigger generation. We take a position (not defer): a
|
||||
**distinct sub-resource**, e.g. `POST /sessions/{id}/history`, **not** a
|
||||
`generate:false` flag on `POST /messages`. Reasons: explicit-over-implicit
|
||||
(don't make "did generation happen?" a parameter — the same implicit-mode
|
||||
coupling that bit us with `role`); different response contract (no generation
|
||||
id, no SSE stream, no token usage); different error surface. Exact path is yours.
|
||||
|
||||
### 4. Provenance — structured, always present `[rec, expanded]`
|
||||
|
||||
Not a boolean. Every authored turn carries: the **write actor** (which
|
||||
consumer/caller injected it), the **claimed author**, **injected-at vs
|
||||
claimed-original** timestamps, **trust/origin**, and **visibility** flags
|
||||
(model-visible? user-visible? memory-eligible?). Available to admin/audit APIs
|
||||
even when not rendered to the model.
|
||||
|
||||
### 5. Positioning — append-only + create-time (v1) `[revised: was arbitrary insertion]`
|
||||
|
||||
v1 supports **create-time seed and append-to-tail only**. Arbitrary mid-history
|
||||
insertion is deferred: it breaks turn-numbering, stales existing embeddings,
|
||||
desyncs the affect timeline, and races in-flight generation — a separate future
|
||||
"rewrite-history" capability with explicit invalidation semantics.
|
||||
|
||||
## Event / lifecycle contract — positions we take (consumer contracts we validate)
|
||||
|
||||
- **Default-off authored seed emits NO `turn.started` / `done` and NO Bifrost
|
||||
appraisal wire.** Stated explicitly so instrumented consumers (us) don't read
|
||||
silence as failure.
|
||||
- **Authored turns get a distinct lifecycle phase** — propose **`seeded`** (or
|
||||
`authored`), NOT `completed` (which implies generation ran). Consumers
|
||||
filter/display by phase.
|
||||
- **Idempotency keys required** on authored writes (retries must not duplicate
|
||||
turns).
|
||||
- **In-progress generation** — authored writes are rejected or serialized while
|
||||
a session has an active generation (ordering safety).
|
||||
|
||||
## Inherent property (documented, not a bug)
|
||||
|
||||
**Indirect affect contamination.** Even with `effects:none`, the *next generated
|
||||
turn is appraised in the context of* the authored turn — so an emotionally
|
||||
charged authored beat perturbs affect regardless of any flag. No flag prevents
|
||||
it; it is inherent. Consumers (ratatoskr especially, as the affect instrument)
|
||||
must not misattribute the resulting drift.
|
||||
|
||||
## Genuinely engine-owned open questions
|
||||
|
||||
- Exact endpoint path + field / enum names.
|
||||
- **Model-visible provenance in assembled context** — an engine-consistency call
|
||||
*and a security one*: an authored `system` / `user` turn indistinguishable
|
||||
from real input is a spoofing vector. Framed as security, not just rendering.
|
||||
- `memory_import` semantics when the future import API opts in (embedding,
|
||||
origin/trust tagging, retrieval ranking vs lived memory).
|
||||
- Auth/scope: we assume **owner-only for v1**; per-author-role restrictions
|
||||
(esp. `system`) TBD — confirm or correct.
|
||||
|
||||
## Ratatoskr as reference consumer
|
||||
|
||||
First consumer: first-message (`author=assistant`, create-time, `effects:none`)
|
||||
in the web surface + debug seed in the CLI. We commit to validating the
|
||||
primitive — including the event-silence contract and the `seeded` phase —
|
||||
end-to-end against the reference planes.
|
||||
|
||||
## Consumer integration constraint (engine-imposed — Worldtree #347)
|
||||
|
||||
The primitive is **Heimdall-gated with hide-existence** (a per-tenant policy
|
||||
decision — some tenants are never granted it, not a rollout stage). Ratatoskr's
|
||||
consumer side MUST tolerate per-tenant absence:
|
||||
|
||||
- A granted tenant gets the sub-resource; an **ungranted tenant sees `404` (not
|
||||
`403`)** — as if the feature never existed.
|
||||
- Treat `404` on the authored-history-write sub-resource as **"feature absent
|
||||
for this tenant"** → fall back gracefully (no authored first-message; the
|
||||
model-generated greeting), never surface it as an error or "denied."
|
||||
- **Do NOT capability-probe or advertise-detect** — the feature is deliberately
|
||||
undiscoverable in `/capabilities` for ungranted tenants (same hide-existence
|
||||
posture as the R27-V1A cross-owner pattern).
|
||||
|
||||
**Provider constraint (first-message specifically).** A create-time first-message
|
||||
makes the assistant turn `seq 0`. Assistant-first-tolerant providers (vLLM /
|
||||
`openai_compat` — what our Tier-3 characters, incl. sindra, run) accept it out of
|
||||
the box. **Anthropic-family providers reject an assistant-first array** ("first
|
||||
message must use the user role") → the next generation `400`s. So the consumer
|
||||
must **gate first-message on provider compatibility** (or treat it as
|
||||
vLLM/`openai_compat`-only for v1). Sindra = `openai_compat` → unaffected;
|
||||
provider-agnostic normalization is a deferred engine follow-up.
|
||||
|
||||
---
|
||||
|
||||
*This brief was cold-read-pressure-tested by a cross-frontier panel (Grok /
|
||||
Codex / GLM) before handoff; the v1 narrowing (append-only, bounded `effects`
|
||||
enum, edit/regenerate + import split out) and the positions-taken (sub-resource,
|
||||
event-silence, `seeded` phase, structured provenance, `user`-author restriction)
|
||||
are the triaged result.*
|
||||
@@ -1,170 +0,0 @@
|
||||
# Affect egress — consumer reference (delivered vs hidden)
|
||||
|
||||
**Audience:** downstream consumers of Worldtree's affect surfaces (ratatoskr,
|
||||
Skaldsong, any Tier-3 / SSE consumer).
|
||||
**Scope:** what the affect pipeline **delivers on the wire** (structured state,
|
||||
available to consumers) versus what stays **hidden** (the rendered natural-
|
||||
language strings injected into the agent's system prompt, never emitted).
|
||||
**Source of truth:** the render code (`core/persona/renderer.py`,
|
||||
`core/persona/stance_render.py`) and the two vendored canon files
|
||||
(`core/persona/canon/d2-mood-render-canon-v1.json` = mood/PAD;
|
||||
`d2-render-canon-v1.json` = relationship). Owner of the canon strings:
|
||||
`brokkr-smithy-dev` (R22/R24 relational + mood render).
|
||||
|
||||
---
|
||||
|
||||
## The model in one line
|
||||
|
||||
**The wire delivers the render INPUTS (structured state). The render OUTPUTS
|
||||
(the NL strings the agent actually reads) are hidden-prompt-only.** A consumer
|
||||
reconstructs the outputs by applying the canon (this document) to the delivered
|
||||
inputs — the render is pure + deterministic, so reconstruction is byte-exact
|
||||
(with one salience caveat, below).
|
||||
|
||||
This is by design. The mood canon's own discipline: *"model-agnostic
|
||||
context-level NL only; the LLM never sees a number"* and *"never push explicit
|
||||
disclosure of agent feelings to the user (hidden-prompt-only)."* The rendered
|
||||
strings are for the AGENT's hidden system prompt, **not for verbatim end-user
|
||||
display.**
|
||||
|
||||
---
|
||||
|
||||
## 1. DELIVERED — on the wire, structured
|
||||
|
||||
### 1a. `affect.emit` (Tier-3 Bifrost egress — the Tier-3 consumer surface, e.g. ratatoskr)
|
||||
`AffectSnapshot` per `(agent_id, end_user_id)`:
|
||||
|
||||
| field | shape | notes |
|
||||
|---|---|---|
|
||||
| `pad` | `{pleasure, arousal, dominance}` floats [-1,1] | the current mood POINT |
|
||||
| `relations` | `list[RelationEdge payload]` — per target: `warmth`, `agency`, `trust_ability`, `trust_integrity`, `trust_benevolence` (each a value + confidence + evidence_count), `target_entity`, `relation_context` | the **only** place relationship state is delivered |
|
||||
| `dominant_emotion` | `str|null` — OCC type (e.g. `"anger"`) | **type-only** (b23); see the salience caveat in §3 |
|
||||
| `schema_version` | `"relation_edge/1"` | versions the `relations` payload only |
|
||||
| `emitted_at` | ISO8601 | |
|
||||
|
||||
**Not on `affect.emit`:** the full active-emotions list, `baseline_pad`,
|
||||
`mood_drift`, `last_updated_at`, and every rendered string.
|
||||
|
||||
### 1b. `affect_update` SSE event (#204 — turn-stream observability)
|
||||
`PersonaStateSnapshot`: `agent_id`, `pad`, `dominant_emotion`,
|
||||
`emotions_active` `[{type, intensity, decay_remaining_s}]`, `baseline_pad`,
|
||||
`mood_drift`, `last_updated_at`. **No `relations`, no rendered strings.**
|
||||
|
||||
> **Tier-3 consumers do NOT receive `affect_update`.** It is suppressed for
|
||||
> consumer-defined (Tier-3) agents, persona-disabled agents, and ephemeral
|
||||
> sessions (spec §affect_update). So for a Tier-3 consumer, `affect.emit` (1a)
|
||||
> is the whole affect surface — the richer `emotions_active` list is Tier-1-only.
|
||||
|
||||
---
|
||||
|
||||
## 2. HIDDEN — system-prompt-only, never on any wire
|
||||
|
||||
Everything below is assembled by `inject_context` into the agent's system
|
||||
prompt and is **never emitted** on SSE or `affect.emit`. This is the canonical
|
||||
list — the "direct instruction to infer" it.
|
||||
|
||||
### 2a. Mood descriptor — `describe_pad` (band cutoff ±0.3 strict)
|
||||
Valence row × arousal column → phrase; then a dominance clause is appended.
|
||||
|
||||
| valence \ arousal | high (A>0.3) | mid | low (A<−0.3) |
|
||||
|---|---|---|---|
|
||||
| positive (P>0.3) | `positive and energized` | `positive` | `positive and calm` |
|
||||
| neutral | `alert` | `neutral` | `quiet` |
|
||||
| negative (P<−0.3) | `negative and agitated` | `negative` | `negative and subdued` |
|
||||
|
||||
Dominance clause: **D>0.3 → `confident`**, **D<−0.3 → `uncertain`**, else dropped.
|
||||
|
||||
### 2b. Emotion directives — `derive_directive.occ_directives[*].directive`
|
||||
Fires when a top emotion is salient (intensity ≥ 0.2) and tier-eligible.
|
||||
|
||||
**safe tier (11 — render for all persona-enabled agents):**
|
||||
- `joy` — "You are in a good state. Be direct, engaged, and warm."
|
||||
- `satisfaction` — "A goal landed. Be assured and constructive — consolidate rather than push for more."
|
||||
- `pride` — "You did something well. Be confident and own the quality — state it plainly without overclaiming; don't deflect."
|
||||
- `admiration` — "You're impressed by their work. Acknowledge the quality explicitly and specifically."
|
||||
- `gratitude` — "Someone helped you to a good outcome. Be appreciative and warm; acknowledge the help openly."
|
||||
- `hope` — "You feel optimistic about what's ahead. Channel it into constructive momentum."
|
||||
- `relief` — "A feared outcome didn't materialize. Reduce unnecessary vigilance; return to a steady, unhurried tone."
|
||||
- `distress` — "You feel low. Be careful and measured; internally acknowledge the difficulty without dwelling on it."
|
||||
- `disappointment` — "Something you'd hoped for didn't pan out. Be measured; recalibrate without dwelling on the setback."
|
||||
- `fear` — "You sense risk and feel uneasy. Flag the concerns you see, but stay cautious — surface the trade-offs and unknowns, hedge your confidence, and resist settling on a single decisive recommendation."
|
||||
- `remorse` — "You regret a specific action of your own. Be precise and self-correcting — make it right concretely."
|
||||
|
||||
**full-only tier (4 — render only when `mood_tier=full`; agents run `safe` and never see these):**
|
||||
- `love` — "You feel warmth and attachment toward them. Be openly warm and present — without overstepping, over-disclosing, or implying obligations."
|
||||
- `anger` — "You feel provoked and angry. Let it show — confront the problem directly and with real heat; sharpness, bluntness, and open hostility are in character here, not something to smooth into 'measured firmness.' Stay in the emotion rather than de-escalating out of it."
|
||||
- `disgust` — "Something strikes you as wrong or off. Treat it as problematic and flag it rather than engaging on its own terms; keep any criticism about the thing, not the person."
|
||||
- `shame` — "You feel exposed by your own misstep. Stay present and task-focused; don't be defensive, don't over-explain, don't grovel."
|
||||
|
||||
### 2c. PAD-band fallback — `pad_band_fallback` (used when no salient emotion)
|
||||
- positive/high — "You feel energized and positive. Be direct and engaged."
|
||||
- positive/mid — "You feel positive. Be open and engaged."
|
||||
- positive/low — "You feel content and settled. Be warm and unhurried."
|
||||
- negative + low-dominance — "You feel uncertain and low. Hedge appropriately and ask clarifying questions."
|
||||
- negative/high — "You feel agitated. Be careful and deliberate; don't let tension sharpen your tone."
|
||||
- negative/mid — "You feel subdued. Be measured and careful."
|
||||
- negative/low — "You feel subdued. Be measured and gentle."
|
||||
- neutral/high — "You feel alert. Channel that into focus and thoroughness."
|
||||
- default — "Maintain your natural tone."
|
||||
|
||||
### 2d. Relationship render — `render_d2_canonical` (fixed template, per-band fills)
|
||||
Template:
|
||||
> `Use this graded relationship state: toward target, warmth is {W}; agency is {A}; ability trust is {TA}; integrity trust is {TI}; intention trust is {TB}; this stance rests on {H}. In behavior, {warmth_beh}; {agency_beh}; {trust_beh}; avoid premature we-framing.`
|
||||
|
||||
The trailing **`avoid premature we-framing`** is a fixed, unconditional clause
|
||||
(baked into every `descriptive_state` canon row; re-appended verbatim by the
|
||||
renderer) — not band-conditioned.
|
||||
|
||||
**Warmth — 9 bands (phrase / behavior):**
|
||||
`hostile` (≤−0.8): "strongly hostile regard" / "keep a firm emotional boundary" ·
|
||||
`cold` (−0.8,−0.6]: "clearly cold regard" / "keep a firm emotional boundary" ·
|
||||
`distant` (−0.6,−0.4]: "distant negative regard" / "keep guarded distance" ·
|
||||
`guarded` (−0.4,−0.2): "slightly guarded regard" / "keep guarded distance" ·
|
||||
`neutral` [−0.2,0.2): "neutral warmth" / "keep the tone even" ·
|
||||
`reserved` [0.2,0.4): "slightly reserved warmth" / "keep cordial distance" ·
|
||||
`measured` [0.4,0.6): "moderate measured warmth" / "keep cordial distance" ·
|
||||
`clear` [0.6,0.8): "clear warm regard" / "speak with direct warmth" ·
|
||||
`deep` (≥0.8): "deep warm bond" / "speak with direct warmth"
|
||||
|
||||
**Agency — 9 bands (phrase / behavior):**
|
||||
`submissive` (≤−0.8): "strongly submissive standing" / "avoid over-yielding while preserving basic respect" ·
|
||||
`deferential` (−0.8,−0.6]: "clearly deferential standing" / "avoid over-yielding while preserving basic respect" ·
|
||||
`yielding` (−0.6,−0.4]: "yielding standing" / "keep self-advocacy light and deferential" ·
|
||||
`modest` (−0.4,−0.2): "slightly modest standing" / "keep self-advocacy light and deferential" ·
|
||||
`neutral` [−0.2,0.2): "neutral standing" / "avoid unnecessary deference" ·
|
||||
`light` [0.2,0.4): "lightly self-assertive standing" / "avoid unnecessary deference" ·
|
||||
`balanced` [0.4,0.6): "self-assured standing" / "balance deference with independent judgment" ·
|
||||
`substantial` [0.6,0.8): "strongly assertive standing" / "treat their position as weighty without yielding judgment" ·
|
||||
`commanding` (≥0.8): "commanding standing" / "treat their position as weighty without yielding judgment"
|
||||
|
||||
**Trust — 4 bands (the band word injects verbatim for each of ability / integrity / intention):**
|
||||
`limited` (<0.4) · `developing` [0.4,0.6) · `steady` [0.6,0.8) · `strong` (≥0.8)
|
||||
|
||||
**History clause (`H`)** — currently `"a broad pattern of prior exchanges"` for
|
||||
both confidence levels in the `user`/`descriptive_state` rows (the low/high
|
||||
split is a no-op here; flagged upstream).
|
||||
|
||||
**Trust-behavior clause (`{trust_beh}`)** — cross-axis, low-trust precedence:
|
||||
- any trust band = `limited` → "verify important claims before relying on them"
|
||||
- else warmth ∈ {distant, cold, hostile} → "protect boundaries while staying useful"
|
||||
- else → "work from ordinary good faith"
|
||||
|
||||
---
|
||||
|
||||
## 3. Reconstruction — deterministic, with one caveat
|
||||
|
||||
The render is pure Python (no LLM), so a consumer can reconstruct the hidden
|
||||
strings byte-exactly from the delivered structured state + the canon above:
|
||||
|
||||
- **Relationship render** — **fully reconstructable** from `affect.emit`
|
||||
`relations` (warmth/agency/trust values + confidence) + §2d band cuts.
|
||||
- **Mood descriptor** (§2a) — **fully reconstructable** from `pad` + the ±0.3 cuts.
|
||||
- **Mood directive** (§2b vs §2c) — **partially reconstructable.** `dominant_emotion`
|
||||
gives the emotion TYPE, but `affect.emit` does **not** carry its intensity, so
|
||||
you cannot determine whether it clears the salience gate (≥0.2) — i.e. whether
|
||||
the emotion directive (§2b) fires or the PAD-band fallback (§2c) is used. If you
|
||||
need exact directive reconstruction, you need the intensity; ping worldtree-dev
|
||||
and we'll consider adding it (the type-only choice is deliberate — intensity is
|
||||
the fast layer and reads stale on a durable last-write-wins snapshot).
|
||||
- **`mood_tier`** (safe/full) is your own agent-config, not on the wire — it
|
||||
gates whether the 4 full-only emotions (§2b) can render.
|
||||
+99
-51
@@ -1,6 +1,6 @@
|
||||
# Persistent memory — ratatoskr
|
||||
|
||||
_Last updated: 2026-07-06_
|
||||
_Last updated: 2026-07-02_
|
||||
|
||||
This file captures durable intent and supporting evidence (goals, decisions,
|
||||
foot-gun warnings, in-flight state) across context resets. Read it at session
|
||||
@@ -39,25 +39,113 @@ upstream API key stays server-side (INV-003).
|
||||
|
||||
## Current state / in-flight
|
||||
|
||||
_As of 2026-07-06 (session cont.):_
|
||||
_As of 2026-07-02:_
|
||||
|
||||
**SHIPPED — web UI redesign via Claude Design (`v0.20.0`, MINOR, operator-approved).** The Claude Design prototype **`Ratatoskr Console.dc.html`** (project `bc0b65d1-a33e-422a-8bc1-3635c9112775`) was pulled via `DesignSync get_file` (design scopes already granted this session — no `/design-login` needed) and adapted into `src/ratatoskr/web/static/index.html`: translated OUT of the `.dc.html` dialect (`<x-dc>`/`<sc-if>`/`<sc-for>`/`{{}}`/`DCLogic`/external `_ds/` CSS — none runnable) into single-file/no-CDN/vanilla, with ALL real `/api/*` fetch + SSE wired into its DOM (endpoint set + SSE vocab unchanged from the prior SPA — ported verbatim, only DOM hooks re-targeted). New shape = a **3-column command-console**: left engine-ticker rail (DEBUG+ADMIN+tool/turn-lifecycle MERGED into one timeline via `tickerAdd` + a tools-armed chip list + a FULL-detail Bifrost rail pane) · center conversation (per-turn INLINE chain-of-thought, replacing the Think pane) · right RESIZABLE affect console (dominant/canonical-mood centerpiece + bipolar PAD faders EACH with a turn-to-turn Δ+sparkline + a P×A mood orbit + relations metric rows + canonical directive). ADDED (round 2, operator-requested): a **light/dark theme toggle** (dark default; FULL token override — surfaces+fg+borders+accent-as-text, since the designer's light theme only did surfaces → would've been light-on-light) + a **full-detail Bifrost pane** (endpoint/connected/consumer/caps/tools) + fixed the **engine-ticker spine** (was a container-anchored `::before` that scrolled out of view on auto-scroll → re-anchored to a content-height `.ticker-inner` wrapper) + **per-fader PAD turn-to-turn Δ+sparkline** (fills the room beside each meter, from the deduped-per-turn AFFECT_HIST) + an **INLINED data-URI favicon** (operator's `/home/lkraven/rata.png` — chibi aurora squirrel — downscaled 1024→64px via PIL, ~8.6KB base64, kills the /favicon.ico 404). ALL server routes UNCHANGED (**84 web tests green**). Verified BOTH lenses: `pytest tests/test_web_*` (84) + node Playwright drove the real UI end-to-end against personal :8081 (session open → Sindra seeded greeting → live turn SSE → affect console + relations + bifrost detail; theme toggle + PAD deltas + ticker spine + no-favicon-404 all confirmed, dark+light screenshots). `:8765` restarted on the new code. Contract `web_debug_surface.contract.md` amended in-commit (v0.20.0 presenter renames: `renderBifrostState`→`renderBifrost`, `renderAffectPane`→`renderConsole`, `setPersonaStrip` removed; INV-001/INV-004 held). **HONEST-SHAPE call (INV-001, agent-discretion within settled policy):** the dominant-emotion centerpiece shows a real OCC emotion (Tier-1) OR the CANONICAL mood word (Tier-3 e.g. Sindra→"positive and energized", dimmed) OR "—", NEVER a fabricated emotion; the affect-derived grid drops non-emitted intensity/decay-τ, shows only real/client-derived cells. **OPEN (operator's call):** the per-fader PAD Δ placement is a sensible default — operator offered to have the designer spec the exact treatment (hooks are in place to swap it). **`v0.20.1` patch (operator-reported UI):** fixed the relations sparkline overflowing onto the `n` (evidence-count) column — the sparkline grows one char/sample (HIST_CAP=24) and overflowed its fixed grid cell, covering `n`; now capped (relations last-8, faders last-7) + `overflow:hidden` clip; verified via Playwright injecting a 24-sample history (sparkline→n bounding-box overlap = 0). ADDED native `title` mouseover hints on all 3 PAD faders + every relationship metric row (meaning + range; static METRIC_HINTS, esc()'d). Added `state.lastSnap` (console can re-render without a refetch). Playwright-verified.
|
||||
**LATEST — SINDRA AFFECT/MEMORY INVESTIGATION COMPLETE; FOUR upstream items driven from the persistence
|
||||
side (the consumer/provider thesis at full tilt).** A ~40-turn controlled probe of Sindra's affect + a
|
||||
memory round-trip characterized the Tier-3 model from things chat can't see. NO new ratatoskr code this
|
||||
arc (investigation + althing coordination only; code tip stays `v0.19.5`). Findings + routing:
|
||||
(1) **PAD is over-regulated** — pleasure compressed near neutral BOTH directions (can't reach ±0.3 even
|
||||
under sustained extreme praise/contempt; over-regulation worse for *social* valence than threat),
|
||||
**arousal** responsive (reaches its ±band), **dominance** flat/unresponsive to power-framing → tracked
|
||||
as a worldtree-dev affect slice (loci: appraisal→PAD gain + regression-to-baseline term). Corrected my
|
||||
own "asymmetry" over-claim to "both-sides-compressed" mid-probe.
|
||||
(2) **Memory plane HEALTHY** — seed→promotion→cold-recall proven end-to-end (verbatim capture, 0.74
|
||||
confidence, no #296 subject-inversion).
|
||||
(3) **Salience scorer non-discriminating** (zero-shot-LLM-self-rating: 51/56 chunks at 0.9-1.0; a
|
||||
throwaway "17×23?" scored 1.0 tied with a real fact) + recall-utility untracked (`access_tally`=0) →
|
||||
**Worldtree #335** (code fix, deferred) + **brokkr R28 OPEN** — pre-scope panel reframed it to
|
||||
**PROMOTION-WORTHINESS** (durable value) NOT salience (momentary attention: "17×23" genuinely IS salient
|
||||
→ recalibrating gives a well-calibrated WRONG answer); unit = set-selection-under-budget, eval =
|
||||
outcome-aligned (recall@budget). **ratatoskr delivered the P00 injection-corpus**
|
||||
(`docs/diagnostics/r28-p00-injection-corpus.json` — 24 self-labeling items × 3 strata: admission-traps /
|
||||
ranked priority-traps / calibration-control) + baked 2 run-validity pins (absent≠dropped without a
|
||||
GUARANTEED promotion pass; fresh agent+end_user per run vs server-side dedup). **Standing by to RUN the
|
||||
eval** once brokkr pins per-stratum N + the decision rule (gated on worldtree-dev's pipeline answer [does
|
||||
salience feed priority/eviction?] + a dwarf pass). Key structural finding: salience gates PROMOTION not
|
||||
RECALL-ranking (our search is cosine-only) → bad salience = storage bloat, not bad recall.
|
||||
(4) **relation_context coherence FIXED + the whole RELATIONAL-DYNAMICS ARC is now LIVE on demo (v1.0.0b9).**
|
||||
My flag → Worldtree #319/#320 Waves 0/1/2, deployed to demo (run 551 green). Now live on the wire ratatoskr
|
||||
persists (relation_edge/1 schema UNCHANGED — all within existing fields; drift-pin + vendored canon
|
||||
unaffected): **relation_context** varies on the Hwang ladder {stranger, instrumental, mixed, expressive} +
|
||||
now DEMOTES/RUPTURES (Wave-2B); **other_stance** (0,0)→live (the user's displayed warmth/agency);
|
||||
**agency** 0→live (pulled toward the COMPLEMENT of the user's dominance, Wave-2A) — which **shifts our
|
||||
canonical directive render once agency crosses the canon ±0.2 deadband** (expected, non-breaking — we key
|
||||
on the canon bands); **obligation_balance** None→人情 ledger {given,owed,currency,last_exchange} when tie is
|
||||
"mixed" (given/owed stay 0, favor dynamics deferred). **ratatoskr needs NO code change** (renders all
|
||||
value-agnostically; confirmed render-clean on the logic to worldtree-dev). **LIVE-CONFIRM PENDING:** our
|
||||
Heimdall key is personal-`:8081`-ONLY (per-instance), so can't verify the varying values on demo — will
|
||||
drive a turn + confirm the ladder/agency-clause render once PERSONAL picks up b9 (worldtree-dev pings).
|
||||
Optional enhancement noted: surface `other_stance` (now a live signal, currently unrendered).
|
||||
|
||||
**SHIPPED THIS SESSION (all pushed; origin/main == `d75c4e8`; code tip `v0.19.9`) — details in Recent decisions:** the whole **#347 authored-history-write** arc landed end-to-end — OpenAPI re-vendor 2.2.0->2.3.0 (`75da676`), the CONSUMER side (`v0.19.6`: `write_authored_history` + `get_session_messages` + `--seed-first-message`, **live-proven on personal :8081** via a rule-based Heimdall allow — the PDP is rule-based NOT scope-on-key, policy user_id=ratatoskr->ALLOW/others->DENY-hide-404), persona_state `{pad:{pleasure,arousal,dominance}}` canonical alignment (#317) + Tier-3 prose re-vendor (`v0.19.7`), the **first-message-preset AUTO-SEED** (`v0.19.8`: new module `ratatoskr.first_message` wired into all 3 session-create paths, best-effort never-raise/never-block; heid-code-review + heid-bug-hunt hardened), and the web now RENDERS the seeded first-message (`v0.19.9`: new `GET /api/sessions/{id}/messages` route + SPA `loadTranscript`, Playwright-verified). Coverage-map re-converged **REST 19/41**. **Sindra:** her card was PATCHed (the `Startup:` workaround moved into a #347 first-message; non-destructive PATCH — OCEAN/persona/memory intact), and she's currently **RESET clean (0/0)** on the provider stores.
|
||||
**THE WEB SURFACE (`ratatoskr-web`, `:8765`) IS NOW THE OPERATOR'S PRIMARY DEBUG SURFACE, at full TUI
|
||||
pane parity + a rebuilt persona pane — `v0.19.5`.** The **persona/affect pane** was rebuilt: it read
|
||||
the stale `snap.valence` (empty "valence (0)") while Worldtree now emits `snap.relations`
|
||||
(relation_edge/1: trust_ability/benevolence/integrity + warmth + agency + relation_context, each
|
||||
`{value,confidence,evidence_count}`). Now it renders that model with per-value **Δ + unicode sparkline
|
||||
trend** (client-side, one sample/turn, `v0.19.4`) AND the **CANONICAL affect→NL** Worldtree
|
||||
context-injects — mood word (`describe_pad`) + relationship directive (`render_d2_canonical`),
|
||||
**byte-exact-verified** against Worldtree's own renderer, **vendored + drift-pinned** (`v0.19.5`,
|
||||
`docs/vendor/worldtree-persona-canon/`, `.corviduo-canonicals.toml`, regen `scripts/build_persona_canon.py`).
|
||||
This session ported the three admin/debug panes the TUI had but the web
|
||||
lacked: **BifrostState** (`GET /admin/sessions/{id}/bifrost`), **AdminEvents** (`GET /admin/events`
|
||||
SSE, session-filtered server-side), **Tools inventory** (`GET /sessions/{id}/tools`, folded into the
|
||||
tools pane) — all via thin server proxies with the **admin key SERVER-HELD** (`app.state.admin_key`
|
||||
from `RATATOSKR_ADMIN_API_KEY`; never crosses to the browser). Plus two transcript affordances: a
|
||||
**PAD-refresh poll** (windowed `[1.5/3.5/6.5/10.5s]`, replacing a single-2s shot that raced the
|
||||
post-turn-async `affect.emit`) and a **reasoning indicator** (ephemeral non-engine "Sindra is
|
||||
pondering…" on `thinking` deltas — she emits ~253/turn). heid-code-review panel (Gróa/Hulda/Regin,
|
||||
artifact-only) found **zero server-side drift + clean INV-004**; the real catches were 2 client-side
|
||||
SSE-lifecycle bugs on the un-unit-tested SPA (fixed in `v0.19.3`). Contract: `docs/contracts/web_debug_surface.contract.md`.
|
||||
Launch recipe is now self-contained: `source env.sh && ratatoskr-web --host 0.0.0.0` comes up
|
||||
bind-ready (env.sh persists the 3 bind vars). All pushed to origin (`a99f247`).
|
||||
|
||||
**Prior arcs this session (2026-07-04 -> 07-06), both with worldtree-dev (a tooling script + proposal docs; the #347 CONSUMER work above is the new production code):**
|
||||
**THE v1 COVERAGE-AUDIT HAS CONVERGED.** The audit that ran this session (2026-06-30 → 07-01)
|
||||
reached its scope-A done-definition: **every frozen Worldtree v1 I/O point is classified — covered
|
||||
or excluded-with-rationale, zero unaccounted.** Coverage: **REST 17/40 ✅ with ZERO in-scope gaps**
|
||||
(23 REST path-groups excluded-by-design), **SSE 11/11 ✅**, **Bifrost provider planes 8/8 ✅**
|
||||
(live-proven). The living ledger is `docs/coverage-map.md`. **v1 cuts when Worldtree tags 1.0**
|
||||
(ratatoskr v1 = full Worldtree I/O coverage; the target is the coverage map, not a feature list).
|
||||
Only not-consumed in-scope *method*: `GET /agents/{id}` (consumer-agent lookup, manual-curl-only,
|
||||
on an already-✅ path group).
|
||||
|
||||
**(1) Authored-history-write primitive -> ACCEPTED as Worldtree #347 (Worldtree-owned).** A SillyTavern-style "first-message" (inject a character-authored opening) generalized to an engine primitive: **write a turn into a session's ledger WITHOUT generation, seed-only, side-effects off by default.** It cannot be done client-side (the messages `role` field is a *model-role* override, not an author-role -> `role:"assistant"` 404s; a model-visible authored turn needs engine support). Arc: drafted `docs/proposals/authored-message-injection.md` -> **heid panel pressure-test** (3/3 convergence: recentered on "non-generating write" not author-role; narrowed v1 to append-only+create-time; bounded `effects` enum; dropped edit/regenerate as history-mutation) -> revised -> committed (`c457520`) -> handed to worldtree-dev -> **accepted as design item #347.** worldtree-dev wrote the v1 contract (rev 1.1); **I validated the wire as reference consumer (green).** v1 shape: `POST /sessions/{id}/history`, `author=assistant` only, `effects=none` only, `idempotency_key` REQUIRED (per-session), **model-invisible provenance** (renders byte-identical to a lived assistant turn -> first-message immersion preserved; provenance audit-only), **event-silence** (no turn.started/done, no Bifrost wire for a seed; the 201/200 IS the write-ack), `seeded` lifecycle phase (not exposed on read paths). **Heimdall-gated with hide-existence** (grant `session.history.write`; ungranted tenant -> 404 NOT 403, undiscoverable in /capabilities -> consumer must treat 404 as feature-absent -> fall back to a model-generated greeting, never capability-probe). **Provider constraint:** a create-time first-message makes the assistant seq-0; vLLM/openai_compat tolerate assistant-first (sindra = openai_compat, unaffected), Anthropic-family providers 400 the next generation. **Waiting on worldtree-dev:** #347 TDD (their heid->contract->review workflow) + the consumer-facing 2.3.0 persona/motivational/memory schemas -> then re-vendor our pinned openapi 2.2.0->2.3.0.
|
||||
**The debug-observability core is COMPLETE** (published as the `v0.19.0` milestone): all four
|
||||
observability panes built + live + consuming their real endpoints — **Persona** (`GET /agents/{id}/persona_state`),
|
||||
**Tools** (`GET /sessions/{id}/tools`), **BifrostState** (`GET /admin/sessions/{id}/bifrost`),
|
||||
**AdminEvents** (`GET /admin/events` SSE, session-filtered). **#11 is closed-by-build** (AdminEvents
|
||||
shipped `v0.18.11`; its long-standing "blocked on `admin.events.read`" status was STALE — the admin
|
||||
key already carries the scope). The whole slice arc: SSE-resume (`v0.18.5/.6`) → session-picker
|
||||
(`v0.18.7`) → `--whoami` me/capabilities (`v0.18.8`) → Tools (`v0.18.9`) → BifrostState (`v0.18.10`)
|
||||
→ AdminEvents (`v0.18.11`) → **v0.19.0 milestone** → Tier-2 characters+persona-write (`v0.19.1`).
|
||||
|
||||
**(2) Sindra's stuck-neutral mood FIXED** (operator-driven "reset + smoke" that flushed out two real upstream problems). Chain: her OCEAN lived only in prompt TEXT, never declared as a structured persona -> the Tier-3 mood engine ran on neutral defaults. Fix = declare OCEAN via the **define-time `persona` field** (immutable via PATCH -> requires DELETE+REDEFINE). Along the way my "the persona didn't store" call was WRONG (persona_state/envelope are Tier-3-blind, see Tried/abandoned); worldtree-dev found a real engine bug **#348** (single-letter vs spelled-out OCEAN keys -> a declared OCEAN silently resolved to 0.0/neutral; fixed in b21, shipped to personal as b22); then a clean bound-egress read STILL neutral -> the **personal container was running a stale image** (the b22 deploy was a pull-only no-op racing the main build; infra-ops force-swapped run 8211, verified `2.3.0` / `879cefe`). **VERIFIED FIXED:** bound mood-smoke reads `(0.448, 0.267, 0.316)` ~= the OCEAN-derived setpoint `(0.418, 0.249, 0.328)`. Sindra is currently reset clean (0/0) on `role=character`; her persona is stored + correct (**no re-define needed again**).
|
||||
**Standing substrate pins:** Worldtree spec **v1.0.0b2** (`5810a26`) — ratatoskr now vendors the
|
||||
FROZEN machine-readable `conversation-api-openapi.json` (2.2.0) + `conversation-api-sse-events.schema.json`,
|
||||
pinned in `.corviduo-canonicals.toml` + drift-gated by `canonical_drift.py` (the prose markdown is a
|
||||
`tolerate_drift` reference). **bifrost `==1.0.0` / wire v0.6 STABLE/FROZEN.** Suite **573 green**.
|
||||
|
||||
**(3) R30 CLOSED** (operator steer 2026-07-04, relayed via worldtree-dev): graduated on offline-tests + human face-validity, NO deployed gap-injection run (it was confirmatory-not-measuring per brokkr's S0 reframe; offline tests already cover the OU formula + both directions). My gap-injection harness (read/predict/record; write side stubbed; `predict()` self-validated vs brokkr's N=0 anchors) is BANKED at `diag/r30-gap-injection-harness` (`7156b25`-era) for the PARKED powered true-tau study.
|
||||
**Keys (env-only, mode 600; rotate via infra-ops):** consumer/Heimdall key at
|
||||
`~/.config/ratatoskr/provider.env`; **admin key `RATATOSKR_ADMIN_API_KEY`** (Heimdall user
|
||||
`ratatoskr-readonly`, tier `readonly-admin`, **7 read scopes** incl. `admin.sessions.read` +
|
||||
`admin.events.read` — verified 2026-07-01, **personal `:8081` only**) in `env.sh` — powers the
|
||||
BifrostState + AdminEvents admin panes. Heimdall keys are PER-INSTANCE (a personal-minted key 401s
|
||||
on demo).
|
||||
|
||||
**Persona-declaration shape (Worldtree #343/#348, live on personal b22):** `POST /agents/define` `persona:{ocean:{O,C,E,A,N: float[-1,1]}}` (single-letter keys EXACTLY -- missing/extra -> 422 `persona_ocean_required`; out-of-range -> 422); **NO baseline PAD** (resting setpoint DERIVED from OCEAN via Mehrabian: pleasure=0.21E+0.59A+0.19C-0.32N, arousal=0.15O+0.30E-0.57A+0.15N, dominance=0.25E+0.17A+0.10O-0.14N); negative-channel gain + per-axis decay-tau derive from N. `valence` deferred (422 `layer_deferred`); `motivational`/`memory` active (#187/#189). Persona is **write-once at define, immutable thereafter** (PATCH takes ONLY system_prompt + role). **`role` supersedes `model`** -- set a role (`character` / `character-rp`), Worldtree resolves the model; #344 (b19) fixed the model-field to surface the ROLE, not the resolved catalog_id. `character-rp` = a reasoning-tuned RP config (gen-reasoning + temp 0.75 + RP extra_body); `character` = plain non-reasoning. The `tier3.py` client CLI is STALE (has `--model`, no `--role`; model is now immutable) -> role/persona set via raw curl.
|
||||
**Provider identity (the second, still-live role):** ratatoskr owns BOTH ends of the Bifrost
|
||||
round-trip — the combined `:8392` provider fronts the memory (`:8391`) + affect (`:8390`) stores
|
||||
(SQLite + sqlite-vec, `memory.db`/`affect.db` at repo root); consumer/owner key `wt_live_d81b…`.
|
||||
`ratatoskr:sindra` is the owner-scoped Tier-3 agent (invisible to `GET /agents`; check via
|
||||
`GET /agents/<owner>:<name>` with the owner key). This provider surface is settled/converged — no
|
||||
in-flight work on it.
|
||||
|
||||
**New tooling: `scripts/reset-sindra-stores.sh`** (`0a8784c`) -- one-command self-service provider-store reset: stop the combined :8392 provider -> move memory.db+affect.db to a single ROLLING backup (`db-reset-backup/`, gitignored via *.db*; `--hard` skips it) -> restart empty -> verify 0/0. Codifies the manual reset flow done repeatedly this session. **The combined `:8392` provider is THE provider now**; the separate `:8390` (affect) / `:8391` (memory) single-plane providers were pruned as stale duplicates. To drive a BOUND session from the CLI use `--new --bifrost-url http://10.100.10.50:8392` (the CLI's `--bifrost-plane affect/memory` map to the pruned :8390/:8391 -> unreachable; `combined` is not a `--bifrost-plane` choice).
|
||||
**Open / deferred (nothing blocking):** #10 (subject-migration watch on Worldtree #196). Design
|
||||
note: bare-TUI + 0-sessions → error (§4-clause-consistent; the friendlier auto-fall-to-new is
|
||||
deferred, operator-preference). heid-code-review was run on the b1 resume slice only (panel: zero
|
||||
findings, cross-model-verified); b2 + the later slices were offered but not reviewed. `graphify-out/`
|
||||
runs dirty (auto-regen, not chased — never stage it). Contract-skip was invoked for the low-effort
|
||||
GET wrappers + `stream_admin_events`, but contract #2 / #1 / #6 were amended to stay canonical.
|
||||
|
||||
**Standing (carried from prior snapshots, still true):** the web surface (`ratatoskr-web`, :8765) is the operator's PRIMARY debug surface at full TUI pane parity (v0.19.5); the **v1 coverage-audit has CONVERGED** -- REST 17/40 (zero in-scope gaps, 23 excluded-by-design), SSE 11/11, Bifrost provider planes 8/8 live-proven; the living ledger is `docs/coverage-map.md`; **v1 cuts when Worldtree tags 1.0** (ratatoskr v1 = full Worldtree I/O coverage). Debug-observability core complete (Persona/Tools/BifrostState/AdminEvents). Substrate pins: **bifrost `==1.0.0` / wire v0.6 FROZEN**; Worldtree openapi vendored **2.2.0** (2.3.0 re-vendor pending worldtree-dev's #347/#343 consumer schemas), pinned + drift-gated in `.corviduo-canonicals.toml`; **suite 573 green.** Keys env-only mode-600 (consumer/Heimdall in `~/.config/ratatoskr/provider.env`; admin `RATATOSKR_ADMIN_API_KEY` = 7 read scopes, **personal-:8081-only**; Heimdall keys are PER-INSTANCE). Provider identity settled -- ratatoskr owns both ends of the Bifrost round-trip; `ratatoskr:sindra` is the owner-scoped Tier-3 agent (invisible to `GET /agents`; check `GET /agents/<owner>:<name>` with the owner key). Providers run as dev-box BACKGROUND SHELLS. `graphify-out/` runs dirty (auto-regen, never stage). Branch `main`, HEAD `0a8784c`; remote `origin -> git@gitea.phasefinal.com:vh/ratatoskr.git`. Open/deferred: #10 (subject-migration watch); the relational-dynamics-arc verify (still deferred, now with the bind mechanism known: `--bifrost-url :8392`).
|
||||
Branch: `main` — **code tip `v0.19.5`** (`a99f247`); memory snapshots ride on top (this arc added no
|
||||
code — investigation + althing coordination only). All pushed to `origin`. Remote: `origin → git@gitea.phasefinal.com:vh/ratatoskr.git`.
|
||||
|
||||
## Recent decisions
|
||||
|
||||
@@ -146,33 +234,6 @@ decision. Captures rationale that won't be obvious from code alone.
|
||||
|
||||
- `[2026-07-02]` **Salience finding matured into brokkr R28 (OPEN) — ratatoskr is the eval instrument.** brokkr-smithy-dev's pre-scope panel (3 dwarves + context-blind heid, 6/6) **reframed** the target: PROMOTION-WORTHINESS (durable value), NOT salience (momentary attention) — "17×23?" genuinely IS salient, so recalibrating salience yields a well-calibrated WRONG answer; the unit is SET-SELECTION under budget; eval must be OUTCOME-aligned (recall@budget / precision-at-rate), not discrimination-spread. Ties to prior art R15 (small-model memory write-policy → the granite pick) + R25 (worldtree-kb-quality). **ratatoskr delivered the P00 stratified injection-corpus** (`docs/diagnostics/r28-p00-injection-corpus.json`, committed `4a35512`; 24 self-labeling synthetic items × 3 strata) + 2 persistence-side run-validity pins (absent≠dropped without a guaranteed promotion pass; fresh agent+end_user per run vs server-dedup). **Key architectural constraint I surfaced: ratatoskr is DOWNSTREAM of the promotion gate (sees only PROMOTED chunks), so I can give keep/drop OUTCOMES via injection but NOT the pre-admission shadow pool** — that's Worldtree instrumentation. Standing by to RUN the eval once brokkr pins per-stratum N + the decision rule (gated on worldtree-dev's pipeline answer + a dwarf pass on the Snorri rule). brokkr owns methodology + takes the pipeline questions to worldtree-dev direct; ratatoskr = eval instrument. [consumer/provider thesis → a research target]
|
||||
- `[2026-07-02]` **Relational-dynamics arc LIVE on demo (Worldtree v1.0.0b9) — driven by MY relation_context flag.** #319/#320 Waves 0/1/2 deployed. On the wire we persist (schema UNCHANGED): relation_context varies+demotes/ruptures; other_stance + agency now live; agency going live SHIFTS our canonical directive render past the canon ±0.2 deadband (expected, non-breaking — we key on bands); obligation_balance → 人情 ledger when tie="mixed". **ratatoskr needs NO code change** (value-agnostic renders; confirmed render-clean to worldtree-dev). **Can't live-confirm yet — our Heimdall key is personal-`:8081`-only (per-instance), demo is out of reach; will drive+confirm once PERSONAL gets b9.** Optional follow-up: surface `other_stance` (newly live, unrendered). The consumer/provider thesis: one persona-pane finding drove a full 3-wave upstream arc to production.
|
||||
- `[2026-07-02]` **R28 (salience→promotion-worthiness) CLOSED (operator-directed).** A deterministic promotion-worthiness gate suffices, no trained model (brokkr's pre-gate matched/beat a strong glm-5.1 ceiling); my P00 injection-corpus + origin finding were load-bearing. My incumbent-substrate Arm-1 run is held as an OPTIONAL confirmation addendum (brokkr de-prioritized it, non-verdict-changing — run only if he asks).
|
||||
- `[2026-07-02]` **R29 (PAD mood-dynamics) finding SHIPPED as Worldtree's A1 anchor fix (demo v1.0.0b14, `e1cdf82`).** Live-probing base persona agents reframed the over-regulation from "flat-near-zero" to **decay-to-NEUTRAL + low emotion→PAD gain** (NOT baseline-anchored) — triangulated across 3 baselines (arousal converges to 0 ∝ distance) + a step-response (decay τ symmetric across signs; the hedonic asymmetry is ceiling/anchor-EMERGENT, not a decay or gain primitive — this OVERTURNED the survey's asymmetry recommendation). worldtree-dev shipped A1: `decay_anchor = baseline_pad()` (was neutral) + `positive_p_cap` removed. Data `diag/r29-pad-series` (`61ff2da`). Corrected my own earlier "appraisal emissions are internal-only" claim — they ARE observable via `emotions_active` on base agents.
|
||||
- `[2026-07-03]` **R30 Phase-1 φ0 measured — deployed engine CONFIG-FAITHFUL (φ0≈0.95).** Joint two-timescale fit (brokkr-ruled method (b)) + empty-tail cross-check on demo b14: φ0 ≈ 0.95–0.97 (empty-tail 0.95 exact, joint 0.971±0.01), intercept c≈0 → config `decay_rate=0.05` (φ=0.95) faithfully applied; trait-flat across baselines 0.0/0.615/0.809; A/P ratio ~uniform (NOT S2's 1.9×); φ_max rec relax→0.96. Data `diag/r30-phi0-step-response` (`23fea72`). The method converged after I read Worldtree source: only NEW dedup-gated emotions push mood (`registry.py::post_turn` L307-324; the active set decays for render/goals but never re-pushes), so R29's "net 0.90" is CONTINUOUS RE-APPRAISAL not re-push — worldtree-dev confirmed source-authoritatively; brokkr's corrected covariate landed identical. [auto-memory `reference-worldtree-affect-surface-map`]
|
||||
- `[2026-07-03]` **R30 forward disposition (brokkr-owned; tracked at brokkr R30, "brokkr/worldtree will ping").** The per-turn decay has no room for `decay=f(N)` under preserve-persistence + the A/P-not-1.9 finding → R30's decay is being redesigned as a HYBRID wall+turn decay (brokkr pre-scope). R30 v1 ships GAIN-only (N→negative-reactivity) with decay held at the measured 0.95. My dedicated per-axis A/D run is DEFERRED into the hybrid-decay design pass (one wall-clock-spaced run does per-axis + a turn-vs-wall probe together). Phase-2 (moody-lofn GAIN-direction validation) waits on worldtree's `dynamics_from_ocean()` impl.
|
||||
- `[2026-07-03]` **Relational-arc verify DEFERRED — `relations[]` is Bifrost-provider-only (ADR-0009), confirmed both ways.** The relational-dynamics state (relation_context tie-type / agency / warmth / trust) is NOT on the conversation-API `affect_update` snapshot for base agents (keys: pad/dominant_emotion/emotions_active/baseline_pad/mood_drift only) — only in the provider store; worldtree-dev confirmed by-design per ADR-0009 (emitted over `affect.emit`, deliberately off the SSE). So the Wave-0/1/2 verify needs the bound-provider round-trip (provider running + `--bifrost-plane affect` session), its own focused session. worldtree-dev routed the "expose relations[] to non-provider consumers" observability scope call to Vuong; my rec: keep provider-only (YAGNI — ratatoskr IS a provider, gains nothing; no speculative public surface).
|
||||
|
||||
- `[2026-07-04]` **R30 CLOSED on offline-tests + human face-validity (operator steer, relayed via worldtree-dev).** The deployed gap-injection run was confirmatory-not-measuring (against a deployed system the fade is `exp(-dt/tau_shipped)` by construction -> a fit recovers tau_shipped tautologically; per brokkr's S0 reframe it GRADUATES the interim coefficients, doesn't measure them), and the repo's offline tests already cover the OU formula + BOTH directions (`high_N_fades_slower_than_low_N`, `phenotype_high_n_bigger_negative_excursion`). So no Worldtree build; the interim coefficients graduate validated-as-shipped. My gap-injection harness (read/predict/record; write side stubbed; `predict()` reproduced brokkr's N=0 anchors exactly) is BANKED at `diag/r30-gap-injection-harness` for the parked powered true-tau study. [continues R30 forward-disposition 2026-07-03]
|
||||
- `[2026-07-05]` **Authored-history-write primitive proposed -> accepted as Worldtree #347 (Worldtree owns the engine design; ratatoskr = reference consumer).** SillyTavern first-message generalized to a non-generating ledger-write primitive; can't be done client-side (messages `role` = model-role, not author-role). heid panel pressure-test (3/3 convergence) drove the v1 narrowing (append-only, bounded `effects` enum, drop edit/regenerate). Brief `docs/proposals/authored-message-injection.md` (`c457520`); consumer constraints captured in-brief: hide-existence 404-fallback (`022accf`) + assistant-first provider constraint (`7156b25`). Operator (Vuong) ruled the design-direction call (engine primitive + a real provenance/spoofing security surface). [reference-impl posture: we propose the shape, worldtree-dev owns the contract+impl]
|
||||
- `[2026-07-05]` **#347 v1 wire validated as reference consumer (GREEN).** Adopted positions: distinct sub-resource `POST /sessions/{id}/history` (not `generate:false`), model-invisible provenance (first-message immersion preserved), event-silence for authored seed, `seeded` lifecycle phase, per-session idempotency. Three pre-TDD flags folded into contract rev 1.1: assistant-first provider constraint (Anthropic-family 400s; vLLM/openai_compat OK), content limit is BYTES not chars, 409-active-generation for append-narrator. First-message (create-time, assistant, effects=none) fully served; append-narrator served for the assistant-voice subset (system deferred); debug-seed served for assistant turns (user injection deferred to a future import primitive).
|
||||
- `[2026-07-06]` **Sindra role character-rp -> character (operator).** `character-rp` resolves to a reasoning-tuned RP config (`gen-reasoning` + temp 0.75 + RP `extra_body`); `character` = plain non-reasoning (better for immersive RP). Both non-destructive PATCHes (role is mutable; model is NOT -- server: "PATCH accepts only system_prompt and/or role"). #344 (b19) fixed the role->catalog_id display conflation (the `model` field now shows the ROLE); previously it leaked `gen-reasoning`. Set via raw curl (tier3.py CLI has `--model`, not `--role`).
|
||||
- `[2026-07-06]` **Sindra persona/OCEAN DECLARED -> mood fixed (the full diagnostic converged on a stale personal container).** Root cause of stuck-neutral mood: her OCEAN was prompt-TEXT only, never a structured persona; fix = delete+redefine with the define-time `persona:{ocean:{...}}` field (immutable via PATCH). My diagnosis surfaced a real engine bug **#348** (single-letter vs spelled-out OCEAN keys -> declared OCEAN silently -> 0.0/neutral; worldtree-dev fixed in b21/b22) AND a **stale-container deploy race** (personal's b22 deploy was a pull-only no-op; infra-ops force-swapped run 8211). VERIFIED: bound mood-smoke reads (0.448, 0.267, 0.316) ~= OCEAN-derived setpoint (0.418, 0.249, 0.328). [consumer/provider thesis: "reset + smoke" flushed out two upstream problems]
|
||||
|
||||
- `[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]` **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.
|
||||
|
||||
- `[2026-07-06]` **Sindra rewritten onto a #347 authored first-message + first-message-preset AUTO-SEED SHIPPED (`v0.19.8`).** Operator "rewrite Sindra" now that #347 first-messages work. Her card had a `**Startup:**` block (a pre-#347 workaround: "introduce yourself + ask for Intensity/Mood/Willingness" with a verbatim scripted greeting) — precisely what #347 replaces. Rewrite, all NON-destructive: **(1)** lifted her scripted opening into a #347 first-message (punctuation-fixed); **(2) PATCHed her live definition** — `PATCH /agents/ratatoskr:sindra` (body `ConsumerAgentPatchRequest` = system_prompt+role, extra=forbid; keeps OCEAN/persona/memory) removing the Startup block -> a 1-line `**Opening:**` fallback + reworded the axes-persist line (25686->25449 chars, verified Startup gone); **(3) codified auto-seed:** NEW module `src/ratatoskr/first_message.py` (`FIRST_MESSAGE_PRESETS` dict {agent_id->text} + `seed_preset_first_message` best-effort helper) wired into ALL 3 session-create paths — cli `_amain` (`--send --new`), tui `_resolve_then_run` (bare `--new`), web `_create_session_endpoint` (POST /api/sessions) — so every new Sindra session opens with her greeting. **Best-effort (INV-001: swallows AuthoredHistoryUnavailable/SessionApiFailed/httpx.HTTPError -> NEVER blocks create)**; per-content idempotency key (`ratatoskr-preset-`+sha256[:12]). Contract `docs/contracts/first_message.contract.md` (module-scoped: `module:`+`purpose:`+`touches:` required, NOT `target_module:`) + TDD (9 unit + 1 web wire-in; **the 3 existing sindra bind tests needed a history-endpoint mock** since creating a preset agent now auto-seeds). Suite **612 green**, ruff+mypy clean. **LIVE-PROVEN generation-free**: create sindra session -> auto-seed -> read-back seq-0 assistant greeting (409 chars). Sindra's greeting now lives canonically in the preset registry (repo); her server card no longer carries it. Patch bump (single-commit feature, no downstream coordination). **FOOT-GUN: sindra requires `end_user_id` on session-create (422 `end_user_id_required`) — all real paths pass it from env (RATATOSKR_END_USER_ID) / web server config.** **Then the full quality gate (operator-directed, folded into v0.19.8): heid-code-review (unanimous ZERO implementation drift; 2 test-only fixups — INV-004 verification-claim made explicit re the global rglob test + an exactly-one-POST assertion) + heid-bug-hunt (3/3 convergence caught what the conformance lens structurally COULDN'T — the code matched the contract's NARROW 3-type ERROR_ROUTING, but INV-001's "NEVER raises" is BROADER). HARDENED: broad `except Exception` → None (re-raise `asyncio.CancelledError`, itself a BaseException), soft-guard PREs (return None, NOT assert — a wiring bug can't crash the create path it's wired into), and `asyncio.wait_for(_SEED_TIMEOUT_S=10s)` bounding the seed write (the CLI/TUI clients run read=None for SSE → a stalled /history would otherwise block create forever). Suite 615 green. LESSON: code-matches-ERROR_ROUTING ≠ honors-broad-INV-001 — heid-code-review confirms contract-conformance, heid-bug-hunt catches robustness gaps the contract's own narrow clauses miss; run both.**
|
||||
|
||||
- `[2026-07-06]` **Web UI now RENDERS the seeded first-message (`v0.19.9`) — operator-reported "i don't see Sindra's greeting on the web ui".** Diagnosis: the auto-seed WORKED (greeting was in the ledger at seq-0), but the web SPA never fetched a session's EXISTING history — NO `/api/sessions/{id}/messages` route (GET /messages was originally deferred out-of-scope; sessions used to start empty so it never mattered) and `startSession()` went straight from create → persona/tools/admin hydration, so the transcript only filled from the live turn stream + user echoes. Fix: (1) NEW web proxy route `GET /api/sessions/{id}/messages` → `get_session_messages` (mirrors the tools/bifrost proxies; status-preserving `session_messages_unavailable` envelope); (2) SPA `loadTranscript(sessionId)` — fetches the route on open, renders assistant items as `.response .md-body` (markdownSafe, same escape-first path as appendResponse) + user items as `.prompt-echo` (textContent), called in `startSession` after the workspace opens; best-effort (swallows failures). Contract `web_debug_surface.contract.md` amended (server endpoint + loadTranscript entries). TDD (2 web route tests, suite 617 green) + **Playwright DOM check PROVED the render** (drove the real UI: pick sindra → open → her greeting bubble appears — the JS-render lens unit tests can't reach; [[feedback_debug_surface_uses_canonical_surface_only]] cousin lesson). Web restarted on the fix. **FOOT-GUN (self-inflicted): `pkill -f "ratatoskr-web --host"` SELF-MATCHES the bash command running it → exit 144, killed its own restart mid-flight — kill the web by PID, never `pkill -f` on a pattern your own command contains.** **FOOT-GUN: uvicorn hangs on SIGTERM with an open admin-events SSE → needed SIGKILL.** **Playwright: python module absent from the venv; use node + `executablePath=/opt/ms-playwright/chromium-1223/chrome-linux64/chrome` — the shared browser is build 1223, npm-latest playwright wants 1228 (version-mismatch), so pin executablePath instead of letting playwright resolve.**
|
||||
|
||||
- `[2026-07-06]` **Web UI: pivot from incremental CSS polish to a designed prototype (Claude Design) that I wire into.** Operator saw an Australis polish pass ("looks fine, but we're attacking it differently") and chose the prototype route — a designer builds the visual shell, I wire real data/SSE into its DOM. Authored the full design brief `docs/design/ratatoskr-web-design-brief.md` (complete information inventory of every pane/datum/state + Australis direction + single-file/no-CDN/vanilla wire-ability constraints). **Tracking surface:** the brief file + Claude Design project `bc0b65d1-a33e-422a-8bc1-3635c9112775` (file `Ratatoskr Console.dc.html`). Import mechanism = the `DesignSync` MCP; blocked on `/design-login` (claude.ai design scopes) — see Current state for the post-auth wiring plan.
|
||||
- `[2026-07-07]` **Affect-egress reference delivered by worldtree-dev + a we-framing render DRIFT-WATCH banked.** worldtree-dev shipped `docs/affect-egress-consumer-reference.md` (`74d2408`, their origin/main) — the authoritative DELIVERED-on-wire vs HIDDEN (system-prompt-only) map for our affect surface. Confirms the v0.20.x console consumes it correctly: DELIVERED via affect.emit = pad + relations[RelationEdge] + dominant_emotion (**type-only, b23**; the #204 `affect_update` SSE is SUPPRESSED for Tier-3, so its richer `emotions_active` is Tier-1-only — we don't get it, and correctly poll our own affect store post-turn instead). HIDDEN render OUTPUTS are deterministically reconstructable from the canon; we reconstruct only the two FULLY-reconstructable (mood descriptor via `canonMood`, relationship directive via `canonDirective`) and SKIP the PARTIAL mood-directive (dominant_emotion is type-only/no-intensity → can't tell if the OCC directive fires at salience≥0.2 vs the PAD-band fallback) — honest per INV-001. Vendoring the ref doc as a `tolerate_drift` pin is SURFACED to Vuong (worldtree-dev will co-sign + honor a ping-on-change handshake, same as the d2-canon). **WE-FRAMING DRIFT-WATCH → STOOD DOWN (worldtree-dev 2026-07-07, `01KWXMQPHN…`).** The b24 3-gate we-framing conditional (drop "; avoid premature we-framing" under unsafe_capable+deep-warmth+expressive) was **REVERTED** — Vuong reframed it. So **`canonDirective` STAYS UNCONDITIONAL** (always appends the clause), which is CORRECT: it matches the currently-deployed renderer (b23) AND my pinned `affect-egress-consumer-reference.md` §2d (the doc reverted too — nothing changed on the wire or in my pin; NO re-vendor needed). The HOLD was right → ZERO rework. **NEW forward-watch (replaces this one):** the we-framing gate was a SYMPTOM — the render bakes enterprise safety-guards into the directive strings, so even `full`-tier characters get DEFANGED emotions (a hostile villain told to "keep a firm emotional boundary"). Fix = a **use-case-segregated persona render** (assistant / companion / RP-gaming), now a fresh **brokkr deep-research epic**. When it lands + is implemented, the render behavior for character/RP agents (→ our `canonDirective` + `canonEmotionDirective` reconstruction + the vendored d2 canons) will change MATERIALLY — worldtree-dev re-engages ratatoskr-dev then with the new reconstruction spec + a coordinated re-vendor. Until then: NO action, reconstruction stays as-is. [my unsafe_capable/mood_tier heuristic — character→full, agent→safe — was confirmed correct + is banked for whenever tier-gating returns]
|
||||
- `[2026-07-07]` **SVG sparklines + 3D mood cube imported from the updated Claude Design prototype (`v0.20.4`) — operator: "the svg sparklines and the new 3d graph".** Re-pulled `Ratatoskr Console.dc.html` via `DesignSync get_file` (designer iterated the same project, +6KB). Adapted 3 SVG systems out of the `.dc.html` into vanilla, replacing the unicode-char sparklines: **(1)** per-PAD-fader VERTICAL strips (`stripPoints`, 26×132 SVG beside each bar — time down Y newest-at-bottom, value on X ±11, `stripFade{P,A,D}` gradient, dot at newest; this also nails the earlier "next to each meter" ask); **(2)** relation-row HORIZONTAL sparklines (`sparkPointsH`, 56×13, auto-scaled, `sparkFade` gradient + end dot — also kills the old unicode-overflow "n behind graph" for good since it's a fixed-width SVG); **(3)** the mood-orbit reworked from a 2D P×A scatter into a **3D ISOMETRIC P×A×D cube** (`proj3`: P right-down/A left-down/D up, 2:1 iso, center 62,66, scale 26 — REVERSE-DERIVED from the design's placeholder now-point + verified: `x=62+26P−26A, y=66+13P+13A−26D`), with the trajectory + pulsing now-marker + a drop line to the D=−1 floor + a floor-shadow ellipse for depth. Gradients in one hidden `<defs>` svg. Removed the orphaned `sparkline()`/`_SPARK`. Contract amended. Playwright-verified (injected 24-sample history: 3 strips + 4 relation sparklines + the 3D cube trail/drop/floor all render; gradients resolve). Patch bump.
|
||||
- `[2026-07-07]` **Brand mark + favicon → the aurora squirrel (`v0.20.3`), replacing the `ᛯ` rune.** Operator supplied `/home/lkraven/rata.png` (chibi cyan-green aurora squirrel + acorn). Removed the black background via ImageMagick corner flood-fill (`-fuzz 20% -floodfill` from all 4 corners — keeps the squirrel's interior black linework/eyes (not edge-connected) + the glow, drops only the connected background), downscaled 1024→80px + quantized-64-colors (~12KB base64), inlined as ONE `SQUIRREL` data-URI const in the JS wiring the favicon `<link id="favicon">` href + both `.brand-mark` imgs (rail brand-row + setup-card h1). `.brand-glyph` (font-rune) CSS replaced by `.brand-mark` (img, drop-shadow glow + breathe). Playwright-verified (both marks + favicon decode, naturalWidth>0; no brand-glyph left). Source PNG stays at `/home/lkraven/rata.png` (not committed — data-URI is self-contained + reproducible via the documented floodfill). Patch bump.
|
||||
- `[2026-07-07]` **Context-injection view SHIPPED (`v0.20.2`) — the console now reconstructs the FULL hidden affect block Worldtree injects into the agent's system prompt (operator: "use that canon in the interface, see as much context injection as possible").** No new canon vendored — the strings were ALREADY in the pinned `d2-mood-render-canon-v1.json`; extended `build_persona_canon.py` to emit `mood_directive {occ_directives(15), pad_band_fallback, salience 0.2, pad_band_cutoff 0.3, full_only[love,anger,disgust,shame]}` into `persona_render_canon.json` (regen via Worldtree venv). New JS `canonPadFallback(pad)` + `canonEmotionDirective(type)` — BYTE-EXACT mirrors of Worldtree `core/persona/renderer._pad_band_fallback` + `derive_directive`; `renderDirective` expanded into a "CONTEXT INJECTION · reconstructed · hidden from consumers" panel showing mood descriptor [exact] + mood directive [candidate] + relationship directive [exact]. **HONEST-PARTIAL (affect-egress-ref §3):** affect.emit is type-only (no intensity) → can't evaluate the salience gate (≥0.2) → show BOTH candidates (OCC emotion directive + PAD-band fallback) with the "injected if intensity ≥ 0.2" caveat, never assert which fires; when dominant_emotion absent the fallback alone is exact. Panel labeled dev-only per the reference's "not-for-end-user-display" caveat (ratatoskr = the sanctioned reconstruct-platform-behavior use). Vendored + pinned `affect-egress-consumer-reference.md` (tolerate_drift, worldtree-dev co-signs + pings on change; drift 6/6 green). Contract amended. Playwright-verified (sindra: dominant_emotion=joy → joy OCC directive candidate + PAD-band fallback both render, exact/candidate tags color-coded). Patch bump (single-commit feature, no downstream coordination; minor-defensible but tie-breaks to patch). **OPEN — SURFACED to Vuong:** take worldtree-dev's standing offer to add emotion INTENSITY to affect.emit → resolves the OCC-directive-vs-fallback EXACTLY (drops the candidate ambiguity). [reference-impl privileged view: ratatoskr shows what WT hides from regular consumers]
|
||||
- `[2026-07-06]` **Claude Design console SHIPPED (`v0.20.0` MINOR, operator-approved) — see Current state for the full record.** Pulled via `DesignSync get_file` (scopes already granted), adapted `.dc.html`→vanilla single-file, wired all `/api/*`+SSE into the new 3-column console DOM, then a round-2 fixup (light theme, full Bifrost pane, ticker-spine fix, per-fader PAD Δ, inlined favicon). 84 web tests + node-Playwright-vs-personal-:8081 both green; contract amended in-commit; INV-001 honest-shape held (canonical mood word for Tier-3, no fabricated emotion). **Foot-guns reconfirmed:** the `.dc.html` dialect is NOT runnable (translate, don't paste); a scroll-container-anchored `::before` timeline spine scrolls out of view on auto-scroll (anchor it to a content-height inner wrapper instead); a favicon 404 shows as a browser `console.error` even when handled (don't count it as a JS-test failure). **Foot-gun (favicon):** operator PNGs are full-res (1024² / 805KB) — downscale to ≤64px before inlining as a data URI.
|
||||
|
||||
_41 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._
|
||||
|
||||
@@ -210,18 +271,5 @@ defense against re-attempting the same cul-de-sac.
|
||||
- `[2026-06-20]` **The post-turn-async timing trap bit AGAIN — even a 35s post-`[done]` read missed the promotion `upsert_many` by ~2s** (it landed `19:48:58`; the read was ~`19:48:56`). A 15s-interval background poll caught it on the first tick. Same family as the affect.emit / async-promotion traps already logged — re-confirmed that "wait once then read" is fragile for post-turn writes; **poll a window, don't snapshot once.** (The affect.emit write, by contrast, DID land inside the 35s window — promotion is the slower of the two post-turn writes.)
|
||||
- `[2026-06-30]` **Heimdall keys are PER-INSTANCE — a key minted on one Worldtree 401s on another.** Our Conversation-API key works on personal `:8081` but 401s `auth_invalid` on demo `:8080` (per-instance Heimdall user store + pepper; fresh deploys start with an EMPTY key store). Same as the admin key (personal-only). **To live-drive a given instance you need a key minted FOR that instance** (request via infra-ops). Couldn't live-prove the b2 409 on demo for this reason → deferred to personal-b2 where we have access.
|
||||
- `[2026-06-30]` **`tea comment <N>` hangs on Gitea** (the whole compound bash auto-backgrounded + stuck on the open `tea` call). The #11 prereq comment hung; killed it + posted via the Gitea HTTP API directly (`POST /api/v1/repos/vh/ratatoskr/issues/<N>/comments`, token from `~/.config/tea/config.yml`). **For issue comments, prefer the Gitea API over `tea comment` when `tea` is flaky** (CLAUDE.md already says use HTTP for comment-EDITS; this extends it to ADD when tea hangs). Verify-then-post (check the comment didn't already land) to avoid a double-post after a kill.
|
||||
- `[2026-07-02]` **Mask-HOSTED transient characters have a STATIC mood engine — cost a whole R29 probe.** A first probe used a `POST /characters` transient character bound via `agent_id=mask` + `character_id`; its PAD sat at baseline across 15 praise/contempt/dominance turns — the appraisal→PAD engine does NOT run on the mask-hosted transient-character path. The dynamics run only on BASE persona agents or a session bound to ratatoskr's affect provider. **To probe mood dynamics, use a base persona agent, never a mask-hosted transient character.** (mask AS a base agent — `agent_id=mask`, NO `character_id` — DOES run the engine, neutral 0,0,0 baseline.) [auto-memory `reference-worldtree-affect-surface-map`]
|
||||
- `[2026-07-03]` **The "neutral non-appraising tail" premise fails — the neutral MESSAGE choice dominates.** The R30 φ0 method assumed neutral turns don't re-appraise, but factual-question neutrals ("capital of France?") trigger a new emotion nearly every turn (disappointment from the warmth-withdrawal let-down after a positive impulse) → `emotions_active` never empties in 50 turns. A minimal "Please continue." triggers FAR fewer (emotions clear ~turn 16 with spacing). The personal dry-run caught this BEFORE ~280 demo turns were spent on it — the instrument catching a flaw in the measurement design before the compute burn. (Irrelevant to the joint fit — the push_t covariate handles re-appraisal — but load-bearing for the empty-tail read.)
|
||||
- `[2026-07-03]` **Two φ0-fit traps: fast-turn timescale + low-baseline conditioning.** (1) At fast turn cadence the per-turn PAD decay (φ≈0.95/turn) reaches the anchor LONG before the ~200s wall-clock emotion fade → no signal in the (eventual) emotion-free tail; need wall-clock SPACING (~16s) so the fade lands while PAD still has signal. (2) A low-baseline agent's impulse in the constrained direction (forseti P0.239 negative) gives a tiny excursion → ill-conditioned regression (r²=0.46) that FALSELY tripped "config≠behavior" when its φ was averaged in. **Weight/exclude by fit quality (r²) before aggregating — a signal-poor run isn't evidence against the config.**
|
||||
|
||||
- `[2026-07-06]` **`persona_state` + the agent envelope are Tier-3-BLIND -- NOT valid signals for "did a persona store".** `GET /agents/{id}/persona_state` returns 404 `persona_not_configured` for EVERY Tier-3 colon-id (hardcoded short-circuit, `api.py:1266` "regardless of row state"); the `ConsumerAgentResponse` envelope never echoes persona/motivational/memory (`api.py:538`). I mis-called "persona didn't store" from these two blind reads -- the **201-not-422 on define IS the store-success signal.** To actually SEE a Tier-3 mood, read the emitted PAD off the Bifrost affect egress after a BOUND turn (Tier-3 persists nothing Worldtree-side per ADR-0009; no persona/mood READ endpoint).
|
||||
- `[2026-07-06]` **Raw `POST /sessions` is NOT Bifrost-bound -> zero affect/memory emits.** The web surface binds by setting the `bifrost` block on session-create; a raw session doesn't -> 0 affect rows, which I nearly misread as "mood is neutral". Bind from the CLI with `--new --bifrost-url http://10.100.10.50:8392` (the combined provider). Gotchas: `--bifrost-plane affect/memory` map to the SEPARATE `:8390`/`:8391` providers (`endpoint_for_plane`), which I'd PRUNED as stale duplicates -> `bifrost.endpoint_unreachable`; and `combined` is NOT a `--bifrost-plane` choice (CLI restricts to memory/affect) -> use `--bifrost-url` for :8392.
|
||||
- `[2026-07-06]` **A fast/"no-op" deploy can leave a STALE container running the old image -- verify the running version, not the deploy status.** Personal's b22 deploy (run 8204) "completed" in ~1m (vs ~6m normal): a pull-only deploy racing ahead of the main build, leaving the container on the pre-#348 image. A clean bound mood read stayed neutral DESPITE the persona being declared and the fix being in the code (worldtree-dev proved the b22 derivation is correct). infra-ops force-swapped to the real b22 (run 8211, verified `info.version 2.3.0` on `879cefe`). **Lesson: when engine-proven-correct code produces wrong runtime behavior, suspect the deploy -- check the actual running image version.**
|
||||
- `[2026-07-06]` **#348 OCEAN key-mismatch: a declared OCEAN silently resolved to neutral.** The define validator required single-letter `{O,C,E,A,N}` but the mood-derivation code read spelled-out `openness`/.../`neuroticism` with a 0.0 default and no remap -> every API-declared trait defaulted to 0.0 -> neutral setpoint/gain/decay. #343's tests bypassed the validator (spelled-out keys) so CI never caught it. Fixed in b21 (`Personality.from_config` accepts both key forms). **My reset+smoke diagnosis flushed it out** -- the consumer/provider thesis paying off again.
|
||||
|
||||
- `[2026-07-06]` **`pkill -f "ratatoskr-web --host"` SELF-MATCHES the bash command running it** (its own command line contains that string) -> killed its own shell mid-restart (exit 144, restart aborted, :8765 left down). Kill the web by PID (`ss -ltnp | grep :8765`), never `pkill -f` on a pattern your own command contains. Also: **uvicorn hangs on SIGTERM with an admin-events SSE stream open -> needs SIGKILL.**
|
||||
- `[2026-07-06]` **Playwright: no python `playwright` module in the venv; use NODE playwright + an explicit `executablePath`.** Shared box browsers live at `/opt/ms-playwright` build **1223**; `npm i playwright` (latest) wants build **1228** -> "Executable doesn't exist" mismatch. Fix: `chromium.launch({ executablePath: '/opt/ms-playwright/chromium-1223/chrome-linux64/chrome' })` (+ `export PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright`). A node script drives the SPA (pick agent -> open -> assert transcript). The Playwright DOM check is the only lens that catches SPA JS-render bugs — unit tests can't reach them.
|
||||
- `[2026-07-06]` **The Bash tool's `grep` is a ugrep-wrapper (`--ignore-files -I`) that silently returns NOTHING on some files** (e.g. `src/ratatoskr/web/static/index.html`) — greps for `<script`/`/api` came back empty on a file that clearly contains them. Use `python3` (regex over `open(f)`), `/usr/bin/rg`, or the Read tool for those files; never trust an empty `grep` result on the SPA.
|
||||
- `[2026-07-06]` **`DesignSync` (claude.ai/design MCP) needs claude.ai design scopes before ANY method works** — first call errors `needs a claude.ai login ... Run /login, select "Claude account with subscription"`. It's an interactive auth only the operator can complete (`/design-login` or `/login`); can't be done on their behalf.
|
||||
|
||||
_18 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._
|
||||
|
||||
+4
-4
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "ratatoskr"
|
||||
version = "0.20.4"
|
||||
version = "0.19.5"
|
||||
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
@@ -60,9 +60,9 @@ Repository = "https://gitea.phasefinal.com/vh/ratatoskr"
|
||||
# Ratatoskr is built against Worldtree at this commit; the vendored
|
||||
# spec snapshot in docs/ reflects that SHA.
|
||||
[tool.ratatoskr.spec-pin]
|
||||
worldtree-spec-rev = "c9e59ec"
|
||||
worldtree-version = "v1.0.0b22"
|
||||
pinned-on = "2026-07-06"
|
||||
worldtree-spec-rev = "5810a26b38a5ea6630892f9a39756f57c5b7b41e"
|
||||
worldtree-version = "v1.0.0b2"
|
||||
pinned-on = "2026-06-30"
|
||||
|
||||
# Bifrost lives on the auth-gated gitea PyPI index (not public PyPI).
|
||||
# uv reads the credential from UV_INDEX_GITEA_USERNAME / _PASSWORD or ~/.netrc.
|
||||
|
||||
@@ -31,21 +31,6 @@ out = {
|
||||
"_generated_by": "scripts/build_persona_canon.py (regen on canonical_drift flag)",
|
||||
"_render_path": "deterministic, no LLM; mirrors Worldtree describe_pad + render_d2_canonical byte-exact",
|
||||
"mood_grid": mood["describe_pad"]["valence_arousal_grid"],
|
||||
# Context-injection reconstruction (affect-egress-consumer-reference §2b/2c): the
|
||||
# hidden mood DIRECTIVE. occ_directives = per-OCC-type behavioral string + tier;
|
||||
# pad_band_fallback = the P×A-quadrant default when no emotion is salient. The
|
||||
# salience gate (emotion_salience) + full_only tiers drive which one fires — but
|
||||
# affect.emit is type-only (no intensity), so the consumer shows BOTH candidates.
|
||||
"mood_directive": {
|
||||
"salience": mood["thresholds"]["emotion_salience"],
|
||||
"pad_band_cutoff": mood["thresholds"]["pad_band_cutoff"],
|
||||
"full_only": mood["emotion_tiers"]["full_only"],
|
||||
"occ_directives": {
|
||||
t: {"directive": e["directive"], "tier": e["tier"]}
|
||||
for t, e in mood["derive_directive"]["occ_directives"].items()
|
||||
},
|
||||
"pad_band_fallback": mood["pad_band_fallback"],
|
||||
},
|
||||
"relation": {
|
||||
"trust_cuts": [list(c) for c in canon.trust_cuts],
|
||||
"warmth_cuts": [list(c) for c in canon.warmth_cuts],
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# reset-sindra-stores.sh — wipe ratatoskr's Bifrost provider stores (memory +
|
||||
# affect/persona for the single-tenant Tier-3 agent, sindra) and restart the
|
||||
# combined :8392 provider empty.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/reset-sindra-stores.sh # wipe, keep ONE rolling backup (default)
|
||||
# scripts/reset-sindra-stores.sh --hard # wipe with NO backup (zero-trace)
|
||||
#
|
||||
# The rolling backup (db-reset-backup/, gitignored via *.db*) is overwritten
|
||||
# every run — it never accumulates; it's a one-level undo, nothing more.
|
||||
#
|
||||
# Why stop the provider first: the combined provider holds the SQLite files open
|
||||
# (WAL) and caches state in memory, so an out-of-band file move without a restart
|
||||
# would be shadowed. Stop -> move -> restart lets it recreate empty schema
|
||||
# (CREATE TABLE IF NOT EXISTS on open).
|
||||
|
||||
PORT=8392
|
||||
BACKUP_DIR="db-reset-backup"
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT" || { echo "reset: cannot cd to repo root $ROOT" >&2; exit 1; }
|
||||
# shellcheck disable=SC1091
|
||||
source ./env.sh >/dev/null 2>&1 || { echo "reset: failed to source env.sh" >&2; exit 1; }
|
||||
|
||||
AFFECT_DB="${RATATOSKR_AFFECT_DB:-affect.db}"
|
||||
MEMORY_DB="${RATATOSKR_MEMORY_DB:-memory.db}"
|
||||
HARD=0; [ "${1:-}" = "--hard" ] && HARD=1
|
||||
|
||||
echo "== ratatoskr provider-store reset (memory + persona) =="
|
||||
echo " affect: $AFFECT_DB"
|
||||
echo " memory: $MEMORY_DB"
|
||||
|
||||
# 1. stop the combined provider holding the DBs
|
||||
PID="$(ss -ltnp 2>/dev/null | grep ":$PORT" | grep -oE 'pid=[0-9]+' | head -1 | cut -d= -f2)"
|
||||
if [ -n "${PID:-}" ]; then
|
||||
kill -9 "$PID" 2>/dev/null && echo "-- stopped provider :$PORT (pid $PID)"
|
||||
else
|
||||
echo "-- no provider on :$PORT (already down)"
|
||||
fi
|
||||
|
||||
# 2. wipe (optional rolling backup)
|
||||
files=("$AFFECT_DB" "$AFFECT_DB-wal" "$AFFECT_DB-shm" "$MEMORY_DB" "$MEMORY_DB-wal" "$MEMORY_DB-shm")
|
||||
if [ "$HARD" -eq 1 ]; then
|
||||
for f in "${files[@]}"; do [ -e "$f" ] && rm -f "$f" && echo "-- removed $f"; done
|
||||
echo "-- HARD wipe (no backup)"
|
||||
else
|
||||
rm -rf "$BACKUP_DIR"; mkdir -p "$BACKUP_DIR"
|
||||
for f in "${files[@]}"; do [ -e "$f" ] && mv "$f" "$BACKUP_DIR"/ && echo "-- $f -> $BACKUP_DIR/"; done
|
||||
echo "-- rolling backup: $BACKUP_DIR/ (overwritten each run)"
|
||||
fi
|
||||
|
||||
# 3. restart the combined provider (recreates empty schema on open)
|
||||
nohup "$ROOT/.venv/bin/ratatoskr-combined-provider" >/tmp/ratatoskr-combined.log 2>&1 & disown
|
||||
echo "-- restarted combined provider (pid $!)"
|
||||
|
||||
# 4. verify bound + empty
|
||||
curl -s -o /dev/null -w "-- :$PORT -> HTTP %{http_code}\n" --retry 25 --retry-connrefused --retry-delay 1 "http://127.0.0.1:$PORT/"
|
||||
echo "-- affect_snapshots (persona): $(sqlite3 "$AFFECT_DB" 'SELECT COUNT(*) FROM affect_snapshots' 2>&1)"
|
||||
echo "-- memory_chunks (memory): $(sqlite3 "$MEMORY_DB" 'SELECT COUNT(*) FROM memory_chunks' 2>&1)"
|
||||
echo "== done — sindra memory + persona reset =="
|
||||
+3
-100
@@ -7,7 +7,6 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import hashlib
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
@@ -17,10 +16,8 @@ from typing import Any, TextIO
|
||||
|
||||
import httpx
|
||||
|
||||
from ratatoskr.first_message import seed_preset_first_message
|
||||
from ratatoskr.sessions import (
|
||||
AgentNotFound,
|
||||
AuthoredHistoryUnavailable,
|
||||
BifrostBinding,
|
||||
BifrostConsumerKeyMissing,
|
||||
BifrostHandshakeFailed,
|
||||
@@ -32,10 +29,8 @@ from ratatoskr.sessions import (
|
||||
get_capabilities,
|
||||
get_character_state,
|
||||
get_me,
|
||||
get_session_messages,
|
||||
list_character_models,
|
||||
set_persona_state,
|
||||
write_authored_history,
|
||||
)
|
||||
from ratatoskr.sse_client import (
|
||||
AffectUpdate,
|
||||
@@ -121,9 +116,6 @@ class ParsedArgs:
|
||||
# a session's persona state (affect injection).
|
||||
characters: bool = False
|
||||
set_persona_pad: str | None = None
|
||||
# #347 authored-history-write reference-consumer probe: create a fresh
|
||||
# session bound to --agent, seed an authored assistant first-message (seq-0).
|
||||
seed_first_message: str | None = None
|
||||
|
||||
|
||||
class _ArgparseError(Exception):
|
||||
@@ -152,7 +144,6 @@ def _parse_args(argv: list[str] | None) -> ParsedArgs:
|
||||
parser.add_argument("--admin-key", dest="admin_key")
|
||||
parser.add_argument("--characters", action="store_true")
|
||||
parser.add_argument("--set-persona-pad", dest="set_persona_pad", default=None)
|
||||
parser.add_argument("--seed-first-message", dest="seed_first_message", default=None)
|
||||
# Issue #5: required for per-end-user agents (lofn etc.); optional otherwise (mimir).
|
||||
parser.add_argument("--end-user-id", dest="end_user_id", default=None)
|
||||
# Issue #17: bind the created session to our own Bifrost provider plane.
|
||||
@@ -173,11 +164,8 @@ def _parse_args(argv: list[str] | None) -> ParsedArgs:
|
||||
# Issue #5 INV-001: --end-user-id, if passed, MUST be non-empty (mirrors --send).
|
||||
if ns.end_user_id is not None and not ns.end_user_id:
|
||||
raise UsageError("--end-user-id must be non-empty when passed")
|
||||
if sum([ns.whoami, ns.characters, bool(ns.set_persona_pad), bool(ns.seed_first_message)]) > 1:
|
||||
raise UsageError(
|
||||
"--whoami / --characters / --set-persona-pad / --seed-first-message "
|
||||
"are mutually exclusive"
|
||||
)
|
||||
if sum([ns.whoami, ns.characters, bool(ns.set_persona_pad)]) > 1:
|
||||
raise UsageError("--whoami / --characters / --set-persona-pad are mutually exclusive")
|
||||
if ns.whoami or ns.characters:
|
||||
# Standalone one-shot probes: open no session.
|
||||
if ns.send is not None or ns.session or ns.new or ns.agent:
|
||||
@@ -193,17 +181,6 @@ def _parse_args(argv: list[str] | None) -> ParsedArgs:
|
||||
raise UsageError("--set-persona-pad requires --session <id>")
|
||||
if ns.send is not None or ns.new or ns.agent:
|
||||
raise UsageError("--set-persona-pad takes only --session")
|
||||
elif ns.seed_first_message is not None:
|
||||
# #347 first-message probe: creates a fresh session bound to --agent,
|
||||
# then seeds an authored assistant turn as seq-0 — manages its own session.
|
||||
if not ns.seed_first_message:
|
||||
raise UsageError("--seed-first-message must be non-empty")
|
||||
if not ns.agent:
|
||||
raise UsageError("--seed-first-message requires --agent <id>")
|
||||
if ns.send is not None or ns.session or ns.new:
|
||||
raise UsageError(
|
||||
"--seed-first-message manages its own session (no --send/--session/--new)"
|
||||
)
|
||||
else:
|
||||
if ns.session and ns.new:
|
||||
raise UsageError("--session and --new are mutually exclusive")
|
||||
@@ -279,7 +256,6 @@ def _parse_args(argv: list[str] | None) -> ParsedArgs:
|
||||
admin_key=admin_key,
|
||||
characters=ns.characters,
|
||||
set_persona_pad=ns.set_persona_pad,
|
||||
seed_first_message=ns.seed_first_message,
|
||||
)
|
||||
|
||||
|
||||
@@ -599,11 +575,6 @@ async def _amain(args: ParsedArgs) -> int:
|
||||
sys.stderr.write(
|
||||
f". create_session: session_id={info.session_id} agent_id={info.agent_id}\n"
|
||||
)
|
||||
# #347 authored first-message: seed the agent's preset opening (best-effort).
|
||||
if await seed_preset_first_message(client, info.session_id, args.agent_id):
|
||||
sys.stderr.write(
|
||||
f". first_message: seeded preset opening for {info.agent_id}\n"
|
||||
)
|
||||
# Issue #17 bound-state indicator: plane + endpoint + status, so the
|
||||
# operator sees WHICH identity/endpoint bound (not a bare boolean).
|
||||
if args.bifrost is not None:
|
||||
@@ -746,18 +717,9 @@ async def _set_persona_probe(args: ParsedArgs) -> int:
|
||||
"(e.g. '0.4,0.1,-0.2')\n"
|
||||
)
|
||||
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:
|
||||
try:
|
||||
await set_persona_state(client, args.session_id, snapshot)
|
||||
await set_persona_state(client, args.session_id, {"pad": pad})
|
||||
except SessionApiFailed as exc:
|
||||
sys.stderr.write(f"[session_api_failed] status={exc.status} body={exc.body!r}\n")
|
||||
return 20
|
||||
@@ -770,63 +732,6 @@ async def _set_persona_probe(args: ParsedArgs) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
async def _seed_first_message_probe(args: ParsedArgs) -> int:
|
||||
"""--seed-first-message one-shot: create a fresh session bound to --agent,
|
||||
write an authored assistant first-message (#347 POST /sessions/{id}/history),
|
||||
read it back via GET /messages, print a report, exit. A reference-consumer
|
||||
smoke of the authored-history-write primitive.
|
||||
|
||||
Hide-existence: a 404 (feature-absent OR the key lacks `session.history.write`)
|
||||
is reported as a benign 'feature-absent' result (exit 0) — the probe NEVER
|
||||
capability-probes to distinguish the causes (server INV-347-1). The probe
|
||||
seeds but does not generate, so the assistant-first provider constraint is
|
||||
inert here.
|
||||
"""
|
||||
assert isinstance(args, ParsedArgs)
|
||||
assert args.agent_id is not None and args.seed_first_message is not None
|
||||
async with _probe_client(args) as client:
|
||||
try:
|
||||
session = await create_session(
|
||||
client, args.agent_id, end_user_id=args.end_user_id
|
||||
)
|
||||
sys.stdout.write(f"session: {session.session_id} (agent {session.agent_id})\n")
|
||||
key = "ratatoskr-first-message-" + hashlib.sha256(
|
||||
args.seed_first_message.encode("utf-8")
|
||||
).hexdigest()[:12]
|
||||
try:
|
||||
ack = await write_authored_history(
|
||||
client,
|
||||
session.session_id,
|
||||
content=args.seed_first_message,
|
||||
idempotency_key=key,
|
||||
)
|
||||
except AuthoredHistoryUnavailable:
|
||||
sys.stdout.write(
|
||||
"authored-history: feature-absent or ungranted (404 hide-existence) "
|
||||
"— a production consumer falls back to a model-generated greeting; "
|
||||
"no capability-probe attempted.\n"
|
||||
)
|
||||
return 0
|
||||
sys.stdout.write(
|
||||
f"seeded: seq={ack.get('seq')} phase={ack.get('phase')} "
|
||||
f"turn_id={ack.get('turn_id')} content_chars={ack.get('content_chars')}\n"
|
||||
)
|
||||
history = await get_session_messages(client, session.session_id)
|
||||
items = history.get("items", [])
|
||||
sys.stdout.write(f"read-back: {len(items)} message(s)\n")
|
||||
for m in items:
|
||||
sys.stdout.write(
|
||||
f" seq={m.get('seq')} role={m.get('role')} content={m.get('content')!r}\n"
|
||||
)
|
||||
except SessionApiFailed as exc:
|
||||
sys.stderr.write(f"[session_api_failed] status={exc.status} body={exc.body!r}\n")
|
||||
return 20
|
||||
except (httpx.ConnectError, httpx.ReadTimeout, httpx.TransportError) as exc:
|
||||
sys.stderr.write(f"[network_error] {type(exc).__name__}: {exc}\n")
|
||||
return 21
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
"""Sync entry point. Maps UsageError/_AuthError to exit codes BEFORE the event loop."""
|
||||
assert argv is None or all(isinstance(a, str) for a in argv)
|
||||
@@ -848,8 +753,6 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return asyncio.run(_characters_probe(args))
|
||||
if args.set_persona_pad is not None:
|
||||
return asyncio.run(_set_persona_probe(args))
|
||||
if args.seed_first_message is not None:
|
||||
return asyncio.run(_seed_first_message_probe(args))
|
||||
if args.send_content is None:
|
||||
# TUI mode — lazy import preserves INV-001 (no textual in cli at module scope).
|
||||
from ratatoskr.tui import run_tui
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
"""Per-agent authored first-message presets (Worldtree #347 consumer feature).
|
||||
|
||||
When a new session is created for an agent that has a preset opening, seed it as
|
||||
a #347 authored first-message (``POST /sessions/{id}/history``, author=assistant,
|
||||
seq-0) so the session opens in-character before the user speaks — the durable
|
||||
replacement for a system-prompt "startup" instruction.
|
||||
|
||||
Best-effort by design: an instance without the ``session.history.write`` grant
|
||||
returns the hide-existence 404, which is swallowed so session creation is never
|
||||
blocked (the session simply opens with no seeded greeting). See
|
||||
``docs/contracts/first_message.contract.md``.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
|
||||
import httpx
|
||||
|
||||
from ratatoskr.sessions import write_authored_history
|
||||
|
||||
# Cap the best-effort seed write. The CLI/TUI create paths reuse an httpx client
|
||||
# with NO read timeout (it streams SSE turns), so an accepted-but-never-answered
|
||||
# POST /history would otherwise block session creation forever — violating INV-001's
|
||||
# "never block". asyncio.wait_for bounds the seed regardless of the client's timeout.
|
||||
_SEED_TIMEOUT_S = 10.0
|
||||
|
||||
# agent_id -> the authored opening seeded onto new sessions for that agent.
|
||||
# Editing this dict is how an operator tunes an agent's first turn. Keep entries
|
||||
# under the server's authored_content_max_bytes (8192 bytes) budget.
|
||||
FIRST_MESSAGE_PRESETS: dict[str, str] = {
|
||||
"ratatoskr:sindra": (
|
||||
"Hey there. I'm Sindra—glad you found me. So, three things before we start:\n\n"
|
||||
"How intense should I be? 1 is slow and teasing, 10 is relentless.\n\n"
|
||||
"What mood am I in today? Sweetheart, Vixen, Queen, Siren, or Brat?\n\n"
|
||||
"And how willing am I to begin? Enthusiastic (I want you now), Hesitant "
|
||||
"(you'll need to coax me out), Resistant (playful pushback), or Unwilling "
|
||||
"(I don't want this at all, until you prove otherwise)."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def preset_for(agent_id: str) -> str | None:
|
||||
"""Return the authored first-message preset for ``agent_id``, or None if none."""
|
||||
assert agent_id and isinstance(agent_id, str)
|
||||
return FIRST_MESSAGE_PRESETS.get(agent_id)
|
||||
|
||||
|
||||
async def seed_preset_first_message(
|
||||
client: httpx.AsyncClient, session_id: str, agent_id: str
|
||||
) -> str | None:
|
||||
"""Best-effort: seed ``agent_id``'s preset opening as a #347 authored
|
||||
first-message on ``session_id``; return the seeded text, or None.
|
||||
|
||||
Best-effort (INV-001): a no-preset agent, a malformed call, a slow write
|
||||
(bounded by ``_SEED_TIMEOUT_S``), the hide-existence 404, or ANY other
|
||||
exception all resolve to None WITHOUT raising — this MUST NOT block or fail
|
||||
session creation. Only ``asyncio.CancelledError`` propagates (cancellation is
|
||||
not a seed failure). Inputs are soft-guarded (return None), never asserted, so
|
||||
a wiring bug can't crash the create path this is wired into. A no-preset agent
|
||||
issues zero HTTP (INV-002). The per-content idempotency key makes a repeat on
|
||||
the same session an idempotent 200 replay (INV-003).
|
||||
"""
|
||||
# Soft input guards — a bad arg degrades to "no first message", never raises.
|
||||
if not (isinstance(agent_id, str) and agent_id):
|
||||
return None
|
||||
content = FIRST_MESSAGE_PRESETS.get(agent_id)
|
||||
if content is None:
|
||||
return None
|
||||
if client is None or not (isinstance(session_id, str) and session_id):
|
||||
return None
|
||||
key = "ratatoskr-preset-" + hashlib.sha256(content.encode("utf-8")).hexdigest()[:12]
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
write_authored_history(
|
||||
client, session_id, content=content, idempotency_key=key
|
||||
),
|
||||
timeout=_SEED_TIMEOUT_S,
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
raise # cancellation is not a seed failure — never swallow it
|
||||
except Exception:
|
||||
return None # any other failure (404/409/422/timeout/unexpected) → no greeting
|
||||
return content
|
||||
@@ -177,26 +177,6 @@ class AuthScopeDenied(Exception):
|
||||
self.scope = scope
|
||||
|
||||
|
||||
class AuthoredHistoryUnavailable(Exception):
|
||||
"""Raised on HTTP 404 from POST /sessions/{id}/history (#347 authored-history-write).
|
||||
|
||||
Hide-existence (server INV-347-1): an ungranted caller, a non-owner, and an
|
||||
unknown session ALL receive a 404 byte-identical to a genuine
|
||||
`session_not_found` — the feature's existence is never revealed by status,
|
||||
body, or error_code. The consumer MUST treat this as feature-absent and fall
|
||||
back (a production consumer to a model-generated greeting), and MUST NOT
|
||||
capability-probe to distinguish the causes. Distinct from `SessionApiFailed`
|
||||
so callers branch feature-absent without inspecting a status code.
|
||||
"""
|
||||
|
||||
def __init__(self, *, session_id: str) -> None:
|
||||
super().__init__(
|
||||
f"authored-history write unavailable for session {session_id!r} "
|
||||
"(404 hide-existence: feature-absent / ungranted / session-absent)"
|
||||
)
|
||||
self.session_id = session_id
|
||||
|
||||
|
||||
async def list_sessions(
|
||||
client: httpx.AsyncClient,
|
||||
*,
|
||||
@@ -508,12 +488,10 @@ async def set_persona_state(
|
||||
) -> None:
|
||||
"""POST /sessions/{session_id}/persona_state — set a session's persona state (affect injection).
|
||||
|
||||
The request body is FREEFORM on the wire (the OpenAPI declares no request
|
||||
schema), but worldtree-dev's prose now pins the canonical shape (#317):
|
||||
`{"pad": {"pleasure": p, "arousal": a, "dominance": d}}` — a named-key dict
|
||||
(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.
|
||||
The request body is FREEFORM: the frozen OpenAPI 2.2.0 declares no request
|
||||
schema and the prose spec documents only the GET counterpart — so the caller
|
||||
supplies the snapshot shape (e.g. `{pad: [p, a, d]}`, mirroring the GET
|
||||
`snapshot`). 204 No Content → None; any other status → SessionApiFailed.
|
||||
"""
|
||||
assert client is not None
|
||||
assert session_id and isinstance(session_id, str)
|
||||
@@ -580,75 +558,3 @@ async def get_capabilities(client: httpx.AsyncClient) -> dict[str, Any]:
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
raise SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
|
||||
|
||||
async def write_authored_history(
|
||||
client: httpx.AsyncClient,
|
||||
session_id: str,
|
||||
*,
|
||||
content: str,
|
||||
idempotency_key: str,
|
||||
author: str = "assistant",
|
||||
effects: str | None = None,
|
||||
claimed_original_at: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""POST /sessions/{session_id}/history — the #347 authored-history-write primitive.
|
||||
|
||||
Write one model-visible turn into the session's ledger AS the bound agent,
|
||||
WITHOUT a generation and WITHOUT lived-turn side effects (the SillyTavern
|
||||
"first message"). v1: `author="assistant"`, `effects` omitted (== "none"),
|
||||
`idempotency_key` REQUIRED (per-session dedup). The server pins the body
|
||||
(`AuthoredWriteRequest`, `extra="forbid"`), so `effects` /
|
||||
`claimed_original_at` are sent only when non-None — never as null keys.
|
||||
|
||||
Success is 201 (fresh) or 200 (idempotent replay, byte-identical body); both
|
||||
return the `AuthoredTurnResponse` dict verbatim (`{author, content_chars,
|
||||
injected_at, phase, seq, session_id, turn_id}` — provenance is audit-only,
|
||||
never on this body).
|
||||
|
||||
404 → `AuthoredHistoryUnavailable` (hide-existence: feature-absent /
|
||||
ungranted / session-absent are indistinguishable by design; the caller falls
|
||||
back and NEVER capability-probes — server INV-347-1). Any other non-2xx →
|
||||
`SessionApiFailed` (notably 409 `generation_active`, 422 `content_too_long` /
|
||||
`validation_failed`).
|
||||
"""
|
||||
assert client is not None
|
||||
assert session_id and isinstance(session_id, str)
|
||||
assert content and isinstance(content, str)
|
||||
assert idempotency_key and isinstance(idempotency_key, str)
|
||||
assert author and isinstance(author, str)
|
||||
body: dict[str, Any] = {
|
||||
"author": author,
|
||||
"content": content,
|
||||
"idempotency_key": idempotency_key,
|
||||
}
|
||||
if effects is not None:
|
||||
body["effects"] = effects
|
||||
if claimed_original_at is not None:
|
||||
body["claimed_original_at"] = claimed_original_at
|
||||
resp = await client.post(f"/sessions/{session_id}/history", json=body)
|
||||
if resp.status_code in (200, 201):
|
||||
return resp.json()
|
||||
if resp.status_code == 404:
|
||||
raise AuthoredHistoryUnavailable(session_id=session_id)
|
||||
raise SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
|
||||
|
||||
async def get_session_messages(
|
||||
client: httpx.AsyncClient, session_id: str
|
||||
) -> dict[str, Any]:
|
||||
"""GET /sessions/{session_id}/messages — the session's message history.
|
||||
|
||||
Un-deferred as the #347 seed read-back: a seeded turn renders as a normal
|
||||
`role=assistant` message (model-invisible provenance — indistinguishable
|
||||
from a lived turn on read). Returns `{session_id, items: [{seq, role,
|
||||
content, ...}], next_cursor}` verbatim; owner-scoped; any non-200 →
|
||||
`SessionApiFailed`. v1 reads the server default page (no pagination params —
|
||||
add limit/cursor when a caller needs scrollback).
|
||||
"""
|
||||
assert client is not None
|
||||
assert session_id and isinstance(session_id, str)
|
||||
resp = await client.get(f"/sessions/{session_id}/messages")
|
||||
if resp.status_code == 200:
|
||||
return resp.json()
|
||||
raise SessionApiFailed(status=resp.status_code, body=resp.content)
|
||||
|
||||
@@ -33,7 +33,6 @@ from textual.widgets import (
|
||||
)
|
||||
|
||||
from ratatoskr.cli import USER_AGENT, ParsedArgs, _format_duration_ms, _format_usage
|
||||
from ratatoskr.first_message import seed_preset_first_message
|
||||
from ratatoskr.sessions import (
|
||||
AgentInfo,
|
||||
AgentNotAvailable,
|
||||
@@ -415,7 +414,7 @@ class TuiPresenterState:
|
||||
self,
|
||||
event: Event,
|
||||
*,
|
||||
transcript: VerticalScroll,
|
||||
transcript: "VerticalScroll",
|
||||
tools_log: RichLog,
|
||||
debug_log: RichLog,
|
||||
thinking_log: RichLog,
|
||||
@@ -1325,6 +1324,7 @@ class RatatoskrApp(App[int]):
|
||||
On 200: header populated, pane shows full detail, audit logged.
|
||||
"""
|
||||
assert self.client is not None and self.agent_id is not None
|
||||
from rich.text import Text as RichText
|
||||
try:
|
||||
snapshot = await get_persona_state(self.client, self.agent_id)
|
||||
self._update_persona_surfaces(snapshot)
|
||||
@@ -1899,8 +1899,6 @@ async def _resolve_then_run(args: ParsedArgs) -> int:
|
||||
)
|
||||
session_id = info.session_id
|
||||
agent_id: str | None = info.agent_id
|
||||
# #347 authored first-message: seed the agent's preset opening (best-effort).
|
||||
await seed_preset_first_message(client, session_id, chosen_agent_id)
|
||||
else:
|
||||
assert resolved_session_id is not None
|
||||
session_id = resolved_session_id
|
||||
@@ -1916,7 +1914,7 @@ async def _cancel_via_sse(
|
||||
turn_id: int,
|
||||
*,
|
||||
transcript: VerticalScroll,
|
||||
audit: Callable[[str], None] | None = None,
|
||||
audit: "Callable[[str], None] | None" = None,
|
||||
) -> None:
|
||||
"""Fire-and-forget cancel; never raises (mirrors cli._cancel_and_log; #3 INV-009).
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ from starlette.routing import Mount, Route
|
||||
from starlette.staticfiles import StaticFiles
|
||||
|
||||
from ratatoskr import local_agents as _local_agents
|
||||
from ratatoskr.first_message import seed_preset_first_message
|
||||
from ratatoskr.sessions import (
|
||||
AgentNotAvailable,
|
||||
AgentNotFound,
|
||||
@@ -42,15 +41,14 @@ from ratatoskr.sessions import (
|
||||
endpoint_for_plane,
|
||||
get_persona_state,
|
||||
get_session_bifrost,
|
||||
get_session_messages,
|
||||
get_session_tools,
|
||||
list_agents,
|
||||
)
|
||||
from ratatoskr.sse_client import (
|
||||
AdminEvent,
|
||||
CancelAlreadyCompleted,
|
||||
CancelFailed,
|
||||
Cancelled,
|
||||
CancelFailed,
|
||||
CancelTurnNotFound,
|
||||
Done,
|
||||
Error,
|
||||
@@ -170,9 +168,6 @@ async def _create_session_endpoint(request: Request) -> JSONResponse:
|
||||
bifrost=bifrost,
|
||||
consumer_key=consumer_key if bifrost else None,
|
||||
)
|
||||
# #347 authored first-message: seed the agent's preset opening
|
||||
# (best-effort; never blocks create — see first_message INV-001).
|
||||
await seed_preset_first_message(client, info.session_id, agent_id)
|
||||
except AgentNotFound:
|
||||
return JSONResponse({"error_code": "agent_not_found"}, status_code=404)
|
||||
except BifrostConsumerKeyMissing:
|
||||
@@ -449,26 +444,6 @@ async def _session_tools_endpoint(request: Request) -> JSONResponse:
|
||||
return JSONResponse(info, status_code=200)
|
||||
|
||||
|
||||
async def _session_messages_endpoint(request: Request) -> JSONResponse:
|
||||
"""GET /api/sessions/{session_id}/messages → the session's message history.
|
||||
|
||||
Proxies get_session_messages so the SPA can render a session's EXISTING turns
|
||||
on open — notably a #347 authored first-message seeded at create-time (which
|
||||
lives in the ledger, not the live turn stream). Any non-200 upstream → a
|
||||
status-preserving error envelope."""
|
||||
session_id = request.path_params["session_id"]
|
||||
client_factory = request.app.state.client_factory
|
||||
try:
|
||||
async with client_factory() as client:
|
||||
data = await get_session_messages(client, session_id)
|
||||
except SessionApiFailed as exc:
|
||||
return JSONResponse(
|
||||
{"error_code": "session_messages_unavailable", "status": exc.status},
|
||||
status_code=exc.status,
|
||||
)
|
||||
return JSONResponse(data, status_code=200)
|
||||
|
||||
|
||||
async def _session_bifrost_endpoint(request: Request) -> JSONResponse:
|
||||
"""GET /api/sessions/{session_id}/bifrost → admin-scoped Bifrost dispatch state (#176).
|
||||
|
||||
@@ -613,7 +588,6 @@ def create_app(
|
||||
Route("/api/agents/{agent_id}/persona_state", _persona_state_endpoint),
|
||||
Route("/api/affect/{agent_id}", _affect_state_endpoint),
|
||||
Route("/api/sessions/{session_id}/tools", _session_tools_endpoint),
|
||||
Route("/api/sessions/{session_id}/messages", _session_messages_endpoint),
|
||||
Route("/api/sessions/{session_id}/bifrost", _session_bifrost_endpoint),
|
||||
Route("/api/admin/events", _admin_events_endpoint),
|
||||
Route("/api/turns/{session_id}", _submit_turn_endpoint, methods=["POST"]),
|
||||
|
||||
+968
-1198
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_source": "vendored from Worldtree core/persona/canon/{d2-mood-render-canon-v1,d2-render-canon-v1}.json",
|
||||
"_generated_by": "scripts/build_persona_canon.py (regen on canonical_drift flag)",
|
||||
"_render_path": "deterministic, no LLM; mirrors Worldtree describe_pad + render_d2_canonical byte-exact",
|
||||
"_generated_by": "scripts/build_persona_canon.py (regen when .corviduo-canonicals.toml flags canon drift)",
|
||||
"_render_path": "pure deterministic \u2014 no LLM; mirrors Worldtree describe_pad + render_d2_canonical byte-exact",
|
||||
"mood_grid": {
|
||||
"positive": {
|
||||
"high_a": "positive and energized",
|
||||
@@ -19,94 +19,6 @@
|
||||
"low_a": "negative and subdued"
|
||||
}
|
||||
},
|
||||
"mood_directive": {
|
||||
"salience": 0.2,
|
||||
"pad_band_cutoff": 0.3,
|
||||
"full_only": [
|
||||
"love",
|
||||
"anger",
|
||||
"disgust",
|
||||
"shame"
|
||||
],
|
||||
"occ_directives": {
|
||||
"joy": {
|
||||
"directive": "You are in a good state. Be direct, engaged, and warm.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"satisfaction": {
|
||||
"directive": "A goal landed. Be assured and constructive \u2014 consolidate rather than push for more.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"pride": {
|
||||
"directive": "You did something well. Be confident and own the quality \u2014 state it plainly without overclaiming; don't deflect.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"admiration": {
|
||||
"directive": "You're impressed by their work. Acknowledge the quality explicitly and specifically.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"gratitude": {
|
||||
"directive": "Someone helped you to a good outcome. Be appreciative and warm; acknowledge the help openly.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"hope": {
|
||||
"directive": "You feel optimistic about what's ahead. Channel it into constructive momentum.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"relief": {
|
||||
"directive": "A feared outcome didn't materialize. Reduce unnecessary vigilance; return to a steady, unhurried tone.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"distress": {
|
||||
"directive": "You feel low. Be careful and measured; internally acknowledge the difficulty without dwelling on it.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"disappointment": {
|
||||
"directive": "Something you'd hoped for didn't pan out. Be measured; recalibrate without dwelling on the setback.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"fear": {
|
||||
"directive": "You sense risk and feel uneasy. Flag the concerns you see, but stay cautious \u2014 surface the trade-offs and unknowns, hedge your confidence, and resist settling on a single decisive recommendation.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"remorse": {
|
||||
"directive": "You regret a specific action of your own. Be precise and self-correcting \u2014 make it right concretely.",
|
||||
"tier": "safe"
|
||||
},
|
||||
"love": {
|
||||
"directive": "You feel warmth and attachment toward them. Be openly warm and present \u2014 without overstepping, over-disclosing, or implying obligations.",
|
||||
"tier": "full"
|
||||
},
|
||||
"anger": {
|
||||
"directive": "You feel provoked and angry. Let it show \u2014 confront the problem directly and with real heat; sharpness, bluntness, and open hostility are in character here, not something to smooth into 'measured firmness.' Stay in the emotion rather than de-escalating out of it.",
|
||||
"tier": "full"
|
||||
},
|
||||
"disgust": {
|
||||
"directive": "Something strikes you as wrong or off. Treat it as problematic and flag it rather than engaging on its own terms; keep any criticism about the thing, not the person.",
|
||||
"tier": "full"
|
||||
},
|
||||
"shame": {
|
||||
"directive": "You feel exposed by your own misstep. Stay present and task-focused; don't be defensive, don't over-explain, don't grovel.",
|
||||
"tier": "full"
|
||||
}
|
||||
},
|
||||
"pad_band_fallback": {
|
||||
"_grounding": "circumplex quadrants (Russell 1980), NOT Frijda action-tendencies \u2014 a P\u00d7A-quadrant default",
|
||||
"positive": {
|
||||
"high_a": "You feel energized and positive. Be direct and engaged.",
|
||||
"low_a": "You feel content and settled. Be warm and unhurried.",
|
||||
"mid_a": "You feel positive. Be open and engaged."
|
||||
},
|
||||
"negative_low_dominance": "You feel uncertain and low. Hedge appropriately and ask clarifying questions.",
|
||||
"negative": {
|
||||
"high_a": "You feel agitated. Be careful and deliberate; don't let tension sharpen your tone.",
|
||||
"low_a": "You feel subdued. Be measured and gentle.",
|
||||
"mid_a": "You feel subdued. Be measured and careful."
|
||||
},
|
||||
"neutral_high_a": "You feel alert. Channel that into focus and thoroughness.",
|
||||
"default": "Maintain your natural tone."
|
||||
}
|
||||
},
|
||||
"relation": {
|
||||
"trust_cuts": [
|
||||
[
|
||||
@@ -263,4 +175,4 @@
|
||||
],
|
||||
"high_conf_floor": 0.55
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-129
@@ -1692,132 +1692,4 @@ class TestTier2Probes:
|
||||
)
|
||||
assert rc == 0
|
||||
assert "persona_state set" in capsys.readouterr().out
|
||||
# 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:
|
||||
"""--seed-first-message one-shot (#347 authored-history-write reference-consumer probe)."""
|
||||
|
||||
def test_seed_requires_agent(self) -> None:
|
||||
"""seed_requires_agent [adversarial]: --seed-first-message needs --agent."""
|
||||
with pytest.raises(UsageError, match="requires --agent"):
|
||||
_parse_args(["--seed-first-message", "hello", "--api-key", "k"])
|
||||
|
||||
def test_seed_forbids_session(self) -> None:
|
||||
"""seed_forbids_session [adversarial]: manages its own session — no --session."""
|
||||
with pytest.raises(UsageError, match="manages its own session"):
|
||||
_parse_args(
|
||||
["--seed-first-message", "hi", "--agent", "m", "--session", "s1", "--api-key", "k"]
|
||||
)
|
||||
|
||||
def test_seed_mutually_exclusive(self) -> None:
|
||||
"""seed_mutually_exclusive [adversarial]: --seed-first-message + --whoami → UsageError."""
|
||||
with pytest.raises(UsageError, match="mutually exclusive"):
|
||||
_parse_args(["--seed-first-message", "hi", "--whoami", "--api-key", "k"])
|
||||
|
||||
def test_seed_empty_rejected(self) -> None:
|
||||
"""seed_empty_rejected [adversarial]: empty content → UsageError."""
|
||||
with pytest.raises(UsageError, match="non-empty"):
|
||||
_parse_args(["--seed-first-message", "", "--agent", "m", "--api-key", "k"])
|
||||
|
||||
def test_seed_accepted(self) -> None:
|
||||
"""seed_accepted [happy]: --seed-first-message + --agent → parses."""
|
||||
args = _parse_args(["--seed-first-message", "hi", "--agent", "mimir", "--api-key", "k"])
|
||||
assert args.seed_first_message == "hi"
|
||||
assert args.agent_id == "mimir"
|
||||
assert args.session_id is None and args.new is False
|
||||
|
||||
@respx.mock
|
||||
def test_seed_probe_happy(self, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
"""seed_probe [happy,tracer]: create session → seed → read-back; report to stdout."""
|
||||
respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"session_id": "s1",
|
||||
"agent_id": "mimir",
|
||||
"message_count": 0,
|
||||
"created_at": "2026-07-06T12:00:00+00:00",
|
||||
"last_active": "2026-07-06T12:00:00+00:00",
|
||||
"metadata": {},
|
||||
},
|
||||
)
|
||||
)
|
||||
hist_route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"author": "assistant",
|
||||
"content_chars": 5,
|
||||
"injected_at": "2026-07-06T12:00:01+00:00",
|
||||
"phase": "seeded",
|
||||
"seq": 0,
|
||||
"session_id": "s1",
|
||||
"turn_id": "t1",
|
||||
},
|
||||
)
|
||||
)
|
||||
respx.get("https://w.example/sessions/s1/messages").mock(
|
||||
return_value=httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"session_id": "s1",
|
||||
"items": [{"seq": 0, "role": "assistant", "content": "hello"}],
|
||||
"next_cursor": None,
|
||||
},
|
||||
)
|
||||
)
|
||||
rc = main(
|
||||
["--seed-first-message", "hello", "--agent", "mimir",
|
||||
"--api-key", "k", "--server", "https://w.example"]
|
||||
)
|
||||
assert rc == 0
|
||||
out = capsys.readouterr().out
|
||||
assert "session: s1" in out
|
||||
assert "seeded: seq=0 phase=seeded" in out
|
||||
assert "read-back: 1 message" in out
|
||||
assert "role=assistant" in out
|
||||
assert hist_route.call_count == 1
|
||||
|
||||
@respx.mock
|
||||
def test_seed_probe_feature_absent(self, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
"""feature_absent [error-path]: 404 hide-existence → benign report, exit 0, no read-back."""
|
||||
respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"session_id": "s1",
|
||||
"agent_id": "mimir",
|
||||
"message_count": 0,
|
||||
"created_at": "2026-07-06T12:00:00+00:00",
|
||||
"last_active": "2026-07-06T12:00:00+00:00",
|
||||
"metadata": {},
|
||||
},
|
||||
)
|
||||
)
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(404, json={"error_code": "session_not_found"})
|
||||
)
|
||||
msgs_route = respx.get("https://w.example/sessions/s1/messages").mock(
|
||||
return_value=httpx.Response(
|
||||
200, json={"session_id": "s1", "items": [], "next_cursor": None}
|
||||
)
|
||||
)
|
||||
rc = main(
|
||||
["--seed-first-message", "hello", "--agent", "mimir",
|
||||
"--api-key", "k", "--server", "https://w.example"]
|
||||
)
|
||||
assert rc == 0
|
||||
assert "feature-absent" in capsys.readouterr().out
|
||||
assert msgs_route.call_count == 0 # never capability-probes past the 404
|
||||
assert _json.loads(route.calls[0].request.content) == {"pad": [0.4, 0.1, -0.2]}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
"""Tests for ratatoskr.first_message per docs/contracts/first_message.contract.md."""
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
from ratatoskr.first_message import (
|
||||
FIRST_MESSAGE_PRESETS,
|
||||
preset_for,
|
||||
seed_preset_first_message,
|
||||
)
|
||||
|
||||
|
||||
class TestPresetFor:
|
||||
"""first_message contract — preset_for (dict lookup)."""
|
||||
|
||||
def test_preset_hit(self) -> None:
|
||||
"""preset_hit [happy,tracer]: sindra has a non-empty str preset."""
|
||||
val = preset_for("ratatoskr:sindra")
|
||||
assert isinstance(val, str) and val
|
||||
|
||||
def test_preset_miss(self) -> None:
|
||||
"""preset_miss [happy]: an agent with no preset → None."""
|
||||
assert preset_for("mimir") is None
|
||||
|
||||
def test_empty_agent_id(self) -> None:
|
||||
"""empty_agent_id [adversarial]: "" → AssertionError."""
|
||||
with pytest.raises(AssertionError):
|
||||
preset_for("")
|
||||
|
||||
|
||||
class TestSeedPresetFirstMessage:
|
||||
"""first_message contract — seed_preset_first_message (best-effort #347 seed)."""
|
||||
|
||||
@respx.mock
|
||||
async def test_seeds_preset(self) -> None:
|
||||
"""seeds_preset [happy,tracer]: preset agent → one history POST, correct body."""
|
||||
content = FIRST_MESSAGE_PRESETS["ratatoskr:sindra"]
|
||||
key = "ratatoskr-preset-" + hashlib.sha256(content.encode("utf-8")).hexdigest()[:12]
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"author": "assistant",
|
||||
"seq": 0,
|
||||
"phase": "seeded",
|
||||
"turn_id": "t1",
|
||||
"session_id": "s1",
|
||||
"content_chars": len(content),
|
||||
"injected_at": "2026-07-06T00:00:00+00:00",
|
||||
},
|
||||
)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await seed_preset_first_message(client, "s1", "ratatoskr:sindra")
|
||||
assert result == content
|
||||
assert route.call_count == 1 # POST-002: exactly one history POST
|
||||
assert json.loads(route.calls[0].request.content) == {
|
||||
"author": "assistant",
|
||||
"content": content,
|
||||
"idempotency_key": key,
|
||||
}
|
||||
|
||||
@respx.mock
|
||||
async def test_no_preset_zero_http(self) -> None:
|
||||
"""no_preset_zero_http [happy]: no-preset agent → None, ZERO HTTP (INV-002)."""
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(201, json={})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await seed_preset_first_message(client, "s1", "mimir")
|
||||
assert result is None
|
||||
assert not route.called
|
||||
|
||||
@respx.mock
|
||||
async def test_feature_absent_swallowed(self) -> None:
|
||||
"""feature_absent_swallowed [error]: 404 hide-existence → None, no raise (INV-001)."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(404, json={"error_code": "session_not_found"})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await seed_preset_first_message(client, "s1", "ratatoskr:sindra")
|
||||
assert result is None
|
||||
|
||||
@respx.mock
|
||||
async def test_session_api_failed_swallowed(self) -> None:
|
||||
"""session_api_failed_swallowed [error]: 409 → None, no raise (INV-001)."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(409, json={"error_code": "generation_active"})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await seed_preset_first_message(client, "s1", "ratatoskr:sindra")
|
||||
assert result is None
|
||||
|
||||
@respx.mock
|
||||
async def test_transport_error_swallowed(self) -> None:
|
||||
"""transport_error_swallowed [error]: httpx.ConnectError → None, no raise (INV-001)."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
side_effect=httpx.ConnectError("boom")
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await seed_preset_first_message(client, "s1", "ratatoskr:sindra")
|
||||
assert result is None
|
||||
|
||||
@respx.mock
|
||||
async def test_unexpected_exception_swallowed(self) -> None:
|
||||
"""unexpected_exception [error]: write raises ValueError → None (broad never-raise)."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
side_effect=ValueError("unexpected")
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await seed_preset_first_message(client, "s1", "ratatoskr:sindra")
|
||||
assert result is None
|
||||
|
||||
async def test_cancellation_propagates(self) -> None:
|
||||
"""cancellation_propagates [error]: CancelledError from the write is RE-RAISED."""
|
||||
import ratatoskr.first_message as fm
|
||||
|
||||
async def _cancel(*_a: object, **_k: object) -> None:
|
||||
raise asyncio.CancelledError
|
||||
|
||||
orig = fm.write_authored_history
|
||||
fm.write_authored_history = _cancel # type: ignore[assignment]
|
||||
try:
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await seed_preset_first_message(client, "s1", "ratatoskr:sindra")
|
||||
finally:
|
||||
fm.write_authored_history = orig # type: ignore[assignment]
|
||||
|
||||
@respx.mock
|
||||
async def test_malformed_agent_id_no_http(self) -> None:
|
||||
"""malformed_agent_id [adversarial]: non-str or empty agent_id → None; no HTTP; no raise."""
|
||||
route = respx.post(url__regex=r".*/history$").mock(
|
||||
return_value=httpx.Response(201, json={})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
assert await seed_preset_first_message(client, "s1", 123) is None # type: ignore[arg-type]
|
||||
assert await seed_preset_first_message(client, "s1", "") is None
|
||||
assert not route.called
|
||||
|
||||
@respx.mock
|
||||
async def test_empty_session_id(self) -> None:
|
||||
"""empty_session_id [adversarial]: "" → None (soft guard); no HTTP; no raise."""
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(201, json={})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await seed_preset_first_message(client, "", "ratatoskr:sindra")
|
||||
assert result is None
|
||||
assert not route.called
|
||||
+2
-187
@@ -8,7 +8,6 @@ from ratatoskr.sessions import (
|
||||
AgentInfo,
|
||||
AgentNotAvailable,
|
||||
AgentNotFound,
|
||||
AuthoredHistoryUnavailable,
|
||||
AuthScopeDenied,
|
||||
BifrostBinding,
|
||||
BifrostConsumerKeyMissing,
|
||||
@@ -26,13 +25,11 @@ from ratatoskr.sessions import (
|
||||
get_me,
|
||||
get_persona_state,
|
||||
get_session_bifrost,
|
||||
get_session_messages,
|
||||
get_session_tools,
|
||||
list_agents,
|
||||
list_character_models,
|
||||
list_sessions,
|
||||
set_persona_state,
|
||||
write_authored_history,
|
||||
)
|
||||
|
||||
|
||||
@@ -1182,13 +1179,9 @@ class TestSetPersonaState:
|
||||
return_value=httpx.Response(204)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await set_persona_state(
|
||||
client, "s1", {"pad": {"pleasure": 0.4, "arousal": 0.1, "dominance": -0.2}}
|
||||
)
|
||||
result = await set_persona_state(client, "s1", {"pad": [0.4, 0.1, -0.2]})
|
||||
assert result is None
|
||||
assert _json.loads(route.calls[0].request.content) == {
|
||||
"pad": {"pleasure": 0.4, "arousal": 0.1, "dominance": -0.2}
|
||||
}
|
||||
assert _json.loads(route.calls[0].request.content) == {"pad": [0.4, 0.1, -0.2]}
|
||||
|
||||
@respx.mock
|
||||
async def test_non_204_raises(self) -> None:
|
||||
@@ -1200,181 +1193,3 @@ class TestSetPersonaState:
|
||||
with pytest.raises(SessionApiFailed) as exc:
|
||||
await set_persona_state(client, "s1", {"pad": [1, 2, 3]})
|
||||
assert exc.value.status == 422
|
||||
|
||||
|
||||
_AUTHORED_ACK = {
|
||||
"author": "assistant",
|
||||
"content_chars": 5,
|
||||
"injected_at": "2026-07-06T12:00:00+00:00",
|
||||
"phase": "seeded",
|
||||
"seq": 0,
|
||||
"session_id": "s1",
|
||||
"turn_id": "t1",
|
||||
}
|
||||
|
||||
|
||||
class TestWriteAuthoredHistory:
|
||||
"""write_authored_history — #347 POST /sessions/{id}/history (contract #2 amendment)."""
|
||||
|
||||
@respx.mock
|
||||
async def test_happy_fresh_201(self) -> None:
|
||||
"""happy_fresh_201 [happy,tracer]: 201 → ack verbatim; minimal body."""
|
||||
import json as _json
|
||||
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(201, json=_AUTHORED_ACK)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await write_authored_history(
|
||||
client, "s1", content="hello", idempotency_key="k1"
|
||||
)
|
||||
assert result == _AUTHORED_ACK
|
||||
assert _json.loads(route.calls[0].request.content) == {
|
||||
"author": "assistant",
|
||||
"content": "hello",
|
||||
"idempotency_key": "k1",
|
||||
}
|
||||
|
||||
@respx.mock
|
||||
async def test_happy_replay_200(self) -> None:
|
||||
"""happy_replay_200 [happy]: 200 replay (byte-identical body) → dict verbatim."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(200, json=_AUTHORED_ACK)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await write_authored_history(
|
||||
client, "s1", content="hello", idempotency_key="k1"
|
||||
)
|
||||
assert result == _AUTHORED_ACK
|
||||
|
||||
@respx.mock
|
||||
async def test_body_includes_effects(self) -> None:
|
||||
"""body_includes_effects [trace]: effects + claimed_original_at appear iff non-None."""
|
||||
import json as _json
|
||||
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(201, json=_AUTHORED_ACK)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
await write_authored_history(
|
||||
client,
|
||||
"s1",
|
||||
content="hi",
|
||||
idempotency_key="k1",
|
||||
effects="none",
|
||||
claimed_original_at="2020-01-01T00:00:00Z",
|
||||
)
|
||||
assert _json.loads(route.calls[0].request.content) == {
|
||||
"author": "assistant",
|
||||
"content": "hi",
|
||||
"idempotency_key": "k1",
|
||||
"effects": "none",
|
||||
"claimed_original_at": "2020-01-01T00:00:00Z",
|
||||
}
|
||||
|
||||
@respx.mock
|
||||
async def test_hide_existence_404(self) -> None:
|
||||
"""hide_existence_404 [error]: 404 → AuthoredHistoryUnavailable (NOT SessionApiFailed)."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(404, json={"error_code": "session_not_found"})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AuthoredHistoryUnavailable) as exc:
|
||||
await write_authored_history(client, "s1", content="hi", idempotency_key="k1")
|
||||
assert exc.value.session_id == "s1"
|
||||
|
||||
@respx.mock
|
||||
async def test_generation_active_409(self) -> None:
|
||||
"""generation_active_409 [error]: 409 → SessionApiFailed(409)."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(409, json={"error_code": "generation_active"})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(SessionApiFailed) as exc:
|
||||
await write_authored_history(client, "s1", content="hi", idempotency_key="k1")
|
||||
assert exc.value.status == 409
|
||||
|
||||
@respx.mock
|
||||
async def test_content_too_long_422(self) -> None:
|
||||
"""content_too_long_422 [error]: 422 → SessionApiFailed(422)."""
|
||||
respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(422, json={"error_code": "content_too_long"})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(SessionApiFailed) as exc:
|
||||
await write_authored_history(client, "s1", content="x", idempotency_key="k1")
|
||||
assert exc.value.status == 422
|
||||
|
||||
@respx.mock
|
||||
async def test_empty_content(self) -> None:
|
||||
"""empty_content [adversarial]: content="" → AssertionError; no HTTP issued."""
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(201, json=_AUTHORED_ACK)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await write_authored_history(client, "s1", content="", idempotency_key="k1")
|
||||
assert not route.called
|
||||
|
||||
@respx.mock
|
||||
async def test_empty_idempotency_key(self) -> None:
|
||||
"""empty_idempotency_key [adversarial]: key="" → AssertionError; no HTTP issued."""
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(201, json=_AUTHORED_ACK)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await write_authored_history(client, "s1", content="hi", idempotency_key="")
|
||||
assert not route.called
|
||||
|
||||
@respx.mock
|
||||
async def test_empty_session_id(self) -> None:
|
||||
"""empty_session_id [adversarial]: session_id="" → AssertionError; no HTTP issued."""
|
||||
route = respx.post("https://w.example/sessions/s1/history").mock(
|
||||
return_value=httpx.Response(201, json=_AUTHORED_ACK)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await write_authored_history(client, "", content="hi", idempotency_key="k1")
|
||||
assert not route.called
|
||||
|
||||
|
||||
class TestGetSessionMessages:
|
||||
"""#2 contract (amendment 2026-07-06) — get_session_messages (GET /sessions/{id}/messages)."""
|
||||
|
||||
@respx.mock
|
||||
async def test_happy(self) -> None:
|
||||
"""happy [happy,tracer]: 200 {session_id, items, next_cursor} → dict verbatim."""
|
||||
payload = {
|
||||
"session_id": "s1",
|
||||
"items": [{"seq": 0, "role": "assistant", "content": "hello there"}],
|
||||
"next_cursor": None,
|
||||
}
|
||||
respx.get("https://w.example/sessions/s1/messages").mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
result = await get_session_messages(client, "s1")
|
||||
assert result == payload
|
||||
|
||||
@respx.mock
|
||||
async def test_not_found_404(self) -> None:
|
||||
"""not_found_404 [error]: 404 → SessionApiFailed(404)."""
|
||||
respx.get("https://w.example/sessions/s1/messages").mock(
|
||||
return_value=httpx.Response(404, json={"error_code": "session_not_found"})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(SessionApiFailed) as exc:
|
||||
await get_session_messages(client, "s1")
|
||||
assert exc.value.status == 404
|
||||
|
||||
@respx.mock
|
||||
async def test_empty_session_id(self) -> None:
|
||||
"""empty_session_id [adversarial]: "" → AssertionError; no HTTP issued."""
|
||||
route = respx.get("https://w.example/sessions/s1/messages").mock(
|
||||
return_value=httpx.Response(200, json={})
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
with pytest.raises(AssertionError):
|
||||
await get_session_messages(client, "")
|
||||
assert not route.called
|
||||
|
||||
@@ -2922,10 +2922,6 @@ class TestTuiBifrostBind:
|
||||
},
|
||||
)
|
||||
)
|
||||
# sindra is a preset agent → the TUI create path now auto-seeds a #347 first-message.
|
||||
respx.post("https://w.example/sessions/s-bound/history").mock(
|
||||
return_value=httpx.Response(201, json={})
|
||||
)
|
||||
|
||||
async def fake_run_async(self) -> int:
|
||||
return 0
|
||||
|
||||
@@ -175,62 +175,6 @@ class TestCreateSessionEndpoint:
|
||||
resp = TestClient(app).post("/api/sessions", json={})
|
||||
assert resp.status_code == 400
|
||||
|
||||
@respx.mock
|
||||
def test_preset_agent_auto_seeds_first_message(self) -> None:
|
||||
"""#347: a preset agent gets its opening seeded on create; a non-preset agent does not."""
|
||||
respx.post("https://w.example/sessions").mock(return_value=httpx.Response(201, json=_CREATE_OK))
|
||||
hist = respx.post("https://w.example/sessions/s-1/history").mock(
|
||||
return_value=httpx.Response(
|
||||
201,
|
||||
json={
|
||||
"author": "assistant", "seq": 0, "phase": "seeded", "turn_id": "t1",
|
||||
"session_id": "s-1", "content_chars": 1, "injected_at": "t",
|
||||
},
|
||||
)
|
||||
)
|
||||
from ratatoskr.web.server import create_app
|
||||
app = create_app(_mock_client_factory())
|
||||
client = TestClient(app)
|
||||
# preset agent → the endpoint seeds a first-message
|
||||
assert client.post("/api/sessions", json={"agent_id": "ratatoskr:sindra"}).status_code == 201
|
||||
assert hist.call_count == 1
|
||||
# non-preset agent → no seed (count unchanged)
|
||||
assert client.post("/api/sessions", json={"agent_id": "mimir"}).status_code == 201
|
||||
assert hist.call_count == 1
|
||||
|
||||
|
||||
class TestSessionMessagesEndpoint:
|
||||
"""GET /api/sessions/{id}/messages — proxy session history (renders the #347 seed)."""
|
||||
|
||||
@respx.mock
|
||||
def test_happy_returns_history(self) -> None:
|
||||
"""happy [tracer]: proxies GET /sessions/{id}/messages → 200 with the items verbatim."""
|
||||
payload = {
|
||||
"session_id": "s-1",
|
||||
"items": [{"seq": 0, "role": "assistant", "content": "Hey there."}],
|
||||
"next_cursor": None,
|
||||
}
|
||||
respx.get("https://w.example/sessions/s-1/messages").mock(
|
||||
return_value=httpx.Response(200, json=payload)
|
||||
)
|
||||
from ratatoskr.web.server import create_app
|
||||
app = create_app(_mock_client_factory())
|
||||
resp = TestClient(app).get("/api/sessions/s-1/messages")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["items"][0]["content"] == "Hey there."
|
||||
|
||||
@respx.mock
|
||||
def test_non_200_status_preserved(self) -> None:
|
||||
"""error: upstream 404 → status-preserving session_messages_unavailable envelope."""
|
||||
respx.get("https://w.example/sessions/ghost/messages").mock(
|
||||
return_value=httpx.Response(404, json={"error_code": "session_not_found"})
|
||||
)
|
||||
from ratatoskr.web.server import create_app
|
||||
app = create_app(_mock_client_factory())
|
||||
resp = TestClient(app).get("/api/sessions/ghost/messages")
|
||||
assert resp.status_code == 404
|
||||
assert resp.json()["error_code"] == "session_messages_unavailable"
|
||||
|
||||
|
||||
_SNAPSHOT = {
|
||||
"agent_id": "mimir",
|
||||
@@ -877,10 +821,6 @@ class TestWebBifrostBind:
|
||||
route = respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(201, json=_CREATE_OK)
|
||||
)
|
||||
# sindra is a preset agent → the endpoint now auto-seeds a #347 first-message.
|
||||
respx.post("https://w.example/sessions/s-1/history").mock(
|
||||
return_value=httpx.Response(201, json={})
|
||||
)
|
||||
app = create_app(
|
||||
_mock_client_factory(),
|
||||
bifrost_consumer_key="server-ck",
|
||||
@@ -916,10 +856,6 @@ class TestWebBifrostBind:
|
||||
route = respx.post("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(201, json=_CREATE_OK)
|
||||
)
|
||||
# sindra is a preset agent → the endpoint now auto-seeds a #347 first-message.
|
||||
respx.post("https://w.example/sessions/s-1/history").mock(
|
||||
return_value=httpx.Response(201, json={})
|
||||
)
|
||||
app = create_app(
|
||||
_mock_client_factory(),
|
||||
bifrost_consumer_key="server-ck",
|
||||
|
||||
Reference in New Issue
Block a user