Let the model use read-only tools¶
An agent loop is an llm step with a tools policy. The model can request one or several allowlisted read-only MCP calls, inspect validated results, and produce the step's final JSON or text output. It remains one step in a sequential flow: the model cannot choose another flow, add tools, grant access, or persist memory.
First configure the model profile and MCP server in config/settings.yaml. The model must declare supports_tools: true and support the chosen output kind. The MCP profile must declare each tool's exact input schema, optional output schema, and effect: read. See models and MCP setup.
One lookup, JSON answer¶
List answer in config/support_triage/triage/flow.yaml. Save this step in config/support_triage/triage/answer.step.yaml, with a local answer.schema.json defining the required status string:
type: llm
input:
message:
pointer: /payload/message
instructions: Use the lookup to answer the customer's invoice-status question. Do not invent a status.
output:
schema: answer.schema.json
tools:
server: support_records
allow:
- lookup_invoice
choice: required
max_iterations: 2
config/support_triage/triage/answer.schema.json can contain:
{
"type": "object",
"properties": {"status": {"type": "string"}},
"required": ["status"],
"additionalProperties": false
}
For a successful lookup, the public step can contain "status": "completed" and "result": {"status": "paid"}, where the object is validated against your schema. choice: required means at least one allowed tool call must succeed before final output. For exactly one named tool, set choice: to a mapping with name: lookup_invoice. With choice: auto, the model may answer without calling a tool.
Several allowed lookups, text answer¶
The same server policy may allow several tool names. The model selects among them and may make more than one call while limits remain:
# config/support_triage/triage/draft.step.yaml
type: llm
input:
message:
pointer: /payload/message
instructions: Answer using verified invoice or subscription records only.
output: text
tools:
server: support_records
allow:
- lookup_invoice
- lookup_subscription
choice: required
max_iterations: 3
A completed result here is a string, for example "Invoice INV-42 is paid; the subscription remains active." Tool calls emitted together execute sequentially. A step policy names one MCP server, though it may allow several tools from that server. Use separate steps for different servers. The allow list must be nonempty and unique; named choice must be on it.
Set the limits where they apply¶
max_iterations belongs on the LLM step. It counts logical model turns, including the final answer, and defaults to 4 (allowed range 1–1024). The runtime stops before a fifth turn at the default; a final answer on turn four succeeds. Provider retries of one turn do not consume another iteration. This limit also applies to LLM steps with no tools.
execution in config/settings.yaml supplies separate local limits:
execution:
model_requests_per_step: 4
tool_calls_per_step: 3
run_timeout: 300
model_timeout: 60
tool_timeout: 30
These are the defaults. model_requests_per_step counts actual provider attempts, including retries; tool_calls_per_step counts tool attempts. Attempts are reserved before I/O and failures still consume them. The root run_timeout is an absolute invocation deadline. model_timeout bounds a logical model request after its first admission, shared by that request's retries; tool_timeout bounds tool work. A model profile also has its own request_timeout, admission and retry policy, and an MCP profile has call/admission/output limits. The earliest applicable deadline wins. See execution limits, models, and MCP setup.
The iteration and attempt limits are independent. For example, max_iterations: 3 permits three model turns, but the default four provider attempts may be exhausted sooner if a request retries. Conversely, raising the provider-attempt budget does not let a loop exceed three turns. A tool call consumes no extra model iteration by itself; the model turn after the tool result does.
If the MCP server requests caller input, the step returns needs_review with result: null. If a required or named call never succeeds, final validation fails with invalid_output. Exceeding an iteration or attempt limit fails with budget_exhausted; invalid arguments, authorization denial, catalog drift, and timeouts are technical failures. Each invocation starts with fresh model/tool state. Evaluate tool selection and final answers with reviewed cases; the model tool-loop tutorial has an offline scripted example.