Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions desktop/src/features/agents/ui/agentSessionTranscript.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1634,13 +1634,14 @@ test("buildTranscript same-seq different-timestamp session/new events both produ
);
});

test("buildTranscript four-section system prompt card is standalone with all sections; CheckCheck context contains only Buzz/thread context", () => {
// Production scenario: harness emits [Base]/[System]/[Agent Memory — core]/[Channel Canvas]
test("buildTranscript five-section system prompt card is standalone with all sections; CheckCheck context contains only Buzz/thread context", () => {
// Production scenario: team-pack agent harness emits
// [Base]/[System (with team delimiter)]/[Agent Memory — core]/[Channel Canvas]
// in systemPrompt. The display layer must:
// (a) Render it as a standalone single block (acpSource "session/new"),
// NOT inside any turn's prompt bundle.
// (b) The standalone item must carry all four sections in order:
// Base → System → Core Memory → Channel Canvas.
// (b) The standalone item must carry all five sections in order:
// Base → System → Team Instructions → Core Memory → Channel Canvas.
// (c) The prompt segment's context (CheckCheck dialog) must contain only
// the session/prompt:context sections (Buzz event + Thread context),
// never the system-prompt sections.
Expand Down Expand Up @@ -1676,6 +1677,10 @@ test("buildTranscript four-section system prompt card is standalone with all sec
"[System]",
"Custom persona.",
"",
"---",
"# Team Instructions",
"Always tag on handoff.",
"",
"[Agent Memory — core]",
"I am Duncan.",
"",
Expand Down Expand Up @@ -1740,14 +1745,14 @@ test("buildTranscript four-section system prompt card is standalone with all sec
"exactly one standalone system-prompt single block",
);

// (b) The standalone item carries all four sections in order.
// (b) The standalone item carries all five sections in order.
const spItem = systemPromptBlocks[0].item;
assert.ok(spItem, "system-prompt block must have an item");
const titles = (spItem.sections ?? []).map((s) => s.title);
assert.deepEqual(
titles,
["Base", "System", "Core Memory", "Channel Canvas"],
"system-prompt standalone card must carry Base → System → Core Memory → Channel Canvas in order",
["Base", "System", "Team Instructions", "Core Memory", "Channel Canvas"],
"system-prompt standalone card must carry Base → System → Team Instructions → Core Memory → Channel Canvas in order",
);

// (c) The system-prompt item must NOT be inside any turn group.
Expand Down Expand Up @@ -1781,7 +1786,7 @@ test("buildTranscript four-section system prompt card is standalone with all sec
const contextSectionTitles = (promptContextItem.sections ?? []).map(
(s) => s.title,
);
// Must have Buzz event and Thread context sections, NOT Base/System/Core Memory/Channel Canvas.
// Must have Buzz event and Thread context sections, NOT Base/System/Team Instructions/Core Memory/Channel Canvas.
assert.ok(
contextSectionTitles.some((t) => t.toLowerCase().includes("buzz")),
"prompt context must contain a Buzz event section",
Expand All @@ -1791,9 +1796,10 @@ test("buildTranscript four-section system prompt card is standalone with all sec
(t) =>
t === "Base" ||
t === "System" ||
t === "Team Instructions" ||
t === "Core Memory" ||
t === "Channel Canvas",
),
"prompt context must NOT contain system-prompt sections (Base/System/Core Memory/Channel Canvas)",
"prompt context must NOT contain system-prompt sections (Base/System/Team Instructions/Core Memory/Channel Canvas)",
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -489,3 +489,195 @@ test("parseSystemPromptSections keeps an embedded canvas-like line literal when
{ title: "Channel Canvas", body: "this IS the appended canvas" },
]);
});

// ── Team Instructions extraction ───────────────────────────────────────────

test("parseSystemPromptSections extracts Team Instructions as its own section after System (Base+System+Team)", () => {
// compose_prompt() appends "\n\n---\n# Team Instructions\n{instructions}" to the persona body.
// The canonical delimiter must split System from Team Instructions.
const framed = [
"[Base]",
"You are a helpful assistant.",
"",
"[System]",
"You are Agent X.",
"",
"---",
"# Team Instructions",
"Always respond in markdown.",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{ title: "Base", body: "You are a helpful assistant." },
{ title: "System", body: "You are Agent X." },
{ title: "Team Instructions", body: "Always respond in markdown." },
]);
});

test("parseSystemPromptSections extracts Team Instructions after System-only (no Base)", () => {
// When [Base] is absent the [System] header starts the input.
const framed = [
"[System]",
"You are Agent Y.",
"",
"---",
"# Team Instructions",
"Keep responses concise.",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{ title: "System", body: "You are Agent Y." },
{ title: "Team Instructions", body: "Keep responses concise." },
]);
});

test("parseSystemPromptSections extracts Team Instructions with Core Memory and Channel Canvas (full 5-section shape)", () => {
// Full production-shaped system prompt: Base → System → Team Instructions → Core Memory → Channel Canvas.
// compose_prompt() produces the canonical delimiter; with_core() and with_canvas() append their frames.
const framed = [
"[Base]",
"You are a helpful AI assistant running in Buzz.",
"",
"[System]",
"You are Observer Agent. You coordinate multi-agent workflows.",
"",
"---",
"# Team Instructions",
"Always tag on handoff.",
"Never expand scope without approval.",
"",
"[Agent Memory — core]",
"I am Observer Agent.",
"## Lessons Learned",
"Always tag on handoff.",
"",
"[Channel Canvas]",
"Canvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"Last modified: 2026-07-11T10:00:00Z",
"Fetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{ title: "Base", body: "You are a helpful AI assistant running in Buzz." },
{
title: "System",
body: "You are Observer Agent. You coordinate multi-agent workflows.",
},
{
title: "Team Instructions",
body: "Always tag on handoff.\nNever expand scope without approval.",
},
{
title: "Core Memory",
body: "I am Observer Agent.\n## Lessons Learned\nAlways tag on handoff.",
},
{
title: "Channel Canvas",
body: "Canvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\nLast modified: 2026-07-11T10:00:00Z\nFetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
},
]);
});

test("parseSystemPromptSections does NOT split on a bare '---' without the '# Team Instructions' heading", () => {
// A horizontal rule alone inside a persona body is kept literal.
const framed = [
"[System]",
"Some text.",
"",
"---",
"",
"More text after a separator.",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{
title: "System",
body: "Some text.\n\n---\n\nMore text after a separator.",
},
]);
});

