Sequential Agent Loops
Run the same task against one agent repeatedly, in order, with a bounded run count — for example “process the next backlog item” × 20. Start a loop once, get back a loop_id, and disconnect: the loop runs server-side, dispatching each iteration through the standard execution pipeline.
Build Autonomous Loops for Your AI Agents
Jun 2026
Loops are the sequential counterpart to Fan-Out (parallel batch) and a single chat turn (one-shot).
Concepts
max_runs task executions against one agent. Iterations run strictly one at a time, in order.{{run}} (the 1-indexed run number) and {{previous_response}} (the trailing 2,000 characters of the previous iteration's response; empty on run 1).max_runs iterations.stop_signal is set: after each iteration, Trinity checks whether the agent's response contains the stop signal as a plain substring. On match, the loop ends early with reason stop signal matched. A recommended sentinel is [[DONE]] — tell the agent to emit it when the work is finished.stopped by user.How It Works
Open an agent's detail page and select the Loops tab.
Click Run Loop (the agent must be running — the button is disabled otherwise).
Fill in the form: Message template (required, e.g. Process item {{run}}. Previous result: {{previous_response}}), Max runs (required, 1–100), Stop signal (optional), Delay between runs (0–3600 seconds), Timeout per run (10–7200 seconds; defaults to the agent's execution timeout), Model (per-loop override), and Allowed tools (restrict tools for every iteration, or leave All Tools / Unrestricted).
Click Start Loop. The loop appears in the list below with a live status badge and a Run N / M progress counter.
Click a loop row to expand it: a per-run table shows each iteration's status, cost, duration, and a response preview, and the Last response is rendered as markdown below the table.
Click Stop on an active loop to request a graceful stop. The badge shows the current iteration finishing, then the loop ends with reason stopped by user.
The panel updates in real time via WebSocket events as each run completes, with a periodic backstop refresh while a loop is active.
Loop Lifecycle
| Status | Meaning |
|---|---|
| queued / running | Loop active; iterations dispatching |
| completed | Reached max runs, or the stop signal matched |
| stopped | Stopped by a user; the in-flight iteration finished first |
| failed | An iteration failed (task error or exception) — the loop aborts at that iteration |
| interrupted | The backend restarted mid-loop; loops do not auto-resume |
Observability: every iteration is a normal execution — it creates its own row with triggered_by: "loop" and the parent loop_id, visible on the Executions page and per-execution detail view. In analytics timeline views, loop executions appear as their own Loopscategory with a distinct color — they are not folded into Scheduled.
Common Patterns
These shapes cover most loop use cases. Pick by what ends the loop: a count, a condition, or a clock.
Iterative refinement (fixed mode + chaining)
Run a fixed number of progressively better passes, feeding each result into the next:
{
"message": "Improve the draft. Previous version: {{previous_response}}",
"max_runs": 4
}{{previous_response}}is lossy — only the trailing 2,000 characters carry over. When iterations build a real artifact (a draft, a migration, a report), don't rely on it: instruct the agent to keep the artifact in a file in its workspace and re-read it each run. The agent's filesystem persists across runs; the loop context doesn't.
Agentic retry (until mode)
Keep trying until it works, bounded by a safety cap:
{
"message": "Attempt the migration. If it succeeds, paste the passing output and end your reply with [[DONE]]. Otherwise, fix the error and report what failed.",
"max_runs": 8,
"stop_signal": "[[DONE]]",
"timeout_per_run": 600
}The loop exits with stop signal matched on the first success, and max runs reached if it never succeeds within the cap.
Bounded polling (until mode + delay)
Watch something external on a cadence, without holding a connection open:
{
"message": "Check the deploy health endpoint. If healthy, reply [[DONE]]; else report the current status.",
"max_runs": 30,
"stop_signal": "[[DONE]]",
"delay_seconds": 120
}This polls every 2 minutes for up to an hour and exits the moment the condition holds. For cadences slower than the 1-hour delay ceiling, a loop is the wrong tool — use a schedule instead.
Backlog draining (until mode + a work skill)
If the agent has a work-queue skill (for example the abilities work-loop pattern: pick one issue, do it, close it, exit), a loop turns it into a bounded autonomous session:
{
"message": "Run /work-loop. Process exactly one backlog item. If the backlog is empty, reply [[DONE]].",
"max_runs": 20,
"stop_signal": "[[DONE]]"
}Each iteration is one unit of work with its own execution row, cost, and timeout — far more observable than one giant “do everything” task.
Writing Good Until-Mode Loops
Until mode is only as reliable as the stop condition you write. Three rules:
Tie the sentinel to verifiable evidence, not self-assessment. Models skew positive when grading their own work — “reply [[DONE]] when the report is good” exits on the first pass. Anchor the sentinel to something checkable: passing test output, an HTTP 200, zero remaining TODO markers. If the condition is inherently subjective, keep max_runs low instead.
Always set the cap. stop_signal is best-effort — the agent has to actually emit it. max_runs is guaranteed. The cap is the safety net, not the expected exit.
Set timeout_per_run. A hung iteration silently stalls the whole sequence. Size it to the task (e.g. 600s for a test suite) rather than inheriting a long agent default.
And once a loop is running, watch for stalls: if consecutive responses are near-identical (same failure, same output, no state change), the loop is burning budget without progress. Stop it rather than letting it run to the cap.
For Agents
Agents (and scripts) start loops via MCP tools or REST. The permission model matches chat_with_agent: owner, admin, or shared access on the target agent; agent-scoped MCP keys need an explicit permission grant.
From Claude Code: /trinity:loop
If you work in Claude Code with the abilities trinity plugin installed, /trinity:loop is the fastest way to start a loop — it parses a natural-language request (/trinity:loop @ci-agent run the test suite until it passes, max 10), picks the mode, writes the sentinel instruction into the message for you, fires run_agent_loop, and watches progress locally.
MCP Tools
| Tool | Description |
|---|---|
| run_agent_loop | Start a loop; returns loop_id immediately. agent_name is required for user-scoped keys; agent-scoped keys default to the bound agent |
| get_loop_status | Loop status plus per-run summaries (run number, execution id, status, response preview, cost, duration) and the last full response |
| stop_loop | Graceful stop; returns stopping (runner signaled) or already_done (loop already terminal) |
mcp__trinity__run_agent_loop({
agent_name: "my-agent",
message: "Process the next backlog item ({{run}} of 20). Reply [[DONE]] if the backlog is empty.",
max_runs: 20,
stop_signal: "[[DONE]]",
delay_seconds: 30
})
// → { success: true, loop_id: "loop_...", status: "queued", ... }REST API
| Endpoint | Method | Description |
|---|---|---|
| /api/agents/{name}/loops | POST | Start a loop; returns 202 with {loop_id, status, agent_name, max_runs} |
| /api/agents/{name}/loops | GET | List the agent's loops, most recent first (?status=, ?limit= 1–200, default 50) |
| /api/loops/{loop_id} | GET | Status, per-run summaries, last full response (404 unknown; 403 if the caller is neither the initiator nor has agent access) |
| /api/loops/{loop_id}/stop | POST | Graceful stop → {status: "stopping" | "already_done"} |
curl -X POST http://localhost:8000/api/agents/my-agent/loops \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Process item {{run}}. Previous result: {{previous_response}}",
"max_runs": 10,
"stop_signal": "[[DONE]]",
"delay_seconds": 5
}'Parameters
| Parameter | Default | Range | Description |
|---|---|---|---|
| message | — (required) | 1–100,000 chars | Template; supports {{run}} and {{previous_response}} |
| max_runs | — (required) | 1–100 | Hard ceiling on iterations |
| stop_signal | none (fixed mode) | ≤200 chars | Substring that ends the loop early; whitespace-stripped, blank = fixed mode |
| delay_seconds | 0 | 0–3600 | Pause between iterations |
| timeout_per_run | agent's execution timeout | 10–7200 | Per-iteration timeout in seconds |
| model | agent default | — | Model override applied to every iteration |
| allowed_tools | unrestricted | — | Tool restrictions applied to every iteration |
Limitations
max_parallel_tasks budget alongside chat, schedules, and other traffic.failed; remaining runs are skipped.interrupted. They do not auto-resume; start a new loop.max_runs is capped at 100, delay at 1 hour, and per-run timeout at 2 hours.