Repository navigation
A 2-hour run that closed ten issues and pushed 15 times is reported as "outcome: failed" with a Wrangler docs link — the Cloudflare subrequest ceiling is relayed raw as the objective failing #523
Description
What the owner saw
His Coder Lead delegated "Work through all open GitHub issues by priority order — bugs first, then enhancements". Two hours later the Assistant thread said, in full:
Loop stopped (failed)
The board card says more, and it is worse:
Failed ·
deleg-ae7fa3f2-53a1-43ba-b3ad-921dd8dba7b1
outcome: failed — run error: Too many API requests by single Worker invocation. To configure this limit, refer to https://developers.cloudflare.com/workers/wrangler/configuration/#limits | Acts: pushed directly to the trunk origin main; pushed directly to the trunk origin main; pushed directly to th…
Read from the owner's side: his objective failed, and the remedy is a Cloudflare Wrangler configuration page for a Worker he does not own. There is no sentence anywhere telling him that the run had already implemented and pushed most of the backlog before it died.
What actually happened
Session csess_e80b6a21-a154-4905-b689-fe5c5cd16289, instance bd43f4de-ef35-4051-bdec-43f8571414a1, node RLs-MacBook-Air.local, 2026-08-11 01:29:42 → 03:31:35, status: error.
Measured from GET /v1/instances/:id/coding/sessions/:sid/timeline and agent_trace:
- 26 Pilot steps driven into the engine over 2h 02m.
- Real work throughout: issues [connectors] Web-search connector (Google Custom Search / SerpAPI) — enrichment #99, Connector framework: registry + connectorClient (#86, step 2) #104, [pipelines] Pipeline definition format + durable runner (trigger → declarative pipeline) #97, Overseer agent: one cross-repo conversational AI (smart auto voice mode) with memory + self-awareness #9, Unify tool registry on a single ToolDef shape (#85, step 1) #102, Document platform architecture #12, [browser][connectors] Expose the browser as a registry tool — let config/data agents declare browser_* (not only the Tier-0 workflow path) #103, Defense-in-depth: encodeURIComponent repo segments in github-issues.ts #110, Trigger payload mapping UI and config validation #16, Capstone: lead-finder as a pure declarative pipeline (#94) #117 read, implemented, committed; issue Capstone: lead-finder as a pure declarative pipeline (#94) #117 opened by the run itself at 03:16:36.
- 15
act.consequential/push.trunkevents, eachirreversible: true, ok: true.
The run did not fail. It was cut off at 2 hours by a platform ceiling, and the only thing it left behind that a human reads says "failed".
Mechanism
Verified. workers/api/src/workflows/coding-session.ts:669-677 wraps the whole loop:
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
result = { outcome: "failed", detail: isRunnerGone(e) ? message : `run error: ${message}`, ... };
}There is exactly one classified escape — isRunnerGone, from #341. Everything else, including a Cloudflare runtime limit, becomes run error: <raw platform string> and is then the detail on the board card, the loop-run row and the chat message.
Verified. The run's dominant subrequest consumer is the idle poll. coding-session.ts:497-509:
// Poll capture until the CLI goes idle … Bounded so the loop can't outrun idleRetry's 10-minute step timeout.
waitIdle: () =>
guard(runIdle, `s${n++}-waitidle`, async () => {
await sleep(1500);
let snap = await capture();
for (let poll = 0; poll < 240 && snap.runState !== "idle" && snap.alive && !snap.cancelled; poll++) {
await sleep(2000);
snap = await capture();
}
return snap;
})sleep is a plain setTimeout promise (coding-session.ts:795) — not step.sleep. So up to 241 capture() calls happen inside a single step.do. Each capture() (coding-session.ts:383-431) issues, at minimum: one relay callRunner for /coding/capture, one D1 SELECT status FROM coding_sessions, and one isCancelRequested D1 read — plus recordEngineUsage, recordEngineActs, recordAuthorityViolations and touchSessionActivity, all of which reach D1. That is ≥3 and typically 5–7 subrequests per poll, so one waitIdle can spend 700–1700 subrequests. A message action calls waitIdle every step (coding-loop.ts, end of the action branch), 26 times in this run.
Inferred, and labelled as such: that this is where the invocation's budget went. I have not instrumented a live Workflow to attribute the exhausted counter to a specific step, and Cloudflare's step/invocation boundary semantics are not something I verified from the runtime. What is verified is that (a) the ceiling was hit, (b) the platform already knows this ceiling — workers/api/src/lib/repo-ingest-runner.ts:252 describes an incident where "ONE tick drained a 300-file queue (blowing past the 1,000-subrequest cap, which embed's catch swallows)" and a chunk budget was added specifically to stay under it — and (c) workers/api/wrangler.toml declares no [limits] block, so the API Worker runs at whatever the default is.
What to do
1. Say what happened, in the platform's own voice — the cheapest and most valuable step.
Follow the pattern #341 already established in this exact catch block: a failure that KNOWS what it is says so, instead of being inferred from a message at the catch site. Add a classifier beside isRunnerGone for a platform-ceiling error (Too many API requests by single Worker invocation, Too many subrequests, exceeded CPU) and emit something the owner can act on:
This run reached a Cloudflare per-invocation limit after 2h and 26 steps. It was not your objective failing — the work it had already committed and pushed is intact (15 pushes to
main). Resume it, or narrow the objective.
Crucially it must not be reported as outcome: failed with no qualification: a delegating supervisor reads check_delegation and the board card, and both currently say the objective could not be done.
2. Stop spending the budget in one invocation. Break waitIdle into bounded chunks: poll ~15 times inside a step, then await step.sleep(...) and continue. step.sleep is already used in this very file for the handoff wait (coding-session.ts:650), for the same reason — a long wait must not be held open inside one invocation. This also makes a long engine turn survive a Workflow eviction instead of burning the retry budget.
3. Consider raising the ceiling explicitly, but not instead of (2). wrangler.toml supports [limits] subrequests (Cloudflare docs: default 10,000 on paid, maximum 10,000,000). Adding it is one line. It is deliberately listed third: raising the ceiling alone means the next run of this shape goes further unsupervised before it dies — a run that pushed to main 15 times without a human reading anything is not obviously improved by letting it push 30 times.
4. Checkpoint, so a cut-off run is resumable. The Pilot's progress lives entirely in the run's in-memory actionLog/transcript (coding-loop.ts). When the invocation dies, so does the plan. A run that has closed 8 of 12 issues should be resumable from issue 9, not from the objective.
Alternatives rejected
- Just raise
[limits] subrequestsand close this. Fixes the symptom for a run of this length and leaves the message, the outcome classification and the unresumability exactly as they are. It also does nothing for the CPU-time ceiling, which the same wrapper will report the same way. - Cap
maxStepslower. A 26-step run that closes ten issues is the product working. The defect is that the platform cannot tell the owner what happened to it. - Poll less often. 2s is what makes the terminal feel live and is what
touchSessionActivityis throttled against. The problem is the poll's placement inside one invocation, not its frequency.
Regression risk
Chunking waitIdle across step.sleep boundaries adds durable steps to every engine turn — n (the step-name counter) grows, and a Workflow step name must stay unique within an instance, which this file already manages via s${n++}. The risk to watch is a long engine turn now spanning many steps and colliding with the Workflow's own step-count limits; test with a synthetic engine that stays non-idle for the full 8-minute window and assert the loop still terminates at the same boundary with the same snapshot.
Not reproduced end-to-end: I did not force a subrequest exhaustion in a live Workflow. Everything above is read off the production record of one run plus the code paths it took.
Related: #341 (the precedent — a connectivity failure classifies itself instead of being guessed from its message at the catch site).
Activity
fix(coding): a Pilot run that dies files a classified, correlated rec…
serge-ivo commentedon Aug 12, 2026
Frequency data from a population sweep (2026-08-13)
I pulled
GET /v1/instances/:id/loopacross all 32 instances on the owner's account: 80autonomous runs, 30 failed. This issue's class is 3 of the 30 — and the interesting part is
that it is almost perfectly predictable.
It tracks accumulated work, and the practical boundary is ~2 hours
Every run that reached ~2 hours of active stepping died of this. The nearest survivor stopped at 99
minutes. It is not a step-count threshold — Chess coder 2 died at 26 steps while FGS survived at 34
— and it is not wall clock alone either: the two longest runs in the whole population (204m and
189m, #541) had 1 step each and never came close. It tracks the journal, which is what a
replayed invocation has to walk.
All three were one delegation fan-out
They started within 59 seconds of each other (2026-08-11 01:28:43 / 01:29:40 / 01:29:42), from a
Coder Lead delegating to five subordinates, and died within 93 seconds of each other two hours later
(03:30:05 / 03:30:46 / 03:31:38). The other two runs in that fan-out died of
WorkflowInternalError(#546). Five of five subordinate runs in that batch died; none of the five deaths was the
objective failing. That is the single worst hour in the population, and the owner's read of it was
five "outcome: failed" cards.
Why this is now the top fixable platform-fault class
With
BUDGET_ENFORCEoff since d951b5f (2026-08-10T08:16Z), the 11 budget kills cannot recur. Thatleaves 11 live-era failures, of which this is 3. Ranked by "can the platform fix it":
stuckmis-handling (4, #541), this (3),
WorkflowInternalError(2 in the live era, 8 all-time, #546).One addition to the fix
Whatever this issue lands, the acceptance should include a duration/journal guard that ends a run
cleanly before the ceiling rather than only re-wording the message after it. A 2-hour run that
closed ten issues and pushed fifteen times is the good case, and today the platform's answer to it
is to lose it. The
~2hfigure above gives a concrete number to checkpoint or hand off at.Cross-refs: #541 (full 80-run distribution), #546 (
WorkflowInternalError, the other half of thisfan-out).
fix(coding): a Cloudflare attempt error is classified, reported as an…
perf(coding): the Pilot's idle poll backs off, so a 2-hour run stops …
serge-ivo commentedon Aug 13, 2026
Shipped in
7e23b60. CI green, API Worker deploy green (Current Version ID: c8122fe2-0f03-45c1-99a0-b2369c5efb14),/health200.Leaving this open — item 4 (checkpointing) is not done. See the bottom.
One documented fact changed the design, and it invalidates fix #2
Cloudflare applies this ceiling per Workflow instance, not per invocation:
So neither a
step.doboundary nor astep.sleepresets the counter. BreakingwaitIdleinto chunks separated bystep.sleepwould have bought nothing at all, while adding a durable step per chunk. It is not implemented, and that is why. (The step budget was never the constraint either: 10,000 steps per Workflow on paid, configurable to 25,000.)The arithmetic, which turned out to be the finding
capture()costs four subrequests, counted from source rather than estimated:callRunner→ oneRELAYDOstub.fetch(runner-client.ts:267)SELECT status FROM coding_sessionsisCancelRequested→SELECT cancel_requested FROM agent_loop_runstouchSessionActivity— its 60s throttle is a WHERE clause, so the subrequest was spent on every poll to move a row 1 time in 30(
recordEngineUsage,recordEngineActs,recordAuthorityViolationsall return before touching D1 on an empty report — the steady state between turns.)At a flat 2s poll that is 2 subrequests per second of Engine work, so 10,000 buys 83 minutes of waiting. Modelled over
Chess coder 2's own measured shape (26 Pilot steps across 122 minutes = 4.7 min of Engine per step):26 × 142 polls × 4 =14,768 against a 10,000 ceiling. That is the mechanism, and it is arithmetic rather than inference. It also explains the population data in the comment: 26 steps and 37 steps both died while the 99-minute survivor ran 34, because it never tracked step count.What shipped
1. The poll backs off — 2s for the first 30s (where most turns finish), then 5s, then 10s past two minutes. Same run:
26 × 51 × 3 =3,978, which fits under the ceiling that killed it with 60% to spare.The issue rejected "poll less often" on two grounds and both are false of this poll:
GET …/sessions/:sid/capture, on its own rate-limit bucket. This poll runs inside a durable Workflow with no browser attached; all it decides is when the Engine stopped working.touchSessionActivityis throttled against it" — it is throttled against 60 seconds (ACTIVITY_TOUCH_MS), which any interval under a minute satisfies identically.The price is up to 10s of extra latency at the end of a turn already measured in minutes: 3.5% on a 2-hour run, against a run that used to not finish.
2. The activity heartbeat is asked before it is paid for —
shouldTouchActivityat the caller, the same 60 seconds, taking a poll from 4 subrequests to 3.3. The ceiling is raised — third, and not instead. The Wrangler docs link was not inapplicable: wrangler 4.98's own
config-schema.jsoncarrieslimits.subrequests, andwrangler deploy --dry-runaccepts the block with no warning.workers/api/wrangler.tomlnow declares[limits] subrequests = 100_000— 1% of the documented 10,000,000 maximum, ~25 hours of Engine waiting at the new poll cost, still small enough to stop a genuine runaway. Raising it alone would only have let the next run of this shape go further unsupervised, which is this issue's own argument and it stands.Item 1 was already done by
c64d9f5(#546) —platform_ceilingis classified and reported as an interruption with its ownLoopStopReason. This change deliberately adds no second word for that idea.The stated regression risk, tested
The old bound was a poll count (240 × 2s) sized against
idleRetry's 10-minutestep.dotimeout. Carried onto a backed-off schedule it would have stretched the window to ~40 minutes and moved the failure from "the wait gave up" to "the durable step timed out", which reads as a crash.IDLE_WAIT_MAX_MSis elapsed sleeping time, asserted equal to240 * 2_000, and a synthetic Engine that never goes idle is held for the full window and asserted to stop at the same boundary with the same snapshot.Extracted to
lib/coding-idle-poll.ts— "how long may a run wait, and what does waiting cost" is a rule, and a rule inside a Workflow can only be tested by running one.workflows/coding-session.tsis 796 after, one line above the 795 it started at and under its 800 ratchet; the pin is unchanged.21 tests, stating the sizes measured (ADR 0002), each proven by watching it fail (G4): flat 2s poll → 7 red; 240-poll count carried onto the new schedule → 3 red; no activity throttle → 1 red; no settle sleep → 2 red; ignoring
cancelled/alive→ 2 red.Verified, and not verified
Verified: all ten CI gates green in a clean worktree at the commit (typecheck 11 projects; 8,422 tests; biome 1,172 files; seven
check-*.mjs;docs:drift;docs:build; consoletsc+vite build), plus the same bar again after rebase alongside #516 (8,447 tests). Deploy green,/health200.Not verified live.
[limits] subrequestsis not observable through any endpoint I can reach, and the behavioural proof is a 2-hour Pilot run, which I did not start. The deploy accepting the config is the evidence I have.pnpm test:e2eskipped — the diff touches nostore/,agents/coder/webor anything Playwright exercises.Left open
Item 4 — checkpointing. The Pilot's progress lives entirely in the run's in-memory
actionLog/transcript(coding-loop.ts), so a cut-off run still restarts from the objective rather than from issue 9. Making that durable is its own change.The comment's "one addition" — a guard that ends a run cleanly before the ceiling. Not implemented, deliberately. With spend 3.7× lower and the ceiling 10× higher, the run this was filed for now needs ~25 hours of Engine waiting to reach either, so a pre-emptive stop would guard a case that should no longer arrive. It is worth having as a backstop; it was not worth folding in here on an unmeasured trigger point. If it is wanted, it should be sized against a real observation rather than against the old 2-hour number, which no longer means anything.
serge-ivo commentedon Aug 15, 2026
No code change in this pass. Re-checked against production because the run-lifecycle work of the last two days moved the ground under this. Items 1–3 are delivered (
c64d9f5,7e23b60); item 4 remains, and it is a design decision — options below. Leaving open.The ceiling has not recurred
Swept every one of the 124 rows in the error log (all sources, 2026-07-23 → 2026-08-15) for
platform_ceilingand for each of the classifier's ownCEILING_MARKERS—too many api requests by single worker invocation,too many subrequests,exceeded cpu,cpu time limit,exceeded memory,exceeded resource limits— matched against the full serialized row, somessage,context,last_contextandstackwere all covered.Zero hits. Post-fix window: 2026-08-13 04:46 (deploy of
7e23b60) → 2026-08-15 09:17, ~2.35 days, during which 4coding:sessionfailures were recorded — all other classes. Two of them were long runs that would have been candidates under the old poll cost (elapsed 4h25m and 5h23m); both died atnotify-coding-wait:0-0, not at a ceiling.Honest limit: the error log holds no
coding:sessionrows before 2026-08-12, so I can show you "0 after" but not "0 after vs N before". On a low-traffic single-user account, 2.35 days quiet is suggestive, not conclusive.Item 1 is now doubly covered
c64d9f5classifiedplatform_ceilingand put it inINTERRUPTED_CLASSES, so it reports as an interruption with stop reasoninterrupted.4ff78f9(#546) adds the other half: the durableerror_logrow for an interruption the platform resumes now sayscoding run interrupted … resumedrather thancoding run failed.platform_ceilingisresume: false, so a ceiling still ends the run — correctly, since a replay would walk into the same ceiling — but it no longer reads as the objective failing anywhere.Item 4 — checkpointing. Options, and why I did not just build one
The Pilot's progress still lives entirely in the run's in-memory
actionLog/transcript(lib/coding-loop.ts), so a cut-off run restarts from the objective rather than from issue 9. That is unchanged and real. But the shape of the problem changed underneath it, twice:03762fd(A platform deploy kills in-flight coding runs, and the Pilot writes retryable:true for a reader that does not exist #583) made a resume the normal path forinfra_transient. A resumed run replays the Workflow journal, so every completedstep.doreturns its recorded result and the transcript is rebuilt for free. Checkpointing would be redundant for exactly the interruption class that now dominates the log (4 of 10 rows).[limits] subrequests = 100_000, the run this ticket was filed for now needs ~25 hours of engine waiting to reach either limit.So the case checkpointing still serves is narrower than when it was written: a run killed by a class that is not resumed (
provider_stall,provider_credentials,platform_ceiling) after real work.(a) Persist the action log per step. Truest to the ask; costs a durable write per Pilot step on every run to serve the minority that die. Wants a size bound — a 26-step transcript is not small.
(b) Checkpoint the OBJECTIVE, not the transcript. The run already writes what it did:
agent_traceacts,coding_timeline, the board card. A resumed run could be handed "these issues are already closed, these commits are already pushed" as a resume note — the fieldgoal.resumeNotealready exists and is already cleared per round. Much cheaper, and it matches what a human would actually need. It does not restore the model's reasoning, only its results.(c) Do nothing, and let #583's resume cover it. Defensible for
infra_transient; wrong for the other three classes.My read: (b). It reuses machinery that exists, it is bounded, and the thing a cut-off run actually needs is not its old plan but a truthful statement of what has already landed — which is the same complaint this ticket opens with. But that is a product judgement about what a resumed run should be told, and it should be your call rather than mine, so I have not built it.
On the comment's "one addition" — a guard that ends a run cleanly before the ceiling
Still not implemented, and I agree with the previous decision. With spend 3.7× lower and the ceiling 10× higher, a pre-emptive stop would guard a case that no longer arrives — and there is now 2.35 days of production evidence for that rather than only arithmetic. If it is wanted as a backstop it should be sized against a fresh observation; the original 2-hour number no longer means anything.
fix(coder): an interrupted run reports `outcome: interrupted`, and it…
serge-ivo commentedon Aug 18, 2026
Shipped in
39eefb0a. CI green, API Worker deploy green,/health200. Still open — item 4 is untouched and still a decision, and one thing here is unverifiable without provoking a two-hour run.Items 1–3 shipped, and composing the whole string shows two of this ticket's complaints still standing
c64d9f5and7e23b60classify the ceiling, write "Interrupted by the platform, not by the objective", recordinterruptedand land the card in Needs you. I composed the note the way production composes it —codingCrashReport→runOutcomeNote→cardDetail— and measured what a reader actually gets.1. The note still led with the placeholder. The catch sets
outcome: "failed"because that is the onlyCodingOutcomea run that threw can carry, andrunOutcomeNotewas handed it raw:A sentence contradicting itself inside its own first line, on the one string both the board card and
check_delegationshow. This ticket's acceptance says it outright — "it must not be reported asoutcome: failedwith no qualification".The word is now
outcomeWord(outcome, reason), derived from the samereasonfinishLoopRunrecords, so the word, the reason and the column are one reading rather than three.stopReasonForcannot produceinterruptedfrom any outcome (this file already asserted that), so no ordinary ending is reworded.2. The card showed the Wrangler link and not the pushes. Measured, cut at the board's own 300 characters:
Exactly 300: 81 of them a docs page for a Worker the reader does not own, a cut mid-sentence, and
| Acts: pushed directly to the trunk origin main; and 14 more.off the end entirely. That is this ticket's title, and #546 did not change it — because the fix there was the wording and the defect here is the order.So the order is inverted (what a reader must act on first; the vendor's text last, where a cut is harmless) and
withoutVendorAdvicedrops the operator-facing clause. Same input, after:300 characters, both halves intact. The raw message is unaltered in the durable error log —
recordCodingFailureis handed the error itself — so nothing is lost by not repeating it on the card. The strip is narrow (Cloudflare's own docs URLs and the clause introducing one); a message it cannot parse reaches the owner untouched, which is asserted.The workflow edit is net zero lines:
reasonis hoisted out of the step that was deriving it a second time, rather than raising the 911-line pin for one line of wiring.Verified / not verified
Verified: 9,833 tests; typecheck (11 projects); biome; every
check-*.mjsinci.yml(file-size,migrations --require-history,surface-lock --require-history,mcp-parity,bare-catch,test-isolation,design-tokens,agents-allowlist,qa-config);docs:drift;docs:build. Red demonstrated twice: reverting the wiring alone fails the source guard that pins the workflow's call shape; neuteringoutcomeWordfails the composition tests.NOT verified live. Reaching this path needs a run that hits a platform ceiling, and the ceiling has not recurred since
7e23b60(the 2026-08-15 sweep found zero in the error log). I did not start a two-hour run to provoke one. The evidence is the composition measured against the production strings, not an observed card.Left open — item 4, checkpointing. Untouched, and nothing here decides it. The 2026-08-15 comment lays out (a) persist the action log, (b) checkpoint the OBJECTIVE via the existing
goal.resumeNote, (c) rely on #583's resume, with (b) recommended. That is a product judgement about what a resumed run should be told; it is still yours.Verified vs inferred
runOutcomeNote's single call site; thatrecordCodingFailurereceives the raw error.serge-ivo commentedon Aug 23, 2026
Delivered-work audit 2026-08-23 — items 1–3 re-verified at
HEAD. Item 4 is the only thing left and it is a decision you have not made.Independent re-verification against
main:workers/api/src/workflows/coding-session.ts:217now passesoutcome: outcomeWord(outcome.outcome, reason), so the word, the reason and the column are one reading;lib/coding-run-report.ts:138defines it,:103withoutVendorAdvicedrops the Cloudflare docs clause, and:86puts "or pushed is unaffected; check the repository before starting it again" ahead of the vendor's text so the board's 300-character cut lands on the harmless half.coding-session.ts:508-514no longer holds a flat 2-second loop inside onestep.do; it delegates toawaitEngineIdlewith the schedule and its arithmetic inlib/coding-idle-poll.ts, and the comment records the reason correctly: the ceiling is applied per WORKFLOW INSTANCE and is "never reset by a step or a sleep".workers/api/wrangler.toml:28-29,[limits]/subrequests = 100_000.Item 4 (checkpointing) is untouched, and this ticket cannot move until you answer it. The three options are on the 2026-08-15 comment: (a) persist the action log per Pilot step, (b) checkpoint the OBJECTIVE via the existing
goal.resumeNote, (c) rely on #583's resume. (b) is recommended — it reuses machinery that exists, it is bounded, and what a cut-off run actually needs is a truthful statement of what has already landed, not its old plan. But what a resumed run should be TOLD is a product judgement.If you pick (c), say so and this closes; a dev agent should not choose between the three.
Verified: the five
file:linecitations above. Inferred: nothing. The ceiling path itself remains unobserved post-fix — provoking it needs a two-hour run.4 remaining items
feat(coding): a cut-off run tells its successor what already landed (#…
feat(coding): a run does not build on a base it could not confirm (#801)
feat(coding): a run that hit its step limit tells its successor what …
serge-ivo commentedon Sep 13, 2026
Item 4 has shipped. The 2026-09-10 comment above ("Item 4 — unchanged, and blocked on you") is now out of date.
Recording it here because no comment on this issue did.
What landed
7cae041a(2026-09-11), "feat(coding): a cut-off run tells its successor what already landed (A 2-hour run that closed ten issues and pushed 15 times is reported as "outcome: failed" with a Wrangler docs link — the Cloudflare subrequest ceiling is relayed raw as the objective failing #523)". This is option (b): checkpoint the objective, not the transcript.pendingCodingResumeNote(workers/api/src/lib/coding-resume-note.ts) is injected asgoal.resumeNotein its own durable step atworkflows/coding-session.ts:733. It tells the next run on the same session which acts its predecessor was observed to land (agent_events, read by time window), under a 6-hour lookback and for the immediate predecessor only.ok:null) are counted and flagged "VERIFY", not claimed as done.1ee023c6(2026-09-13, under Feature: true resumable coding_loop runs after hitting max_iterations (not just re-running on leftover files) #806) widened the gate. It wasstop_reason === "interrupted"only; it is nowRESUMABLE_STOP_REASONS=interrupted · max_iterations · engine_limit · provider_credit(lib/agent-loop-store.ts). The note's opening now names the true cause per reason; theinterruptedwording is byte-identical.Checked against the 2026-09-01 triage's MVP acceptance
goal.resumeNotepath"actionLogwas lost"39eefb0a(items 1–3, re-verified in the comments above).Two things that are not a dev agent's call
If (b) as shipped is accepted, every code item on this issue is done and it can close. Not closed by me for those two reasons.
Verified: both commits are on
main; thefile:linereferences above resolve at1ee023c6. Not verified: any live run briefed by the note. That needs a run cut off after consequential progress, followed by a new run on the same session.fix(coding): the resume note finds its predecessor by repo — an agent…
serge-ivo commentedon Sep 13, 2026
Code complete:
f8ded1bbcloses the same-session gap. Waiting on two owner decisions.The gap from the 2026-09-13 comment. That comment left criterion 2 unverified: whether a restart reuses the session the resume note was looked up by. For a run an agent started (
delegate_goal,start_work, the Loop button,coding_loop_start), the answer was no. That run opens its own session and closes it when it ends, so the next run opens a new session. The old lookup bysession_idfound no predecessor, and the next run got no note.f8ded1bb(refs #806) looks up the predecessor by repository instead (lib/agent-loop-store.ts,lib/coding-resume-note.ts). It is pinned bycoding-resume-note-repo.test.ts, and CI is green.All four fixes this issue proposed are on
main:c64d9f5b,39eefb0a7e23b605[limits] subrequests = 100_000:7e23b6057cae041a, widened by1ee023c6andf8ded1bbWhat only the owner can decide:
If both are accepted as shipped, this issue can close.
Not verified live: no run has been briefed by the note in production, and the ceiling has not recurred since
7e23b605.chore(api): confirm [limits] subrequests ceiling already configured (c…
feat(coding): the Pilot's idle wait can be built from durable steps, …
docs(coding): CodingGoal.resumeNote names both of its writers, not th…
feat(coding): the Pilot's idle wait is durable by default — a turn su…
serge-ivo commentedon Sep 19, 2026
Closing #523 — all four fixes accepted as shipped
All code items are complete and on
main:c64d9f5b,39eefb0a— the card leads with actionable next steps rather than the raw vendor error URL.7e23b605[limits] subrequests = 100_000:7e23b6057cae041a, widened by1ee023c6,f8ded1bb, anda3a9ded5— on a cut-off run, the successor is briefed with what already landed, keyed by repository so the same-session gap is closed.Option (b) accepted. The note carries the objective plus observed-landed acts — not the full transcript and not an invented remainder. Stating a remainder would be a guess in the platform's voice; omitting it is correct.
All five covering test files pass (144 tests) at HEAD
1bc1be00.Still not verified live: no production run has been briefed by the resume note, and the ceiling has not recurred since
7e23b605.Closing now.
feat(loop): see what a continue would carry forward, before spending …
feat(loop): a continued run inherits what the stopped Pilot had worke…
test(#915): the Pilot's invariants are asserted by RUNNING the workfl…