test("parseSystemPromptSections does NOT split on '# Team Instructions' with only a single preceding newline", () => {
// The canonical delimiter requires \n\n---\n before the heading.
// A single-newline variant is kept literal inside System.
const framed = [
"[System]",
"Persona preamble.",
"---",
"# Team Instructions",
"These look canonical but lack the double newline before ---.",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{
title: "System",
body: "Persona preamble.\n---\n# Team Instructions\nThese look canonical but lack the double newline before ---.",
},
]);
});

test("parseSystemPromptSections does NOT split on '# Team Instructions' heading without the '---' separator", () => {
// Only the exact composed form \n\n---\n# Team Instructions\n triggers the split.
const framed = [
"[System]",
"Persona text.",
"",
"# Team Instructions",
"This is just a heading in the persona body.",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{
title: "System",
body: "Persona text.\n\n# Team Instructions\nThis is just a heading in the persona body.",
},
]);
});

test("parseSystemPromptSections non-team persona (no delimiter) is unaffected", () => {
// A standard Base+System prompt without any team instructions must produce
// exactly two sections — no Team Instructions row added.
const framed = [
"[Base]",
"You are a helpful assistant.",
"",
"[System]",
"You are a coding assistant.",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{ title: "Base", body: "You are a helpful assistant." },
{ title: "System", body: "You are a coding assistant." },
]);
});

