Prompt looping: designing a loop that actually closes

Pull-quote: “If a loop cannot name the reason it stopped, it did not stop. It ran out.”
Prompt looping only counts as a loop when the system can observe the result of its own action and change course because of what it saw. Everything else called a loop is a chain: a fixed sequence of model calls, decided at design time, that runs the same route whether or not reality cooperates. Chains are useful. They are also why so many agents fail on the first task that does not go to plan.
This is the second post in a ten-part course on LLM harnessing. It assumes the vocabulary from the harness, not the model: a step is one iteration, a budget is a ceiling in code, verification is an independent read of the source system, and a trajectory is the record of a run. Here you build the loop, and learn to tell one that converges from one that flails.
What you will be able to do
- Distinguish a single call, a chain, and a real loop, and say which one you have.
- Name what each stage of the perceive, reason, act, verify cycle requires.
- Implement four stop conditions in code and log a named exit reason on every run.
- Detect oscillation with action hashing, state hashing, and a progress metric.
- Decide when a reflection step earns its tokens, and read a trajectory to explain why a run did not terminate.
What is prompt looping, and how is it different from chaining?
Prompt looping is calling a model repeatedly inside a control loop that feeds each observed result back as input, so the number and content of the calls depend on what happened rather than on a route fixed in advance. One question settles which you have: given the same starting goal, can two runs make a different sequence of calls because the world differed? If not, it is a chain.

The three shapes fail differently. A single call has no recovery: there is no second call. A chain propagates: step two receives whatever step one produced, correct or not, and the pipeline continues with a corrupted input because no stage may change the route. A loop has a third option, which is to notice and do something else.
Build up in that order when you are learning. Start with one call and a strict output schema, then chain two calls and watch what happens when the first returns something plausible and wrong, then close the loop: observe the world after acting, check the observation, and grant the authority to change the plan.
What does each stage of the perceive, reason, act, verify cycle require?
Each stage requires specific machinery, and each has a counterfeit that looks identical in a demo. The cycle closes only when all four are real.

| Stage | What it must produce | Machinery it requires | How it gets faked |
|---|---|---|---|
| Perceive | Current world state, with provenance | A fresh read each step, not a cached snapshot | Reusing context assembled at step zero |
| Reason | The next concrete action, and why | A plan in state, revisable between steps | One chain of thought generated once |
| Act | A change in the world | Typed tool call, declared side effects, permission check | Prose asserting the action was taken |
| Verify | Evidence the change landed | Independent read of the source, compared in code | Asking the model whether it succeeded |
Perceive and verify are the two that get skipped, and the two that matter. Perceive means reading the world again on this step, because between step four and step five another process may have changed the record. Verify means comparing observed to intended, in code, on fields named in advance. A tool returning HTTP 200 reports on the call, not on the world. Only one of those is verification.
Reason deserves a note. The plan must live in state that survives the step, not only in the transcript, because a transcript gets truncated and a plan you cannot read is a plan you cannot revise. Write it as structured data: goal, ordered remaining actions, constraints discovered so far, last verification result.
What stop conditions does a loop need, and how do you see why it did not stop?
A loop needs four stop conditions, all evaluated by the harness: goal satisfied, budget exhausted, no progress, and escalation. Every finished run then writes exactly one exit reason, which is what makes non-termination diagnosable instead of mysterious.

