← back to writing
#AI Engineering · #Agentic AI · #Loop Engineering · #Long-Running Agents · #AI Reliability

Loop Engineering, Part 3: State, Recovery and the Right to Continue

A loop is not durable because the chat is long. It is durable when a fresh worker can reconstruct what happened, resume from a cursor, and prove whether another action is safe.

The most dangerous question after a crash is not “where were we?” It is “did the action already happen?”

Imagine a loop that opens a pull request, publishes a release, sends a message, or deploys a service. The external system accepts the action. Before the loop records the result, the worker dies.

The next worker arrives to an honest blank:

This is where loop engineering stops being a clever prompt pattern and becomes systems engineering.

A loop earns the right to continue only when it can reconstruct the past well enough to make the next action safe.

The conversation is not the state

Long context windows are useful. Summaries are useful. Neither should be the source of truth for a durable loop.

Conversation is a working view. It is shaped for the model, compacted for cost, and sometimes disconnected from the world between turns. Durable state has a different job: preserve the facts another process needs to resume.

A fresh worker should be able to answer:

  1. What goal is this loop pursuing?
  2. Which steps definitely completed?
  3. Which side effects are confirmed?
  4. What evidence did the checker return?
  5. What budget remains?
  6. Is the run active, waiting, done, or failed?

If any answer exists only in the previous model’s memory, the loop is not resumable. It is merely hopeful.

Durable history and model context are separate products. The append-only event log keeps the full ordered truth for replay, audit, recovery, and learning. A context manager builds a bounded working view using unresolved decisions, critical evidence, a summary of older events, and the newest events for the next model step.

Store events, not a polished story

The most useful primitive I found while building the NEXUS loop runtime was an append-only event log.

Every meaningful step becomes an event. The exact names vary by runtime; across the NEXUS loop and the Frontier human-approval rail, the useful vocabulary includes:

Each event gets a monotonically increasing sequence number. The sequence becomes a cursor: “I have safely observed everything through event 41.”

A durable Loop Engineering cycle. A trigger opens or resumes a session, the server-side driver acquires ownership, grounds, acts, observes, and checks while appending every step to an event log. A cursor lets a fresh worker replay only unseen events, but replay alone does not grant permission to advance. Unknown outcomes must be reconciled and completion proof must still be current.

This design gives you two things a summary cannot:

Replay

A new process reconstructs the run from facts in order. It does not need the old process or its private memory.

Resume

A client can disconnect, reconnect with its last cursor, and receive only the events it missed. A hard refresh becomes an inconvenience, not a lost run.

The event log should be the source of truth. Live notifications are only a wake-up mechanism. If a notification is missed, the cursor still finds the event.

Recovery requires ownership, not just replay

A resumable run can still execute twice.

A scheduler retries after a timeout. The old worker is slow rather than dead. A new worker acquires the same session, replays the same events, and both processes believe they own the next action.

The event log preserves order. It does not decide who may append the next event.

For a local runtime, an exclusive lease can be enough:

with store.lease(session_id):
    events = store.events(session_id)
    state = replay(events)
    advance_once(state)

For a distributed runtime, lease expiry is not enough. The old worker may wake after ownership has moved. Every mutation needs a fencing token that changes with the owner:

def append_event(session_id, event, fence):
    if fence != store.current_fence(session_id):
        raise StaleOwner("run ownership moved")
    store.append(session_id, event)

Resumable does not mean concurrently replayable. Before a fresh worker advances the run, it needs both the history and the current right to write.

The right-to-continue gate. A fresh worker may advance only after four independent conditions hold: the observation record is complete, the worker owns a valid lease or fencing token, ambiguous side effects have been reconciled, and the acceptance proof is current and bound to this run. Any failed condition stops or hands off instead of guessing.

Idempotency answers “may I try again?”

An append-only log tells you what was recorded. It does not automatically tell you whether an unrecorded external action happened.

For side effects, the loop needs an idempotency key: a stable identity for one intended action.

If the worker retries deploy:production:067138a, the deployment system should either:

It should not start a second production rollout because the network response was lost.

Where the external system does not support idempotency, add a status lookup before retry. The unsafe option is blind repetition.

The honest state between intent and observation is unknown.

If the log contains an authorised tool request but no result, the run should not resume model or tool work as though nothing happened:

status = "failed"
reason = "incomplete_tool_turn"
error  = "external outcome unknown; reconciliation required"

Failing closed here is not the same as declaring that the external action failed. It means the loop cannot prove whether the action happened, so it has not earned another side effect.

The ambiguous side-effect gap. The loop records an intended action and the external system may complete it, but the worker crashes before recording the result. The run enters an explicit unknown state and fails closed. Blind retry risks duplication, assumed success risks omission, and only same-key replay or exact status reconciliation can restore permission to continue.

This is why retries belong inside the loop contract. “Retry three times” is not resilience unless the operation is safe to repeat.

The log cannot contain what the observer missed

Durability does not repair incomplete observation.

In August, one of my scheduled pull-request review loops ran repeatedly against what appeared to be a stable six-item queue. A seventh pull request had opened, but it never appeared in the run snapshots. The log faithfully preserved every decision the loop made from an incomplete view.

That incident sharpened the contract:

Replay can recover everything the system observed. It cannot recover a fact the sensor never recorded.