test("parseSystemPromptSections splits on the LAST occurrence of the canonical delimiter (embedded lookalike + real appended team suffix)", () => {
// A persona body may itself contain the exact delimiter string verbatim
// (e.g. an example or a quoted earlier instruction set). compose_prompt()
// always APPENDS the real team instructions, so the LAST occurrence is the
// authoritative producer boundary. The earlier embedded occurrence must stay
// inside the System body.
const framed = [
"[System]",
"Here is an example of team framing:",
"",
"---",
"# Team Instructions",
"These are fake — embedded in the persona prose.",
"",
"---",
"# Team Instructions",
"These are real — appended by compose_prompt().",
].join("\n");
const sections = parseSystemPromptSections(framed);
assert.deepEqual(sections, [
{
title: "System",
body: "Here is an example of team framing:\n\n---\n# Team Instructions\nThese are fake — embedded in the persona prose.",
},
{
title: "Team Instructions",
body: "These are real — appended by compose_prompt().",
},
]);
});
57 changes: 49 additions & 8 deletions desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,19 @@ export function parsePromptText(text: string): {

/**
* Split the framed `session/new` `systemPrompt` into its `Base`/`System`/
* `Core Memory`/`Channel Canvas` sub-sections deterministically.
* `Team Instructions`/`Core Memory`/`Channel Canvas` sub-sections
* deterministically.
*
* The harness composes the value in order:
* `[Base]\n{base}\n\n[System]\n{persona}\n\n[Agent Memory — core]\n{core}\n\n[Channel Canvas]\n{canvas}`
* with any section omitted when absent. Extraction runs in reverse producer
* order so that each `lastIndexOf` search operates on the full input and
* each extraction boundary is unambiguous.
* with any section omitted when absent. For team-pack agents the persona body
* already contains the pack-level instructions appended by `compose_prompt()`
* in `buzz-persona/src/resolve.rs`:
* `{persona_body}\n\n---\n# Team Instructions\n{pack_instructions}`
* Extraction runs in reverse producer order so that each `lastIndexOf` search
* operates on the full input and each extraction boundary is unambiguous.
*
* Three extraction passes before Base/System parsing:
* Four extraction passes before Base/System parsing:
*
* 1. **Canvas** (`[Channel Canvas]`): appended last by `with_canvas()`.
* - Start-of-string: canvas-only input.
Expand All @@ -79,6 +83,15 @@ export function parsePromptText(text: string): {
* 3. **Base/System**: remainder after canvas and core extraction.
* Split on the first `\n[System]\n` boundary; no embedded `[...]` line
* inside a body can start a new section.
*
* 4. **Team Instructions**: if the `System` body contains the exact canonical
* delimiter `\n\n---\n# Team Instructions\n` (produced by `compose_prompt()`),
* the body is split at the **last** occurrence of that boundary (same
* last-occurrence guard as canvas and core). The text before becomes the
* `System` body; the text after becomes a `Team Instructions` section
* inserted immediately after `System`. Non-canonical lookalikes (bare `---`
* without the heading, a `# Team Instructions` on a different line, or only
* a single preceding newline) are kept literal inside `System`.
*/
export function parseSystemPromptSections(
systemPrompt: string,
Expand Down Expand Up @@ -121,11 +134,36 @@ export function parseSystemPromptSections(
}

// ── 3. Parse Base/System from the remaining prefix ────────────────────────
// The canonical team-instructions delimiter produced by compose_prompt() in
// buzz-persona/src/resolve.rs:
// format!("{persona_prompt}\n\n---\n# Team Instructions\n{instructions}")
const TEAM_DELIMITER = "\n\n---\n# Team Instructions\n";

// splitSystemBody: split a raw [System] body string at the last occurrence
// of the canonical team delimiter, returning { systemBody, teamBody | null }.
// Using lastIndexOf mirrors the canvas/core last-occurrence guard: a persona
// author can embed an exact delimiter-like passage inside the persona body;
// only the final occurrence is the producer boundary appended by compose_prompt().
function splitSystemBody(raw: string): {
systemBody: string;
teamBody: string | null;
} {
const at = raw.lastIndexOf(TEAM_DELIMITER);
if (at === -1) return { systemBody: raw.trim(), teamBody: null };
return {
systemBody: raw.slice(0, at).trim(),
teamBody: raw.slice(at + TEAM_DELIMITER.length).trim() || null,
};
}

const baseAndSystem = remainder;
if (baseAndSystem) {
if (baseAndSystem.startsWith("[System]\n")) {
const body = baseAndSystem.slice("[System]\n".length).trim();
if (body) sections.push({ title: "System", body });
const raw = baseAndSystem.slice("[System]\n".length);
const { systemBody, teamBody } = splitSystemBody(raw);
if (systemBody) sections.push({ title: "System", body: systemBody });
if (teamBody)
sections.push({ title: "Team Instructions", body: teamBody });
} else {
const marker = "\n[System]\n";
const at = baseAndSystem.indexOf(marker);
Expand All @@ -134,8 +172,11 @@ export function parseSystemPromptSections(
if (baseBody) sections.push({ title: "Base", body: baseBody });

if (at !== -1) {
const systemBody = baseAndSystem.slice(at + marker.length).trim();
const raw = baseAndSystem.slice(at + marker.length);
const { systemBody, teamBody } = splitSystemBody(raw);
if (systemBody) sections.push({ title: "System", body: systemBody });
if (teamBody)
sections.push({ title: "Team Instructions", body: teamBody });
}
}
}
Expand Down
Loading
Loading