Skip to content

fix(desktop): separate system prompt from turn context#1754

Merged
wpfleger96 merged 6 commits into
mainfrom
duncan/observer-core-memory-section
Jul 11, 2026
Merged

fix(desktop): separate system prompt from turn context#1754
wpfleger96 merged 6 commits into
mainfrom
duncan/observer-core-memory-section

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

session/new carries a session-scoped systemPrompt payload; 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/new items are now keyed by (channel, seq, timestamp) — the same dedup pair used by observerRelayStore — 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

buildBlocksForRun uses an openBatch / pendingForTurn map to accumulate session/new markers 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_resolved all 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 multiple session/new markers 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 from session/prompt and 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 the render_canvas_section() contract in buzz-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; production turn_started → session/new → session_resolved wire order; later-null non-hoist; leading singleton before marker; end-of-stream fallback (no turn arrives).
  • observer-feed-screenshots.spec.ts test 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.

@wpfleger96 wpfleger96 requested a review from a team as a code owner July 11, 2026 14:54
@wpfleger96 wpfleger96 changed the title fix(desktop): show agent core memory as its own observer Prompt context section fix(desktop): show core memory in observer context Jul 11, 2026
wpfleger96 pushed a commit that referenced this pull request Jul 11, 2026
@wpfleger96 wpfleger96 changed the title fix(desktop): show core memory in observer context fix(desktop): surface core memory section and consolidate system-prompt presentation in observer feed Jul 11, 2026
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 and others added 4 commits July 11, 2026 12:35
…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>
@wpfleger96 wpfleger96 force-pushed the duncan/observer-core-memory-section branch from e06120f to e1192e9 Compare July 11, 2026 16:37
…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>
@wpfleger96 wpfleger96 force-pushed the duncan/observer-core-memory-section branch from e1192e9 to 9237da6 Compare July 11, 2026 16:46
@wpfleger96 wpfleger96 changed the title fix(desktop): surface core memory section and consolidate system-prompt presentation in observer feed fix(desktop): separate system prompt from turn context Jul 11, 2026
wpfleger96 pushed a commit that referenced this pull request Jul 11, 2026
…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>
wpfleger96 pushed a commit that referenced this pull request Jul 11, 2026
@wpfleger96

Copy link
Copy Markdown
Collaborator Author

01 — Prompt context dialog (CheckCheck, per-turn context only)

System prompt is NOT present — CheckCheck contains only per-turn context (events, thread).

01-prompt-context-dialog

02 — System prompt title restored after restart

"System prompt" title appears after page reload (seq/timestamp keying fix).

02-system-prompt-title-restored

03 — Permission approved

Tool permission approval card renders correctly.

03-permission-approved

04 — Permission cancelled

Tool permission cancellation card renders correctly.

04-permission-cancelled

05 — Prompt context expanded (per-turn context)

Expanded prompt context dialog showing event + thread context sections.

05-prompt-context-expanded

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).

06-system-prompt-expanded

07 — Current mode update

Mode-change observer card renders correctly.

07-current-mode-update

08 — Usage update coalesced

Coalesced usage update card renders correctly.

08-usage-update-coalesced

09 — Available commands update

Available-commands observer card renders correctly.

09-available-commands-update

10 — Config option update

Config-option observer card renders correctly.

10-config-option-update

11 — First-turn ordering: standalone system-prompt card + CheckCheck ingress

System prompt appears as its own card; CheckCheck contains only per-turn context (no system prompt injection).

11-first-turn-ordering

wpfleger96 pushed a commit that referenced this pull request Jul 11, 2026
@wpfleger96 wpfleger96 merged commit 84ec63a into main Jul 11, 2026
25 checks passed
@wpfleger96 wpfleger96 deleted the duncan/observer-core-memory-section branch July 11, 2026 17:40
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.

1 participant