Skip to content

Stop Conditions and Execution Budgets

Why explicit stop logic matters

An agentic loop could otherwise run indefinitely:

observe
 ↓
decide
 ↓
act
 ↓
observe
 ↓
repeat...

The runtime must therefore know not only how to continue a run, but also when it must stop.

The model may propose:

STOP_SUCCESS
STOP_FAILED
CONTINUE

but hard termination enforcement belongs to the runtime.

The model proposes termination. The runtime owns termination.

Terminal states

Useful explicit states include:

COMPLETED
FAILED
CANCELLED
BLOCKED
EXPIRED
BUDGET_EXHAUSTED
WAITING_FOR_HUMAN
WAITING_FOR_EXTERNAL_EVENT

The WAITING_* states are usually suspended rather than terminal.

For example:

RUNNING
  ↓
approval required
  ↓
WAITING_FOR_HUMAN
  ↓
approval arrives
  ↓
RUNNING

Success stop conditions

A strong success condition is not:

LLM says: "Done."

but explicit evidence.

For a coding agent:

Goal:
Fix failing retry test.

Success:
- patch exists
- target test passes 20 consecutive runs
- related regression suite passes
- repository remains clean except intended changes

The runtime can enforce:

model proposes STOP_SUCCESS
        ↓
check success contract
        ↓
all true? → COMPLETED
otherwise → continue / replan

Failure stop conditions

Not every failure is retryable.

Examples:

NOT_AUTHORIZED
FORBIDDEN_ACTION
INVALID_GOAL
REQUIRED_RESOURCE_PERMANENTLY_MISSING
UNSAFE_TO_CONTINUE

In these cases, stopping or escalating is often correct. The agent should not search for a “clever workaround” to a hard authorization or safety boundary.

Cancellation

A user or system may cancel a run.

RUNNING
  ↓ cancel requested
CANCELLING
  ↓ cleanup / settle in-flight work
CANCELLED

Cancellation raises an important question:

What happens to a side effect that has already started?

An email may already have been sent and a deployment may already be running. Cancellation is not magical rollback.

The runtime should distinguish:

requested cancellation
vs
in-flight operation outcome
vs
possible compensation

Execution budgets

A budget is an explicit limit on loop autonomy.

Common dimensions include:

  • maximum iterations,
  • maximum model calls,
  • maximum tool calls,
  • wall-clock deadline,
  • maximum token usage,
  • maximum estimated or actual cost,
  • maximum retries,
  • per-tool quotas,
  • maximum nested sub-runs.

Example:

{
  "max_iterations": 20,
  "max_tool_calls": 40,
  "deadline_seconds": 300,
  "max_cost_usd": 2.50,
  "max_retries_per_action": 2
}

Hard vs soft budgets

Hard limit

Cannot be exceeded.

Example:

max_cost_usd = 5

The runtime may reject the next expensive call before executing it.

Soft limit

Can trigger a behavior change.

For example, at 70% of the cost budget:

- switch to a cheaper model
- reduce exploration
- ask the human whether to continue

These should be modeled separately.

Check budgets before a step

Weak:

call expensive model
 ↓
cost becomes $8
 ↓
check $5 budget

Better:

estimate/reserve step cost
       ↓
within remaining budget?
  ├── yes → execute
  └── no  → stop/escalate

Exact cost may not always be known in advance, but conservative estimates are useful.

Iteration limits

The simplest safety net is:

iteration <= N

but it is not enough. Ten iterations can still consume excessive money or produce many side effects.

A multidimensional budget is stronger:

iterations + tool calls + time + cost

No-progress detection

A runtime can stop or trigger replanning even when budget remains if the run is not making progress.

Examples:

iteration 4: search same file
iteration 5: search same file
iteration 6: search same file

or:

same tool
same arguments
same error
repeated 3 times

This is loop thrashing.

Useful progress signals include:

  • a new relevant observation,
  • a completed subgoal,
  • reduced uncertainty,
  • a changed artifact,
  • a passed verifier,
  • a new plan path.

Action fingerprints