Retry transient failure, not every failure

A rate limit, timeout, or temporary service error may deserve exponential backoff. An invalid request, denied permission, or failed policy check usually does not.

The loop should record every retry:

{
  "type": "retry",
  "operation": "model_step",
  "attempt": 2,
  "delay_seconds": 4,
  "error": "429 rate limited"
}

That record matters later. It shows whether the system recovered from a transient condition or spent its budget repeating a deterministic failure.

The breaker can then make a better decision:

Completion proof belongs to the current run

Replay can show that an earlier worker passed a check. It does not prove the world still satisfies that check now.

A deployment may have drifted. A file may have changed after acceptance. A fresh run may inherit a green workspace produced by the previous run without performing the failure-first work required by its own contract.

The completion receipt should therefore bind:

A prior run’s victory is historical evidence, not permission for a new run to declare success.

Context must be managed separately from history

The durable event log grows. The model’s context window cannot grow forever with it.

Do not solve that by deleting history. Keep the full record durable, then build a bounded working context for each model step.

A practical context manager:

  1. keeps the newest events verbatim;
  2. compacts older events into a summary;
  3. preserves unresolved decisions and critical evidence;
  4. records that compaction happened;
  5. stays inside a fixed token budget.

The model sees a useful working set. The system keeps the full truth.

That separation is easy to miss. If the compacted prompt becomes the only history, every summary mistake becomes permanent. If the full event log is always sent to the model, cost and context eventually explode.

Durable history and model context are different products.

Learning needs an outcome, not just activity

Once a loop is durable, it is tempting to make it self-improving: count what the agent used, reinforce it, and feed it into future runs.

Be careful. Activity is not success.

In NEXUS, this problem appeared in retrieval. A document could be boosted simply because search returned it often. That creates a popularity loop:

returned often -> ranked higher -> returned more often

Nothing in that cycle proves the document helped.

The fix is to record a trajectory:

query -> retrieved evidence -> action -> outcome

Then reinforce only from a positive, independent outcome: the source was cited, the answer was grounded, the check passed, or the task succeeded.

This principle applies beyond retrieval:

A loop that learns from its own activity can become confidently worse. A loop that learns from verified outcomes has a chance to improve.

That is a design requirement, not a claim that the runtime has solved general self-improvement. The recovery tests later in this article verify replay, ambiguity, and ownership; they do not verify that a learned policy improves future outcomes.

What the operating loop taught me

The theory became clearer after several weeks of hourly pull-request review runs:

ObservationWhat it proved
24 consecutive runs with an empty queue and zero mutationsSafe repetition often means doing nothing, repeatedly and verifiably.
Five reviews posted once, then skipped on later runsIdempotency is visible as the absence of duplicate side effects.
A protected workflow-file conflict was detected and routed to a person without pushing a branchHuman handoff is a valid terminal disposition, not failed autonomy.
Connection errors and an idle timeout were recorded as non-runs with no observationInfrastructure death must not become success-shaped state.
An existing resolver comment was repaired in place, then a candidate branch was verified upstream and skipped while unchangedStable identities and status checks make repeated operation safe.
A newly opened pull request was missing from repeated snapshotsA durable record can still preserve an incomplete reality perfectly.

The most valuable runs were not the ones with the most activity. They were the runs that could explain exactly why no mutation was owed.

The minimum durable loop

You do not need a distributed event platform to begin. A local JSONL file or small database can carry the fundamentals.

For each run, persist:

StateMinimum field
Identitystable session ID
Purposegoal and acceptance condition
Progressappend-only sequenced events
Side effectsidempotency keys and confirmed results
Judgmentchecker verdicts and evidence
Controlattempt, cost, time, and remaining budget
Dispositionrunning, waiting, done, or failed
Learningoutcome reported separately from the attempt

Then prove one recovery scenario before adding scale:

  1. Start the loop.
  2. Let it take one real but reversible action.
  3. Kill the worker between steps.
  4. Start a fresh worker.
  5. Confirm it replays the record, avoids duplicate action, and resumes from the correct point.

If that test is hard, it is telling you where the hidden state lives.

The durable runtime behind this work now keeps four focused recovery regressions:

test_store_appends_monotonically_and_resumes_from_cursor
test_resume_does_not_repeat_taken_turns
test_resume_with_ambiguous_tool_outcome_fails_closed
test_concurrent_runner_fails_fast_without_duplicate_work
....                                                                     [100%]
4 passed

These tests do not prove general autonomy. They prove four narrow claims this article depends on: ordered replay, no repeated completed turn, fail-closed ambiguity, and single-owner advancement.

A loop is a promise across time

The maker-checker-breaker pattern gives a loop bounded authority. Durable state gives that authority continuity without turning memory into guesswork.

The event log says what was observed. The cursor says what has been seen. The lease says who may advance. Idempotency says what may be retried. Reconciliation resolves what remains unknown. The checker says what passed now. The outcome says what is worth learning.

Together they answer the only question that matters after interruption:

Does this loop have the evidence to continue?

If yes, resume. If no, stop honestly and hand the uncertainty to a person.

That is not a limitation of autonomy. It is what makes autonomy trustworthy enough to repeat.


Start with the series: The Repeat Is the Product explains the six-part loop contract, followed by Maker, Checker, Breaker for bounded autonomy.