Skip to lesson
Exit
Building AI-Native Workflows1 / 3

1 min lesson

Where loops actually fail

Work through the cases in "Where loops actually fail", pairing each signal with the move that fits.

Step 1 of 3

Where loops actually failthe two failure modes you will demo

No progress

The model retries the same edit, re-reads the same file or oscillates between two wrong fixes.

Symptom: step count climbs, the diff stops changing in any meaningful way.

Runaway cost

The loop keeps calling the biggest model on a growing context with no ceiling.

Symptom: token spend per task has no upper bound you can name before you run it.

Learn more

Advanced table

Tool design for code tasks

Tool design for code tasksthe contracts that make a loop debuggable

The tools matter more than the prompt. A loop with sharp, observable tools recovers from a confused model; a loop with vague tools fails even when the model is right.

Tool
read_file
Make it idempotent by
Returning the same content for the same path + revision
Make it observable by
Including line numbers and the resolved path it actually read
Tool
edit_file
Make it idempotent by
Applying an anchored patch, not a blind overwrite
Make it observable by
Returning the exact diff that landed, not just “ok”
Tool
run_tests
Make it idempotent by
Running the same suite the same way every call
Make it observable by
Returning pass/fail counts and the first failing assertion
Tool
search
Make it idempotent by
Deterministic ordering for the same query
Make it observable by
Returning ranked hits with file + line, not a blob

Idempotent tools let you retry safely. Observable tools give the model a real signal to re-plan on.

A loop skeleton with the controls that keep it from running away.
def run_agent(task, *, max_steps=12, cost_ceiling_usd=2.0):
    state = init_state(task)
    spent = 0.0
    for step in range(max_steps):
        action, usage = model.plan(state)      # propose ONE tool call
        spent += usage.cost_usd
        if spent > cost_ceiling_usd:
            return stop("cost_ceiling", state)
        result = tools.invoke(action)          # idempotent + observable
        state = state.observe(action, result)  # feed the real result back
        if state.task_verified():              # tests green, types clean
            return stop("verified", state)
        if state.no_progress(window=3):        # diff unchanged 3 steps
            return escalate_to_human(state)
    return escalate_to_human(state)            # hit step budget