Loops
Loops let the agent execute an arbitrary task as a structured execution path — an ordered set of named steps that the agent traverses, deciding when to loop back, advance, or stop based on outcomes.
Unlike a fixed pipeline, loops are agent-driven: the agent calls tools to jump between steps, swap agents mid-flow, and stop when done. This makes them a better way to execute repeatable workflows like read → build → test → review → report where the agent may need to loop back (build → test → build → test → review → stop).
Enable
Loops is a builtin plugin, disabled by default. Enable it in your config:
{
"builtin": { "loops": { "enabled": true } }
}
The server also exposes endpoints: GET /builtin, POST /builtin/:name/enable, POST /builtin/:name/disable.
Configuration
{
"loops": {
"compaction_threshold": 0.5,
"compaction_model": "toothfairyai/glm-5p2",
"definitions": {
"fix-bug": {
"description": "Read, build, test, review, report",
"steps": [
{
"name": "read",
"agent": "plan",
"prompt": "Read and understand the task."
},
{ "name": "build", "prompt": "Implement the change." },
{
"name": "test",
"prompt": "Run the tests. If failing, call loop_next({ step: 'build' }) with the reason."
},
{
"name": "review",
"review": true,
"prompt": "Review the change for correctness and security."
},
{
"name": "report",
"agent": "TF Engineer",
"prompt": "Report the outcome."
}
]
}
},
"auto": ["fix-bug"]
}
}
Top-level
| Field | Default | Description |
|---|---|---|
compaction_threshold | 0.5 | Compact between steps when context usage exceeds this ratio (0–1). |
compaction_model | — | Model for compaction (defaults to the session's active model). |
max_step_repeats | 3 | Max times a single step can be visited before the loop pauses (doom-loop guard). 0 disables. |
max_transitions | 20 | Hard cap on total step transitions. The loop pauses when exceeded. 0 disables. |
definitions | — | Named loop workflows keyed by id. |
auto | — | Loop ids to auto-start on non-interactive sessions (requires --auto-loops). |
Step
| Field | Description |
|---|---|
name | Unique step id the agent references in loop_next. |
prompt | Task prompt for this step. |
agent | Agent for this step (build, plan, TF Engineer, …). |
skill | Skill/command name whose content is inlined into this step's prompt. |
review | Nudges the agent to call the reviewer tool during this step. |
model | (phase 2) Swap to this model for this step. |
Tools
When a loop is active, two tools are registered:
loop_next
Jump to any named step (forward or backward). Optionally swap to a different agent for the next step — enabling multi-agent workflows.
loop_next({ step: "build" })
loop_next({ step: "review", agent: "TF Engineer", reason: "needs security review" })
If the agent does not call loop_next, the loop auto-advances to the next step in order.
loop_done
Stop the loop early when the goal is satisfied.
loop_done({ reason: "all tests pass and review approved" })
When the last step completes with no loop_next, the loop finishes automatically.
Multi-agent workflows
Each transition can hand off to a different agent. There are two ways to control which agent runs a step:
- Static — set
agenton the step definition. - Dynamic — pass
agenttoloop_nextat runtime to override the step's configured agent.
This lets the agent decide mid-flow which agent is best for the next step.
Backward jumps
The agent can jump back to any earlier step when it discovers a root-cause issue. For example, at step test the agent might realize the plan from read was incorrect:
loop_next({ step: "read", reason: "plan was incorrect — need to re-read the spec" })
The system prompt guides the agent to:
- Jump back and fix the root cause rather than patching around it in the current step.
- Provide a
reasonexplaining why it's jumping back (for audit history).
Doom-loop prevention
If a step keeps failing and the agent loops back repeatedly, the loop auto-pauses to prevent infinite cycles. Two configurable guards:
max_step_repeats(default3) — if a single step is visited more than this many times, the loop pauses with a doom-loop message.max_transitions(default20) — if total transitions across all steps exceed this, the loop pauses.
Both can be set to 0 to disable the guard entirely (not recommended).
When a doom loop is detected:
- The loop pauses (not stops) — the state is preserved.
/loops statusshows which step triggered the pause and how many times it was visited./loops resumerestarts the loop from the paused step. The agent should try a different approach.- The
loop_nexttool returns a warning when a step is approaching its visit limit, nudging the agent to try a different approach or callloop_doneif stuck.
The system prompt also instructs the agent to call loop_done with a reason if it's stuck, and to use the reviewer tool for a fresh perspective on why a step keeps failing.
Reviewer, model_swap, and skills
Within any step, the agent can use:
- reviewer — get a second opinion from a stronger model (architecture, security, performance, correctness).
- model_swap — switch to a different model mid-step when the task calls for different capabilities or cost.
- skill — load any available guided workflow at runtime.
The review step field nudges the agent to call the reviewer. The skill step field inlines a skill's content into the step prompt.
Compaction
Between steps, if context usage exceeds compaction_threshold (default 50%), the loop automatically compacts the session to preserve context. This keeps long loops running without hitting context limits.
Commands
| Command | Description |
|---|---|
/loops run <name> | Start a loop workflow |
/loops status | Show current loop state and history |
/loops list | List available loops from config |
/loops pause | Pause the active loop |
/loops resume | Resume a paused loop |
/loops clear | Stop and remove the active loop |
Auto-start
To auto-start a loop on non-interactive sessions (e.g. tfcode run), add loop ids to auto and pass --auto-loops:
tfcode run --auto-loops "Process the queue"
Or set the environment variable:
export OPENCODE_AUTO_LOOPS=true
Auto-start never applies to interactive TUI sessions — use /loops run <name> there. State is persisted to ~/.tfcode/loops-plugin/state.json and recovered as paused on restart.
Skill
Loops ships with a loops skill that documents the loop_next/loop_done contract and transition patterns. The agent can invoke it via the skill tool, or you can reference it in a step with "skill": "loops".
Managing loops at runtime
In addition to defining loops statically in config JSON, agents can create, read, update, and delete loop definitions at runtime using the loop tool. This is useful when an agent needs to set up a workflow dynamically based on the task at hand.
Actions
| Action | Description |
|---|---|
list | List all defined loops with step count and description |
read | Read a specific loop's full definition (steps, description) |
create | Create a new loop definition (refuses to overwrite existing) |
update | Update an existing loop's description and/or steps |
delete | Remove a loop definition from config |
Example
An agent can create a loop during a session:
{
"action": "create",
"name": "fix-bug",
"description": "Read, build, test, review",
"steps": [
{
"name": "read",
"prompt": "Read and understand the task.",
"agent": "plan"
},
{ "name": "build", "prompt": "Implement the change." },
{ "name": "test", "prompt": "Run tests. If failing, loop back to build." },
{ "name": "review", "prompt": "Review for correctness.", "review": true }
]
}
The loop is written to the project's tfcode.json under loops.definitions and is immediately available for use with /loops run fix-bug.
Loops settings via config tool
Top-level loops settings (compaction, doom-loop guards, auto-start) can be managed at runtime via the tfcode_config tool with the loops_settings section:
{
"action": "update",
"section": "loops_settings",
"value": {
"compaction_threshold": 0.6,
"max_step_repeats": 5
}
}
See Tools → Config Management for details.