mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
107 lines
5.9 KiB
Markdown
107 lines
5.9 KiB
Markdown
# Transcript-Derived View — Raw Session Files as Source of Truth
|
||
|
||
Status: **draft — approved direction, phased implementation**
|
||
Branch: `feat/transcript-derived-view` (stacked on `fix/transcript-restore-fidelity`)
|
||
Companion: [`conversations-timeline-refactor.md`](conversations-timeline-refactor.md) (Phases 1–2, 4–5 landed; this plan supersedes its Phase 5 hydration story for settled turns)
|
||
|
||
## Goal
|
||
|
||
Stop maintaining chat state that must be *synced* with what is live. Derive the settled
|
||
transcript from the raw session files (`session_raw/*.jsonl`), and demote every other
|
||
store to a cache over that file. Live token streaming is untouched: in-flight turns
|
||
render from ephemeral socket-fed state exactly as today; the file is authoritative only
|
||
for **settled** turns.
|
||
|
||
This is the Codex rollout model (one JSONL replayed into both model context and UI,
|
||
with an explicit persistence policy) adapted to our layout, plus hermes-agent's
|
||
soft-compaction lesson (history is never destroyed, only superseded).
|
||
|
||
## Why derivation is unsafe today (must fix first)
|
||
|
||
1. **Destructive compaction.** `agent/harness/session/transcript.rs::write_transcript`
|
||
full-rewrites the file so context reduction deletes earlier turns from disk.
|
||
2. **Display data missing from the file**: interrupted partial answers, `request_id`
|
||
turn boundaries, narration items. They exist only in `turn_state` snapshots.
|
||
3. **Internal scaffolding in message content**: channel-context prefixes on user
|
||
messages, tool-policy preamble in system content — must be sanitized (or tagged) at
|
||
projection, never shown raw.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
live turn (unchanged)
|
||
socket events ──► chatRuntimeSlice (ephemeral) ──► renderer
|
||
│ chat_done
|
||
▼ invalidate
|
||
settled turns:
|
||
session_raw/{root}.jsonl ─┐
|
||
session_raw/{root}__sub-*.jsonl ─┴─► core projection (threads.transcript_get)
|
||
│ mtime-keyed cache
|
||
▼
|
||
typed display items ──► renderer (same components)
|
||
```
|
||
|
||
- **Model context** keeps reading the same file via the existing loader, now replaying
|
||
compaction records instead of trusting a rewritten file.
|
||
- **`turn_state`** shrinks to live-turn crash recovery (interrupted `streamingText`
|
||
until the interrupted line is appended to the file, then only the in-flight turn).
|
||
- The 20-turn retention cap stops being user-visible loss: history comes from the file.
|
||
|
||
## Phases
|
||
|
||
### Phase A — append-only transcript (Rust, prerequisite)
|
||
`transcript.rs` + harness call sites:
|
||
- Replace full-rewrite with **append-only** line writes. Context reduction appends a
|
||
`compaction` record `{ kind: "compaction", replacement_ids | replacement_history }`;
|
||
the model-context loader (`read_transcript` path) replays records to reconstruct the
|
||
post-compaction context; a new display reader returns *all* records.
|
||
- Stamp `request_id` on every line of a turn (turn boundary markers); keep `iteration`,
|
||
`ts`, `seq` alignment with the progress-bridge envelope.
|
||
- On turn abort/interrupt, append the partial assistant line flagged
|
||
`{ interrupted: true }` so the partial answer is in the file, not only in turn_state.
|
||
- Migration: existing files are valid append-only files with zero compaction records —
|
||
no migration needed. Old cores reading new files must skip unknown `kind` lines
|
||
(verify the `_extra` flatten tolerates this; add a version field to `_meta`).
|
||
- Tests: compaction round-trip (model context reduced, display history complete),
|
||
interrupted-partial append, request_id stamping, legacy-file read.
|
||
|
||
### Phase B — projection RPC (Rust)
|
||
New `threads.transcript_get(thread_id, {cursor?, limit?})` in the threads domain
|
||
(canonical module shape: ops/schemas):
|
||
- Resolve root transcript via `find_root_transcript_for_thread`; discover
|
||
`__sub-*.jsonl` children; project into typed display items:
|
||
`user_message | assistant_message | reasoning | tool_call {args, result, status} |
|
||
subagent {id, items} | turn_boundary {request_id} | interrupted_partial`.
|
||
- Sanitize scaffolding (channel-context prefix, tool-policy preamble) at projection;
|
||
tag rather than mutate where ambiguity exists.
|
||
- Cache: per-thread projection keyed on (file paths, mtimes, lengths); invalidated
|
||
implicitly by key change. No cache writes to disk — pure memory cache.
|
||
- Pagination newest-first with cursor; default window sized for one screen.
|
||
- Tests: JSON-RPC E2E (`tests/json_rpc_e2e.rs`) — write file, call RPC, assert items;
|
||
subagent merge; sanitization; cache-key invalidation.
|
||
|
||
### Phase C — frontend switch (TS)
|
||
- Thread-open restore path: replace `turn_state_history` hydration for settled turns
|
||
with `transcript_get`; map items onto the existing renderers
|
||
(`PastTurnInsights`/`ToolTimelineBlock`/`ProcessingTranscriptView`, bubbles).
|
||
- Live turn: untouched (socket → chatRuntimeSlice). On `chat_done`, drop the live
|
||
turn's ephemeral state and refetch/append the settled projection.
|
||
- Keep the turn_state hydration path as fallback behind a flag for one release.
|
||
- Tests: restore renders identical item sequence to live for a scripted turn;
|
||
interrupted partial visible; legacy thread (no request_id) fallback.
|
||
|
||
### Phase D — cleanup
|
||
- Demote/remove `turn_state` ring retention (keep live-turn snapshot only), drop the
|
||
`.md` mirror or mark derived-only, delete the fallback flag.
|
||
|
||
## Non-goals
|
||
- No change to the socket streaming protocol or delta handling.
|
||
- No SQLite consolidation (sessions.db stays an index; separate discussion).
|
||
- Phase 3 of the timeline refactor (dedup consolidation) remains its own track.
|
||
|
||
## Risks
|
||
- **Old-core / new-file compat** — unknown `kind` lines must be skipped, not fatal.
|
||
- **File growth** — append-only grows; mitigate later with Codex-style zstd of old
|
||
sessions; out of scope here.
|
||
- **Sanitization false positives** — prefer tagging + frontend hiding over deletion.
|