skip to content
Replays

Teaching Codex to Wait Out a Usage Limit

Codex usage-limit hits become a timed pause and auto-resume, not a dead run

Gantry sprints

0 milestones 2 sprints

Codex usage-limit hits become a timed pause and auto-resume, not a dead run

29m 31s total 10m 38s per sprint

13.6M tokens in 88.4k tokens out

$14.56 nominal cost

Codex usage-limit failures were not reaching the part of Gantry that decides whether an agent run is retryable. The final answer file was empty, stderr did not carry the useful signal, and the provider's reset message lived only in the captured stdout event stream.

This job changed that in two steps. Gantry first made the event stream an explicit input to harness classification without changing any verdict, then taught the Codex harness to recognize the usage-limit message, derive a reset wait when the message names one, fall back to ordinary transient retry when it does not, and render the pause through the same retry machinery used by Claude.

How this walkthrough is structured

Feature

What did the run build and ship?

Gantry now carries Codex's captured event stream into classification and turns a recognized usage-limit event into a bounded reset wait instead of a terminal empty-answer failure.

Build

How did Gantry structure the work?

The run kept the risky behavior behind a proven plumbing change: one sprint widened the parse input as a no-op, and the next used that channel for Codex reset waits without needing repair or re-planning.

Route JSON Output Stream

0 agents 7m 39s wall time 25.6k tokens out $4.60 nominal cost

The harness boundary now has a `ParseInputs` struct with `answer`, `stderr`, `stdout_json`, and `exit_code`, and the runner fills `stdout_json` from the captured event log before calling `parse`. That is still present in `src/engine/harness/mod.rs` and `src/engine/agent.rs`. For stdout-answer harnesses the field is redundant, while file-answer harnesses such as Codex receive a separate channel they did not previously have.

This piece was asked to carry only the new signal path. The brief made it agent-sized by forbidding behavior change: the runner would read the captured stdout event stream, the harness trait would receive it, and every existing classifier would keep returning the same outcomes. The run record shows no repair or re-plan here, which matches a boundary that turned the work into a contract change plus proof that the new input actually reached a classifier.

Implement Usage Limit Backoff

0 agents 13m 37s wall time 46.8k tokens out $6.74 nominal cost

Codex classification now treats an empty answer plus a transient event on `stdout_json` as retryable, with a reset-derived wait when a `try again at` clause can be parsed and no wait hint when it cannot. The parser and tests remain in `src/engine/harness/codex.rs`, including the host-local assumption, clamp behavior, and later support for dated reset phrases. The retry loop still separates hinted usage-reset waits from ordinary transient delays, and the headless output renders Codex reset phrases through the shared usage-limit sleep line; `docs/harnesses/codex.md` documents the same behavior.

The second sprint could focus on Codex because the stream already reached classification. Its brief named the hard parts directly: extract the usage-limit message from Codex's event carriers, parse a local wall-clock reset phrase with deterministic tests, clamp the near-boundary oversleep case, preserve the unhinted fallback, and reuse existing retry accounting and rendering. It also stayed green, so the stress the brief anticipated was absorbed in the tests and parser shape rather than in a later fix pass.