Lesson 17 / 26
Agentic Coding
Measuring the checkpoint interval when a multi-step job is handed off: when the 480 steps of sixty fictional jobs are run at six different intervals, stopping at every step gives 478/480 correct steps but asks for 479 stops, stopping every eight steps drops to 60 stops and falls to 355/480. Wasted steps per caught error rise from 1.00 to 5.64; missed errors from 1 to 21. The number of calls is almost constant across the six intervals (between 549 and 598), so the cost of stopping is not calls but human attention. Tokens per correct step are lowest at the three-step interval in both runs (146.8 and 144.9), so the most accurate interval and the cheapest interval are not the same.
Published Updated
In this lesson
- Shows how an error spreads to later steps in a multi-step handoff and where the checkpoint cuts it off.
- Compares missed errors, wasted steps, and correctly delivered steps by varying the checkpoint interval.
- Calculates the cost of stopping separately as stop count, calls, and tokens.
- Interprets why the interval with the highest accuracy and the interval that is cheapest per unit are different.
Contents
In the previous two lessons every task was a single call: the context went, the suggestion came, the developer accepted it or did not. The decision point was single and in the same place as the suggestion. When reading a file, changing a function, updating the callers, and reformatting the report are delegated with a single request, this arrangement breaks: the tool takes steps one after another, each step is built on top of the previous one, and the developer no longer looks at every step but at the place where they stop.
This lesson measures that stopping place and nothing else. How the tool plans, what it remembers, and how it closes its loop are not covered here; those are the subject of the AI Agents course of this curriculum. The only question here is this: when stopping every how many steps, is the error caught before it propagates, and what does that cost.
- AD17. The setup is the same as in the previous two lessons: the fictional codebase (five modules, 53 lines), twenty-four fictional tasks, and the assistant simulator. No real agentic coding tool is run.
- AD18. The multi-step job: 60 fictional jobs, 8 steps each, 480 steps in total. The context of each step is selected, that is, only the files that step requires are sent.
- AD19. Propagation rule: if a rejected step goes unnoticed, the later steps are built on top of it and are counted as contaminated. This is the only structural fact this lesson measures.
- AD20. A checkpoint is placed every
dsteps and at the end of the job; at the stop, the human looks at that window. - AD21. The detection share falls as the window grows:
0.95 - 0.07 x (d - 1), lower bound0.40. Reading an eight-step change in one go is harder than reading a single step. - AD22. A caught error is rerun: the steps from the faulty step to the stop are called again and assumed to be fixed. These are wasted steps.
- AD23. An error that is not caught enters the baseline: the remaining steps of the job are contaminated and later stops cannot see it, because what is looked at is not the accepted baseline but the new work.
- AD24. The cost has three separate items: stop count (the number of times the human looks), calls, and tokens; each stop also adds a 50-token status summary.
- AD25. The delivery measure is the correct step: in a job with a missed error, the number of correct steps extends only up to the place where the error is.
- AD26. Step results are produced with the same seed independently of the interval; the only thing that changes between intervals is where it stops. The second run uses seed 20260219.
The Trace of a Job
First a single job is run with two different intervals. The same eight steps, the same results; only the place of the stops changes.
# THE SAME FICTIONAL SETUP -- codebase of five modules (53 lines), 24 fictional tasks, # assistant simulator. No real agentic coding tool is run. SEED, M32 = 20260218, 0xFFFFFFFF def rng(t): x = ((t ^ (t >> 16)) * 2246822507) & M32 x = ((x ^ (x >> 13)) * 3266489909) & M32 s = [(x ^ (x >> 16)) & M32] def next_value(): s[0] = (s[0] * 1664525 + 1013904223) & M32 return s[0] / 4294967296 return next_value def mix(m): h = 2166136261 for c in m: h = ((h ^ ord(c)) * 16777619) & M32 return h CLASSES = ("tamamlama", "aciklama", "donusturme") FILES = ("okuma.py", "tarife.py", "denetim.py", "ozet.py", "bicim.py") REQUIRED = { 0: ("okuma.py",), 1: ("tarife.py",), 2: ("denetim.py", "okuma.py"), 3: ("ozet.py", "denetim.py"), 4: ("bicim.py", "ozet.py"), 5: ("tarife.py", "okuma.py"), 6: ("ozet.py", "okuma.py", "tarife.py"), 7: ("denetim.py", "bicim.py"), } TASKS = [ {"name": c + "-" + str(i + 1), "label": c, "required": REQUIRED[i], "difficulty": 0.05 * (i % 4)} for c in CLASSES for i in range(8) ] BASE = {"tamamlama": 0.34, "aciklama": 0.72, "donusturme": 0.18} def assistant(task, context, seed=SEED): # SIMULATOR -- FICTIONAL r = rng(seed ^ mix(task["name"] + "|" + ",".join(sorted(context)))) overlap = sum(1 for f in task["required"] if f in context) / len(task["required"]) irrelevant = sum(1 for f in context if f not in task["required"]) / len(FILES) score = BASE[task["label"]] + 0.55 * overlap - 0.30 * irrelevant - task["difficulty"] score = min(max(score, 0.0), 1.0) accepted = r() < score tokens = 40 + 26 * len(context) + (18 if task["label"] == "donusturme" else 0) return {"accepted": accepted, "score": score, "tokens": tokens} JOB_COUNT, STEPS, STATUS_TOKENS = 60, 8, 50 def detection(d): """The human's share of seeing the error falls as the checkpoint window grows.""" return max(0.95 - 0.07 * (d - 1), 0.40) def handoff(j, d, seed=SEED, verbose=False): """Running a single multi-step job with a stop every d steps.""" count = {"calls": 0, "tokens": 0, "stops": 0, "caught": 0, "missed": 0, "wasted": 0, "correct": 0} faulty, escaped = None, False for k in range(STEPS): task = TASKS[(j * STEPS + k) % len(TASKS)] result = assistant(task, task["required"], seed ^ mix("adim|" + str(j) + "|" + str(k))) count["calls"] += 1 count["tokens"] += result["tokens"] if escaped: if verbose: print(f" step {k} {task['name']:<14} contaminated (on top of a missed error)") continue if faulty is None and not result["accepted"]: faulty = k if verbose: label = "accept" if result["accepted"] else "REJECT" if faulty is not None and faulty < k: label = "contaminated" print(f" step {k} {task['name']:<14} {label}") if (k + 1) % d == 0 or k == STEPS - 1: count["stops"] += 1 count["tokens"] += STATUS_TOKENS if faulty is None: if verbose: print(f" STOP step {k}: clean") continue r = rng(seed ^ mix("denetim|" + str(j) + "|" + str(k))) if r() < detection(d): redo = k - faulty + 1 count["caught"] += 1 count["wasted"] += redo count["calls"] += redo count["tokens"] += redo * result["tokens"] if verbose: print(f" STOP step {k}: error at step {faulty} was seen," f" {redo} steps rerun") faulty = None else: count["missed"] += 1 escaped = True if verbose: print(f" STOP step {k}: not seen, the error entered the baseline") count["correct"] = faulty if escaped else STEPS return count print("job 0, step interval 4:") a = handoff(0, 4, verbose=True) print(" ->", {x: a[x] for x in ("correct", "wasted", "stops", "calls", "tokens")}) print("\njob 0, step interval 8:") b = handoff(0, 8, verbose=True) print(" ->", {x: b[x] for x in ("correct", "wasted", "stops", "calls", "tokens")})
job 0, step interval 4:
step 0 tamamlama-1 accept
step 1 tamamlama-2 accept
step 2 tamamlama-3 REJECT
step 3 tamamlama-4 contaminated
STOP step 3: error at step 2 was seen, 2 steps rerun
step 4 tamamlama-5 accept
step 5 tamamlama-6 accept
step 6 tamamlama-7 REJECT
step 7 tamamlama-8 contaminated
STOP step 7: error at step 6 was seen, 2 steps rerun
-> {'correct': 8, 'wasted': 4, 'stops': 2, 'calls': 12, 'tokens': 1178}
job 0, step interval 8:
step 0 tamamlama-1 accept
step 1 tamamlama-2 accept
step 2 tamamlama-3 REJECT
step 3 tamamlama-4 contaminated
step 4 tamamlama-5 contaminated
step 5 tamamlama-6 contaminated
step 6 tamamlama-7 contaminated
step 7 tamamlama-8 contaminated
STOP step 7: error at step 2 was seen, 6 steps rerun
-> {'correct': 8, 'wasted': 6, 'stops': 1, 'calls': 14, 'tokens': 1312}
The two traces carry the same eight steps and the underlying results are also the same: steps 2
and 6 are rejected. The only thing that changes is the place of the stops, but the trace is not the
same. In the four-step interval the first error is caught after spreading two steps, and so is the
second; four steps are rerun in total. In the eight-step interval the first error spreads six steps
and the second error is never seen — step 6 is already contaminated at that point, so it is not
counted as a separate error. Both deliver eight correct steps in the end, but one spends 12 calls
and 1178 tokens, the other 14 calls and 1312 tokens.
This single job is a good example but it is not a number: here the check saw the error in both. What happens when it does not see can only be read over enough jobs.
Sweeping the Interval
Sixty jobs, four hundred and eighty steps, six different checkpoint intervals. Step results are independent of the interval; the only thing that changes is the place of the stops.
def run_all(d, seed=SEED): total = {"calls": 0, "tokens": 0, "stops": 0, "caught": 0, "missed": 0, "wasted": 0, "correct": 0} for j in range(JOB_COUNT): for key, v in handoff(j, d, seed).items(): total[key] += v return total TOTAL_STEPS = JOB_COUNT * STEPS HEADER = (" every detect stops caught missed waste right step calls tokens tok/right") def show(d, seed): a = run_all(d, seed) print(f"{d:>6}{detection(d):>7.2f}{a['stops']:>7}{a['caught']:>11}{a['missed']:>7}" f"{a['wasted']:>6}{a['correct']:>8}/{TOTAL_STEPS}{a['calls']:>7}{a['tokens']:>10}" f"{a['tokens'] / a['correct']:>11.1f}") print("run A (seed 20260218)") print(HEADER) for d in (1, 2, 3, 4, 6, 8): show(d, SEED) print("\nrun B (seed 20260219)") print(HEADER) for d in (1, 2, 3, 4, 6, 8): show(d, SEED + 1)
run A (seed 20260218)
every detect stops caught missed waste right step calls tokens tok/right
1 0.95 479 90 1 90 478/480 570 78648 164.5
2 0.88 233 69 8 109 455/480 589 67790 149.0
3 0.81 172 52 11 104 441/480 584 64746 146.8
4 0.74 117 45 11 118 430/480 598 63320 147.3
6 0.60 104 23 23 69 367/480 549 57784 157.4
8 0.46 60 14 21 79 355/480 559 56414 158.9
run B (seed 20260219)
every detect stops caught missed waste right step calls tokens tok/right
1 0.95 467 71 4 71 463/480 551 76070 164.3
2 0.88 224 52 11 79 430/480 559 64318 149.6
3 0.81 168 44 12 78 428/480 558 62010 144.9
4 0.74 107 29 14 85 392/480 565 59550 151.9
6 0.60 105 24 17 79 393/480 559 58790 149.6
8 0.46 60 18 16 101 393/480 581 59032 150.2
The Measure of Propagation
The first half of the question — is the error caught before it propagates — is the quotient of two columns in the table. Wasted steps divided by the number of caught errors give how many steps the error spread before it was caught. In the first run this number is, in order, 1.00 / 1.58 / 2.00 / 2.62 / 3.00 / 5.64; in the second run 1.00 / 1.52 / 1.77 / 2.93 / 3.29 / 5.61. The two runs say the same thing separately: when stopping at every step the error does not spread; when stopping every eight steps it is caught on average after spreading five and a half steps.
The missed-error column gives the second half. Stopping at every step misses 1 error in the first run and 4 in the second; stopping every eight steps misses 21 and 16. The reason is written in the table: the detection share falls from 0.95 to 0.46, because reading an eight-step change in one go is hard.
One column is misleading here and should not be read: the number of caught errors. As the interval grows it falls from 90 to 14, as if the errors had decreased. What decreases is not the errors but the work in which we can count errors; since a missed error contaminates the rest of the job, no other error is counted in that job. The quality of delegated work cannot be measured by “how many errors were found”. The only thing that can be measured is the correct steps delivered: from 478/480 to 355/480, and in the second run from 463/480 to 393/480.
The same dependence shows in the stop column. In the run that stops at every step the number of stops is not 480 but 479; in the second run 467. The missing stops are the remainder of jobs whose error was missed: because the baseline is already contaminated, there is no clean work left to look at in those stops and they are not counted. The columns of the table are not independent of each other, because the delegated job is not made of independent steps either.
The Cost of Stopping
The cost has three items and the three do not go in the same direction. The number of calls stays between 549 and 598 across the six intervals, and between 551 and 581 in the second run — that is, almost constant. Stopping often creates reruns, stopping rarely creates longer reruns; the two cancel each other out. Stopping is not a matter of calls.
Tokens fall steadily: from 78648 to 56414, that is, stopping rarely saves about twenty-eight percent of the total tokens. Most of this comes from the 50-token status summary added per stop; the difference between 479 stops and 60 stops alone is 20950 tokens.
The real cost is in the third item and it is not paid in tokens: the stop count. Stopping at every step calls the human to the desk 479 times, stopping every eight steps 60 times. The difference is eightfold, and what is gained in return is 123 correct steps. For every correct step saved, the human has looked about 3.4 times more. This trade-off is an engineering decision and it is written in the table; which side to choose depends on how reversible a step is.
The last column shows a third optimization point. Tokens per correct step are lowest at the three-step interval in both runs: 146.8 and 144.9. This is not the interval that gives the most correct steps (one); nor is it the interval that asks for the fewest stops (eight). Three separate criteria, three separate winners. Choosing a checkpoint interval is therefore not an optimization but a declaration: an interval chosen without saying which cost it lowers is not counted as measured.
Summary
- In a multi-step handoff, an error that is not caught spreads to later steps; the checkpoint is where this contamination is cut, and the only thing it measures is where it stops.
- The number of steps spread before being caught is 1.00 when stopping at every step and 5.64 when stopping every eight steps; 1.00 and 5.61 in the second run.
- Missed errors rise from 1 to 21 (from 4 to 16 in the second run), because the detection share falls from 0.95 to 0.46 as the window grows.
- The number of caught errors is not a quality measure: it falls as the interval grows, because a missed error makes the rest of the job uncountable. The only thing that can be measured is the correct steps delivered (478/480 against 355/480).
- The three items of cost separate: the number of calls is almost constant (between 549 and 598), tokens fall by twenty-eight percent, the stop count falls eightfold. The real price of frequent stopping is human attention.
- Tokens per correct step are lowest at the three-step interval in both runs (146.8 and 144.9); the most accurate interval, the cheapest interval, and the interval with the fewest stops are three separate rows.
Next Step
In this lesson the checkpoint was treated as a place, and the detection share was squeezed into a single number: the human who stops sees the error with a probability, and if they do not, the error enters the baseline. What was inside that number was never opened. What exactly is looked at at the stop — is the code running, is it giving the right result, is it calling a dependency that does not exist, what was carried in the text that entered the context. The checkpoint was placed, but what will be checked was not said. The next lesson fills this gap and turns the detection share from an assumption into a measured number: independent checks are run on the same generated code, the defect each one catches is counted separately, and the residual that none of them catches is written by name.
To keep your progress and take notes, Log in
My notes
Log in to take notes.