Home/
Part XIII — Expert Mode: Systems, Agents, and Automation/37. Agentic Workflows (Without Letting It Run Wild)/37.4 Termination conditions and budgets
37.4 Termination conditions and budgets
Overview and links for this section of the guide.
On this page
Why Budgets Matter
Without budgets, an agent can:
- Run indefinitely, burning money
- Get stuck in infinite loops
- Expand scope beyond the original task
- Consume your entire API quota
Budgets are your kill switch.
Types of Budgets
| Budget Type | What It Limits | Typical Values |
|---|---|---|
| Step Budget | Number of agent iterations | 5-20 steps |
| Token Budget | Total tokens consumed | 50K-500K tokens |
| Time Budget | Wall-clock execution time | 30s-5min |
| Cost Budget | Dollar amount spent | $0.10-$5.00 |
| Tool Budget | Number of tool calls | 10-50 calls |
// budget-config.ts
interface AgentBudget {
maxSteps: number;
maxTokens: number;
maxDurationMs: number;
maxCostUsd: number;
maxToolCalls: number;
// Per-tool limits
toolLimits?: {
[toolName: string]: number;
};
}
const DEFAULT_BUDGET: AgentBudget = {
maxSteps: 15,
maxTokens: 100000,
maxDurationMs: 120000, // 2 minutes
maxCostUsd: 1.00,
maxToolCalls: 30,
toolLimits: {
'write_file': 5,
'run_command': 10,
'search_web': 3
}
};
Termination Conditions
An agent should stop when any of these are true:
// termination.ts
interface TerminationCheck {
shouldTerminate: boolean;
reason: string;
graceful: boolean; // Did we finish the task?
}
function checkTermination(state: AgentState, budget: AgentBudget): TerminationCheck {
// 1. Task completed successfully
if (state.goalAchieved) {
return { shouldTerminate: true, reason: 'Goal achieved', graceful: true };
}
// 2. Model says it's done
if (state.lastAction === 'finish') {
return { shouldTerminate: true, reason: 'Agent declared completion', graceful: true };
}
// 3. Step budget exceeded
if (state.steps >= budget.maxSteps) {
return { shouldTerminate: true, reason: `Step limit (${budget.maxSteps}) reached`, graceful: false };
}
// 4. Token budget exceeded
if (state.tokensUsed >= budget.maxTokens) {
return { shouldTerminate: true, reason: `Token limit (${budget.maxTokens}) reached`, graceful: false };
}
// 5. Time budget exceeded
const elapsed = Date.now() - state.startTime;
if (elapsed >= budget.maxDurationMs) {
return { shouldTerminate: true, reason: `Time limit (${budget.maxDurationMs}ms) reached`, graceful: false };
}
// 6. Loop detection
if (detectLoop(state.history)) {
return { shouldTerminate: true, reason: 'Loop detected', graceful: false };
}
// 7. Consecutive failures
if (state.consecutiveFailures >= 3) {
return { shouldTerminate: true, reason: 'Too many consecutive failures', graceful: false };
}
return { shouldTerminate: false, reason: '', graceful: false };
}
function detectLoop(history: AgentStep[]): boolean {
if (history.length < 4) return false;
const recent = history.slice(-4);
const actions = recent.map(s => s.action?.tool).join(',');
const prevActions = history.slice(-8, -4).map(s => s.action?.tool).join(',');
return actions === prevActions;
}
Implementation
// budgeted-agent.ts
export class BudgetedAgent {
private budget: AgentBudget;
private state: AgentState;
async run(task: string): Promise {
this.state = {
steps: 0,
tokensUsed: 0,
startTime: Date.now(),
history: [],
consecutiveFailures: 0,
goalAchieved: false
};
while (true) {
// Check termination BEFORE each step
const termCheck = checkTermination(this.state, this.budget);
if (termCheck.shouldTerminate) {
return {
success: termCheck.graceful,
reason: termCheck.reason,
history: this.state.history
};
}
try {
// Execute one step
const step = await this.executeStep(task);
// Track usage
this.state.steps++;
this.state.tokensUsed += step.tokensUsed;
this.state.history.push(step);
this.state.consecutiveFailures = 0;
// Check if goal achieved
if (step.action === 'finish') {
this.state.goalAchieved = true;
}
} catch (error) {
this.state.consecutiveFailures++;
this.state.history.push({
thought: 'Error occurred',
action: null,
observation: error.message
});
}
}
}
// Real-time budget reporting
getBudgetStatus(): BudgetStatus {
const elapsed = Date.now() - this.state.startTime;
return {
steps: { used: this.state.steps, limit: this.budget.maxSteps },
tokens: { used: this.state.tokensUsed, limit: this.budget.maxTokens },
time: { used: elapsed, limit: this.budget.maxDurationMs },
percentUsed: Math.max(
this.state.steps / this.budget.maxSteps,
this.state.tokensUsed / this.budget.maxTokens,
elapsed / this.budget.maxDurationMs
) * 100
};
}
}
Always Fail Safe
When a budget is exceeded, terminate cleanly. Return partial results, explain what happened, and don't leave the system in a broken state.