Composite Bifrost endpoint + PAD read-endpoint — memory + live PAD in one session (#17 follow-on) #18

Closed
opened 2026-06-18 01:16:01 -07:00 by vh · 0 comments
Owner

Context

Follow-on to #17 (Bifrost-binding the chat client — shipped this session, v0.17.8–v0.17.13, live-smoke proven). #17 delivered single-plane bind + the dispatch-layer op-feed, with two limits deliberately parked at v1:

  1. One plane per session (INV-003). Worldtree's POST /sessions binds exactly one bifrost.endpoint_url. Our memory (:8391) and affect (:8390) providers are separate processes, so a session binds memory or affect — not both. A Sindra session that should both remember and show live PAD can't do both at once.
  2. The persona pane can't show Tier-3 PAD. Binding the affect plane works — Worldtree dispatches affect.emit to our :8390 store and it persists (live-proven this session: op-feed op:"emit", session_id:"7f65a975…", status:"ok", stored:true). But the web persona pane still reads Worldtree's persona_state (loadPersona, index.html:707), which 404s for every Tier-3 colon-id agent (ADR-0009 — persona_state is Tier-1-only). So the pane prints "persona telemetry isn't exposed for Tier-3 … yet" even though our store HAS the PAD. The pane was never wired to read from our own store — the "persona-pane payoff" the #17 affect-binding was meant to unlock.

Goal

Make a single bound Sindra session both remember (memory plane) and show live PAD/valence in the persona pane (affect plane), in one seat. Two deliverables:

1. Composite Bifrost endpoint (un-parks #17 open-question)

A single ASGI facade (new port, e.g. :8392) fronting BOTH our memory + affect stores, advertising BOTH memory+affect caps at the handshake. One session binds it → Worldtree dispatches both memory-calls AND affect-calls to it. Bifrost's own reference server already mounts both planes in one app (reference_server/server.py: handshake + memory-call + affect-call routes taking both stores), so this is a thin combined builder mirroring the two existing ones (build_memory_provider_app / build_affect_provider_app).

Per the #17 contract's parking note, this is NOT just "handshake lists both caps":

  • per-plane failure status (a facade routing bug is a new failure class);
  • per-underlying-store parity checks;
  • the op-feed's instrument_provider_app currently takes a FIXED plane param — for the combined app it must derive plane per-request from the path (/bifrost/memory-call vs /bifrost/affect-call), which it already half-does in _op_from.

2. PAD read-endpoint → persona pane renders our store

A small non-bifrost read route on the affect provider (recommended over the web server opening affect.db directly — keeps the provider the owner of its store, no web↔SQLite coupling). The store already has get(agent_id, end_user_id) → the PAD/valence snapshot. Then:

  • a web endpoint (GET /api/affect/{agent_id}) proxies it server-side;
  • loadPersona (or a sibling) renders PAD/valence from OUR store instead of (or as fallback to) the Tier-3-gated persona_state.

affect.fetch over bifrost is reserved/blocked (Worldtree can't fetch) — irrelevant here: we own the store and read it directly, no Worldtree dependency. (WT #289 affect.fetch/affect:read is an OPTIONAL mediated-read — their intelligence over our raw store — NOT a prerequisite.)

Operator decisions (this session)

  • Composite endpoint: approved (operator "A is correct").
  • Read path: provider read-endpoint (recommended; deliverable 2 above) — confirm in the contract.

Acceptance

  1. One session binds the composite endpoint → handshake grants both memory+affect.
  2. A turn into that session → op-feed shows BOTH a memory search AND an affect emit, each correlated to the bound session_id.
  3. The web persona pane renders live PAD/valence for a Tier-3 agent (e.g. ratatoskr:sindra) from OUR :8390 store — no "telemetry isn't exposed" for affect-bound sessions.
  4. Per-plane failure isolation: a down/erroring affect store doesn't break memory dispatch on the same session (and vice-versa), surfaced distinctly.

Dependencies / scope

  • Builds on #17 (shipped). No Worldtree-side change required to bind (composite is still one endpoint_url).
  • Contract-first (new surface): scope a .contract.md covering both pieces before code.
  • Out of scope: WT #289 mediated affect-read; production hardening (TLS/RS256).
## Context Follow-on to #17 (Bifrost-binding the chat client — shipped this session, v0.17.8–v0.17.13, live-smoke proven). #17 delivered single-plane bind + the dispatch-layer op-feed, with two limits deliberately parked at v1: 1. **One plane per session (INV-003).** Worldtree's `POST /sessions` binds exactly one `bifrost.endpoint_url`. Our memory (`:8391`) and affect (`:8390`) providers are separate processes, so a session binds memory **or** affect — not both. A Sindra session that should both *remember* and show *live PAD* can't do both at once. 2. **The persona pane can't show Tier-3 PAD.** Binding the affect plane works — Worldtree dispatches `affect.emit` to our `:8390` store and it persists (live-proven this session: op-feed `op:"emit", session_id:"7f65a975…", status:"ok", stored:true`). But the web persona pane still reads Worldtree's `persona_state` (`loadPersona`, index.html:707), which **404s for every Tier-3 colon-id agent** (ADR-0009 — persona_state is Tier-1-only). So the pane prints "persona telemetry isn't exposed for Tier-3 … yet" even though our store HAS the PAD. The pane was never wired to read from our own store — the "persona-pane payoff" the #17 affect-binding was meant to unlock. ## Goal Make a single bound Sindra session both **remember** (memory plane) and show **live PAD/valence** in the persona pane (affect plane), in one seat. Two deliverables: ### 1. Composite Bifrost endpoint (un-parks #17 open-question) A single ASGI facade (new port, e.g. `:8392`) fronting BOTH our memory + affect stores, advertising BOTH `memory`+`affect` caps at the handshake. One session binds it → Worldtree dispatches both memory-calls AND affect-calls to it. Bifrost's own reference server already mounts both planes in one app (`reference_server/server.py`: handshake + memory-call + affect-call routes taking both stores), so this is a thin combined builder mirroring the two existing ones (`build_memory_provider_app` / `build_affect_provider_app`). Per the #17 contract's parking note, this is NOT just "handshake lists both caps": - per-plane failure status (a facade routing bug is a new failure class); - per-underlying-store parity checks; - the op-feed's `instrument_provider_app` currently takes a FIXED `plane` param — for the combined app it must derive plane per-request from the path (`/bifrost/memory-call` vs `/bifrost/affect-call`), which it already half-does in `_op_from`. ### 2. PAD read-endpoint → persona pane renders our store A small **non-bifrost** read route on the affect provider (recommended over the web server opening `affect.db` directly — keeps the provider the owner of its store, no web↔SQLite coupling). The store already has `get(agent_id, end_user_id)` → the PAD/valence snapshot. Then: - a web endpoint (`GET /api/affect/{agent_id}`) proxies it server-side; - `loadPersona` (or a sibling) renders PAD/valence from OUR store instead of (or as fallback to) the Tier-3-gated `persona_state`. `affect.fetch` over bifrost is **reserved/blocked** (Worldtree can't fetch) — irrelevant here: we own the store and read it directly, no Worldtree dependency. (WT #289 `affect.fetch`/`affect:read` is an OPTIONAL mediated-read — their intelligence over our raw store — NOT a prerequisite.) ## Operator decisions (this session) - **Composite endpoint: approved** (operator "A is correct"). - **Read path: provider read-endpoint** (recommended; deliverable 2 above) — confirm in the contract. ## Acceptance 1. One session binds the composite endpoint → handshake grants both `memory`+`affect`. 2. A turn into that session → op-feed shows BOTH a memory `search` AND an affect `emit`, each correlated to the bound session_id. 3. The web persona pane renders live PAD/valence for a Tier-3 agent (e.g. `ratatoskr:sindra`) from OUR `:8390` store — no "telemetry isn't exposed" for affect-bound sessions. 4. Per-plane failure isolation: a down/erroring affect store doesn't break memory dispatch on the same session (and vice-versa), surfaced distinctly. ## Dependencies / scope - Builds on #17 (shipped). No Worldtree-side change required to bind (composite is still one `endpoint_url`). - Contract-first (new surface): scope a `.contract.md` covering both pieces before code. - Out of scope: WT #289 mediated affect-read; production hardening (TLS/RS256).
vh added the enhancement label 2026-06-18 01:16:01 -07:00
vh closed this issue 2026-06-20 08:00:04 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vh/ratatoskr#18