fix(kernel,talkie,context): three instruction-corruption fixes from the 2026-08-26 context audit
Each of the three lost or rewrote an instruction before the model saw it. 1. Orphan corrective nudges. pushBack() and the final tools-disabled emission built their nudge as a toolResult with a fresh sourceId, so it had no matching assistantToolCall and reconcileToolPairs() deleted it — the orchestrator believed it had corrected the model while the correction never reached the prompt. Affected the invalid-emit_artifact, premature-stage_complete, missing-write, read-loop, rejection-loop and final-JSON nudges. They are now USER turns (sourceType orchestratorCorrection, REQUIRED bucket, STRUCTURED in ContextClassifier so pruning cannot shred them), appended last so the builder's positional ordinal puts them at the end of the transcript. A superseded nudge is dropped rather than stacking stale demands. 2. Steering laundered through the router. The SteeringNoteAddedEvent carried the router model's paraphrase of the operator's message, not the message — negations, filenames, constraints and priority could change before the orchestrator saw them. It now carries the raw input; the router turn is still produced and shown as conversational acknowledgement, it is just not the mandate. Resolves the ponytail: note at TalkieFacade.kt:220. 3. Journal compaction erased its own history. compactIfNeeded() summarized only state.records while the reducer overwrote summaryArtifactId and dropped covered records, so the second compaction lost everything the first had preserved — and a low-salience-only batch replaced it with the "(no high-salience decisions)" fallback. Compaction is cumulative now, and a blank or fallback-only rewrite never replaces real history. Tests: rendered-prompt regression for (1) — verified to fail against the old toolResult shape — updated steering expectations for (2), two cumulative-compaction tests for (3). Full build green, 1831 tests, 0 failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -217,16 +217,16 @@ class DefaultTalkieFacade(
|
||||
emitIdeasCaptured(sessionId, ideas.ideas)
|
||||
}
|
||||
|
||||
// ponytail: STEERING launders the user's text through the router LLM (the `content` above)
|
||||
// before injecting it as a note. For a clear instruction that's an extra inference that can
|
||||
// distort intent; the reformulation only earns its cost when the input is terse/context-
|
||||
// dependent. Upgrade path: inject the raw (validated) input directly and skip the rewrite
|
||||
// unless a heuristic flags the message as too short/ambiguous to stand alone.
|
||||
val steeringEmitted = mode == ChatMode.STEERING && rawContent.isNotBlank()
|
||||
// The steering note carries the operator's OWN text, not the router's paraphrase of it.
|
||||
// Routing it through inference first let a lossy rewrite acquire the authority of a user
|
||||
// directive — negations, filenames, constraints and priority could all change before the
|
||||
// orchestrator saw them. The router turn above is still produced and shown to the operator
|
||||
// as conversational acknowledgement; it is just not the mandate.
|
||||
val steeringEmitted = mode == ChatMode.STEERING && input.isNotBlank()
|
||||
if (steeringEmitted) {
|
||||
val validationError = validateSteering?.invoke(content)
|
||||
val validationError = validateSteering?.invoke(input)
|
||||
if (validationError == null) {
|
||||
emitSteeringNote(sessionId, content, effectiveStageId)
|
||||
emitSteeringNote(sessionId, input, effectiveStageId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user