# Why I don't use Codex for long-running agent loops

> A Codex loop root can re-read its cached context every few seconds instead of every 19 minutes. pi is my default for OpenAI-model loops; the historical comparison explains why.

- Canonical page: https://rob-knight.com/articles/why-i-dont-use-codex-for-long-running-agent-loops/
- Kind: Article
- Published: 2026-09-29
- Updated: 2026-10-21
- Topics: agents

A long-running loop needs its root to do nothing while lanes and CI run, and to wake on the event that matters. I can't get that reliably from the Codex CLI, so pi is my default for OpenAI-model loops, with my own extensions published as [rknightion/loop-pi](https://github.com/rknightion/loop-pi). Codex runs one only if I name it, as I did for two loops in a private repository on 13 October. Claude Code has its own [harness document](/reference/loop-harness-claude/) for Claude models. The measurements below compare Codex and pi runs from late September and early October.

In plain terms: every time an agent thinks, the harness sends its whole conversation so far back to the model. The provider caches most of it, so each resend is cheaper and faster, but it is still a resend of the whole thing. An agent that checks on its helpers every few seconds pays that resend every few seconds. On one of my public repositories a Codex root did that for 18 hours and re-read 323 million cached tokens. Checking once every 19 minutes, which is what the loop asks of it, would have come to about 9 million.

<figure class="diagram figure-breakout">
  <img class="on-light" src="/diagrams/46-tldr-why-i-dont-use-codex-for-long-running-agent-loops-light.svg" alt="At a glance: a Codex loop root woke every few seconds and re-read its whole cached context each time, so pi is my default for OpenAI-model loops, where the root has no wait tool to misuse. Three one-hour strips of tick marks, one per root model call while lanes run, at average spacing, with the cached tokens re-read per hour at the right. Codex root: 310 calls an hour, one about every 12 seconds, a near-solid band, 42.9M cached tokens an hour. pi root: 83 calls an hour, one about every 43 seconds, 20.8M. One wake every 19 minutes, what the loop asks of a root: three ticks at 19, 38 and 57 minutes, about 0.5M. An axis marks 0 to 60 minutes. Below, two columns. Why the Codex root spins: it calls the wrong wait tool, which fails at once and costs a full turn, 5,070 failed waits in 16 hours, and no hook can see it. Why pi: there is no wait tool; a lane returning, a watcher exiting or a timer firing starts the next root turn. A note at the foot: ticks are at average spacing; the groups of loops ran on different repositories and work, every root on Sol at medium, so this is not a controlled test." width="1440" height="1092" loading="lazy" decoding="async">
  <img class="on-dark" src="/diagrams/46-tldr-why-i-dont-use-codex-for-long-running-agent-loops-dark.svg" alt="" aria-hidden="true" width="1440" height="1092" loading="lazy" decoding="async">
  <figcaption>These are averages over every hour with a lane running. The worst single run, synthkit loop 44, re-read 323 million cached tokens in 18 hours, where one wake every 19 minutes would have come to about 9 million.</figcaption>
</figure>

Scope: the root of a multi-hour loop under [the loop contract](/reference/loop-contract/), on Codex CLI 0.156 to 0.159 with the GPT-6 models. I haven't measured interactive Codex sessions and I'm not making any claim about them.

## What a loop root needs

A few terms first, because the rest leans on them.

- The **root** is the agent running the loop. It reads the plan, hands work out and decides what happens next.
- A **lane** is a helper agent the root starts for one piece of work: implement this, review that, watch that CI run.
- A **model call** is one turn of the model. The harness sends the whole conversation so far and the model answers.
- The **context** is that conversation. A root a few hours in carries around 150,000 tokens on Codex in my loops, and around 230,000 on pi.
- The **cache** is the provider remembering a conversation it has already seen, so a resend costs less. Less, not nothing: every call still reads the whole context back out of the cache.

Most of the time a root has nothing to do while lanes implement, gates run and CI finishes, and in that time it should:

- sit idle without spending model turns;
- wake on the event that matters, a lane returning, a process exiting or a deadline;
- never generate inference just to stay alive.

Waiting costs by the wake. A Codex root blocked in a wait makes no model call, but each return is one call over its whole context, about 150,000 tokens, nearly all of them cached. A root that wakes every 30 seconds re-reads its whole context 120 times an hour; one that wakes every 19 minutes does it about three times.

