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
12 changes: 12 additions & 0 deletions src/agent/prompts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ describe("shared discipline block appears exactly once per built prompt", () =>
});
});

describe("sub-agent report contract", () => {
it("requires all four headings with None. instead of omitting empty sections", () => {
const prompt = buildSubAgentSystemPrompt(undefined, undefined, undefined, {
orchestrator: false,
grokAntiThrash: false,
});
expect(prompt).not.toContain("omit empty sections");
expect(prompt).toMatch(/emit all four headings/);
expect(prompt).toContain('"None."');
});
});

describe("shared verification guidance", () => {
it("requires evidence-carrying verification in worker prompts", () => {
const prompt = buildSubAgentSystemPrompt(undefined, undefined, undefined, {
Expand Down
2 changes: 1 addition & 1 deletion src/agent/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ export function buildSubAgentReportContract(
"- Stick to the dispatch brief. Do not invent scope or wander into unrelated work.",
"- 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.",
"- If the brief lists Do not, respect those constraints; do not invent scope outside Intent / Do not.",
"- 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):",
'- 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):',
"",
"## Summary",
"One or two sentences: what you accomplished or concluded.",
Expand Down
22 changes: 22 additions & 0 deletions src/subagent/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,9 @@ describe("sub-agent stop helpers", () => {
const cancelledParsedEmpty = parseSubAgentReport(emptyCancelled);
expect(cancelledParsedEmpty.findings).toContain("no partial findings");
expect(emptyCancelled.toLowerCase()).not.toContain("summarize progress");
// Empty Paths still renders its heading so the envelope stays complete.
expect(hasReportEnvelope(emptyCancelled)).toBe(true);
expect(emptyCancelled).toContain("## Paths\nNone.");

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

test("round-trip preserves the envelope when a section body is empty", () => {
const reply = [
"## Summary",
"Did the work.",
"",
"## Findings",
"Touched the gate.",
"",
"## Blockers",
"",
"## Paths",
"src/gate.ts",
].join("\n");
expect(hasReportEnvelope(reply)).toBe(true);
const roundTripped = formatSubAgentReport(parseSubAgentReport(reply));
expect(hasReportEnvelope(roundTripped)).toBe(true);
expect(parseSubAgentReport(roundTripped).blockers).toBe("None.");
});

test("forcedStopReport renders a Stopped line for display; classification uses the typed reason", () => {
expect(
forcedStopReport("cancelled", "partial", { detail: "Session closed" }),
Expand Down
18 changes: 9 additions & 9 deletions src/subagent/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,15 @@ export function formatSubAgentReport(report: SubAgentReport): string {
lines.push(
"## Summary",
report.summary.length > 0 ? report.summary : "(no summary)",
"",
"## Findings",
report.findings.length > 0 ? report.findings : "None.",
"",
"## Blockers",
report.blockers.length > 0 ? report.blockers : "None.",
"",
"## Paths",
report.paths.length > 0 ? report.paths : "None.",
);
if (report.findings.length > 0) {
lines.push("", "## Findings", report.findings);
}
if (report.blockers.length > 0) {
lines.push("", "## Blockers", report.blockers);
}
if (report.paths.length > 0) {
lines.push("", "## Paths", report.paths);
}
return lines.join("\n");
}
Loading