Search⌘ K
AI Features

Step Limits, Budget Limits, and Full-Fidelity Logs

Explore how to apply deterministic controls to AI workflow loops by setting step limits and budget caps, enforcing strict validation, and generating comprehensive per-step logs. Understand how to handle stop reasons explicitly and safely manage tool executions to create predictable, auditable, and bounded agent-like AI workflows.

A tool-using loop becomes predictable when deterministic controls wrap the probabilistic part. The model proposes the next action, then the workflow either executes it or stops for a named reason.

This lesson stays platform-agnostic and focuses on the loop contract around triage that can call 0–N tools, then produce a final draft plus internal rationale fields.

With this diagram, we can step through one loop iteration and watch state, counters, and logs change in a controlled way.

Only step 2, the model's action proposal, is probabilistic. Every other step, validation, tool execution, logging, counters, and the stop check, is deterministic
Only step 2, the model's action proposal, is probabilistic. Every other step, validation, tool execution, logging, counters, and the stop check, is deterministic

The loop shape stays the same even as we add more tools and more steps. We initialize a state object, ask the model for next_action JSON, validate that proposal against an allowlist, execute a tool only if validation passes, append a step log, then check a stop condition that returns control back to the platform.

A minimal pseudo-workflow looks like this:

state = {
input_id,
triage_json, // category, priority, next_action, needs_human
step_count: 0,
budget_remaining: 5, // budget units
messages: [],
run_log: []
}
ALLOWED_TOOLS = ["kb_search"]
while true:
proposal = model_propose_next_action(state) // probabilistic JSON
validation = validate(proposal, ALLOWED_TOOLS) // deterministic
if !validation.ok:
append_step_log(state, proposal, validation, tool_call=null, tool_result=null)
stop_reason = "INVALID_ACTION"
break
tool_result = call_tool(proposal.tool_name, proposal.args) // deterministic side effect
state = apply_tool_result(state, tool_result) // deterministic state update
append_step_log(state, proposal, validation, tool_call=proposal, tool_result=tool_result)
state.step_count += 1
if should_stop(state):
stop_reason = compute_stop_reason(state)
break
A minimal, platform-agnostic pseudo-workflow for the bounded agent loop

The important boundary is that only model_propose_next_action(state) is non-deterministic. Validation, counters, state updates, and stop reason selection must behave the same way on every run given the same inputs.

Step limits and stop reasons

Once the loop exists, the next failure mode is an endless sequence of plausible tool calls. A step limit prevents that by forcing a stop after max_steps tool executions, even if the model keeps proposing actions.

Stop reasons work best when they are explicit and mutually exclusive, because logs and dashboards can group by stop_reason without guessing. A compact set that stays readable in audits is:

  • DONE when the model proposes a terminal action and the workflow accepts it

  • MAX_STEPS when step_count >= max_steps

  • BUDGET_EXHAUSTED when budget_remaining <= 0

  • INVALID_ACTION when validation fails

  • ...