Put the control flow in code, not in the prompt
Sequencing belongs in a script and judgement in the model. One routine commit cost 180k tokens as reasoning and seconds as bash. Here is the line.
A routine multi-repository commit was dispatched to a reasoning agent. It cost roughly 180,000 tokens and seven and a half minutes.
The same operation exists as a bash script. It takes seconds and costs nothing, because it is the same logic — discover the pull requests, commit bottom-up across three repositories, write the binding — expressed as steps instead of as a description of steps.
The case this post is about
One six-step sequence, run once per post, where the order is load-bearing:
process_post() {
local slug="$1"
# WHETHER and WHEN: fixed, identical on the first post and the hundredth.
# The order is load-bearing — a description that fails preflight must never
# reach the build, and the record must never be written before the build.
local -a steps=(check_refs apply_seo build_hero run_preflight build_site persist_record)
}
A model given those six steps in prose can run five of them, reorder two, or decide that a post which "looks fine" does not need the preflight. None of those leaves a trace, and the run reports success.
Inside the loop it cannot do any of them. This post follows those six steps, because the division that makes this work is narrower than "orchestration in code, intelligence in the model" — the script owns whether and when, and the model still owns what.
The symptom
When you write instructions for an agent, sequencing and judgement arrive in the same document, in the same voice:
Read the manifest first. Then, if the post has no cover, generate one. Run the SEO check, then preflight. If preflight fails, fix it and re-run. Then persist the record.
Every clause there is an instruction, but they are not the same kind of thing. "Run the SEO check then preflight" is control flow. "Fix it" is judgement. The document does not distinguish them, so the model treats both as advice — and advice is something you can reasonably decide not to take under pressure.
The failure that follows is not dramatic. It is a step quietly not happening, in a run that reports success, because nothing in the system was obliged to notice that step nine of eleven never executed.
Why the obvious fix failed
The obvious fix is emphasis. Bold the step. Add MANDATORY. Number the sequence. Write never skip this.
I have a file where a step is marked mandatory in capitals, with a note explaining that it was skipped once and what it cost. It is better than nothing and it is still a sentence competing with a task.
The second attempt is a checklist the agent ticks off. This is worse than it looks, because the tick is produced by the same process that did — or did not — do the work. A checklist filled in by the actor is a claim, not a record.
The third is to make the instructions more precise. Precision helps with judgement and does nothing
for sequencing: a perfectly precise description of eleven steps is still a description, and what you
want is a for loop.
The actual mechanism
Move the parts that have no judgement in them into code, and let the model supply only the parts that do.
The test I use is: would two competent people ever disagree about what happens here? If not, it is control flow, and it belongs in a script.

