fix(task): let task.softRequestBudget lower bundled subagent budgets

The soft request budget resolved to `SOFT_REQUEST_BUDGET[agent.name] ??
configured`, so the bundled entries for scout and sonic replaced the
configured value outright. Lowering `task.softRequestBudget` to tighten
the guard therefore did nothing for exactly the two agents that spawn
most often: a scout kept its 100-request budget no matter how small the
user set the knob. Only 0 (disable) and raising the value for
non-bundled agents had any effect.

Treat both numbers as upper bounds and take the smaller one. The bundled
entries stay ceilings, so a runaway scout is still stopped at 100 by
default and existing behavior is unchanged for anyone who has not
lowered the setting; a configured 0 still disables the guard entirely.
Resolution moves into `resolveSoftRequestBudget`, which also normalizes
negative and fractional inputs, so the rule is testable without standing
up a subprocess run.

This composes with `task.maxEffort` on a separate axis: effort caps how
hard each request thinks, this caps how many requests a run may spend.

(cherry picked from commit f0db29f8f725f11390b64ca9342300c482ff5c5d)
This commit is contained in:
Nik Divjak
2026-07-29 23:09:01 +02:00
committed by can1357
parent 44907cef75
commit c3011fff3c
4 changed files with 47 additions and 8 deletions
+17 -4
View File
@@ -87,8 +87,10 @@ const MCP_CALL_TIMEOUT_MS = 60_000;
* agent is driven to one forced final `yield` so partial findings come back
* as a real report; only if it still refuses to yield within
* {@link BUDGET_STOP_GRACE_REQUESTS} more requests is the run hard-aborted.
* The `default` key applies to agents without an explicit entry and can be
* overridden via the `task.softRequestBudget` setting (0 disables the guard).
* Entries are ceilings, not fixed values: the `default` key applies to agents
* without an explicit entry, and the `task.softRequestBudget` setting can only
* lower an agent's budget, never raise it above its bundled entry (0 disables
* the guard entirely).
*/
export const SOFT_REQUEST_BUDGET: Record<string, number> = {
scout: 100,
@@ -96,6 +98,18 @@ export const SOFT_REQUEST_BUDGET: Record<string, number> = {
default: 200,
};
/**
* Resolves the effective soft request budget for an agent. The configured
* `task.softRequestBudget` and the agent's bundled entry are both upper
* bounds, so the tighter one wins; a configured budget of 0 disables the
* guard regardless of the bundled entry.
*/
export function resolveSoftRequestBudget(agentName: string, configuredBudget: number): number {
const normalized = Math.max(0, Math.trunc(configuredBudget));
if (normalized === 0) return 0;
return Math.min(normalized, SOFT_REQUEST_BUDGET[agentName] ?? normalized);
}
/** Extra requests allowed after a budget stop for the forced yield to land before the run is hard-aborted. */
export const BUDGET_STOP_GRACE_REQUESTS = 5;
@@ -2446,8 +2460,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
0,
Math.trunc(Number(settings.get("task.softRequestBudget") ?? SOFT_REQUEST_BUDGET.default) || 0),
);
const softRequestBudget =
configuredDefaultBudget === 0 ? 0 : (SOFT_REQUEST_BUDGET[agent.name] ?? configuredDefaultBudget);
const softRequestBudget = resolveSoftRequestBudget(agent.name, configuredDefaultBudget);
const softRequestBudgetNotice = settings.get("task.softRequestBudgetNotice") ?? false;
const parentDepth = options.taskDepth ?? 0;
const childDepth = parentDepth + 1;