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
+7 -3
View File
@@ -89,8 +89,9 @@ type Params = {
* computers, and which name, role and core instructions each crew member
* gets, and what goes) and the install's skills, MCP servers and custom
* tools (`grabCoordinatorExtensionTools`) or, in a workspace,
* set_bot_workspace_instructions, plus set_bot_type to make a worker a
* workspace's admin. A workspace admin, in the workspace it leads, gets
* set_bot_workspace_instructions and update_bot for the workspace's crew
* (a bot's core prompt is asked for from wherever the user is), plus
* set_bot_type to make a worker a workspace's admin. A workspace admin, in the workspace it leads, gets
* `grabWorkspaceAdminTools`. Every gate reads the bot's types (`powers`),
* never `bots.kind`. Anything else on a computer goes through a task,
* never mid-reply.
@@ -182,7 +183,10 @@ export default async function grabBotChatTools({
requester_name: bot.name || "The coordinator",
workers,
}));
tools.push(setWorkspaceInstructionsTool({ user_id, workspace_id, workers }));
tools.push(
setWorkspaceInstructionsTool({ user_id, workspace_id, workers }),
updateBotTool({ user_id, workers }),
);
if (powers.can("set_bot_types")) {
tools.push(setBotTypeTool({ user_id, bot_id, workers, workspaces, workspace_id }));
@@ -39,7 +39,7 @@ export default function setWorkspaceInstructionsTool({
return {
name: "set_bot_workspace_instructions",
description: `Replace a crew member's instructions for this workspace with this full text (markdown): how they should do their job here, on top of their own instructions. Use it when the user tells you how someone should work in this workspace. It replaces what they had for it, so keep what still applies. Crew here: ${crew}.`,
description: `Replace a crew member's instructions for this workspace with this full text (markdown): how they should do their job here, on top of their own instructions. Use it when the user tells you how someone should work in this workspace. It replaces what they had for it, so keep what still applies. This is not the bot's own prompt: when the user means who the bot is and what it does everywhere ("its prompt", "its instructions", an empty prompt to fill), use update_bot where you have it. Crew here: ${crew}.`,
schema: z.object({
bot_id: z.number().int(),
instructions: z.string().trim().max(AppData.PromptMaxLength),
+1 -1
View File
@@ -35,7 +35,7 @@ export default function updateBotTool({ user_id, workers }: Params): HarnessTool
return {
name: "update_bot",
description: `Change an existing crew member: name, the one-line role it's shown with, and/or its core instructions (markdown: what they do and how they work, the start of their prompt in their own chat and in every workspace). Only the fields you pass change. Instructions replace what they had, so keep what still applies, their own edits included. This is not their instructions for one workspace: use set_bot_workspace_instructions for that. Crew: ${crew || "none"}.`,
description: `Change an existing crew member: name, the one-line role it's shown with, and/or its core instructions (markdown: what they do and how they work, the start of their prompt in their own chat and in every workspace). Only the fields you pass change. Instructions replace what they had, so keep what still applies, their own edits included. This is what "its prompt" means: use it when the user asks for a bot's prompt or instructions to be written or filled in. It is not their instructions for one workspace: use set_bot_workspace_instructions only for how they should work there. Crew: ${crew || "none"}.`,
schema: z
.object({
bot_id: z.number().int(),