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>
IdeaReader folded eventStore.allEvents() on every activeIdeas()/capturedOf()
call — a full deserializing log scan per CHAT context build. Now folds once at
construction and stays current incrementally via subscribeAll(); the fold is
idempotent so seed/live overlap needs no dedupe.
The actions line is session-wide, but the old 'Actions taken this stage' label
made a literal narrator (Qwen2.5-1.5B) contradict itself ('no files read this
stage'). Relabel to match reality.
Live run showed the actions were in the prompt but the 1.2B narrator ignored
them: they sat in a system-role entry (small models under-weight system context)
and were stage-scoped to the pausing stage, which usually isn't the one that ran
the tools. Fold the recent actions into the USER trigger instruction instead, and
take the last N tool calls across the whole run rather than per-stage. Narration
now names the concrete actions ("reviewing server directories and reading the
specified file") instead of generic filler.
Narration was generic ("Stage X completed. Summarise.") because nothing about
what the stage actually did reached the narrator — the L2 summary is a
placeholder and StageCompletedEvent carries no output. narrate() now reads the
stage's ToolInvocationRequested events from the log and feeds the narrator a
concrete 'Actions taken this stage: file_read(path), ...' entry, and the system
prompt asks it to prefer those concrete actions over restating the stage name.
The 'router' name was misleading — it's the always-on conversational front-end
(CHAT triage + STEERING into a running workflow), not a routing layer, and it's
distinct from InferenceRouter. Rename core:router -> core:talkie, package
com.correx.core.router -> com.correx.core.talkie, and RouterFacade/Config/State/
Repository/ContextBuilder/Projector/Reducer/Response -> Talkie*. Config section
[router] -> [talkie] (legacy [router] still read as fallback).
Persisted wire formats are preserved: ChatTurnRole.ROUTER and the
RouterNarrationEvent @SerialName("RouterNarration") stay, so existing event
logs still replay. RouterNarrationEvent the class is now TalkieNarrationEvent.