fix(desktop): separate system prompt from turn context#1754
Conversation
…xt section parseSystemPromptSections previously only recognized [Base] and [System] boundaries, folding any appended core content into whichever section preceded it. On a Base+System+Core prompt the core was silently merged into the System body; on Base+Core it merged into Base; on a core-only frame it was mislabeled Base. Extract the trailing [Agent Memory — core] block using the LAST occurrence of its exact header so an earlier mention of that line inside a persona body stays literal when the real appended core frame follows. The remaining prefix is then split on the existing Base/System logic unchanged. The core block is appended as its own PromptSection at the end, making it a visible 'Agent Memory — core' tab in the observer Prompt context dialog. Six new tests cover Base+System+Core, Base+Core, System+Core, core-only, the embedded-header non-happy-path (last-wins dedup), and the realistic Workspace+Base+System+Core harness shape. All six existing parseSystem PromptSections tests continue to pass unchanged. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Map the wire header [Agent Memory — core] to the display title "Core Memory" in the observer Prompt context section. The parser boundary constant (CORE_HEADER) is unchanged — still matches the exact wire header so an earlier mention inside a persona body stays literal. Update all test assertions to expect "Core Memory" and fix the Workspace+Base+System+Core fixture to include a real [Workspace] preamble before [Base], matching the actual Buzz harness shape. The assertion pins the existing fold-Workspace-into-Base behavior and proves the appended core is returned as a distinct "Core Memory" section. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The mid-string core extraction marker was `\n[Agent Memory — core]\n` (single preceding newline). The only producer that appends a core block (`with_core()`) emits `\n\n[Agent Memory — core]\n` (blank-line separator). A no-core persona containing that exact header line on its own would be split incorrectly by the looser match. Tighten CORE_MARKER_INLINE to the double-newline form so a bare `[Agent Memory — core]` line inside a persona body (single preceding newline) is never mistaken for the real appended frame. Core-only input (start-of-string) is unchanged. Update the JSDoc comment to distinguish the two extraction cases and name the producer shape explicitly. Add a regression test where a no-core System persona contains the exact single-newline header line and proves the whole body remains in System. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
session/new.systemPrompt is session-scoped, not turn-scoped. Previously the grouper held it in a pendingSystemPrompt slot and injected it into the first turn's CheckCheck dialog alongside per-turn prompt context, creating two possible presentations for the same data. After this change the grouper always emits session/new as a standalone 'System prompt' single block placed before the first turn's user bubble, for every session: normal first turn, no-user-prompt, restart, and incomplete archive. The CheckCheck dialog now contains only per-turn context (session/prompt / steer context) and setup status — never Base/System/Core Memory sections. Changes: - buildBlocksForRun: pendingSystemPrompt is emitted standalone before the first turn block in every code path (user-prompt or not). Removed the systemPromptForTurn/consumedSystemPrompts machinery. - classifyTurnItems: removed externalSystemPrompt parameter; prompt segments no longer carry a systemPrompt field. - TranscriptTurnSegment 'prompt' kind: removed systemPrompt field. - TurnPromptBlock / PromptUserMessage: removed systemPrompt prop; contextSections now contains only per-turn context sections. - flattenDisplayBlocks: removed segment.systemPrompt emission. - Tests: inverted firstTurnSequence assertion (standalone present, not in turn bundle); simplified restart and multi-turn ordering tests to check standalone single block; removed stale seg.systemPrompt refs. - E2E test 11: inverted to assert System prompt card is visible in feed and CheckCheck dialog contains only per-turn context (2 sections). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
e06120f to
e1192e9
Compare
…nd test coverage - Key system-prompt metadata items by (channel, seq, timestamp) — same dedup pair as observerRelayStore — so distinct sessions across archive rebuilds each retain their own card even when two processes emit the same seq. Removes the per-channel key that caused loss on restart. - Replace pendingNewRunBuffer reinit with accumulate: multiple session/new markers arriving before the new session resolves (rapid restart loop) are now all accumulated in order. Trailing null-session frames between markers are also buffered in the same pending queue and flush together before the first turn, preserving wire order and the documented 'marker(s) plus trailing null-session items' contract. - Replace pendingSystemPrompts + separate null-deferred queue with a unified pendingBeforeTurn buffer in buildBlocksForRun: once the first session/new item opens the buffer, subsequent null-turnId items (lifecycle frames, etc.) are also deferred so the full wire-order sequence emits together before the first turn block. - Add Channel Canvas parser: with_canvas() appends the frame last, so extraction runs in reverse producer order (Canvas first, then Core, then Base/System). Last-occurrence guard prevents an embedded header inside a persona body from being mistaken for the appended frame. - Update stale docs and comments: agentSessionTranscriptGrouping.ts top-level JSDoc, splitIntoSessionRuns marker(s) language, inline comment at session/new handler, restart test comments. - New tests: Canvas extraction variants (full 4-section, canvas-only, Core+Canvas, Base+Canvas, omitted, embedded-single-newline guard, and embedded-with-real-appended-canvas guard); two-session E2E pinning all four standalone card sections and asserting CheckCheck contains only Buzz/thread context; same-seq-different-timestamp collision guard; two pre-resolution session/new markers with interleaved null-frame (proves accumulate path and null-frame survival); grouping restart test assertions updated to two-card/two-session shape. - Playwright shot 11 and 06 updated: four-section fixture, expand card before asserting section headings, explicit forbidden-label check on dialog article texts (Base/System/Core Memory/Channel Canvas absent). Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
e1192e9 to
9237da6
Compare
…tures Replace raw canvas body content in E2E screenshot spec and unit test fixtures with the exact metadata format emitted by render_canvas_section: Canvas revision (event ID): <hex64> Last modified: <YYYY-MM-DDTHH:MM:SSZ> Fetch current content with: buzz canvas get --channel <uuid> Timestamps use seconds-only RFC3339 (SecondsFormat::Secs) to match the Rust producer. Updates observer-feed-screenshots.spec.ts (both session/new fixtures), agentSessionTranscript.test.mjs, and agentSessionTranscriptHelpers.test.mjs. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
01 — Prompt context dialog (CheckCheck, per-turn context only)System prompt is NOT present — CheckCheck contains only per-turn context (events, thread). 02 — System prompt title restored after restart"System prompt" title appears after page reload (seq/timestamp keying fix). 03 — Permission approvedTool permission approval card renders correctly. 04 — Permission cancelledTool permission cancellation card renders correctly. 05 — Prompt context expanded (per-turn context)Expanded prompt context dialog showing event + thread context sections. 06 — System prompt expanded (standalone card, 4 sections)Standalone system prompt card shows all four sections: Base, System, Core Memory, Channel Canvas. Channel Canvas body is metadata-only (event ID, Last modified with seconds-only RFC3339, fetch command — no raw canvas body content). 07 — Current mode updateMode-change observer card renders correctly. 08 — Usage update coalescedCoalesced usage update card renders correctly. 09 — Available commands updateAvailable-commands observer card renders correctly. 10 — Config option updateConfig-option observer card renders correctly. 11 — First-turn ordering: standalone system-prompt card + CheckCheck ingressSystem prompt appears as its own card; CheckCheck contains only per-turn context (no system prompt injection). |