Repeated actions can be detected with a fingerprint such as:

fingerprint = action_type + normalized arguments + relevant state version

For example:

query_logs(service=payment, range=1h)

Repeated with the same state and same result, this is suspicious.

Not every repetition is wrong: polling can be legitimate, so policy must consider context.

No-progress counters

For example:

{
  "consecutive_no_progress_steps": 3,
  "max_no_progress_steps": 4
}

When the threshold is reached, the runtime can:

replan
ask human
or stop BLOCKED

Time budget and deadlines

Distinguish active execution time from wall-clock waiting time.

A run may wait 30 minutes for approval without consuming active compute.

Possible limits:

active_execution_deadline = 5 min
approval_expiry = 24 h
run_expiry = 48 h

Nested-loop budgets

If skills or sub-agents create additional loops:

Parent run
  ↓
Sub-run A
  ↓
Sub-run B

it is dangerous for every child to receive a fresh full budget.

Weak:

parent max cost: $5
sub-agent A: $5
sub-agent B: $5

which may become $15.

Better:

parent budget
   ↓ allocate
sub-run budget slice

Hierarchical budgets should aggregate into the parent.

Tool-specific budgets

Some actions deserve special limits.

Examples:

web_search: max 20
production_restart: max 1 + approval
email_send: max 5

Not all tool calls have the same risk or cost.

Liveness vs safety

Two goals must be balanced.

Liveness

The system should make progress toward its goal.

Safety

It should not run forever or perform unnecessary or forbidden actions.

Too strict a budget causes premature stopping. Too loose a budget permits runaway loops. Choosing the right limits is also an evaluation problem.

Example: incident diagnosis

Budget:

max_iterations: 12
max_log_queries: 5
max_cost: $1.50
active_time: 3 min

Run:

1. query error rate
2. inspect deployment
3. query logs
4. query same logs with same filter
5. query same logs again

The runtime detects repeated no-progress behavior and triggers REPLAN.

If a new strategy still yields no useful evidence:

BLOCKED: insufficient diagnostic evidence

This is better than random tool roulette until the budget is exhausted.

Budget-exhaustion result

Do not return only a generic error.

{
  "status": "BUDGET_EXHAUSTED",
  "goal_progress": "PARTIAL",
  "completed": ["repository inspected", "root-cause candidates narrowed"],
  "remaining": ["verify candidate fix"],
  "reason": "maximum tool-call budget reached"
}

A human or later run can continue from this state.

Stop-condition ordering

At a step boundary, the runtime may check in an explicit order such as:

1. cancellation requested?
2. unsafe / forbidden state?
3. hard budget exhausted?
4. success conditions satisfied?
5. terminal failure?
6. no-progress threshold?
7. continue

The exact order is domain-specific; making it explicit is what matters.

Anti-pattern: budget enforcement through prose

Prompt:
"Please do not use more than 10 tool calls."

This is not a hard guarantee.

The runtime should count:

tool_calls_used += 1

and physically reject the 11th call.

Anti-pattern: only max iterations

max_iterations=100 says nothing about:

  • cost,
  • side effects,
  • tool bursts,
  • nested agents,
  • wall-clock duration.

Use multidimensional budgets.

Anti-pattern: success hallucination

The model says:

"The fix should work now, so we're done."

If the success contract requires tests, then:

no test evidence → not complete

Completion evidence is stronger than model confidence.

Takeaways

  • Stop logic and budget enforcement belong to the runtime.
  • COMPLETED, FAILED, BLOCKED, CANCELLED, and BUDGET_EXHAUSTED should be explicit outcomes.
  • The model can propose stopping, but application code verifies hard success/failure contracts.
  • Budgets should cover iterations, time, tools, tokens, cost, and retries.
  • Distinguish hard and soft limits.
  • Check budgets before actions when possible.
  • No-progress detection is better than blindly exhausting an iteration limit.
  • Nested loops/sub-agents should consume slices of the parent budget.
  • Cancellation is not automatic rollback; side effects and compensation are separate concerns.
  • Completion evidence is more reliable than the model claiming it is done.