Let the install set how many steps a run may take

The two limits were compiled in: a task run stopped at
AppData.HarnessMaxToolSteps (200) and a chat reply at
AppData.BotReplyMaxToolSteps (8), so an install that needed a longer task
had to rebuild.

Both are now `app_settings` rows (`harness_max_tool_steps` and
`bot_reply_max_tool_steps`), read at the two loop call sites through
grabHarnessMaxToolSteps and grabReplyMaxToolSteps, and edited by the super
admin on Settings > General beside the prompt appendix. Zero clears a
limit back to the code default, like the bot file quota.

readStepLimit keeps the saved number only when it is a whole number of at
least 1 and no more than AppData.BotStepLimitMax (1000), so a blank, a
zero or a typo falls back instead of stopping every run before its first
turn, and a huge number still ends in the wrap-up report.

runTextLoop keeps AppData.HarnessMaxToolSteps as its default, so it stays
free of database reads and its tests run as they did.
This commit is contained in:
2026-10-23 16:20:38 +01:00
parent 06586a6dc8
commit 024d847b9a
16 changed files with 420 additions and 5 deletions
@@ -0,0 +1,75 @@
import { AppData } from "@/src/data/app-data";
import { BotReplyMaxToolStepsSettingKey } from "@/src/dict/bot-dict";
import { HarnessMaxToolStepsSettingKey } from "@/src/dict/harness-dict";
import readStepLimit from "@/src/utils/read-step-limit";
import grabAppSetting from "./grab-app-setting";
/**
* Model turns a task run may take: the install's own limit
* (`harness_max_tool_steps`, Settings > General), else
* `AppData.HarnessMaxToolSteps`. `runTextLoop`'s default is the code's one,
* so a task run passes this in.
*/
export async function grabHarnessMaxToolSteps() {
return readStepLimit({
saved: await grabAppSetting({ key: HarnessMaxToolStepsSettingKey }),
fallback: AppData.HarnessMaxToolSteps,
max: AppData.BotStepLimitMax,
});
}
/**
* Model turns a chat reply may take: the install's own limit
* (`bot_reply_max_tool_steps`, Settings > General), else
* `AppData.BotReplyMaxToolSteps`.
*/
export async function grabReplyMaxToolSteps() {
return readStepLimit({
saved: await grabAppSetting({ key: BotReplyMaxToolStepsSettingKey }),
fallback: AppData.BotReplyMaxToolSteps,
max: AppData.BotStepLimitMax,
});
}
/**
* Both limits with their defaults, for the settings page.
*/
export default async function grabRunLimitsOverview() {
const [task_steps, reply_steps] = await Promise.all([
grabHarnessMaxToolSteps(),
grabReplyMaxToolSteps(),
]);
const overview: RunLimitsOverview = {
task_steps,
reply_steps,
task_default: AppData.HarnessMaxToolSteps,
reply_default: AppData.BotReplyMaxToolSteps,
max: AppData.BotStepLimitMax,
};
return overview;
}
export type RunLimitsOverview = {
/**
* The limit a task run uses right now.
*/
task_steps: number;
/**
* The limit a chat reply uses right now.
*/
reply_steps: number;
/**
* What a task run uses when the install saved no limit.
*/
task_default: number;
/**
* What a chat reply uses when the install saved no limit.
*/
reply_default: number;
/**
* The most the settings page accepts.
*/
max: number;
};