Q113: a goal audit approves only with the decision alone on its last line
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7e38414591
commit
0ae014af05
2 files changed
+36
-8
No files matched your search
@@ -173,7 +173,7 @@ async function runAudit(
|
||||
"The executor's claim:",
|
||||
evidence,
|
||||
`Inspect the work before deciding: list_files and read_file for the workspace, list_documents and read_document for the library. An approval counts only after you have read some of it. ${canRun ? "run_command runs in a throwaway copy of the workspace: build and run the tests yourself instead of trusting claims that they pass; nothing you do there reaches the real workspace." : "You cannot run commands here, so a claim that something was run or passes is unverified unless the workspace holds its results."} Judge outcomes, not artifacts about outcomes: plans, specs, status reports, and skeletons do not make a goal achieved; working deliverables do. If the goal promises software, look for the actual implementation, not documents describing it.`,
|
||||
"End your final message with exactly one marker: <approved/> if the goal is genuinely achieved, or <disapproved/> followed by one short sentence naming the decisive gap.",
|
||||
"Keep your reasoning apart from your decision. End your final message with the decision alone on its last line: <approved/> by itself if the goal is genuinely achieved, or <disapproved/> followed by one short sentence naming the decisive gap. Use no marker anywhere else.",
|
||||
].join("\n\n"),
|
||||
}];
|
||||
const instructions = "You are the independent completion auditor. You did not do the work and owe its author nothing; your only loyalty is to what actually exists. Be strict: an unverifiable claim is a false claim.";
|
||||
@@ -225,10 +225,14 @@ async function runAuditTool(deps: AgentToolDeps, ctx: AgentToolContext, call: To
|
||||
}
|
||||
}
|
||||
|
||||
/** Approved only when the last line is <approved/> alone and no other marker appears; quoted, indented or hedged markers do not count. */
|
||||
function parseVerdict(text: string): AuditVerdict {
|
||||
const reason = text.replace(/<\/?(approved|disapproved)\s*\/?>/gi, " ").replace(/\s+/g, " ").trim();
|
||||
if (/<approved\s*\/>/i.test(text)) return { approved: true, reason: reason || "no gaps found" };
|
||||
return { approved: false, reason: reason || "the auditor did not state an explicit approval" };
|
||||
const markers = text.match(/<\/?\s*(approved|disapproved)\s*\/?>/gi) ?? [];
|
||||
const reason = text.replace(/<\/?\s*(approved|disapproved)\s*\/?>/gi, " ").replace(/\s+/g, " ").trim();
|
||||
const lastLine = text.split("\n").map((line) => line.trimEnd()).filter(Boolean).at(-1);
|
||||
if (markers.length === 1 && lastLine === "<approved/>") return { approved: true, reason: reason || "no gaps found" };
|
||||
if (markers.some((marker) => /disapproved/i.test(marker))) return { approved: false, reason: reason || "the auditor disapproved without a reason" };
|
||||
return { approved: false, reason: reason ? `${reason} (no <approved/> alone on the last line)` : "the auditor did not end with <approved/> alone on the last line" };
|
||||
}
|
||||
|
||||
function actorIsCeoOrGoalOwner(deps: AgentToolDeps, ctx: AgentToolContext, ownerAgentId: string | null): boolean {
|
||||
|
||||
@@ -97,7 +97,7 @@ describe("complete_goal with independent audit", () => {
|
||||
const readResult = input.messages.at(-1);
|
||||
expect(readResult?.role).toBe("tool");
|
||||
expect(input.messages.at(-2)).toMatchObject({ role: "assistant", replay });
|
||||
return turnOf("The implementation exists and runs. <approved/>");
|
||||
return turnOf("The implementation exists and runs.\n<approved/>");
|
||||
};
|
||||
|
||||
const readOnly = argsFor(h, h.ceoId, auditor, call({ evidence: "src/index.ts implements the app and runs." }));
|
||||
@@ -128,7 +128,7 @@ describe("complete_goal with independent audit", () => {
|
||||
ranIn = results[1].output;
|
||||
return turnOf("Reading.", [{ id: "c3", name: "read_file", arguments: { path: "built.txt" } }]);
|
||||
}
|
||||
return turnOf("Tests pass when run. <approved/>");
|
||||
return turnOf("Tests pass when run.\n<approved/>");
|
||||
};
|
||||
const output = await completeGoalTool(argsFor(h, h.ceoId, auditor, call({ evidence: "All tests pass." })));
|
||||
expect(output).toContain("achieved");
|
||||
@@ -199,7 +199,7 @@ describe("complete_goal with independent audit", () => {
|
||||
const auditor = async (input: AuditorInput): Promise<AuditorTurn> => {
|
||||
const last = input.messages.at(-1);
|
||||
prompts.push(last && "text" in last ? String(last.text) : "");
|
||||
return turnOf("Looks done. <approved/>");
|
||||
return turnOf("Looks done.\n<approved/>");
|
||||
};
|
||||
const output = await completeGoalTool(argsFor(h, h.ceoId, auditor, call({ evidence: "Trust me." })));
|
||||
expect(output).toContain("audit rejected");
|
||||
@@ -221,7 +221,7 @@ describe("complete_goal with independent audit", () => {
|
||||
turnOf("History only.", [{ id: "c3", name: "read_document", arguments: { doc_id: docId, history: true } }]),
|
||||
turnOf("<approved/>"),
|
||||
turnOf("Reading the report.", [{ id: "c2", name: "read_document", arguments: { doc_id: docId } }]),
|
||||
turnOf("The report shows customers. <approved/>"),
|
||||
turnOf("The report shows customers.\n<approved/>"),
|
||||
];
|
||||
const seen: string[][] = [];
|
||||
const histories: AuditorInput["messages"][] = [];
|
||||
@@ -240,6 +240,30 @@ describe("complete_goal with independent audit", () => {
|
||||
h.connection.close();
|
||||
});
|
||||
|
||||
test("only a final message that ends in a single approval marker closes the goal", async () => {
|
||||
const verdicts: Array<[string, string]> = [
|
||||
["<approved/> on style, but <disapproved/> the tests fail.", "active"],
|
||||
["I would write <approved/> if the tests passed. They do not.", "active"],
|
||||
["Everything checks out: `<approved/>`", "active"],
|
||||
["<approved/> <approved/>", "active"],
|
||||
["The example approval token is:\n> <approved/>", "active"],
|
||||
["Verified. <approved/>", "active"],
|
||||
["Example:\n <approved/>", "active"],
|
||||
["The build and tests pass.\n<approved/>\n", "achieved"],
|
||||
];
|
||||
for (const [verdict, expected] of verdicts) {
|
||||
const h = createHarness();
|
||||
let turns = 0;
|
||||
const auditor = async (): Promise<AuditorTurn> => {
|
||||
turns += 1;
|
||||
return turns === 1 ? turnOf("Reading.", [{ id: "c1", name: "read_file", arguments: { path: "src/index.ts" } }]) : turnOf(verdict);
|
||||
};
|
||||
await completeGoalTool(argsFor(h, h.ceoId, auditor, call({ evidence: "src/index.ts works" })));
|
||||
expect([verdict, goalStatus(h)]).toEqual([verdict, expected]);
|
||||
h.connection.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("no explicit approval means not achieved", async () => {
|
||||
const h = createHarness();
|
||||
const auditor = async (): Promise<AuditorTurn> => turnOf("Looks plausible I guess.");
|
||||
|
||||
Reference in new issue
Block a user