mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): parse runtime team instructions section (#2645)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
This commit is contained in:
co-authored by
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
parent
72bbaece4b
commit
269ef357f7
@@ -681,3 +681,114 @@ test("parseSystemPromptSections splits on the LAST occurrence of the canonical d
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// ── Modern [Team Instructions] bracket-header extraction ─────────────────────
|
||||
|
||||
test("parseSystemPromptSections (modern) extracts bracket Team Instructions after Base+System", () => {
|
||||
// with_team() emits "\n\n[Team Instructions]\n{instructions}" as a top-level
|
||||
// bracket section — the same framing used by with_core() and with_canvas().
|
||||
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 (modern) extracts bracket Team Instructions after System-only", () => {
|
||||
// [Base] absent; [Team Instructions] is a direct top-level bracket section.
|
||||
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 (modern) handles bracket Team Instructions as start-of-string", () => {
|
||||
// with_team() also handles prompt=None → "[Team Instructions]\n{instructions}".
|
||||
const framed = ["[Team Instructions]", "Instructions only."].join("\n");
|
||||
const sections = parseSystemPromptSections(framed);
|
||||
assert.deepEqual(sections, [
|
||||
{ title: "Team Instructions", body: "Instructions only." },
|
||||
]);
|
||||
});
|
||||
|
||||
test("parseSystemPromptSections (modern) pins full 5-section shape: Base+System+Team+Core+Canvas", () => {
|
||||
// Production shape from with_team() + with_core() + with_canvas(): all five sections present.
|
||||
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 (modern) does NOT split on bracket [Team Instructions] preceded by only a single newline", () => {
|
||||
// The inline marker is "\n\n[Team Instructions]\n" — a single preceding newline
|
||||
// must be kept literal inside System, same guard as canvas/core.
|
||||
const framed = [
|
||||
"[System]",
|
||||
"Persona preamble.",
|
||||
"[Team Instructions]",
|
||||
"This is persona text, not a real team block.",
|
||||
].join("\n");
|
||||
const sections = parseSystemPromptSections(framed);
|
||||
assert.deepEqual(sections, [
|
||||
{
|
||||
title: "System",
|
||||
body: "Persona preamble.\n[Team Instructions]\nThis is persona text, not a real team block.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -61,15 +61,12 @@ export function parsePromptText(text: string): {
|
||||
* 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. 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.
|
||||
* `[Base]\n{base}\n\n[System]\n{persona}\n\n[Team Instructions]\n{team}\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.
|
||||
*
|
||||
* Four extraction passes before Base/System parsing:
|
||||
* Five extraction passes:
|
||||
*
|
||||
* 1. **Canvas** (`[Channel Canvas]`): appended last by `with_canvas()`.
|
||||
* - Start-of-string: canvas-only input.
|
||||
@@ -80,18 +77,23 @@ export function parsePromptText(text: string): {
|
||||
* 2. **Core** (`[Agent Memory — core]`): appended before canvas by `with_core()`.
|
||||
* Same two cases, same last-occurrence guard.
|
||||
*
|
||||
* 3. **Base/System**: remainder after canvas and core extraction.
|
||||
* 3. **Team Instructions** (`[Team Instructions]`): appended before core by
|
||||
* `with_team()` in `buzz-acp/src/pool.rs`. Same two cases (start-of-string
|
||||
* or `\n\n[Team Instructions]\n` inline), same last-occurrence guard. Output
|
||||
* position: after System, before Core Memory.
|
||||
*
|
||||
* 4. **Base/System**: remainder after the three top-level section extractions.
|
||||
* 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`.
|
||||
* 5. **Legacy Team Instructions** (backward compat): if the `System` body
|
||||
* contains the exact canonical delimiter `\n\n---\n# Team Instructions\n`
|
||||
* (produced by the now-removed `compose_prompt()` in buzz-persona), the body
|
||||
* is split at the **last** occurrence of that boundary. 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,
|
||||
@@ -133,7 +135,29 @@ export function parseSystemPromptSections(
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Parse Base/System from the remaining prefix ────────────────────────
|
||||
// ── 3. Extract [Team Instructions] (modern runtime framing) ─────────────
|
||||
// with_team() in buzz-acp/src/pool.rs appends "\n\n[Team Instructions]\n{instructions}"
|
||||
// after [System] and before core/canvas. Same two cases as canvas/core:
|
||||
// start-of-string (team-only input) or the inline double-newline marker
|
||||
// (last occurrence guards against embedded lookalikes preceded by a single \n).
|
||||
const TEAM_HEADER = "[Team Instructions]";
|
||||
const TEAM_MARKER_INLINE = `\n\n${TEAM_HEADER}\n`;
|
||||
let modernTeamBody: string | null = null;
|
||||
|
||||
if (remainder.startsWith(`${TEAM_HEADER}\n`)) {
|
||||
modernTeamBody = remainder.slice(`${TEAM_HEADER}\n`.length).trim();
|
||||
remainder = "";
|
||||
} else {
|
||||
const lastTeam = remainder.lastIndexOf(TEAM_MARKER_INLINE);
|
||||
if (lastTeam !== -1) {
|
||||
modernTeamBody = remainder
|
||||
.slice(lastTeam + TEAM_MARKER_INLINE.length)
|
||||
.trim();
|
||||
remainder = remainder.slice(0, lastTeam);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 4. 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}")
|
||||
@@ -181,7 +205,9 @@ export function parseSystemPromptSections(
|
||||
}
|
||||
}
|
||||
|
||||
// ── 4. Append core and canvas sections in producer order ──────────────────
|
||||
// ── 5. Append team (modern), core, and canvas sections in producer order ──
|
||||
if (modernTeamBody)
|
||||
sections.push({ title: "Team Instructions", body: modernTeamBody });
|
||||
if (coreBody) sections.push({ title: "Core Memory", body: coreBody });
|
||||
if (canvasBody) sections.push({ title: "Channel Canvas", body: canvasBody });
|
||||
|
||||
|
||||
@@ -418,7 +418,7 @@ test.describe("observer feed screenshots", () => {
|
||||
method: "session/new",
|
||||
params: {
|
||||
systemPrompt:
|
||||
"[Base]\nYou are a helpful AI assistant running in Buzz.\n\n[System]\nYou are Observer Agent. You coordinate multi-agent workflows in the #agents channel.\n\n---\n# Team Instructions\nAlways tag on handoff.\n\n[Agent Memory — core]\nI am Observer Agent.\n## Lessons Learned\nAlways tag on handoff.\n\n[Channel Canvas]\nCanvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\nLast modified: 2026-07-11T10:00:00Z\nFetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
|
||||
"[Base]\nYou are a helpful AI assistant running in Buzz.\n\n[System]\nYou are Observer Agent. You coordinate multi-agent workflows in the #agents channel.\n\n[Team Instructions]\nAlways tag on handoff.\n\n[Agent Memory — core]\nI am Observer Agent.\n## Lessons Learned\nAlways tag on handoff.\n\n[Channel Canvas]\nCanvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\nLast modified: 2026-07-11T10:00:00Z\nFetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -698,7 +698,7 @@ test.describe("observer feed screenshots", () => {
|
||||
method: "session/new",
|
||||
params: {
|
||||
systemPrompt:
|
||||
"[Base]\nYou are a helpful AI assistant running in Buzz.\n\n[System]\nYou are Observer Agent. You coordinate multi-agent workflows in the #agents channel.\n\n---\n# Team Instructions\nAlways tag on handoff.\n\n[Agent Memory — core]\nI am Observer Agent.\n## Lessons Learned\nAlways tag on handoff.\n\n[Channel Canvas]\nCanvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\nLast modified: 2026-07-11T10:00:00Z\nFetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
|
||||
"[Base]\nYou are a helpful AI assistant running in Buzz.\n\n[System]\nYou are Observer Agent. You coordinate multi-agent workflows in the #agents channel.\n\n[Team Instructions]\nAlways tag on handoff.\n\n[Agent Memory — core]\nI am Observer Agent.\n## Lessons Learned\nAlways tag on handoff.\n\n[Channel Canvas]\nCanvas revision (event ID): a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\nLast modified: 2026-07-11T10:00:00Z\nFetch current content with: buzz canvas get --channel 94a444a4-c0a3-5966-ab05-530c6ddc2301",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user