Make routines act on their own and check every bot action

Scheduled runs are pre-approved: they carry out their brief instead of
drafting for confirmation. A run that can't do its brief calls
report_run_failed and is retried 5 and 10 minutes later; only then does
the user get one "failed after N tries" message, and the routine keeps
why in last_error.

Every reply and task now gets ActionCheckNote: look for an existing
record before creating one, and verify an action took effect before
saying it's done. Memory and instructions saves are read back, and
create_bot, create_schedule, start_task and delegate_task refuse
duplicates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-20 08:45:49 +01:00
co-authored by Claude Opus 5.5
parent 6c57fae444
commit 535adb6e16
25 changed files with 448 additions and 16 deletions
@@ -0,0 +1,47 @@
import BunSQLite from "@moduletrace/bun-sqlite";
export type BotSavedTextKind =
| "core_memory"
| "core_instructions"
| "user_memory"
| "workspace_memory"
| "workspace_instructions";
type Params = {
kind: BotSavedTextKind;
bot_id: number | string;
/**
* Whose memory, for `user_memory`.
*/
user_id?: number | string;
/**
* Which workspace, for the workspace kinds.
*/
workspace_id?: number | string | null;
};
/**
* Reads back one of a bot's memories or instructions as stored, so a save
* can be checked. Empty when there's none. The caller checks access.
*/
export default async function grabBotSavedText({ kind, bot_id, user_id, workspace_id }: Params) {
const query =
kind == "core_memory"
? { sql: `SELECT memory AS text FROM bots WHERE id = ?`, values: [bot_id] }
: kind == "core_instructions"
? { sql: `SELECT system_prompt AS text FROM bots WHERE id = ?`, values: [bot_id] }
: kind == "user_memory"
? {
sql: `SELECT memory AS text FROM bot_user_memories WHERE bot_id = ? AND user_id = ?`,
values: [bot_id, user_id ?? 0],
}
: {
sql: `SELECT ${kind == "workspace_memory" ? "memory" : "system_prompt"} AS text
FROM workspace_bots WHERE bot_id = ? AND workspace_id = ?`,
values: [bot_id, workspace_id ?? 0],
};
const res = await BunSQLite.sql<{ text: string | null }>({ ...query, sql: `${query.sql} LIMIT 1` });
return res.payload?.[0]?.text || "";
}