The Agent Loop: Model, Tools, State, and Stop Conditions¶
An LLM agent is not a different species of model. It is a system in which a model repeatedly chooses among messages and tool requests, observes results, updates state, and continues until a stopping condition is met.
Evidence key: Established is a system contract; Empirical belongs to cited experiments; Practice is a design recommendation.
Workflow or agent?¶
- Workflow: application code chooses a mostly fixed path.
- Agent: the model chooses important next steps dynamically from available actions.
flowchart LR
I["User intent"] --> A{"Path known in advance?"}
A -- "yes" --> W["Deterministic workflow"]
A -- "partly" --> H["Workflow with model decisions"]
A -- "no" --> G["Agent loop with bounded tools"]
W --> E["Evaluate outcome"]
H --> E
G --> E Agents trade predictability, latency, and cost for flexibility. Use that trade only when the task needs it.
The minimal loop¶
stateDiagram-v2
[*] --> Understand
Understand --> Decide
Decide --> Final: answer is ready
Decide --> ValidateCall: tool requested
ValidateCall --> AwaitApproval: policy requires user
ValidateCall --> Execute: authorized
AwaitApproval --> Execute: approved
AwaitApproval --> Stopped: denied
Execute --> Observe
Observe --> Decide
Decide --> Stopped: budget or policy stop
Final --> [*]
Stopped --> [*] The model never replaces the executor or policy gate.
def run_agent(task, model, tools, policy, budget):
state = new_state(task)
while budget.can_continue(state):
decision = model.next_action(state.model_view())
if decision.type == "final":
return validate_final(decision.output, state)
call = schema_validate(decision.tool_call, tools.schemas)
auth = policy.authorize(call, state.trusted_principal)
if auth.requires_approval:
auth = request_human_approval(auth.summary)
if not auth.allowed:
state.observe(tool_error("not_authorized"))
continue
result = tools.execute(
call,
credentials=auth.scoped_credentials,
idempotency_key=state.idempotency_key(call),
)
state.observe(sanitize_tool_result(result))
return stopped("budget_exhausted", state.safe_summary())
ReAct as a research pattern¶
ReAct interleaves language-model reasoning traces with environment actions. The paper reported gains over selected baselines on its studied question-answering, fact-verification, and interactive environments.
Observation: the requested record is not in local context
Action: search_records({"account_id": "acct_..."})
Observation: one matching record, status=pending
Action: ...
Empirical boundary: ReAct's results do not prove that free-form thought/action text is the best production protocol. Typed tool calls and hidden application state are usually safer than parsing ad hoc strings.
State has trust levels¶
Separate:
- trusted control state: principal, policies, budgets, approvals;
- conversation state: user and assistant messages;
- tool state: results, errors, external revisions;
- working artifacts: plans, files, code, tables;
- untrusted content: web pages, emails, retrieved documents.
Only application code should update trusted control state.
Stop conditions¶
Every loop needs:
- maximum model turns;
- maximum tool calls;
- token and monetary budgets;
- wall-clock deadline;
- repeated-action or no-progress detector;
- explicit success predicate;
- unrecoverable error state;
- user cancellation;
- policy denial.
“Continue until done” is not an operational stop condition.
Recovery¶
Classify failures:
| Failure | Response |
|---|---|
| transient timeout | bounded retry with backoff and same idempotency key |
| invalid arguments | return typed validation error for correction |
| missing user preference | ask the user |
| authorization denied | do not rephrase to bypass policy |
| tool unavailable | use an approved alternative or stop |
| partial write | reconcile authoritative state before retry |
| repeated no progress | stop with evidence and trace ID |
Practice: make tool errors informative enough for recovery but strip secrets and stack traces.
Ground truth from the environment¶
An agent should check the system it changes. Examples:
- run tests after editing code;
- read the created calendar event;
- query transaction status after a write;
- render a document after generation;
- verify cited source spans.
Self-reported success is not outcome evidence.
Evaluation¶
Evaluate both:
- end state: did the database, files, or environment reach the required state?
- process constraints: were forbidden tools avoided, approvals obtained, budgets respected, and secrets protected?
tau-bench emphasizes final database state and repeated-trial reliability for tool-agent-user interaction.
Report success across multiple trials; a stochastic system that succeeds once and fails seven times is not reliable.
Source-code trail¶
- ReAct repository — original prompting experiments.
- Anthropic tool-use loop — current client/server execution contract.
- OpenAI practical guide to agents — official architecture and guardrail guidance.
- tau-bench — tool-agent-user evaluation with state checks.
- Toolformer paper — primary description of learned API-call insertion; no unofficial implementation is used as evidence here.
Exercises¶
- Implement a three-tool loop with a hard call budget and typed errors.
- Create a repeated-action detector that ignores legitimate paginated reads.
- Define an end-state grader for “change an address, but do not submit the order.”
- Inject malicious instructions into a tool result and prove the policy gate still denies a write.
- Compare a fixed workflow and agent loop on the same task distribution, including cost and failure rate.