<figure class="diagram figure-breakout">
  <img class="on-light" src="/diagrams/15-who-waits-v2-light.svg" alt="A table of three harnesses against three questions. pi: a lane waits on its own process (watch_process); the root waits on lanes, watch_start and wake_at; what wakes the root is a lane return, a watcher exit or a timer. Claude Code: a lane waits on its own process; the root waits on lanes and one background command; a task notification or the command's exit wakes it. Codex: a lane waits on its own process with a self-polling exec cell; the root waits in wait_agent for up to 19 minutes and hands process waits to a scout; nothing wakes the root, which holds its turn, marked WARN. Note: only the root pushes, and main's CI is the root's to watch, through a scout on Codex." width="1440" height="588" loading="lazy" decoding="async">
  <img class="on-dark" src="/diagrams/15-who-waits-v2-dark.svg" alt="" aria-hidden="true" width="1440" height="588" loading="lazy" decoding="async">
  <figcaption>Every harness lets a lane block on its own process. The difference is the last column: a Codex root has no event that wakes it, so it holds its turn in a wait instead.</figcaption>
</figure>

## What goes wrong on Codex

Five separate problems. The [Codex harness](/reference/loop-harness-codex/) works around each one; none of the workarounds removes the cause.

### The wrong wait tool

Codex has two tools that wait. `collaboration.wait_agent` waits for lanes. Code mode's `functions.wait` collects an exec cell the root started this turn. Roots call the second while lanes are in flight, with cell ids that don't exist. The call fails at once and costs a full model turn. In one overnight loop in a private repository, loop 149, the root made 5,070 failed `wait` calls in 16 hours, against 37 `wait_agent` calls, out of 7,101 root model calls in all. Put simply, the root kept pressing a button that did nothing, and every press cost a full re-read of its context. The roots described this as the harness misrouting their call; a synthkit root paused with "collaboration calls misrouted repeatedly" while both lanes were still running.

### No hook can see it

