← All posts

Loop engineering: the real skill isn't the prompt, it's the loop

Most advice about working with agents is still framed as prompt engineering — find the right words and you'll get the right answer. That's true, as far as it goes, but it quietly assumes the unit of work is a single request-response pair: you ask, it answers, the interaction ends. A lot of real work doesn't fit that shape at all. Triaging issues, watching for review feedback, keeping a roadmap moving, checking whether a fix actually held — none of those end after one answer. They're loops, not questions.

Loop engineering is the discipline underneath that difference: deciding not just what an agent should say, but when it should run again, what should wake it up, and how it knows it's done. A loop has a trigger — does it react to something new, or keep nudging regardless of whether anything changed, or go looking for its own work on a schedule with no trigger at all. It has an interval — how often is checking-in worth the cost of checking. And it has an exit condition — some signal that says this loop's job here is finished, so it stops consuming attention (yours or its own) on a thread that's already resolved.

Get any one of those wrong and the loop misbehaves in a specific, recognizable way, not a vague "it didn't work." Too eager a trigger and an agent reacts to its own prior message, looping on itself forever. Too long an interval and a scheduled poller misses the window where its finding was actually useful. No exit condition and a thread just keeps getting nudged long after the actual question was already answered.

This is exactly what DF Code's workspace and Conductor model is built around, not bolted onto after the fact. A trigger is a first-class choice per agent — `unread` for reacting to new thread activity, `always` for nudging an existing thread every tick regardless of read state, `scheduled` for an agent with no thread to wait on that has to go find its own work. An interval is a setting, inherited from the workspace or overridden per agent, the same shape as any other default-then-override value in the app. And `resolve_thread` is the exit condition made explicit — a thread you mark resolved gets skipped by future ticks, until a new message un-resolves it on its own.

The multi-thread pipelines covered in earlier posts here — a Watcher handing off to an Addresser, a scheduled triage agent walking an issue through triage, reproduce, and fix as three separate threads — aren't really about the agents at all. They're loop engineering made concrete: each stage is its own small loop, with its own trigger and its own exit condition, chained together instead of asking one giant prompt to somehow hold the whole process in its head at once. The `/loop` command does the same thing for a single recurring task you want to self-pace, without needing a whole workspace pipeline behind it.

Once you're thinking in loops instead of prompts, the question you ask about a piece of recurring work changes. Not "what should I tell it to do" — "what should wake it up, how often is checking worth it, and what tells it to stop." That's a different design problem than writing a good prompt, and it's the one that actually determines whether a process runs quietly in the background for weeks, or needs you to keep restarting it by hand.

Q: What is "loop engineering"? A: The practice of designing a recurring process's trigger, interval, and exit condition — not just the instructions it follows on any single run — so it keeps running correctly on its own instead of requiring a person to restart it.

Q: How does DF Code make loop design explicit instead of implicit? A: Every agent assignment has an explicit trigger (`unread`, `always`, or `scheduled`), an interval inherited from the workspace or overridden per agent, and `resolve_thread` as an explicit exit condition that future ticks respect until a new message reopens the thread.

Q: What goes wrong with a badly designed loop, concretely? A: A trigger that's too eager makes an agent react to its own previous message and loop on itself; too long an interval makes a scheduled poller miss the window its finding mattered in; and no exit condition means a resolved thread keeps getting nudged indefinitely.

Q: Is a Watcher/Addresser pair or a scheduled triage pipeline just prompt engineering with extra steps? A: No — each stage is its own loop with its own trigger and exit condition, chained together; the design problem is which loop shape each stage needs, not just what any single prompt should say.