Skip to content

AI · Building blocks

Loops

11 min read

Loops mean two different things in OpenEng, and it helps to keep them apart:

AspectIteration strategyGlobal loop
What it isHow the engine runs one turn: how it observes, acts, reviews, plans and retriesA request repeated many times until a stopping rule is met: "work every open ticket until none is left"
Who picks itThe engine, for every message; a config can limit and tune the choicesYou: adopt one, select it into a config, then start it
Where you set itThe config's Settings step → Loop postureBuilding blocks → Loops in the Console, then the config's Loops step
CatalogFive built-in strategiesloops.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).

Building blocks → Loops — the Mine and Catalog tabs for global loops. Strategies are set per config, in the config's Settings step.

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:

  1. 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.
  2. 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

StrategyWhat it does
reactThe baseline chat/coding loop — observe, act, repeat. What a low-complexity turn runs.
reflectionActor–critic: a reviewer drives a bounded revise-loop toward a better answer before finishing.
plan-execute-replanPlan a DAG of steps, execute it, and replan on failure — the high-complexity (agentic) path.
orchestrator-workerDelegators fan a task out to sub-agents and workers, each running its own loop.
durableWrap 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_pct 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."

  1. The scorer rates it high (multi-step build) → planned.
  2. 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.
  3. 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

FieldWhat it holds
requestThe prompt each iteration runs. For a work-items loop, {{item}} is replaced with the current item.
sourcerepeat (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)
terminationcondition plus a hard maxIterations bound (1–100,000) and an optional maxTokens budget
cadencecontinuous (the next iteration starts when the last one ends) or interval with secs between starts
threadPolicyfreshPerIteration (a new thread each time; the default) or singleThread (one thread for the whole run; for short loops)
configRefThe 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 a workItems source.
  • conditionMet with a text — 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 until maxIterations.
  • unbounded — no natural end; the loop runs to its maxIterations or 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:

LoopSourceStops whenCadence
Alert triage watch (loop_def_alert_triage)Open incidents from a PagerDuty MCP serverNo unacknowledged incidents remain (max 500)Every 300 seconds
Backlog burn-down (loop_def_backlog_burndown)RepeatThe backlog is empty (max 50)Continuous
Failing-tests until green (loop_def_failing_tests_green)Repeat, one threadThe 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 connectorEvery item is done (max 200)Continuous
Nightly PR review sweep (loop_def_pr_review_sweep)Open, unreviewed pull requests from the GitHub connectorEvery item is done (max 100)Every 86,400 seconds (daily)
Ticket sweep (loop_def_ticket_sweep)Open Jira issues from a JQL queryEvery 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_def_gmail) that no catalog offers yet.
  • Alert triage watch expects an MCP server with the id mcp_pagerduty, but MCP servers you add in the Console get ids of the form mcp_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

  1. 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.
  2. Open the config and pick the loops in its Loops step. The step lists only loops you've adopted.
  3. Publish. The published config carries the full definition of every selected loop, and the engine stores them under ~/.openeng/loops/ when it starts (definitions in defs/, run records in runs/; OPENENG_LOOPS_DIR moves the folder).

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:

CommandWhat it does
/loop or /loop listThe 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_def_missing. 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.