Home » Agent Run Budgets: Setting Limits on Steps, Time, and Tool Calls

Agent Run Budgets: Setting Limits on Steps, Time, and Tool Calls

0 comments
A brass metronome beside three green geometric markers and an empty brass holder on a sunlit workbench.

An agent run needs independently enforced limits on how much work it may start, how long it may continue, and which tools it may call. When a limit stops the run, return the unfinished work and the stopping reason. Reaching a boundary is an execution outcome; task completion still depends on the task's acceptance criteria.

A search agent that retrieves three documents and stops at its call limit has produced three observations. It has not necessarily answered the question. That distinction should survive in the application state, the user interface, and any later continuation.

Count the resource you mean to control

“Ten steps” is ambiguous. A step might be a model invocation, a graph iteration, a tool call, or a batch containing several tool calls. State the unit and scope before picking a number.

LangChain's middleware documentation distinguishes per-invocation limits from limits accumulated across a thread, and supports global or tool-specific call counts. LangGraph's graph documentation defines its recursion limit in super-steps; parallel nodes can occupy the same super-step. These are different counters, even when their configured values happen to match.

For a synthetic repository investigation, a proposed policy might be:

Limit What it controls What it does not establish
12 model invocations per run Further model requests admitted by the orchestrator A fixed token count or exact invoice total
4 documentation searches Attempts to invoke that search tool Number of documents fetched inside the service
90 seconds of elapsed runtime Time available for new work and continuation That an already issued request was canceled
1 write operation requiring approval The workflow's allowed write count Whether the particular write is authorized

The numbers illustrate separate controls, not production defaults. A model call with a large input differs from a short one. A tool can launch several backend requests. Financial limits need provider usage accounting, pricing rules, reservations for work in flight, and whatever hard controls the service actually exposes.

Admit work before starting it

Enforce the budget in the executor, not only in an instruction asking the model to stop. Before a call starts, check its remaining allowance and reserve the resources it will consume. A retry is another attempt unless the accounting contract explicitly says otherwise.

Parallel execution makes this important. If three workers each see one remaining call, an ordinary read followed by an increment can admit all three. Use an atomic reservation or one owner of admission decisions. Decide how canceled reservations and failed attempts affect each counter; do not let failures silently buy unlimited retries.

A deadline needs a compatible timeout at the operation boundary. For an in-process elapsed duration, Python's monotonic clock is unaffected by system clock updates; its absolute reference is undefined. Pass the remaining duration to a supported timeout mechanism. Do not persist a raw monotonic reading as a portable deadline across machines or restarts.

Checking the time between calls cannot interrupt a blocked call. Cancellation and termination depend on the client, process, and remote operation. After a timeout, the external effect may still be unknown. A budget controller must preserve that uncertainty instead of declaring rollback.

Give a stopped run an honest result

A useful result separates execution state from confirmed progress:

{
  "status": "stopped",
  "reason": "step_limit",
  "completed": ["search-1", "search-2"],
  "remaining": ["fetch"],
  "counters": {"steps": 2, "tools": 2}
}

These are application fields, not a standardized agent protocol. In this example, each synthetic step performs one tool operation. A real run may need separate model, tool, and backend counters plus per-operation outcomes.

Store artifact references, unresolved effects, the active task, and the acceptance checks still missing. If a verification step has not run, say so. A model's closing sentence must not turn stopped into completed merely because it is fluent or helpful.

Designing tool calls that fail safely covers the result of an individual operation. The run controller adds the reason the overall workflow stopped and the work that remains across operations.

What the local stopping exercise checked

A Python fixture admitted synthetic operations from the list search-1, search-2, fetch. With a cumulative step ceiling of two, it recorded the first two and returned fetch as remaining. Resuming with the same counters and a deliberately increased ceiling of three admitted only that remaining operation.

Three additional checks covered an expired deadline, an exhausted tool allowance, and an empty plan. The expired and exhausted cases admitted no work; the empty plan completed without consuming resources. All five scenarios passed.

The fixture uses an injected clock and synchronous, in-memory state. It checks admission and continuation accounting. It does not run a model, sleep through a timeout, cancel a process, persist counters, exercise concurrent reservations, or measure spending. The policy's hard cases still need tests against the executor that will run them.

Decide what continuation is allowed to reset

A user returning to a thread should not accidentally reset a lifetime allowance. Conversely, a per-request latency target may reasonably start again with a new request. Keep those scopes explicit. Persist cumulative counters with the run identity, and record any approved budget extension as a change to the policy.

Before resuming, reconcile unknown effects and check whether the original task and authority still apply. A larger budget does not authorize a new destination, new data access, or publication. Preserve useful partial work so the next invocation can continue without paying to rediscover it.

Choose initial limits from representative work and observe stopped runs as well as successful ones. If the ceiling repeatedly cuts off verification, reserve capacity for verification or reduce exploratory work. Raising the number until runs stop complaining hides the decision the budget was supposed to make visible.

Research and fixture date: October 28, 2026. Framework pages are living documentation; the fixture is independent of LangChain and LangGraph.

You may also like