Sequencing, iteration, retry limits, ordering constraints, the decision to persist state after each item — all of these are mechanical. A script that does them cannot forget, cannot reorder under context pressure, and cannot decide that step nine is probably fine to skip because the run is getting long.
What stays with the model is everything where the answer depends on reading something:
- what this post's argument actually is, and which figure would carry it
- whether a rendered asset shows the thing it was supposed to show
- whether a failure is a real defect or a fixture problem
- how to phrase the commit message for the diff that actually landed
Those are not steps. They are readings, and no amount of bash will produce them.
The fix
The boundary I ended up with is narrower than "orchestration in code, intelligence in the model", and the difference matters.
The script owns whether and when. The model owns what.
A concrete instance from the pipeline that produced this post: the script runs the SEO check, then preflight, then the site build, then persists the record — that order is fixed, and the script will run it identically on the hundredth post. But the description the SEO check validates was written by the model, and when preflight fails the model reads the failure and decides what to change.
The second rule is that the deterministic path must be the cheap default, not the advanced option. A framework I work in makes this explicit: for a routine commit, invoke the script; reserve the reasoning path for the cases that are genuinely ambiguous — a merge conflict, a first-time bootstrap, an unexpected repository state. Dispatching a model to reason through a routine commit is not just expensive, it is less reliable, because it re-derives each time something that was already settled.
The third is that state written by the deterministic half should be derived, not remembered. The status board for this blog is computed from the files on disk every time it runs. Nothing carries a count in its head between steps, so no step can be wrong about what happened before it.
All three rules are visible in one driver — fixed order, model-authored content, derived state:
process_post() {
local slug="$1"
# WHETHER and WHEN: fixed, identical on the first post and the hundredth.
# The order is load-bearing — a description that fails preflight must never
# reach the build, and the record must never be written before the build.
local -a steps=(check_refs apply_seo build_hero run_preflight build_site persist_record)
local step
for step in "${steps[@]}"; do
if "$step" "$slug"; then
echo " ok $step"
continue
fi
# The model owns WHAT: it reads this failure and decides the content change.
# The script does not retry, does not guess, and does not reorder around it.
echo " FAIL $step — handing the failure back; nothing after this ran" >&2
return 1
done
}
# DERIVED, not remembered: recomputed from disk every invocation, so no step
# can hold a stale count and no step can be wrong about what came before it.
status_board() {
local ready clean blocked
ready=$(count_posts_with_status ready)
blocked=$(count_posts_failing_preflight)
clean=$(( ready - blocked ))
printf '%d of %d clean · %d remaining\n' "$clean" "$ready" "$blocked"
}
One post through it, with a real preflight failure:
$ process_post what-nano-cannot-do
ok check_refs
ok apply_seo
ok build_hero
FAIL run_preflight — handing the failure back; nothing after this ran
$ status_board
131 of 161 clean · 30 remaining
The two lines that are not there are the point: no build_site and no persist_record. A model
following the same instructions in prose can skip run_preflight when the post "looks fine", or
persist the record before the build because the record is the interesting part — and neither skip
leaves a trace. Inside the loop it cannot do either. And status_board recomputes both numbers from
disk, so the count after a failed run is correct without anything having updated it.
How we proved it
The two paths for the same routine operation:
reasoning agent ~180,000 tokens ~7.5 min
bash script ~0 tokens seconds

The token number is the headline, but the reliability difference is the reason. The script performs pull-request discovery, three-repository bottom-up ordering, and the binding write in a fixed order every time. The reasoning path re-decides that order on each run, from context, and the failure mode when it gets it wrong is a partially-committed set of repositories.
The same shape showed up in this session's own work. Seven posts went through an identical sequence — check, preflight, composite the cover, regenerate the site, persist the record. Not once did I decide the order, because I did not have the opportunity to; the order is in the runtime. What I decided, seven times, was what each post argued and whether each generated image was any good.

The honest counter-evidence, and it is from the same session: putting control flow in code does not make it correct. The site build in that pipeline runs its quality gate before the rebuild, so the gate audits the previous build's output. It reported a confident pass on 113 files when 112 had just been written. That is an ordering bug, living in exactly the deterministic layer I am recommending — the difference is that it is a bug I can read, reproduce, and fix once, rather than a behaviour that varies per run.
Where this argument stops
It would be neat to conclude that everything mechanical should be code. Two things push back.
A script cannot handle the case it was not written for. The reason the reasoning path exists at all is the merge conflict, the moved repository, the halt that needs a human decision surfaced. Code that meets one of those either fails loudly, which is correct but not progress, or is extended with so many branches that it becomes an unreadable simulation of judgement.
The boundary moves. Something that needed reading last month becomes mechanical once you understand it well enough to state the rule — and the signal that it has moved is watching the model make the same decision the same way repeatedly. Judgement that has become a habit is a function waiting to be written.
So the split is not fixed, and getting it wrong in the cautious direction costs tokens while getting it wrong in the confident direction costs correctness. I would rather pay the tokens while I am still learning which is which.
What this generalizes to
Write down the steps your agent must take, then ask of each one whether two competent people could disagree about what happens. Everything they could not disagree about is control flow — put it in a script, make that script the cheap default path, and derive its state from the world rather than carrying it between steps.
Leave the model the readings: what this thing means, whether that output is right, what to do about the failure. Those are the parts that were worth a model in the first place, and they are much easier to see once the loop around them is no longer something anyone has to remember.
One post a week, no digest
New writing when it lands. No roundups, no course pitch, no "10 tips".