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
@@ -24,8 +24,8 @@ type Params = {
/**
* Records a failed attempt. The job goes back to the queue with a backoff
* delay, or, once out of attempts (or `fatal`), is marked failed and the
* bot tells the user it needs them. Returns the job's new status and the
* failure notice, if one was sent.
* bot tells the user it needs them. Returns the job's new status, the
* attempts it has used and the failure notice, if one was sent.
*/
export default async function failBotJob({
job_id,
@@ -58,9 +58,10 @@ export default async function failBotJob({
}
const status = res.singleRes.status;
const attempts = Number(res.singleRes.attempts || 0);
if (status != "failed") {
return { status, notice: null };
return { status, attempts, notice: null };
}
const notice = await notifyBotJobFailed({
@@ -68,5 +69,5 @@ export default async function failBotJob({
failure_message,
});
return { status, notice };
return { status, attempts, notice };
}
@@ -0,0 +1,24 @@
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
bot_id: number | string;
session_id: number | string;
title: string;
};
/**
* The bot's task in this session with this title that's still queued or
* working, if any, so the same work isn't started twice.
*/
export default async function grabOpenBotTask({ bot_id, session_id, title }: Params) {
const res = await BunSQLite.sql<{ id: number; status: string }>({
sql: `SELECT id, status FROM bots_queue
WHERE bot_id = ? AND session_id = ? AND kind = 'task'
AND status IN ('pending', 'processing')
AND lower(trim(title)) = lower(trim(?))
ORDER BY id DESC LIMIT 1`,
values: [bot_id, session_id, title],
});
return res.payload?.[0] || null;
}