EXITS = ("goal_satisfied", "budget_exhausted", "no_progress", "escalated", "hard_error")
def should_stop(state):
if goal_met(state.world): return "goal_satisfied" # predicate on state
if state.steps >= state.max_steps: return "budget_exhausted"
if state.tokens >= state.max_tokens: return "budget_exhausted"
if state.no_progress_streak >= 3: return "no_progress"
if state.pending_approval: return "escalated"
return None
Goal satisfied is a predicate over observed state, such as record.status == "closed" and record.owner == expected_owner. If the goal check reads the model’s final message instead of the world, you built a system that stops when it feels finished. Escalation is the condition teams forget, and the one that makes an agent safe to deploy: when the loop meets something it cannot resolve, such as a permission it does not hold, it stops and hands over the exact action it wanted to take.
“The user closes the tab” is not a stop condition. It is invisible to the harness, so server-side work continues, spend continues, the run leaves no exit reason to analyse, and you cannot write a test for it.
Instrumentation turns exit reasons into answers. Log these per step: step number, assembled context tokens, action requested, canonical action hash, observed state hash, progress metric, verification result, tokens and seconds and cost consumed, and the value of should_stop. You know the wiring is right when every finished run carries exactly one exit reason and none are blank. Group runs by that field: when no_progress and budget_exhausted dominate, the loop is not converging, and prompt tuning will not change that until you find where progress stopped.
How do you tell convergence from oscillation?
Convergence means a measurable quantity moves toward the goal across steps; oscillation means the state returns to somewhere it has already been. Without a progress metric you cannot distinguish them, so a loop with no metric neither converges nor oscillates as far as you can tell. It runs out of budget.
Pick one scalar computable from observed state and log it every step: failing tests, unresolved required fields, records that do not match between two systems, open validation errors. It must come from the world, not from the model’s opinion of its progress. Then apply three detectors.
Action hashing catches the immediate repeat. Hash the tuple of tool name and canonical arguments, where canonical means sorted keys, normalised whitespace, and rounded numbers, because otherwise identical calls produce different hashes and the detector never fires. The same hash twice with no change in state hash between them means the loop is repeating itself.
State hashing catches the alternation. Hash the fields the agent may change and keep the sequence for the run. The classic failure is a two-cycle: apply fix A, verification fails, apply fix B, verification fails, apply fix A again. The hash sequence reads A, B, A, and you catch it when the second A appears instead of twelve steps later.
The progress metric catches the slow stall, where every step differs and nothing improves. Require improvement within a window of steps, increment a no-progress counter when the window closes without movement, and tune the window against your own successful trajectories.
One root cause produces oscillation reliably. When verification returns a single boolean for a change with several independent parts, the loop cannot tell which part failed, so it discards a whole configuration and tries another one. Report verification per field and the flipping usually stops, because the next plan can keep what worked.
When does a reflection loop help, and when does it just burn tokens?
A reflection step helps when the critique receives information the generation step did not have, and wastes tokens when it does not. That rule is testable: for every critique step, name the new input. If you cannot, the step is a model re-reading its own output, judged by the same distribution that produced it, and the usual result is a confident endorsement with light paraphrasing.
Inputs that make critique worth its cost include a test or compiler result, a schema validation error, a fresh read of the world after acting, a retrieval pass with different evidence, or a rubric applied to the finished artifact rather than to the plan. Run deterministic checks first, because a linter, a schema validator, and a unit test are cheaper and more reliable than a model asked to find the same defects, then give the model only the judgements no checker can make.
Guard the rewrite too. Keep the original, apply the revision, re-run the checks, and discard the revision if the checks do not improve. Reflection without that guard is how a loop talks itself out of a working answer.
Should the loop retry or replan?
Retry when the failure is transient and the action is safe to repeat, and replan when the failure is semantic, because repeating a semantically wrong action produces the same error and spends budget doing it. Classify the failure before reacting to it.
| Failure signal | Class | Loop response |
|---|---|---|
| Timeout, 429, 503, connection reset | Transient | Retry with exponential backoff and jitter, capped attempts |
| 400 with a schema message | Malformed arguments | Repair arguments once, then replan |
| 404 on a target the plan assumed | Precondition false | Re-perceive, then replan |
| 403 permission denied | Unrecoverable in this run | Escalate, with the exact action requested |
| Same error twice, identical arguments | Loop hazard | Replan, and never retry again |
Two details matter. Retries must be idempotent or guarded by an idempotency key, because a retried write that partially succeeded creates duplicates. And retries must consume the same budget as ordinary steps, otherwise a backoff policy is an unbounded spend path with a polite name.
What does a run look like when verification changes the plan?
It looks like a trajectory where a verification failure sends the plan backward instead of forward, and the run ends on escalation rather than on the step ceiling. Here is an agent asked to move a nightly export to a new bucket.
step 1 perceive read_job_config(nightly_export) dest = s3://old-bucket/exports
step 1 reason plan: patch dest, run job, verify objects land
step 1 act update_job_config(dest="s3://new-bucket/exports") -> ok, rev 42
step 1 verify read_job_config(nightly_export) dest = s3://new-bucket/exports PASS
progress config_mismatches 1 -> 0
step 2 act trigger_job(nightly_export) -> run_id 8817, state SUCCEEDED
step 2 verify list_objects("s3://new-bucket/exports") 0 objects FAIL
note the tool said SUCCEEDED, the world says empty
step 3 perceive read_run_log(8817) "AccessDenied: PutObject s3://new-bucket"
step 3 reason replan: the config was correct, the write permission is not
step 3 act (no tool in scope can grant object write)
step 3 stop escalated: needs bucket policy change, action recorded
Three things there are the point of this post. The tool reported SUCCEEDED and verification disagreed, because the tool reported on the job run while verification read the destination. The failure changed the plan instead of triggering a retry, because a permission error is not transient. And the run ended with a named exit reason and a specific handover a human can act on.
Where this goes wrong
The loop never terminates. The symptom is a step count climbing until an unrelated timeout kills it. The cause is stop conditions expressed as prompt instructions rather than harness checks. Implement the four in code, log the exit reason, and alert on budget_exhausted rather than accepting it as normal.
The agent alternates between two wrong fixes forever. The symptom is a trajectory that reads A, B, A, B with confident reasoning each time. The cause is coarse verification plus no repeat-state detection. Add state hashing and per-field verification, and stop when a state hash repeats.
Retries turn into a storm. The symptom is a burst of identical calls and a cost spike with no progress. The cause is retrying semantic failures and exempting retries from the budget. Classify errors first, cap attempts, add jitter, and count every attempt against the same ceiling.
The reflection step made the output worse. The symptom is a second draft that is longer, hedged, and no better. The cause is critique with no new information plus a rewrite accepted unconditionally. Give the critique an external signal and keep the original unless the checks improve.
Common questions
How many steps should a loop be allowed?
Set the ceiling from your own trajectories rather than a round number. Measure the step count of runs that succeeded, take the longest, add a margin, and treat every budget_exhausted exit as something to investigate. A ceiling nobody investigates is a slower way to fail.
Is ReAct the same thing as a loop?
ReAct is a prompting pattern that interleaves reasoning with actions, and it becomes a loop when the harness feeds real observations back and can stop the run itself. The pattern shapes what the model emits; the harness supplies perception, verification, budgets, and termination. You can implement ReAct-style prompting inside a chain and get none of the benefits.
Do I still need a verify step if my tool returns success?
Yes. A return code says the call completed, not that the world reached the intended state, and the two diverge often: partial writes, asynchronous jobs that fail later, blocked side effects, and writes to the wrong record all return success. Verification is a separate read of the source, compared in code against fields you named.
How do I test a loop?
Record golden runs with fixed tool responses so a trajectory replays deterministically, then inject faults: a timeout, a 403, a verification failure, and a tool that returns success while changing nothing. Assert on the exit reason and step count, not only the final answer. A loop that has only ever seen the happy path is untested.
Next steps
Read context engineering as a budget you spend next, because every extra iteration spends context, and a loop that outgrows its window fails in a way that looks like a reasoning problem.
For the same ideas as running code, tools in Hermes Agent shows a loop where each action is followed by a check, and a failure taxonomy for production agents names the loop failures you will meet. More field notes sit in the Signals archive.
Then instrument one loop this week: add the exit-reason enum, log the action and state hashes per step, and group last week’s runs by how they ended. A loop with an escalation path and approval gates on every write is the pattern behind ComplyGrid in federal compliance work, where stopping to ask is part of the design.
