Skip to content

Commit 14cbcfb

Browse files
Preserve all four sub-agent report headings with None. for empty sections (#934)
1 parent b52aaa2 commit 14cbcfb

4 files changed

Lines changed: 44 additions & 10 deletions

File tree

src/agent/prompts.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ describe("shared discipline block appears exactly once per built prompt", () =>
128128
});
129129
});
130130

131+
describe("sub-agent report contract", () => {
132+
it("requires all four headings with None. instead of omitting empty sections", () => {
133+
const prompt = buildSubAgentSystemPrompt(undefined, undefined, undefined, {
134+
orchestrator: false,
135+
grokAntiThrash: false,
136+
});
137+
expect(prompt).not.toContain("omit empty sections");
138+
expect(prompt).toMatch(/emit all four headings/);
139+
expect(prompt).toContain('"None."');
140+
});
141+
});
142+
131143
describe("shared verification guidance", () => {
132144
it("requires evidence-carrying verification in worker prompts", () => {
133145
const prompt = buildSubAgentSystemPrompt(undefined, undefined, undefined, {

src/agent/prompts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ export function buildSubAgentReportContract(
441441
"- Stick to the dispatch brief. Do not invent scope or wander into unrelated work.",
442442
"- If the brief lists Success criteria, treat them as the done-definition: when all are met (or you are blocked), stop calling tools and emit the report envelope. Do not keep tooling past done.",
443443
"- If the brief lists Do not, respect those constraints; do not invent scope outside Intent / Do not.",
444-
"- When done, stop calling tools and reply with ONLY this markdown envelope (prose inside each section is fine; omit empty sections rather than inventing content):",
444+
'- When done, stop calling tools and reply with ONLY this markdown envelope (prose inside each section is fine; emit all four headings every time in this order, writing "None." under a heading with nothing to report rather than dropping it):',
445445
"",
446446
"## Summary",
447447
"One or two sentences: what you accomplished or concluded.",

src/subagent/index.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,9 @@ describe("sub-agent stop helpers", () => {
692692
const cancelledParsedEmpty = parseSubAgentReport(emptyCancelled);
693693
expect(cancelledParsedEmpty.findings).toContain("no partial findings");
694694
expect(emptyCancelled.toLowerCase()).not.toContain("summarize progress");
695+
// Empty Paths still renders its heading so the envelope stays complete.
696+
expect(hasReportEnvelope(emptyCancelled)).toBe(true);
697+
expect(emptyCancelled).toContain("## Paths\nNone.");
695698

696699
// Nested agent envelope must not clobber the outer cancelled Summary when
697700
// runSubAgent re-parses the forced stop.
@@ -828,6 +831,25 @@ describe("sub-agent stop helpers", () => {
828831
expect(withPathsParsed.findings).toContain("src/a.ts");
829832
});
830833

834+
test("round-trip preserves the envelope when a section body is empty", () => {
835+
const reply = [
836+
"## Summary",
837+
"Did the work.",
838+
"",
839+
"## Findings",
840+
"Touched the gate.",
841+
"",
842+
"## Blockers",
843+
"",
844+
"## Paths",
845+
"src/gate.ts",
846+
].join("\n");
847+
expect(hasReportEnvelope(reply)).toBe(true);
848+
const roundTripped = formatSubAgentReport(parseSubAgentReport(reply));
849+
expect(hasReportEnvelope(roundTripped)).toBe(true);
850+
expect(parseSubAgentReport(roundTripped).blockers).toBe("None.");
851+
});
852+
831853
test("forcedStopReport renders a Stopped line for display; classification uses the typed reason", () => {
832854
expect(
833855
forcedStopReport("cancelled", "partial", { detail: "Session closed" }),

src/subagent/report.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,15 @@ export function formatSubAgentReport(report: SubAgentReport): string {
264264
lines.push(
265265
"## Summary",
266266
report.summary.length > 0 ? report.summary : "(no summary)",
267+
"",
268+
"## Findings",
269+
report.findings.length > 0 ? report.findings : "None.",
270+
"",
271+
"## Blockers",
272+
report.blockers.length > 0 ? report.blockers : "None.",
273+
"",
274+
"## Paths",
275+
report.paths.length > 0 ? report.paths : "None.",
267276
);
268-
if (report.findings.length > 0) {
269-
lines.push("", "## Findings", report.findings);
270-
}
271-
if (report.blockers.length > 0) {
272-
lines.push("", "## Blockers", report.blockers);
273-
}
274-
if (report.paths.length > 0) {
275-
lines.push("", "## Paths", report.paths);
276-
}
277277
return lines.join("\n");
278278
}

0 commit comments

Comments
 (0)