Keep a record of each run's tool calls so bots can't claim work they never did

The wrap-up turn now gets the run's actual tool calls, each bot message
keeps them as bot_messages.tool_log, and history follows recorded bot
messages with an [App record] line. The coordinator also gets update_bot
in workspaces, so it can fill a bot's core prompt from there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-25 11:32:56 +01:00
co-authored by Claude Opus 5.5
parent 93896646c7
commit d7f88d8dd3
22 changed files with 293 additions and 19 deletions
+28
View File
@@ -130,6 +130,13 @@ describe("runTextLoop", () => {
expect(seen).toHaveLength(3);
expect(seen[2]!.tools).toBe(undefined);
expect(seen[2]!.messages.at(-1)).toMatchObject({ role: "user" });
expect(seen[2]!.messages.at(-1)!.content).toContain(
"The tools you actually called this run: remember x2.",
);
expect(result.tool_uses).toEqual([
{ name: "remember", ok: true },
{ name: "remember", ok: true },
]);
expect(result.text).toBe("Ran out of room; here's where I got to.");
expect(result.messages.at(-1)).toEqual({
role: "assistant",
@@ -137,6 +144,27 @@ describe("runTextLoop", () => {
});
});
test("records failed tool calls, and tells the wrap up so", async () => {
const { generate, seen } = scripted([
{ tool_calls: [{ id: "c", name: "missing_tool", arguments: "{}" }] },
{ text: "Couldn't do it." },
]);
const result = await runTextLoop({
system: "s",
messages: [{ role: "user", content: "go" }],
tools: [remember],
max_steps: 1,
generate,
resolveCandidates,
});
expect(result.tool_uses).toEqual([{ name: "missing_tool", ok: false }]);
expect(seen[1]!.messages.at(-1)!.content).toContain(
"The tools you actually called this run: missing_tool (failed).",
);
});
test("stops when cancelled", async () => {
const controller = new AbortController();
controller.abort();