Browse the docs
Building agentic workflows
Loop engineering in Destaris — the trigger, verifier, and stop rules around an AI step, as plain deterministic YAML.
In mid-2026 the industry settled on a name for a shift that had been underway for a while: loop engineering. The idea, compressed: stop prompting your agents by hand, and design the loop that prompts them — the trigger that wakes the work up, the verifier that decides whether it's done, and the stop rules that keep it from running forever.
Destaris has an opinion about that loop: it should be boring. The loop itself is deterministic — a trigger, plain steps, explicit conditions, hard caps — and the intelligence is one opt-in step inside it. That's not a feature we added for the trend; it's the model the whole product is built on.
This page covers the patterns you build a loop with — lowercase, the shapes, not an object you create. Two objects use them, and both have their own page:
- A Loop — capital L, and what the word means on its own in Destaris now — is a graph you design that repeats under a small brain until a success condition you wrote is met.
- An Agentic Workflow is the one where the brain designs the graph: its own brain, its memory, and the Agentic Workflow⇄Workflow lifecycle it composes these patterns into.
The four loop shapes
Most production loops are one of four shapes. Each one maps to Destaris primitives you already have:
| Loop shape | What it does | In Destaris |
| --- | --- | --- |
| Cron loop | Runs at set times | trigger.cron — plain-English schedule picker, or raw cron |
| Hook loop | Fires on an event | trigger.webhook — a PR, a form, an alert |
| Heartbeat loop | Polls, acting only on what's new | trigger.cron + the dedupe cache |
| Goal loop | Repeats until a check passes | task.loop + agent.run + a verifier + maxIterations |
The first three are loops around workflows — the same workflow run again and again, safely. The fourth is a loop inside a workflow, and it's where agentic work earns the name.
The anatomy of a well-engineered loop
Whatever the shape, a loop that can run without you has four parts:
- A trigger — whatever wakes it up. In Destaris every workflow starts from a
trigger.*node, so this is never implicit. - A topology — the steps and how they connect. On the canvas that's nodes and edges; in the file it's portable YAML you can review and version like code.
- A verifier — the check that decides "done". Prefer deterministic checks (a
status code, a schema match, a value in a response) — they can't be argued
with. When the criterion is judgment ("is this readable?"), use a second
agent.runwith a structureddecisionas a critic: it grades, it doesn't self-grade. - Stop rules — the reasons to quit.
task.looptakes awhilecondition and amaxIterationscap (the engine enforces a hard ceiling of 25), and the dedupe cache keeps re-runs from double-handling work. When a loop exhausts its cap without passing, route thedonebranch to a human instead of failing silently.
A goal loop, worked: generate → critique → revise
The most useful agentic pattern is also the simplest: one step drafts, a second step judges, and the loop repeats until the judge approves or the cap is hit. Here it is in full:
nodes:
- id: start
type: trigger.command
- id: fetch
type: task.http
config:
url: "https://api.example.com/pulls?state=merged"
- id: refine
type: task.loop
config:
while: "prev.critic.approved != true"
maxIterations: 5
- id: feedback
type: task.transform
config:
expression: "prev.critic.feedback ? prev.critic.feedback : 'none yet'"
- id: draft
type: agent.run
config:
prompt: >
Draft a short release note from these merged pull requests:
{{fetch.items}}. Reviewer feedback to address: {{feedback}}
decision:
type: object
properties:
text: { type: string }
required: ["text"]
- id: critic
type: agent.run
config:
prompt: >
Review this release note for accuracy and plain language: {{draft.text}}.
Approve only if a non-technical reader would understand every sentence.
decision:
type: object
properties:
approved: { type: boolean }
feedback: { type: string }
required: ["approved"]
- id: route
type: task.branch
config:
expression: "refine[-1].outputs.critic.approved ? 'approved' : 'needs-human'"
- id: publish
type: task.http
config:
method: POST
url: "https://api.example.com/notes"
bodyExpression: "refine[-1].outputs.draft"
- id: escalate
type: task.http
config:
method: POST
url: "https://hooks.slack.com/services/…"
bodyExpression: >
{ 'text': 'Release note needs a human: ' &
refine[-1].outputs.critic.feedback }
edges:
- { from: start, to: fetch }
- { from: fetch, to: refine }
- { from: refine, to: feedback, when: "loop" }
- { from: feedback, to: draft }
- { from: draft, to: critic }
- { from: refine, to: route, when: "done" }
- { from: route, to: publish, when: "approved" }
- { from: route, to: escalate, when: "needs-human" }
A few things worth naming:
- The body is the
loopbranch. Steps wired from the loop node withwhen: "loop"run every round; thewhen: "done"branch runs once, after. previs last round's outputs, keyed by step id — thewhilecondition reads last round's verdict from it. One contract detail worth knowing:previs a JSONata-scope variable, so expression fields (while, transforms) see it, but{{…}}references in an agent prompt resolve step outputs, notprev— that's why thefeedbacktransform sits at the top of the body, turning last round's critique into a plain step output the draft prompt can reference.- The loop's output is an array, one entry per round:
refine[-1]is the final round, wherever you need the result downstream. - The escalation path is just a branch. "Couldn't get there in 5 rounds" is a normal, visible outcome that notifies a person — not a silent failure.
Every round shows up in the run history individually, so when a loop took four iterations you can read exactly what the critic objected to in each one.
Heartbeat loops: the other workhorse
Goal loops get the attention, but most real automation is a heartbeat: poll a
source on a schedule, act only on what's new. In Destaris that's a trigger.cron
plus the two cache steps — task.cache-unseen in front of the work,
task.cache-mark behind it — and the loop is idempotent by construction:
schedules can overlap, retries can fire, and each item is still handled exactly
once. The full pattern is in Dedupe & caching.
Why the deterministic loop matters
The failure modes loop engineering warns about — agents grading their own work, loops that never notice they're stuck, costs that compound unattended — are all consequences of putting the control flow inside the model. Destaris keeps it outside: conditions are JSONata over real outputs, caps are enforced by the engine, and the AI step runs as your own CLI on your own plan, so an unattended loop can't run up a bill you didn't already agree to. The loop is a file. You can read it.
Related
- Loops — the Loop object: your graph, your success condition, a brain that judges each pass
- Building Loops — authoring one, and the traps
- Agentic Workflows — the Agentic Workflow object, its memory, and the Agentic Workflow⇄Workflow lifecycle
- Node reference —
task.loop,task.foreach,agent.run - Dedupe & caching
- Expressions (JSONata)
- Running & scheduling