Loops mean two different things in OpenEng, and it helps to keep them apart:
| Aspect | Iteration strategy | Global loop |
|---|---|---|
| What it is | How the engine runs one turn: how it observes, acts, reviews, plans and retries | A request repeated many times until a stopping rule is met: "work every open ticket until none is left" |
| Who picks it | The engine, for every message; a config can limit and tune the choices | You: adopt one, select it into a config, then start it |
| Where you set it | The config's Settings step → Loop posture | Building blocks → Loops in the Console, then the config's Loops step |
| Catalog | Five built-in strategies | loops.openeng.app — six published loops |
Each iteration of a global loop is an ordinary turn, so it runs on an iteration strategy like any message you type. The first half of this page covers strategies; Global loops covers the second kind.
Status on September 25, 2026: OpenEng AI isn't live yet
The Console and Web Console interfaces load, but the OpenEng AI API routes and the Broker aren't deployed. Nothing can be saved or published, and no AI engine can register or connect. Pro, Enterprise and pay-as-you-go launch in Q4 2026 and can't be purchased yet. This page describes OpenEng AI as it works once those services are live (status).
Iteration strategies
A loop strategy is the iteration strategy the engine runs a turn with: how it observes, acts, reviews, plans, and retries. You almost never pick one by hand: the engine scores every request and chooses the right loop for you. Strategies are configured per config (the config builder's Settings step, under Loop posture), so you can constrain or tune which strategies a config may use.
How the engine decides (no modes)
There is no user-facing "chat / coding / goal" mode anymore. For every message you send, the engine does two things, internally:
- Score the complexity. The engine scores the request from 0 to 1 (with an embedded model or a heuristic router) from your message and the previous turn for context. At or above the threshold (0.65 by default) the turn is planned; below it runs direct. In the web app you'll see this under the chat box as planned mode or non-planned mode with the score.
- Pick the loop + the models. A direct turn runs the single agent loop
(
react); a planned turn runs the planner pipeline (plan-execute-replan), and each step is classified again to pick its role model from the config's role models (the agent, a sub-agent, a researcher, and so on).
So "goal mode" is just what a complex request looks like — you never select it. The threshold is operator-tunable (see Configuring loops below).
The strategy catalog
| Strategy | What it does |
|---|---|
| react | The baseline chat/coding loop — observe, act, repeat. What a low-complexity turn runs. |
| reflection | Actor–critic: a reviewer drives a bounded revise-loop toward a better answer before finishing. |
| plan-execute-replan | Plan a DAG of steps, execute it, and replan on failure — the high-complexity (agentic) path. |
| orchestrator-worker | Delegators fan a task out to sub-agents and workers, each running its own loop. |
| durable | Wrap any base strategy with checkpoint-every-step: crash recovery, time-travel, and human-in-the-loop resume. |
Loops apply at every altitude — the top-level turn, a delegator, and a leaf
worker each run a loop. A planned turn's per-node executors run their own
react loop on their role's model; a delegator runs orchestrator-worker.
These five are the complete set a config can use. loops.openeng.app also publishes a longer reference of the internal iteration techniques behind them, at loops.openeng.app/reference; those are documentation, not strategies you can select or loops you can run.
Configuring loops
In the Console, open your config and go to its Settings step; the Loop posture section holds the config's strategy settings. A config carries three:
- Loop strategies (the allowed set) — the strategies this config may use. All five are allowed
by default; at least one must stay allowed. Restrict it (for example, allow only
react+reflection) to keep a config simple and predictable. Allowing orchestrator-worker asks you to acknowledge it first: it fans out to sub-agents, which costs more and adds failure surface. - Per-category defaults — the strategy each category starts on: Chat, Coding, Goal runs and
Research. The shipped defaults are
chat → react,coding → react, and goal runs (the high-complexity, agentic lane) →plan-execute-replan; a category left on (built-in baseline) uses the engine's own default. Only an allowed strategy can be a default, and removing a strategy from the allowed set clears any default that used it. Device Use isn't listed: it runs its own dedicated screenshot-and-act loop. - Policy tuning — bounds on a run: max iterations (28 by default), max sub-agent iterations (12), concurrency cap (10), the reviewer's revise cap (2), a token ceiling (0 means none), the compaction trigger (the share of the context window at which the conversation is compacted, 0.82 by default), and switches for stuck detection (on by default), speculative workers (on) and durable checkpoints (off). Per-iteration validation is shown locked on: the reviewer checks every iteration, and no config can turn that off.
These ride the published config artifact, so the engine resolves them the same way it resolves your models and agents — publish the config and they're live.
Complexity threshold
The plan-vs-direct gate is 0.65 by default. It's
tunable per config through the complexity stage of the config's
execution flow (a 0–1 threshold), and per engine via the local
ai-config.toml [complexity] threshold_ knob, which writes the same threshold as a
percentage (65).
The durable wrapper
durable is special: it doesn't replace a base strategy, it wraps one.
Every step is checkpointed, so a crashed or stopped run resumes from the last
good step instead of restarting, and a human-in-the-loop approval can suspend
and later resume the run. Use it for long, expensive, or approval-gated work.
Worked example
You ask: "Build a small FastAPI app with a SQLite store and a health route, then write tests."
- The scorer rates it high (multi-step build) → planned.
- The engine runs
plan-execute-replan: it plans a DAG (scaffold → db layer → routes → tests), executes each node, and replans a node that fails its build-verification. - Each node is classified — the code nodes run on your agent model, a research node (if any) on your research models (the document and code researchers) — and writes real files through the worker layer, asking your permission before it touches anything outside the workspace.
A follow-up — "how do I run it?" — scores low → direct (react): a single
quick answer, no planning ceremony.
Global loops
A global loop is a loop you define and start: "iterate all my Jira tickets until every one is done." It's a request, a source of iterations, and a stopping rule. The engine runs it one iteration at a time, and every iteration starts fresh from complexity analysis: each one is a full turn that independently takes the direct or the planned path, exactly like a message you type.
Anatomy of a loop
| Field | What it holds |
|---|---|
request | The prompt each iteration runs. For a work-items loop, {{item}} is replaced with the current item. |
source | repeat (run the same request again), or workItems: list the items to work through with a connector, cell or MCP server call (via, target, tool, args, and itemsPath pointing at the array in the response) |
termination | condition plus a hard maxIterations bound (1–100,000) and an optional maxTokens budget |
cadence | continuous (the next iteration starts when the last one ends) or interval with secs between starts |
threadPolicy | freshPerIteration (a new thread each time; the default) or singleThread (one thread for the whole run; for short loops) |
configRef | The config the iterations run against; empty means the engine's default config |
The stopping condition is one of:
allItemsDone— keep listing the work items until the list comes back empty, so items that appear or reopen during the run are worked too. It needs aworkItemssource.conditionMetwith atext— after each iteration, the config's reviewer model judges the result against the text and ends the loop when it's satisfied. With no reviewer model bound, the loop runs untilmaxIterations.unbounded— no natural end; the loop runs to itsmaxIterationsor token bound.
Whatever the condition, maxIterations always applies, so a confused loop can't spin forever.
The loop catalog
As of September 25, 2026, loops.openeng.app publishes six loops:
| Loop | Source | Stops when | Cadence |
|---|---|---|---|
Alert triage watch (loop_def_alert_triage) | Open incidents from a PagerDuty MCP server | No unacknowledged incidents remain (max 500) | Every 300 seconds |
Backlog burn-down (loop_def_backlog_burndown) | Repeat | The backlog is empty (max 50) | Continuous |
Failing-tests until green (loop_def_failing_tests_green) | Repeat, one thread | The full test suite passes (max 30, 4,000,000 tokens) | Continuous |
Inbox-to-done (loop_def_inbox_to_done) | Unread inbox messages from the Gmail connector | Every item is done (max 200) | Continuous |
Nightly PR review sweep (loop_def_pr_review_sweep) | Open, unreviewed pull requests from the GitHub connector | Every item is done (max 100) | Every 86,400 seconds (daily) |
Ticket sweep (loop_def_ticket_sweep) | Open Jira issues from a JQL query | Every item is done (max 200) | Continuous |
The two repeat loops, Backlog burn-down and Failing-tests until green, run as published.
A work-items loop lists its items by calling the connector, cell or MCP server it names (its
target id), so that source has to be available in the config it runs on. Its args (such as the
Jira project in the ticket sweep's query) are part of the loop as published. As of September 25, 2026,
none of the four work-items loops has a source that works as published:
- Inbox-to-done names a Gmail connector (
connector_) that no catalog offers yet.def_ gmail - Alert triage watch expects an MCP server with the id
mcp_pagerduty, but MCP servers you add in the Console get ids of the formmcp_usr_…. - Nightly PR review sweep and Ticket sweep use the GitHub and Jira integrations, whose endpoints return a web page instead of JSON, so their calls fail (Integrations).
Adopt, select, publish
- Open Loops in the Console (Building blocks → Loops) and switch to Catalog. Adopt
makes your own copy of a loop (id
loop_usr_…) that carries the loop's definition and remembers its catalog id; loops you've already adopted show an Adopted badge. Mine lists your adopted loops. - Open the config and pick the loops in its Loops step. The step lists only loops you've adopted.
- Publish. The published config carries the full definition of every selected loop, and the
engine stores them under
~/when it starts (definitions in. openeng/ loops/ defs/, run records inruns/;OPENENG_moves the folder).LOOPS_ DIR
The Loops page is adopt-only: there's no New loop button, because the Console can't author a loop from scratch yet.
Run a loop
In the Web Console at app.openeng.app, a message that starts with /loop
controls global loops instead of starting a chat turn:
| Command | What it does |
|---|---|
/loop or /loop list | The loops defined on this engine, and its most recent runs |
/loop start <loopId> | Start a new run of a loop |
/loop status [runId] | A run's progress, iteration by iteration (the latest run if you leave out the id) |
/loop stop <runId> | Stop a run; the iteration in progress is canceled too |
A run keeps going in the background while you do other things. If the engine restarts, runs that were still going resume from their records: a work-items loop skips the items it already finished, and a repeat loop continues after its last iteration.
When a config selects loops, the engine offers only those: /loop start refuses any other loop id.
Note
/loop <id> followed by a message (rather than list, start, status or stop)
doesn't start anything. It only adds a line to that message's instructions, as prompt guidance.
Plan & tier notes
Adopting loops, setting a config's loop posture and publishing are open on every plan. Running
loops needs the openeng engine, which starts only on an active Pro plan
(Plans & billing).
Troubleshooting
"loop '…' is not selected by the served configuration". The config the engine serves selects loops, and this one isn't among them. Add it to the config's Loops step and republish.
/loop list says no loops are defined. Nothing published has reached this engine yet. Adopt a
loop, select it in the config, publish, and restart the engine so it loads the new version.
Publishing fails with loop_. The adopted loop has no definition, which happens when
its catalog entry couldn't be validated at adoption. Adopt it again from the catalog.
A conditionMet loop runs to its maximum. The reviewer never judged the condition satisfied, or
the config has no reviewer model bound. Bind one in the config's Models step, or make the condition
text more concrete.