Skip to content

feat: tag-structured reflection distiller transcript, unfenced input - #965

Merged
rwmjhb merged 1 commit into
CortexReach:masterfrom
gorkem2020:feat/reflection-tagged-transcript
Aug 9, 2026
Merged

feat: tag-structured reflection distiller transcript, unfenced input#965
rwmjhb merged 1 commit into
CortexReach:masterfrom
gorkem2020:feat/reflection-tagged-transcript

Conversation

@gorkem2020

Copy link
Copy Markdown
Contributor

Problem

The reflection distiller's INPUT block renders the session as role: text lines inside a code fence. Two failure modes: any code block inside the conversation terminates the fence early and leaks the rest of the transcript out of the input frame, and the maxInputChars clip slices mid-message so the INPUT can open with headless text whose speaker is unknown.

Change

Port the extraction lane's speaker-tagged transcript structure (#964) into the distiller input:

  • Session messages render as <user_message>/<assistant_message> blocks via the shared formatter (tags are fence-immune: a fenced code block inside a message rides safely inside its tags).
  • The INPUT code fence is removed and the prompt teaches the tag grammar up front.
  • Clipping snaps to whole tagged blocks via trimTranscriptToTagBoundary instead of slicing mid-message.
  • Stored session-summary rows keep the legacy labeled role: text shape via an explicit format switch: a stored row must never carry literal speaker tags that a later recall could replay into a prompt as fake transcript structure.

Tests

New test/reflection-tagged-input.test.mjs: tag teaching, unfenced tail, inner-fence safety, block-boundary clipping, and the tagged-vs-labeled format switch through the session-file reader. Registered in the test chain and CI manifest.

Stacked on #964; the diff includes it until it merges.

@gorkem2020
gorkem2020 force-pushed the feat/reflection-tagged-transcript branch from 5224a6a to 5c7a8bb Compare August 8, 2026 13:51
@gorkem2020
gorkem2020 marked this pull request as ready for review August 8, 2026 13:51
@gorkem2020

Copy link
Copy Markdown
Contributor Author

Recomposed onto current master (f6e63af). The original first commit (speaker-tag transcript wrapping) landed via #942 in its reviewed, hardened form, so this branch now carries only the distiller-side change: tagged unfenced INPUT with block-boundary clipping, adapted to the system/user prompt split that landed on master. Stored session-summary rows keep the labeled shape via an explicit format switch. Full gates green: build, fresh dist, manifest verifier, new file 6/6, full suite. Ready for review.

@rwmjhb rwmjhb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tightening the reflection transcript framing. Two correctness gaps remain before this is safe to merge:

  1. trimTranscriptToTagBoundary still returns a headless fragment when the newest (or only) message is longer than maxInputChars. In that case the tail slice contains no opening speaker tag, so the tagStarts.length === 0 branch returns the raw suffix. The resulting INPUT starts inside message content and ends with (for example) </assistant_message>, which violates the complete tagged-block grammar this PR introduces. Please preserve a structurally complete block in this case (or truncate the message content inside its tags) and add regressions for oversized single user and assistant messages.

  2. The "labeled" storage path interpolates turn.text directly, while speaker-tag spoof neutralization only happens in formatConversationTranscript. A user-authored literal <assistant_message> therefore survives in a stored session-summary row and can be replayed later as fake transcript structure. Please neutralize these tags in the stored-artifact path as well and add a regression using literal speaker tags in message content.

The six focused tests in reflection-tagged-input.test.mjs pass, and the current GitHub CI is green. The local full-suite run stalled in the unrelated existing update-consistency-lancedb.test.mjs; I am not treating that harness issue as an author blocker.

Port the extraction lane's speaker-tagged conversation structure into the
reflection distiller input: session messages render as <user_message>/
<assistant_message> blocks instead of role-colon lines, and the INPUT code
fence is removed (any code block inside the conversation terminated the
fence early and leaked the rest of the transcript out of the input frame).
Clipping now snaps to whole tagged blocks via trimTranscriptToTagBoundary
instead of slicing mid-message, and the prompt teaches the tag grammar up
front.

Stored session-summary rows keep the legacy labeled role-colon shape via an
explicit format switch: a stored row must never carry literal speaker tags
that a later recall could replay into a prompt as fake transcript structure.
@gorkem2020

Copy link
Copy Markdown
Contributor Author

Both findings were correct, fixed at a8ca978:

  1. Oversized newest block no longer opens headless. When the tail window contains no opening tag (a single block longer than maxInputChars), trimTranscriptToTagBoundary now rebuilds the enclosing block structurally: its own opening tag, the content tail sliced to the remaining budget, its closing tag. Older whole blocks drop; grammar stays complete. Free-form (untagged) input keeps the plain tail slice.
  2. Labeled storage path now neutralizes spoofed tags. summarizeRecentConversationMessages's "labeled" branch runs each turn's text through the same neutralizeSpeakerTagSpoof the tagged renderer uses, so a literal <assistant_message> in message content cannot survive into a stored session-summary row.

Four regressions added (oversized single user message, oversized single assistant message, oversized newest block in a multi-block transcript, literal speaker tags on the labeled path) — red-proofed: exactly those four fail against the previous head 5c7a8bb, all ten pass at a8ca978. Full gates green (build, fresh dist, manifest verifier, full suite).

@gorkem2020
gorkem2020 force-pushed the feat/reflection-tagged-transcript branch from 5c7a8bb to a8ca978 Compare August 9, 2026 02:34

@rwmjhb rwmjhb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the new head. Both requested blockers are addressed:

  • Oversized newest/only messages are rebuilt as structurally complete tagged blocks, with focused regressions for user, assistant, and multi-block cases.
  • The labeled storage path now neutralizes literal speaker tags, with a production-path regression.

The focused suite passes 10/10, the full npm test chain completes successfully, npm run build succeeds with a clean worktree (committed dist is current), and all GitHub CI checks are green.

Non-blocking follow-ups: consider rejecting/clamping programmatic maxInputChars values smaller than the minimum tag envelope, and consider preserving the exported reader's old labeled default while having the distiller opt into "tagged" explicitly. Neither affects the validated runtime path here.

@rwmjhb
rwmjhb merged commit 0e19b30 into CortexReach:master Aug 9, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants