feat(x): configurable model-call limits by workflow (#768)

Replace the hardcoded 20-call turn budget with user settings: a global
model-call limit that every turn inherits (headless/knowledge work,
scheduled agents, sub-agents) plus an optional chat-only override for
interactive turns. Settings live in config/turn_limits.json (1-100,
default 20) and are edited from a new Advanced tab in Settings.

The limit is resolved at turn creation via an ITurnLimitsResolver
injected into TurnRuntime, keyed on humanAvailable; explicit per-call
overrides still win, and the resolved value is persisted in
turn_created.config.maxModelCalls as before, so historical and
in-flight turns are unaffected by settings changes. Sub-agents now
default to and are capped by the global limit instead of the constant.
This commit is contained in:
Gagan 2026-07-21 15:25:21 +05:30
parent 63a99e5fe4
commit eece1af90e
16 changed files with 518 additions and 16 deletions

View file

@ -19,6 +19,7 @@ export * as browserControl from './browser-control.js';
export * as billing from './billing.js';
export * as credits from './credits.js';
export * as notificationSettings from './notification-settings.js';
export * as turnLimits from './turn-limits.js';
export * as codeSessions from './code-sessions.js';
export * as channels from './channels.js';
export * as rowboatApp from './rowboat-app.js';

View file

@ -25,6 +25,7 @@ import { CreditActivatedEventSchema, CreditsStateSchema, ReferralClaimResultSche
import { EmailBlockSchema, GmailThreadSchema } from './blocks.js';
import { PermissionDecision, ApprovalPolicy, CodingAgent, type CodeRunFeedEvent } from './code-mode.js';
import { NotificationSettingsSchema } from './notification-settings.js';
import { TurnLimitsSettingsSchema } from './turn-limits.js';
import { CodeProject, CodeSession, CodeSessionMode, CodeSessionStatus, GitRepoInfo, GitStatusFile, CodeAgentModelOptions } from './code-sessions.js';
import { ChannelsConfig, ChannelsStatus } from './channels.js';
@ -2455,6 +2456,17 @@ const ipcSchemas = {
success: z.literal(true),
}),
},
// Model-call limit settings channels
'turnLimits:getSettings': {
req: z.null(),
res: TurnLimitsSettingsSchema,
},
'turnLimits:setSettings': {
req: TurnLimitsSettingsSchema,
res: z.object({
success: z.literal(true),
}),
},
} as const;
// ============================================================================

View file

@ -0,0 +1,34 @@
import { z } from 'zod';
import { DEFAULT_MAX_MODEL_CALLS } from './turns.js';
/**
* User-configurable model-call budgets (see issue #768).
*
* - maxModelCalls: the global limit every turn inherits by default,
* including headless/knowledge work and spawned sub-agents (it is also
* the cap a parent can grant a sub-agent).
* - chatMaxModelCalls: optional override for interactive chat turns only;
* when absent, chat uses the global limit.
*
* Changing these affects only newly created turns each turn persists its
* resolved limit in turn_created.config.maxModelCalls.
*/
export const MIN_MODEL_CALL_LIMIT = 1;
export const MAX_MODEL_CALL_LIMIT = 100;
const limit = z
.number()
.int()
.min(MIN_MODEL_CALL_LIMIT)
.max(MAX_MODEL_CALL_LIMIT);
export const TurnLimitsSettingsSchema = z.object({
maxModelCalls: limit,
chatMaxModelCalls: limit.optional(),
});
export const DEFAULT_TURN_LIMITS_SETTINGS: TurnLimitsSettings = {
maxModelCalls: DEFAULT_MAX_MODEL_CALLS,
};
export type TurnLimitsSettings = z.infer<typeof TurnLimitsSettingsSchema>;