session/newcarries a session-scopedsystemPromptpayload; each user message carries turn-scoped prompt context. These two payloads were conflated: the system prompt was injected into the first turn's CheckCheck dialog instead of rendered as its own card, and the parser silently folded[Agent Memory — core]and[Channel Canvas]content into the preceding section rather than extracting them as distinct sections.Session-vs-turn payload separation
session/newitems are now keyed by(channel, seq, timestamp)— the same dedup pair used byobserverRelayStore— so distinct sessions across archive rebuilds and process restarts each retain exactly one standalone card even when two processes emit the same seq. The prior per-channel key caused loss on restart.Standalone per-session card
buildBlocksForRunuses anopenBatch/pendingForTurnmap to accumulatesession/newmarkers and any interleaved null-session frames, then flushes them as standalone single blocks immediately before the first turn. The batch seals on the first turn-bound item encountered — whether it creates a new bucket or reuses an existing one (production order:turn_started → session/new → session_resolvedall on the same turn ID). Null-session items arriving after the batch seals are emitted inline and are not hoisted. The end-of-stream fallback emits the open batch as standalone singles when no turn arrives.Restart accumulation (
splitIntoSessionRuns) accumulates rather than reinitialises the pending buffer, so multiplesession/newmarkers and trailing null-session frames from a rapid restart are all preserved in wire order.The CheckCheck dialog (
PromptUserMessage) now receives only per-turn context fromsession/promptand steer events — never Base/System/Core Memory/Channel Canvas sections.Base → System → Core Memory → Channel Canvas parsing
parseSystemPromptSections()extracts all four sections in producer order. The[Agent Memory — core]block is located via its blank-line wire separator, using a last-occurrence guard so an embedded bare header inside a persona body is not mistaken for the appended frame.[Channel Canvas]is extracted by the same reverse-scan pattern. Omitted sections are absent from the returned array; no empty entries are emitted.The
[Channel Canvas]section body is metadata-only: a 64-hex revision event ID, a seconds-precision RFC3339 timestamp (Last modified: YYYY-MM-DDTHH:MM:SSZ), and the exact fetch command (buzz canvas get --channel <uuid>). The raw canvas body content is never injected into the system prompt or rendered by the observer. This matches therender_canvas_section()contract inbuzz-acp/src/pool.rs(#1755).Coverage
agentSessionTranscriptHelpers.test.mjs: Canvas extraction variants — full four-section, canvas-only, core+canvas, base+canvas, absent canvas, embedded single-newline guard, embedded-with-real-appended-canvas guard.agentSessionTranscript.test.mjs: two-session restart asserting both standalone cards survive with correct run placement; same-seq/different-timestamp archive collision guard proving(seq, timestamp)key prevents process-restart loss; reducer/display test asserting the four-section card is present and CheckCheck context excludes all four system-prompt labels.agentSessionTranscriptGrouping.test.mjs: rapid double-marker case with interleaved null frame; productionturn_started → session/new → session_resolvedwire order; later-null non-hoist; leading singleton before marker; end-of-stream fallback (no turn arrives).observer-feed-screenshots.spec.tstest 11: standalone card visible in the feed, all four section headings visible on expand, CheckCheck dialog contains only Buzz event and Thread context, none of the four system-prompt labels present.