The [Codex 0.157.1 wait handler](https://github.com/openai/codex/blob/36650394c5b38c2990ccf2a3457165ca3e9d9726/codex-rs/core/src/tools/code_mode/wait_handler.rs) returns no `PreToolUse` or `PostToolUse` hook payload for code-mode wait. Its comment calls this "runtime control for an existing code cell, not a standalone user action". That is the release responsible for most invalid waits in the sample below. The Stop hook I use elsewhere doesn't help either, because a root spinning on failed waits never ends its turn. Neither of those hook mechanisms can interrupt that wait loop.

A prose rule doesn't either. A rule naming the bad ids, and then a rewrite naming the moments a root slips and the recovery, still left synthkit loop 44 making 1,391 invalid waits in 2,049 root calls.

### The 19-minute floor

Roots pass short timeouts with deadlines hours away, so I set the minimum and default wait to 1,140,000 ms. That works, at a price: a lane's deadline is noticed up to 19 minutes late, and a root that calls `wait_agent` after a child's return has already landed in its context sits out the whole 19 minutes reporting nothing.

### Completion doesn't wake an idle parent

A Codex root has to hold its turn open inside a wait to hear from its lanes at all. A step gated on a clock time is held the same way, by calling `wait_agent` until the gate passes, because ending the turn wakes nothing.

### Collaboration tools don't exist inside a cell

With the default `non_code_mode_only`, spawn and wait are direct calls only, and a cell's tool listing doesn't show them. So a root with lanes in flight can't wait on a process inside a cell without going deaf to its lanes, and each process wait, main's CI or a release, goes to a `scout`, an LLM lane that watches one process and returns the raw readback. One root looked for the tools inside a cell, found none and drained with three ready lanes and about 11 hours of its run left.

## The Codex settings

The [Codex harness](/reference/loop-harness-codex/) runs the root on `gpt-6.1-sol` at high, with these settings in the loop home:

- `[features.multi_agent_v2]` sets `min_wait_timeout_ms` and `default_wait_timeout_ms` both to 1140000, so every root `wait_agent` lasts 19 minutes unless a child messages or finishes.
- `non_code_mode_only` stays at its default, `true`. The model catalogue decides whether code mode is on, so the harness answers it with a direct-dispatch rule.
- `[agents] max_concurrent_threads_per_session = 19` allows nineteen child threads; a spawn refused at the limit waits for a return and retries.
- `model_context_window` is 872000 and `model_auto_compact_token_limit` is 741200.
- Pinned custom agents cover the `worker`, `senior`, `senior-escalation`, `reviewer` and `scout` roles. The root spawns by `agent_type` and passes no model or effort, and a spawn with no matching installed agent is a harness fault.
- There's deliberately no tool hook on sleeping or the cell wait. Codex exempts the cell wait from tool hooks, so a hook could only police half the rule.

The roots measured below ran Sol at medium with a 700,000-token context window.

## How I found it

None of this shows from inside the loop. A root doesn't count its own wakeups, its closeout doesn't either, and here the root's own explanation was that the harness had misrouted the call. The catalogue is what showed it. [The hot and cold conversation archive](/articles/the-hot-and-cold-conversation-archive/) covers how every transcript lands on my homelab server, and [building an agent-transcript indexer](/articles/building-an-agent-transcript-indexer/) covers the catalogue, which classifies each root model call by what it reacted to. A view in it counts the fake-cell waits per root, and counting them over transcripts is what turned "the root seems stuck" into 5,070 failed waits in one night.

## Why pi

[pi](https://github.com/earendil-works/pi) is `earendil-works/pi`, MIT. It has no native subagents and no approval prompts. It does ship MCP, codemode and tool search as built-in extensions, and the loop home switches all of them off; loop work is shell, git, Backlog and CI, and doing without MCP costs it little. The three properties that mattered came from reading its source:

- a custom tool may block indefinitely;
- its `tool_call` event covers every tool, with no exempt one;
- `agent_before_settle` lets an extension intercept a turn that is about to stop.

The [pi v1.0.4 extension reference](https://github.com/earendil-works/pi/blob/7c10bd4337495ee613f2224843ecdf349b80d1df/packages/coding-agent/docs/extensions.md) documents `tool_call` blocking and `agent_before_settle` continuation. It is a later public description of those mechanisms, not proof of the earlier evaluation's installed version or of every internal tool path.

Spawning comes from [`pi-subagents`](https://github.com/nicobailon/pi-subagents): per-agent model and thinking level, fresh context, status, steer, stop, worktree isolation, concurrency and depth caps, and an async completion that starts a new root turn on its own. Its blocking `bg_wait` tool is switched off, so the root has no wait tool to misuse.

I rejected two alternatives.

- **[oh-my-pi](https://github.com/can1357/oh-my-pi):** a separate Bun and Rust fork of pi, not a set of extensions, and in the version I read some of its tool paths emitted no `tool_call` at all, so a guard couldn't see them. Its Rust core can't practically be extracted from.
- **A from-scratch orchestrator:** `pi-subagents` already covers spawn, wake on completion, status, steer, stop, worktree isolation and the caps, so writing my own would rebuild what I can pin.

pi is not stable ground. It releases often, and its extension API still changes between minor releases. So loop-pi pins pi and `pi-subagents` exactly; a bump is its own change, with the full test run, a real loop and a note of what the extension API changed. The installer smoke-loads the root and lane entries before it swaps a build in and keeps older builds for rollback, and CI checks the npm provenance of both pinned packages.

## What loop-pi adds

Eight extensions, pinned agent files and an installer that writes a launcher per pi home, `~/.loop-pi-<context>`, built for loops. [The machinery around the loop](/articles/the-machinery-around-the-loop/) covers the internals; the [pi harness page](/reference/loop-harness-pi/#routes) has the current routes a root reads. Three of the extensions are why the waiting problem goes away.

**`loop-wait`** gives the root `watch_start`, `watch_stop`, `wake_at` and `wake_cancel`. `watch_start` runs a quiet command from the extension and, when it exits or hits its deadline, pushes a message that starts a root turn. `wake_at` does the same for a timer. A lane launched with a deadline gets a timer for it, cancelled when the lane returns, and timers and watchers survive a restart. A scripted-provider test shows an idle root woken with zero assistant messages between arming and the wake. Lanes use `watch_process`, which blocks inside the tool, to wait on their own gates and processes; the guard stops the root using it while any lane is in flight, because a blocking call would delay pushed completions.

**`loop-continuation`** is the Stop-hook contract from the Claude plugin, ported. A root that ends a turn with no tool call is re-prompted, at most three times per chain, unless a counting closeout exists, its last line is `PAUSED:`, or its last line is a `WAITING:` backed by a timer. If no timer is armed at the stated deadline, the extension arms one, so the root wakes by then even if a child hangs silently. The fourth stop goes through and writes an incident file. It also checks the launch before it arms, and refuses a wrong host or repository, or a changed charter or `Standing:` hash.

**`loop-guard`** runs on every tool call, with no exempt tool. It blocks background launches, force and destructive pushes, `git add -A` and `git commit -a`, and runs the same Backlog and staging guard scripts Codex and Claude Code use. Lanes never push: the guard refuses every lane push, and only the root's own shell runs `git push`. Every push the guard sees lands in a push log that the closeout audit checks commit by commit. Lanes are also blocked from secret-store writes, releases, merges, workflow dispatch and mutating `gh api`. The agent files name two models, `gpt-6.1-sol` for every role but escalation, which runs `gpt-6-astra`, and the guard blocks any model outside the configured family and any model, thinking or deadline override on a spawn. It registers its own lane entry and `loop-wait`'s as required child extensions, so no lane can opt out, and if that registration fails the root's `subagent` calls are blocked.

The other five don't touch the waiting. `loop-state` writes the run's event log, `lane-worktrees` gives each lane its own worktree, and `request-ceiling` ends a model request after 15 minutes with no model progress or 75 minutes in all, then re-prompts the session. `call-timing` records first-token timing and response headers, and `loop-status` puts the root's status line on screen.

Started inside a repository, the launcher refuses to run if the repository carries its own pi settings, extensions or agent files, isn't clean, or doesn't gitignore `.pi/`, because a repository's agent files would outrank the home's and pi-subagents writes into `.pi/` in the checkout. It snapshots remotes, tags and releases before the run, and at closeout it reports any change that no charter grant and no logged push covers.

The launcher also has a fast variant, `loop-pi-fast`, which sends every request in the run at OpenAI's priority tier. I choose it at launch; it changes speed, not authority.

## Results

The figures come from the agent-history catalogue, not from loop reports, queried on the evening of 5 October. The comparison is every Codex loop from 25 September onward against every pi loop launched from 28 September to midnight on 4 October, London time, that has a completion receipt: 83 of the 89 launched in that window. Both groups count only loops that ran at least an hour and started at least one lane, and every root ran Sol at medium.

- **Codex:** 20 loops, 184 hours and 368 lanes, across cf2otel, synthkit and four private repositories.
- **pi:** 83 loops, 486 hours and 2,028 lanes, across twelve repositories.

A lane is any helper the root started, pollers, reviewers and mappers included. "While lanes run" means at least one helper was between its first and last event. Input tokens are what the harnesses report, cached plus uncached. Priced cost is the catalogue's estimate at list price; the loops run on my ChatGPT subscription, so it's a measure of size, not money spent.

| Measure                                                | Codex         | pi           |
| ------------------------------------------------------ | ------------- | ------------ |
| Lanes started per hour                                 | 2.0           | 4.2          |
| Root model calls per lane                              | 88.2          | 17.7         |
| Root model calls per hour while lanes run              | 310           | 83           |
| Minutes with no root call while lanes run              | 16%           | 50%          |
| Invalid waits (share of root calls)                    | 7,359 (22.7%) | none to make |
| Root context per call (median)                         | 150k          | 233k         |
| Cached tokens the root re-read per hour, lanes running | 42.9M         | 20.8M        |
| Cached tokens the root re-read per lane                | 12.8M         | 4.6M         |
| Total input tokens per lane                            | 19.2M         | 7.2M         |
| Total input tokens per hour                            | 38.4M         | 30.1M        |
| Cached share of root input                             | 98.9%         | 98.7%        |
| Priced cost per lane                                   | $4.60         | $1.42        |
| Priced cost per hour                                   | $9.20         | $5.92        |

Per unit of work pi is much cheaper: its root makes a fifth of the calls per lane, re-reads about a third of the cached context per lane, and total input per lane is 62% lower. It's cheaper per hour too, even though it starts twice as many lanes an hour: total input per hour is 22% lower and priced cost per hour 36% lower.

The cache line is the one I care about. While lanes were running, a Codex root made a call about every 12 seconds and re-read 43 million cached tokens an hour. One wake every 19 minutes works out at about half a million. pi is nowhere near that floor either: its root calls about every 43 seconds while lanes run, reading returns, checking git and Backlog and dispatching the next lane, and re-reads 21 million an hour, half what Codex does. The difference is that a pi root goes quiet between bursts, with no call in half the lane-running minutes against one in six, and it never spins on a wait.

The spinning is the worst case, and synthkit loop 44 shows it. It ran for 18 hours with seven lanes. The root made 2,049 calls and re-read 323 million cached tokens, and the catalogue classes 1,614 of those calls as reactions to a timed-out or failed wait. At one wake every 19 minutes the whole run would have re-read about 9 million.

The catalogue classifies each root call by what it reacted to. The narrow idle share is the calls that followed a timed-out or failed wait or a no-op. On Codex it was 73.9% for loop 149, 82.4% for synthkit loop 44 and 72.0% for cf2otel loop 11. Across the twelve longest pi roots, 12 to 23 hours each, one call in 12,069 followed a wait and none was a no-op; status checks, which include ordinary git, Backlog and lane-state reads, were 9% to 26%. pi roots make no wait calls at all; across the 83 pi roots they armed 3,772 `wake_at` timers and 1,685 `watch_start` watchers.

<figure class="diagram figure-breakout">
  <img
    class="on-light"
    src="/diagrams/grafana-poll-calls-by-target-codex-light.png"
    alt="Grafana bar gauge titled Poll calls by target for the Codex window. Cell polls dominate at 6.01 thousand, then gate 1.44 thousand, sleep 1.27 thousand, coderabbit 904, other 633, agent 112 and ci 4."
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <img
    class="on-dark"
    src="/diagrams/grafana-poll-calls-by-target-codex-dark.png"
    alt=""
    aria-hidden="true"
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <img
    class="on-light"
    src="/diagrams/grafana-poll-calls-by-target-pi-light.png"
    alt="Grafana bar gauge titled Poll calls by target for the pi window. Coderabbit is 0 and other is 1.04."
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <img
    class="on-dark"
    src="/diagrams/grafana-poll-calls-by-target-pi-dark.png"
    alt=""
    aria-hidden="true"
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <figcaption>
    Grafana screenshots of the Poll calls by target panel, counting every thread, roots and workers. Top,
    Codex, 19:55Z on 26 September to 15:55Z on 27 September; bottom, pi, 16:30Z on 28 September to
    12:30Z on 29 September. Each panel scales its bars to its own largest value, so the pi bar is full
    width: pi made one poll call, and the 1.04 is Grafana's extrapolation of it. The Codex `cell` bar is
    mostly the invalid waits.
  </figcaption>
</figure>

<figure class="diagram figure-breakout">
  <img
    class="on-light"
    src="/diagrams/grafana-active-threads-by-role-codex-light.png"
    alt="Grafana time series titled Active threads by role for the Codex window, 20:00 to 15:55. At least one root thread stays active through the whole night, at two for most of it, while worker threads rise and fall between 0 and 15."
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <img
    class="on-dark"
    src="/diagrams/grafana-active-threads-by-role-codex-dark.png"
    alt=""
    aria-hidden="true"
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <img
    class="on-light"
    src="/diagrams/grafana-active-threads-by-role-pi-light.png"
    alt="Grafana time series titled Active threads by role for the pi window, 16:30 to 12:30. Root threads rise and fall with worker threads, which peak at 22, and all roles sit at zero from about 06:30 to 09:30."
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <img
    class="on-dark"
    src="/diagrams/grafana-active-threads-by-role-pi-dark.png"
    alt=""
    aria-hidden="true"
    width="2880"
    height="1200"
    loading="lazy"
    decoding="async"
  />
  <figcaption>
    Grafana screenshots of the Active threads by role panel over the same two windows, Codex above and pi
    below. The y-axes differ, 16 against 24. A Codex root stays active all night whatever the workers do.
    The pi roots follow their workers, but no pi loop was running from about 07:20Z to 09:20Z, so the
    empty stretch is partly no loop at all.
  </figcaption>
</figure>

The cleanest pair is one private repository that ran both: loop 149 on Codex overnight, and loop 152 on pi two days later. Over the first 831 minutes of each, which is all of loop 152, loop 149 used 1,366M input tokens and loop 152 used 810M, 41% less, while producing half as much output again, 1.77M tokens against 1.18M. Root calls were 6,185 against 1,444. By their reports loop 152 landed ten items with green CI on the exact head, and loop 149 landed one whose push CI failed. The scopes differed, so treat that as colour.

What cuts the other way:

- **The spin is mostly one release:** roots on Codex CLI 0.157.1 made 7,127 of the 7,359 invalid waits. Earlier releases made 218 in 12,883 root calls, under 2%, though the one loop on 0.159.2 still made 14 in 75. A Codex root that doesn't spin is a lot cheaper. When one does, nothing in the harness can stop it. Later releases spin far less, though not never. Queried on 19 October, the 19 Codex root sessions on 0.162.1 that started at least one sub-agent, between 13 and 16 October, made 102 invalid waits in 13,673 root calls, 7.5 per 1,000, and 96 of those came from one root. Counted the same way, roots on 0.157.1 made 356 per 1,000.
- **Two loops hold most of it:** loop 149 and synthkit loop 44 hold 6,461 of the 7,359 invalid waits, 88%. Without them the Codex roots still made 75 calls per lane, against 18 on pi.
- **Bigger pi contexts:** a pi root carries about 1.6 times the context of a Codex root, so a cut to about a quarter of the calls per hour is only a halving of cached re-reads.
- **pi has its own failure:** on one afternoon some pi root requests on Sol at medium reasoned for 20 to 72 minutes after a quick tool result. The three longest ran out their output budget after more than an hour with nothing to show for it; the rest ended in errors or finished late. `request-ceiling` ends a request after 15 minutes without model progress and re-prompts the root.
- **Quiet is not productive:** Codex lanes made 0.80 commits each and pi lanes 0.71. Fewer root calls say nothing about how much work got done.

## How to get it

The extensions are at [rknightion/loop-pi](https://github.com/rknightion/loop-pi), with the pinned versions, the agent files and the tests. Read these before you use them.

- **Versions:** pi and `pi-subagents` are pinned exactly in `package.json`. pi's extension API still changes between minor releases, so expect to re-test on every bump.
- **The guard is not a sandbox:** it stops plainly typed mistakes. A command assembled at runtime inside interpreter code isn't caught. Lanes can't push at all, since the guard refuses it and each one runs behind a no-push launcher, and the push log records the root's pushes. The closeout audit of remote refs, tags and releases is still the actual check.
- **It is built for my protocol:** the continuation extension arms on the launch message the [pi harness](/reference/loop-harness-pi/) describes, and the agent set and turn-endings text are the ones it names. Without that shape of launch, expect to change code.
- **Models:** my loops reach the GPT-6 models through [codex-lb](https://github.com/Soju06/codex-lb), an OpenAI-compatible proxy that I run on my ChatGPT subscription, using pi's `openai` provider and the proxy's own API key. pi's `openai-codex` provider needs a ChatGPT token, which codex-lb doesn't accept, so I don't use it. The agent files name two models and the guard allows only the configured family, so a different provider or model set means editing the home settings and the agent files, and the installer refuses a build where they disagree.

## Limits

This compares groups of loops on different repositories and different work, and is nowhere near a controlled test. Both groups include overnight time: 35% of the Codex wall time and 37% of pi's fell between 22:00 and 07:00 London time. The numbers describe where calls and tokens went and say nothing about whether the work was better. "While lanes run" counts a lane from its first event to its last, so a blocked lane still counts, and for Codex only about a third of loop minutes can be tied to a running lane, so its lane-running rows rest on 64 hours. Shell-based polling isn't classed as a wait, so the invalid-wait counts are a floor. The comparison stops in early October and covers neither side's later changes. The October Codex runs appear only in the invalid-wait count above.

<!--
FIGURES (AGENTS.md "Figures"). 15-who-waits-what-it-costs was removed under v3 (it shows a lane pushing and waiting on its own CI, and a poller); a v3 replacement is proposed. Integrated: two Grafana screenshot pairs in Results (grafana-poll-calls-by-target-{codex,pi}-{light,dark}.png and grafana-active-threads-by-role-{codex,pi}-{light,dark}.png, 2880x1200). 46-tldr-why-i-dont-use-codex-for-long-running-agent-loops is the at-a-glance figure after the opening.
-->
