correlator: LLM tier reads the scene transcript, not the whole call (#102) #112

Merged
logan merged 2 commits from fix/llm-correlator-scene-transcript into main 2026-09-07 04:40:35 -04:00
Owner

Closes #102 — the last leg of the #80/#95 scene-context leak. llm_correlator._call_block read call_doc's whole-call transcript for every scene, so on a multi-scene call every scene's cheap-tier + tiebreaker decision was made against text containing the other scenes.

Mirrors the #80/#95 fix: intelligence.py gives each processed[] scene its own transcript (corrected text, else this scene's segments joined, else the whole transcript); _build_context/preview_correlation/correlate_call take a transcript param and resolve ctx["scene_transcript"] (call-doc fallback kept here, unlike embedding/severity — a scene always has real text and the sweep/single-scene/test paths want the doc's); upload.py scene loops pass scene["transcript"]; recorrelation_sweep passes the doc's text explicitly.

Testing: full c2-core suite green — 296 passed on a sandboxed Linux venv. +1 regression test. drb-correlation-review running.

DO NOT MERGE until the running correlation measurement window closes and its dump is analysed — deploying a correlator change mid-window mixes old + new behaviour in the sample.

🤖 Generated with Claude Code

Closes #102 — the last leg of the #80/#95 scene-context leak. `llm_correlator._call_block` read `call_doc`'s whole-call transcript for every scene, so on a multi-scene call every scene's cheap-tier + tiebreaker decision was made against text containing the other scenes. Mirrors the #80/#95 fix: `intelligence.py` gives each `processed[]` scene its own `transcript` (corrected text, else this scene's segments joined, else the whole transcript); `_build_context`/`preview_correlation`/`correlate_call` take a `transcript` param and resolve `ctx["scene_transcript"]` (call-doc fallback **kept** here, unlike embedding/severity — a scene always has real text and the sweep/single-scene/test paths want the doc's); `upload.py` scene loops pass `scene["transcript"]`; `recorrelation_sweep` passes the doc's text explicitly. **Testing:** full c2-core suite green — **296 passed** on a sandboxed Linux venv. +1 regression test. `drb-correlation-review` running. **DO NOT MERGE until the running correlation measurement window closes and its dump is analysed** — deploying a correlator change mid-window mixes old + new behaviour in the sample. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
logan added 1 commit 2026-09-07 00:07:56 -04:00
The last leg of the #80/#95 scene-context leak. llm_correlator._call_block
read call_doc's whole-call transcript for every scene, so on a multi-scene
call every scene's cheap-tier and tiebreaker decision was made against text
that also contained the other scenes.

- intelligence.py: each processed[] scene now carries its own "transcript" —
  transcript_corrected, else this scene's segments joined, else (single scene)
  the whole transcript.
- _build_context / preview_correlation / correlate_call: take a `transcript`
  param; _build_context resolves ctx["scene_transcript"] from it, falling
  back to the call doc (sweep, single-scene, tests) — the fallback is kept
  here, unlike embedding/severity, because a scene always has real text.
- upload.py: both scene loops pass scene["transcript"].
- llm_correlator._call_block: reads ctx["scene_transcript"] (call-doc
  fallback retained for test-built ctx).
- recorrelation_sweep: passes the call doc's text explicitly.
- +1 regression test. Full c2-core suite green (296 passed, sandboxed venv).

NOT for merge until the running correlation measurement window closes and its
dump is analysed — deploying a correlator change mid-window would mix old and
new behaviour in the sample.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
logan added 1 commit 2026-09-07 00:12:59 -04:00
drb-correlation-review on the prior commit flagged two ways the per-scene
transcript could silently fall back to the whole-call text:

1. _build_transcript_block numbered transmissions "1." while the prompt says
   "0-based indices" — a model echoing the labels it saw returned 1-based
   indices, shifting every scene's slice by one. Labels are now "0." to match
   the documented contract (also fixes the same latent skew in
   _build_scene_embed_text / #80).
2. An empty join (bad / out-of-range / non-int indices) hit
   `transcript or call_doc.get(...)` in _build_context and fell back to the
   whole-call transcript — re-opening the leak exactly when indices are wrong.
   The slice now falls back to this call's own whole transcript *before*
   _build_context sees it, so it is never "". Non-int and negative indices
   are rejected rather than raising.

Slice logic extracted to `_scene_transcript_text` with a dedicated test file
(4 cases: subset, corrected-wins, no-indices fallback, bad-indices fallback).
Call-doc fallback kept (sweep / no-scene path) per the review. Also restored
the `-> ` spacing lost in the prior commit's kwarg edit.

Full c2-core suite green: 300 passed (sandboxed venv). Still DO NOT MERGE
until the measurement window closes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Author
Owner

drb-correlation-review ran. Verdict: ship after two fixes (applied in 7189ba0) and after the measurement window closes.

  • 0-based segment labels — _build_transcript_block numbered transmissions 1. while the prompt documents 0-based indices; a model echoing the labels shifted every scene slice by one. Now 0. to match. (Also fixes the same latent skew for #80s _build_scene_embed_text.)
  • Never-empty slice — a bad/out-of-range/non-int index set produced "", which _build_contexts transcript or call_doc.get(...) turned back into the whole-call transcript — re-leaking exactly when indices are wrong. The slice now falls back to this calls own whole transcript before _build_context sees it, and rejects non-int/negative indices.

Slice logic extracted to _scene_transcript_text + tests/test_scene_transcript.py (4 cases). Full suite 300 passed (sandboxed venv). Call-doc fallback kept for the sweep/no-scene path per the review.

Next leg filed as #114 (summarizer.py reads whole-call transcript per incident — user-visible).

`drb-correlation-review` ran. Verdict: **ship after two fixes** (applied in `7189ba0`) and after the measurement window closes. - **0-based segment labels** — `_build_transcript_block` numbered transmissions `1.` while the prompt documents 0-based indices; a model echoing the labels shifted every scene slice by one. Now `0.` to match. (Also fixes the same latent skew for #80s `_build_scene_embed_text`.) - **Never-empty slice** — a bad/out-of-range/non-int index set produced `""`, which `_build_context`s `transcript or call_doc.get(...)` turned back into the whole-call transcript — re-leaking exactly when indices are wrong. The slice now falls back to this calls own whole transcript before `_build_context` sees it, and rejects non-int/negative indices. Slice logic extracted to `_scene_transcript_text` + `tests/test_scene_transcript.py` (4 cases). Full suite **300 passed** (sandboxed venv). Call-doc fallback kept for the sweep/no-scene path per the review. Next leg filed as #114 (`summarizer.py` reads whole-call transcript per incident — user-visible).
logan merged commit 0712e7a437 into main 2026-09-07 04:40:35 -04:00
Sign in to join this conversation.