Files

ThothII reviewer replay

Riproduce su http://localhost:5333 la vera UI dei widget reviewer di una sessione registrata, senza VPN, Pi, Python, né DWH. Si usano i descriptor dei gate così come furono proposti al revisore (catturati nel transcript Pi), nello stesso ordine, con le scelte realmente fatte mostrate come badge di confronto.

Schema del flusso

browser (SPA reale, :5333)
    │  REST + SSE, same-origin
    ▼
server.mjs  (node:http, zero dipendenze)
    ├─ static: serve il bundle SPA da tools/replay/web/
    └─ replay SSE: GET /sessions/:id/events   → event: ui_request (gate N)
                   POST /sessions/:id/response → badge "scelta reale" + gate N+1
    │
    legge
    ▼
replay.json  (fixture estratta dal transcript)
    │ prodotta da
    ▼
extract.mjs  (legge ~/.pi/agent/sessions/…/*.jsonl + question.md)

Niente backend Fastify, niente pi --mode rpc, niente tht, niente DB: il server gira direttamente sui descriptor registrati.

Comandi

# Estrai la fixture per una sessione (id, file, o directory di transcript):
scripts/replay.sh extract 2026-07-03-132933-fammi-la-lista-dei-pazienti-che-negli-ul
scripts/replay.sh extract ~/.pi/agent/sessions/--Users-mp-projects-ThothII-harness--/

# (una tantum, o dopo modifiche al frontend) costruisci il bundle SPA:
scripts/replay.sh build

# Avvia il server su :5333:
scripts/replay.sh run

# Scorciatoia: extract (default) + build (se manca) + run:
scripts/replay.sh

Cosa può estrarre extract:

Input Comportamento
(nessuno) La sessione di default (3 lug 17:07, 20 gate).
<session-id> Scansiona tutti i transcript nella directory Pi per quel session-id. Gestisce automaticamente sessioni spezzate su più file (resume): raccoglie i gate da tutti i transcript, li ordina cronologicamente, e scarta i gate senza risposta (quelli riproposti al resume).
<file.jsonl> Un singolo transcript.
<directory> Tutti i *.jsonl nella directory; se contengono sessioni diverse, tiene quella dominante (con più gate).

La domanda della sessione è letta da session_manifest.yaml (campo question), cercando la directory sessions in ogni workspace di harness/workspaces/ (via paths.sessions, assoluto o relativo). Niente più path hardcoded.

Cosa vedi nel browser

  1. Apri http://localhost:5333. La lista sessioni mostra una sessione (quella riprodotta) con la domanda reale.
  2. Cliccala: appare il gate 1 — un reviewer_select (widget select) con il titolo, l'intro e le opzioni esatte che il modello propose il 3 luglio, incluso il badge "consigliato" sull'opzione raccomandata.
  3. Clicca un'opzione. Sotto la tua scelta compare un info con la scelta reale del 3 luglio (📌 SCELTA REALE (3 lug 2026): …), e — se coincide — un ✓ Hai scelto come il 3 luglio.
  4. Avanzi attraverso tutti i gate nell'ordine loggato: select (F1 chiarimenti, F6 conferma piano), multiselect (F3 riscrittura, F4 schema linking, F6 piano CTE), artifact-gate (F2/F3/F4/F5 phase advance, F6 approvazione CTE con SQL visibile nell'ArtifactView).
  5. Dopo l'ultimo gate: ✓ Replay completato — 20/20 gate.

I 20 gate della sessione 17:07: 4 select + 11 confirm + 5 decide.

Limiti e fedeltà al log

  • Sessioni multi-file (resume): gestite. L'estrattore raccoglie i gate da tutti i transcript della sessione, li ordina per timestamp del messaggio, e scarta i gate senza toolResult (quelli dove il revisore chiuse Pi senza rispondere e che furono riproposti al resume). Risultato: la sequenza esatta dei gate che il revisore ha effettivamente visto e deciso.
  • Workspace discovery: la domanda è letta da session_manifest.yaml, cercando paths.sessions nei workspace di harness/workspaces/. Funziona per qualunque cliente (non solo psd), purché il workspace yaml sia presente e paths.sessions sia un path assoluto o relativo (non espanso con ${VAR} — in quel caso il path non è risolvibile offline e la domanda resta vuota).
  • Phase compute disattivata: la fase corrente (colorazione F1…F8) non è ricalcolata dal ledger (richiederebbe tht phase); i gate usano un phase-tag neutro replay. L'ordine e i descriptor sono quelli reali.
  • Gate falliti/saltati: alcuni gate non hanno una "scelta reale" perché nella sessione live furono saltati (memorie vuote → avanzamento auto) o falliti (es. cte_plan con tipo invalido). Il badge ⚠ Gate fallito nella sessione reale lo segnala fedelmente — riflette il log, non un bug del replay.
  • text_delta/ragionamenti del modello: omessi (scelta "solo gate"). Per vederli, estendi extract.mjs per raccogliere anche i text_delta.
  • Reserved controls (back/exit/other): nel replay sono ammessi come qualunque altra risposta, ma non hanno semantica (non c'è stato di navigazione da ripristinare); trattali come "procedi".
  • Single-user, single-session: il server tiene un solo cursore globale; non è pensato per più client concorrenti (un refresh del browser riprende dal gate corrente).

File

  • extract.mjs — estrattore fixture dal transcript Pi JSONL.
  • augment-schema-linking.mjs — riscrive il gate F4 tabelle/colonne come descrittore schema-linking (vedi schema-linking-fixture.json).
  • augment-review-gates.mjs — riscrive i gate F6 piano CTE, CTE 1 approvato e Fase 5 completata come descrittori v2 (cte_plan/cte_result/phase, vedi cte-plan-fixture.json, cte-result-fixture.json, phase-summary-fixture.json). Nota: questi tre gate sono ancora fixture sintetiche coerenti coi contratti v2 — dopo la prima sessione live che passa per un gate cte_plan/cte_result/phase reale, rilancia extract.mjs per catturare i descrittori v2 reali e sostituire le fixture.
  • server.mjs — server HTTP+SSE standalone (node:http, zero deps).
  • replay.json — fixture generata (committata per comodità; rigenerata con augment-schema-linking.mjs poi augment-review-gates.mjs, in quest'ordine).
  • web/ — bundle SPA costruito da frontend/ con VITE_BACKEND_URL=http://localhost:5333 (gitignorato; prodotto da build).