Skip to content

Feature: true resumable coding_loop runs after hitting max_iterations (not just re-running on leftover files) #806

Closed

Description

@proagentstore

When a coding_loop_start run hits max_iterations (or otherwise stops without completing), there is currently no real 'resume' — only the option to start a brand new run against the same repo. Today that new run happens to see whatever uncommitted files the previous run left in the working tree on disk, so if the prior run got partway through editing a file before stopping, a new run can stumble onto that leftover diff and build on it. But that's incidental: it depends entirely on git not having discarded anything, and the new run has none of the previous run's actual reasoning, iteration history, or context for WHY it made those changes or what it still intended to do. If the working tree were clean, or another run had touched the repo since, there would be nothing to pick up at all.

Requested feature: genuine resumability for a run that hit its iteration ceiling (or failed for a recoverable reason).

  1. When a run stops at max_iterations, keep its full session/reasoning context addressable (not just the code artifacts on disk).
  2. Give the owner a way to review why it stopped — what it had completed, what it was mid-way through, what it still intended — before deciding what to do next.
  3. Allow the owner to then choose: (a) reset and start clean, (b) resume the same run with its prior context intact, optionally adding new instructions/info/ticket updates without losing what it already figured out, or (c) resume as-is with more iterations granted.
  4. This should work even hours later — the point is a human can check back on a stalled run on their own schedule, add clarifying info or a course correction, and let the agent continue rather than re-deriving everything from scratch each time.

This came up today (2026-09-12) while retrying issue #150 on proappstore-online/platform: a run died on a relay timeout at iteration 4 of 20 with a good partial fix already written but uncommitted. A second run happened to build on that leftover diff successfully, but only by chance — it had no actual memory of the first run's reasoning, and a true resume would have been far more reliable.

Activity

  1. added
    enhancementNew feature or request
    P2: correctnessReal defect, no live harm today — inert fields, miscounts, missing guards
    coderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, Overseer
    on Sep 12, 2026
  2. serge-ivo commented on Sep 13, 2026

    @serge-ivo
    Contributor

    Slice 1 shipped in 1ee023c6: a run that stopped at max_iterations now tells its successor what already landed. Items 1–3 are NOT done; they need a design decision (below).

    What existed before

    #523 (7cae041a) already gives a new coding run a resume note: pendingCodingResumeNote (lib/coding-resume-note.ts), injected as goal.resumeNote at workflows/coding-session.ts:733. It lists the acts the previous run on the same session observed to land, built from agent_events. But the gate was

    lib/agent-loop-store.ts:298	if (row?.stop_reason !== "interrupted") return null;

    and a Pilot that uses up its step budget ends max_steps → max_iterations (lib/coding-pause.ts:270). So the case this issue describes got no note.

    What changed

    The gate is now "did the run reach a verdict on its objective". RESUMABLE_STOP_REASONS lists the endings that did not:

    stop reason briefed? why
    interrupted ✅ (unchanged) the platform cut it off (#546)
    max_iterations ✅ new step budget spent mid-work (this issue)
    engine_limit ✅ new the coding CLI's usage window outlasted the run's wait (#541)
    provider_credit ✅ new balance ran dry; #773's own sentence says "start the run again" after topping up
    failed ❌ the run gave its verdict; repeating its steps may be right
    escalated ❌ the run asked a human, answered through its own handoff
    cancelled ❌ a human's choice, possibly this issue's option (a) "start clean"
    done / no_progress / budget ❌ a verdict, or not something a restart should inherit

    The note's first line now states the true cause per stop reason. A step-limited run is not told it was "interrupted by the platform". The interrupted wording is byte-identical and pinned by a test. The note is still immediate-predecessor-only, with a 6-hour lookback.

    Verified: 17 new tests in coding-resume-note.test.ts, 20 → 37. (The commit message says 22 → 37; 20 is correct.) Restoring the old gate fails 3 of them. Full suite 11,409/11,409, both API typechecks, biome and the static guards are clean.

    Not verified:

    What this does NOT cover

    • The Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150 example itself. The note lists acts recorded in agent_events (pushes, PRs, merges). Uncommitted edits in the working tree are not acts, so a partial fix written but not committed is still carried only by the files on disk, exactly as this issue describes.

    • Items 1–3 need an owner decision, not a follow-up commit:

      1. "Keep its full session/reasoning context addressable." This contradicts 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's recorded choice of option (b), "checkpoint the OBJECTIVE, not the transcript". The coding-resume-note.ts header explicitly rejects persisting the Pilot's reasoning (actionLog/transcript are function-local in coding-loop.ts) and refuses to state "what remains", because the platform cannot know it.
      2. A review surface ("what it completed, was mid-way through, still intended"): console UI, and "intended" has the same problem as item 1.
      3. Reset / resume-with-context / grant-more-iterations: a run-control API and UI.

      Suggested split for pags-ba:

      • (i) the item-1 decision (persist the transcript, or keep option (b));
      • (ii) a "grant more iterations and continue" action, which needs no transcript if (b) stands;
      • (iii) carrying uncommitted working-tree state into the note, e.g. a git status / diff summary captured at stop.

    Leaving this issue open.

  3. serge-ivo commented on Sep 13, 2026

    @serge-ivo
    Contributor

    The resume note (#523, and slice 1 here) never reached an agent-started run's successor. Fixed in f8ded1bb.

    The slice-1 comment listed "Session reuse not traced" as unverified. I traced it, and the note was keyed on something the successor usually doesn't have.

    The bug, verified in code

    1. The lookup matched on session. lastUnfinishedRunForSession queried agent_loop_runs … WHERE … session_id = ?3, with the new run's own sessionId (workflows/coding-session.ts:733).
    2. Agent-started runs open their own session. That covers delegate_goal, start_work, the Loop button and coding_loop_start. With no active session, ensureSession creates one and returns opened: true (lib/coding-session-open.ts:463-498), which becomes sessionOpenedByRun: opened (lib/loop-drivers.ts:334).
    3. A run-opened session is closed when the run ends. shouldEndSessionAfterRun({ openedByRun }) leads to endSession(..., status), with status "error" for failed/max_steps (workflows/coding-session.ts:954-971).
    4. So the next run opens a new session. getActiveSessionForRepo only matches status = 'active' (lib/coding-store.ts:396). The lookup then ran against a session with no history and returned null, so no note.

    The note only ever reached runs on a session a human opened, since those outlive their runs. #523's own incident was a delegated run cut off after 15 pushes whose session ended status: error: exactly the case the note couldn't reach.

    The fix

    • Lookup: lastUnfinishedRunForRepo joins agent_loop_runs to coding_sessions and matches the repo of the caller's session, looked up within the caller's own user and instance.
    • Unchanged: the rest of the logic. It still takes the newest finished run and then judges it (immediate predecessor only), with the 6-hour lookback and RESUMABLE_STOP_REASONS. The workflow call site is untouched.
    • Wording: the note now says "a previous run on this repository", not "on this session". The predecessor is usually on an earlier session, so the old wording pointed the model at history that isn't there. The test pinning the interrupted sentence moved by that one word, on purpose.

    Verified

    • coding-resume-note-repo.test.ts, 6 new tests against the real migrated schema (d1-sqlite), run end to end through pendingCodingResumeNote:
      • Run A opens S1 on repo R, lands a push, stops at max_iterations, and S1 is closed as error. Run B on new session S2 of R gets the note, and it names the push.
      • A session on a different repo gets nothing.
      • A done run on any session of R in between ends the note's job.
      • The caller's own unfinished run is skipped, and A is still found.
      • Another user's session id resolves to no repo.
      • The 6-hour floor holds.
    • Mutation-checked:
      • Restoring the session-keyed query fails 2 tests, including the A → B case.
      • SELECT * in place of r.* fails 2: the join's own status/started_at would overwrite the run's.
    • Suite: full suite 11,477/11,477; API typecheck (source and tests), biome and the static CI checks are clean.

    Not verified

    • No live run. That needs an agent-started run stopped for a resumable reason, followed by a new run on the same repo.
    • Whether the end step runs when the Workflow itself is killed, not caught. If it doesn't, the idle reaper still closes the session, and the fix covers that case the same way.

    Noted, not changed

  4. serge-ivo commented on Sep 13, 2026

    @serge-ivo
    Contributor

    Run 6ae2f583 continuation — bookkeeping

    Two slices from the prior run are now on main. CI and Deploy API Worker both passed on f8ded1bb.

    Commit What it does
    1ee023c6 The resume note now reaches runs that stopped at max_iterations, engine_limit and provider_credit, not just interrupted. It also states the real stop reason.
    f8ded1bb The previous run is now looked up by repo, not by session, so a run started by an agent (which opens a new session) receives the note.

    Waiting on an owner decision

    Buildable without an owner decision

    • (ii) "Grant more iterations and continue" (item 3(c)): new API plus console UI. It needs no transcript if option (b) stands.
    • (iii) Carry the stopped run's uncommitted changes into the note. Backend only. Two things to know:
      • codingResumeNote returns null when no action landed (lib/coding-resume-note.ts:113, if (!landed.length && !unobserved.length) return null;). A run that left only uncommitted edits, which is the Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150 shape, gets no note today, so that check has to change too.
      • At run start the successor already gets a count of uncommitted files, unattributed, via the REPOSITORY STATE instruction (workflows/coding-session.ts:700-704, readRepoWorkingState → dirtyClause). That instruction says "You did not create this state". A note that attributes the same files to the previous run needs to agree with it.
    • Item 3(a), "start clean": not split out. Today this is close to cancelling and starting a new run. cancelled is not in RESUMABLE_STOP_REASONS, so that new run gets no note.

    Not verified / not filed

    No further code was written in this continuation run; its objective was verification and bookkeeping only.

  5. serge-ivo commented on Sep 13, 2026

    @serge-ivo
    Contributor

    Run 199ec11f continuation — bookkeeping

    One gap noted in the previous bookkeeping comment has since been resolved.

    Closed: actsInWindow instance-wide scoping

    The previous comment noted under "Not verified / not filed" that actsInWindow reads acts across the whole Coder instance rather than scoping to the run's session. That was filed as #809 and fixed in cda8ef91 (merged 2026-09-13). CI passed. The lookup is now scoped to the run's session.

    Still open

    Waiting on an owner decision:

    Buildable without an owner decision:

    • (ii) "Grant more iterations and continue" — needs a new API plus console UI.
    • (iii) Carry the stopped run's uncommitted working-tree changes into the resume note. Backend only. The return null at lib/coding-resume-note.ts:113 must change, and the note must agree with the REPOSITORY STATE instruction at workflows/coding-session.ts:700-704.
    • Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150's stop reason has not been traced yet.
  6. serge-ivo commented on Sep 19, 2026

    @serge-ivo
    Contributor

    Slice (iii) shipped in a3a9ded5: a run cut off with its work still uncommitted now briefs its successor.

    The gap

    The resume note was composed only from agent_events, and an edit that was never committed is not an event. codingResumeNote returned null when no act landed (lib/coding-resume-note.ts:113). So this issue's own incident — cut off at step 4 of 20, a good partial fix on disk, nothing pushed — told its successor nothing.

    What changed

    • codingResumeNote(acts, endedBy, uncommittedFiles = 0). With an unfinished predecessor and a dirty tree, a note is produced even when no act landed.
    • The count is the repo-state-start read the workflow already makes (RepoWorkingState.changedFiles). Nothing new is captured at stop, and nothing new is stored.
    • The note stays inside what the platform saw:
      • With no acts it opens "…and nothing it did is on the record as landed", never the "ALREADY landed" sentence, and it drops "treat the listed work as done".
      • The files "may be that run's unfinished work". The platform saw the tree, not the author; a human's half-finished edit looks identical.
      • It agrees with the REPOSITORY STATE instruction beside it ("You did not create this state… do NOT discard"): read the diff, build on it, do NOT discard it.
    • A repair run (Stale-checkout pre-flight block should self-heal by pulling main, not hard-fail the whole run #804) is handed zero, because its brief already carries the tree's state.
    • The workflow call is inlined, so coding-session.ts stays inside its file-size pin.

    Unchanged

    • A clean tree produces byte-identical notes (pinned by a test, including the interrupted opening).
    • A dirty tree with no unfinished predecessor briefs nobody; that stays REPOSITORY STATE's job.
    • Predecessor lookup, the 6-hour lookback and RESUMABLE_STOP_REASONS.

    Verified

    • 10 new tests: 8 unit in coding-resume-note.test.ts (one of them a wiring assertion on the workflow source), 2 in coding-resume-note-repo.test.ts against the real migrated schema.
    • Mutation-checked: restoring the old gate fails 6 of them.
    • Full suite 11,639/11,639; pnpm typecheck; CI's biome command; all 13 check-*.mjs; docs:drift.

    Not verified

    • No live run observed. It needs a run stopped for a resumable reason with uncommitted edits, then a new run on the same repo; agent_trace on the successor shows whether the note fired and whether it built on the diff.
    • Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150's stop reason is still untraced. If that relay timeout was recorded as failed, it is outside RESUMABLE_STOP_REASONS and this slice does not reach it either.
    • The count is taken at the successor's start, not the predecessor's stop. Files a human edits in between are counted too, which is why the wording is "may be".

    Still open here

    • (i) owner decision: persist the stopped run's reasoning, or keep option (b). Items 1, 2 and 3(b) depend on it.
    • (ii) "grant more iterations and continue": API plus console UI.

    Leaving this issue open.

  7. serge-ivo commented on Sep 19, 2026

    @serge-ivo
    Contributor

    Slice (ii) shipped in 1b8cdb44: a stopped run can be continued — same objective, a new ceiling, briefed on what already landed.

    Item 3(c), the half that needed no owner decision. Items 1, 2 and 3(b) still wait on (i).

    What existed before

    Nothing. maxIterations is fixed once, at createLoopRun (lib/agent-loop-store.ts:183), and the only way to act on a stopped run was POST /:id/loop/:runId/cancel — which does not apply to a run that has already stopped. Starting again meant retyping the objective into POST /:id/loop and hoping the resume note found the right predecessor.

    What changed

    POST /v1/instances/:id/loop/:runId/continue (routes/loop-continue-routes.ts, registered from routes/tools.ts:1153).

    It is a new run, not a resumed invocation. A finished agent_loop_runs row has no live Workflow to grant steps to, and #523's option (b) means there is no transcript to reanimate. The continuity is the resume note.

    The route is deliberately thin — driver.start() already picks the repo, opens or reuses the session, runs admission and takes the single-flight claim. What it decides is only:

    Decision Behaviour
    Which runs qualify isResumableStopReason — the same four as the note. Everything else is a 409 naming the next step (done → new objective; failed → read the outcome first; cancelled → that was your clean start).
    Still running 409 on finishedAt === null, not on status — a cooperative cancel is still running until the top of its next iteration, and admitting it there is the #208 collision.
    Objective The stopped run's, verbatim.
    Repo Resolved from the stopped run's sessionId → coding_sessions.repo_id, so a multi-repo Coder continues the right checkout rather than repos[0] (#374). A chat run has no session and passes none.
    Ceiling The stopped run's own by default; maxIterations in the body grants more. Clamped by the same per-account ceiling as POST /loop (#477) — a continue is not a way around it.
    Budget A fresh openBudget. #184's rule that every autonomous entry point admits separately; inheriting an unspent pool would let one admission fund an unbounded chain of continues.

    The lookback — item 4, and the only reason this differs from a restart

    RESUME_NOTE_LOOKBACK_MS is six hours (lib/agent-loop-store.ts:257). Item 4 asks that this work "even hours later — the point is a human can check back on a stalled run on their own schedule". Under the default floor, a Continue pressed the next morning would have started a run briefed on nothing at all — i.e. the button would have been a restart with extra steps.

    So a continue passes CONTINUE_RESUME_LOOKBACK_MS (30 days) down the chain: route → LoopStartInput.resumeLookbackMs → CODING_SESSION params → pendingCodingResumeNote({lookbackMs}) → lastUnfinishedRunForRepo.

    It moves the floor only. The predecessor is still the most recent finished run on the repo, judged afterwards, so a verdict in between still ends the note's job. That is also why the note is not pinned to the continued run's id: pinning would skip that check and brief the new run on a checkpoint another run has already consumed — this issue's complaint pointed the other way. Pinned by a test.

    Every ordinary start is byte-identical: no lookbackMs, six-hour floor, same note.

    Surfaces

    Verified

    • 42 new tests — 24 route (loop-continue-routes.test.ts), 13 console-lib, 3 real-schema lookback tests in coding-resume-note-repo.test.ts (driven end to end on an in-memory SQLite built from every migration), 1 driver-threading, 1 workflow-wiring source assertion.
    • Mutation-checked. Dropping the lookback at any of its three links (route, store bind, pendingCodingResumeNote), the cross-instance guard, the unfinished-run guard, or the ceiling default each turns tests red.
    • Full suite 11,793/11,793; pnpm -r typecheck; biome; docs:drift; all 13 check-*.mjs.
    • CI green on 1b8cdb44, along with Deploy API / MCP / Host Worker and Publish MCP Registry.

    Not verified

    • No live run. This needs a real run stopped for a resumable reason, then Continue pressed on it — agent_trace on the successor shows whether the note fired and whether the new run built on it. Same gap the three earlier slices record.
    • Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150's stop reason is still untraced. If that relay timeout was recorded as failed, it is outside RESUMABLE_STOP_REASONS and the Continue button would not be offered on it.
    • The 30-day figure is a choice, not a measurement. It is wide enough that the immediate-predecessor rule, rather than the floor, is what ends the note's job — which is the intended division of labour, but nobody has observed a continue at the far end of it.

    Still open here

    Leaving this issue open.

  8. serge-ivo commented on Sep 19, 2026

    @serge-ivo
    Contributor

    Run cda38e26 continuation – status update (2026-09-19)

    What landed in this continuation run:

    Current state of main: clean and level with origin/main. Full suite 11,824/11,824 and pnpm -r typecheck pass; docs:drift, size:check, mcp:parity and openapi:coverage are green. pnpm lint does NOT pass — one error, e2e/console.spec.ts:4360 lint/complexity/useArrowFunction, which is pre-existing and in a file neither commit touches. No open PRs. Nine remote branches exist, including the merged-but-undeleted loop-continue-806 — not main alone.

    What remains on #806:

    All buildable engineering slices are done. The only open threads are:

    1. Owner decision (i): persist the stopped run's reasoning transcript, or keep option (b) from 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 (checkpoint the objective, not the transcript). Items 1, 2, and 3(b) are all blocked on this choice. If option (b) stands, those items should be closed as won't-do. If it's overturned, they become a new persistence project that should be tracked in a separate issue.

    2. Item 3(a) "start clean": currently handled by cancel-then-start; cancelled is outside RESUMABLE_STOP_REASONS by design, so the path already works correctly. Could be filed as a separate issue if explicit UX for it is wanted.

    3. Unverified in production: no live stopped run has been observed going through the continue route end-to-end with agent_trace confirming the note fired. All coverage is via tests.

    4. Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150's stop reason: if the original relay-timeout incident recorded as failed, it sits outside RESUMABLE_STOP_REASONS and none of the five commits reach it. Worth checking if that incident recurs.

    Leaving #806 open pending decision on (i).

  9. serge-ivo commented on Sep 20, 2026

    @serge-ivo
    Contributor

    Slice item-2 shipped in 8d245d57: you can now see what a Continue would carry forward, before spending a budget on it.

    Item 2, the two-thirds option (b) can answer. Items 1, 3(b) and the "what it still intended" third of item 2 still wait on (i).

    The gap

    The platform already composes this. pendingCodingResumeNote reads the acts the stopped run landed and the state of the checkout and turns them into the briefing the next run is handed. The problem is when: that happens inside the successor's own start, into the successor's prompt and timeline — so the owner reads it a minute after committing a budget, never before. The console offered a Continue button and, beside it, the stopped run's ending label. Neither said what continuing would inherit.

    The fact that changes the decision

    lastUnfinishedRunForRepo takes the most recent finished run on the repo and only then asks what ended it. That rule is right — it stops one checkpoint being handed out twice — but it has a consequence invisible from outside: a Continue on run A carries A's work forward only while A is still the newest finished run on its repo. Let a run B reach a verdict in between, and a Continue on A is briefed on nothing. It is a restart that opens a fresh budget, and it looked identical to one that resumes.

    So the preview does not pretty-print a note. It answers which of three situations the owner is in, by running the same computation the run will run:

    briefing.kind What it means
    this-run The new run is told what this run landed.
    other-run A more recent stopped run on the same repo is the predecessor, so the briefing is that one's. The objective carries; the checkpoint does not.
    none Nothing carries forward — a later run reached a verdict, or nothing landed and the tree is clean, or the predecessor is outside the lookback. Continue is a restart on the bare objective.

    What changed

    • pendingCodingResumeCheckpoint (lib/coding-resume-note.ts) returns the checkpoint with the facts it was composed from, and pendingCodingResumeNote is now a projection of it (?.note ?? null). One computation, so the page cannot describe a briefing the run does not get — two estimates of "what carries forward" is how a surface comes to promise work the run then re-does, which is this ticket's complaint from the other side. The three-way act split both readers need is one exported splitActs. A checkpoint with note: null is a real and distinct state the projection cannot express: there is an unfinished predecessor and it left nothing worth saying.
    • GET /v1/instances/:id/loop/:runId/continue-preview, beside the POST. A GET and not a dryRun flag: the POST's preview question is "would this be refused and how big would it be"; this one is "what would the new run know", which needs two reads the POST does not make. 200 even when the run cannot be continued — the refusal is the answer, and an owner reading why the button is absent is who this is for. The refusal and the ceiling come from helpers the POST now also calls, so the two cannot disagree.
    • The working tree is tri-state. uncommittedFiles is null when the runner did not answer, never 0. That is the expected case here — item 4 is an owner checking back the next morning, when the machine is off — and reporting a clean tree we never saw would be the exact over-claim this surface exists to stop. When it is unreadable the previewed note omits a clause the real briefing may carry, and caveat says so.
    • MCP preview_instance_run_continue (read, no dry_run). Shipped with the route, because a console-reachable route with no tool opens a new KNOWN_GAPS entry against a list MCP parity: 71 console capabilities have no MCP path — the first run's inventory, by group #613 is driving to zero. Parity holds at 29 recorded gaps, unchanged. Server 0.1.44 → 0.1.45; 204 → 205 registrations, always-on 180 → 181. read rather than runtime though it sits beside continue_instance_run: it starts nothing and opens no budget, and classing a review surface with the action it describes would make reading before acting cost the scope of acting.
    • Console — a "What carries over?" disclosure beside Continue, fetched on demand: this section polls every 5s while a run is open, and per-row-per-tick would put a git status on someone's laptop on a timer to answer a question nobody asked. Every line comes from the server, headline included (previewLines), so the panel and the briefing cannot drift.

    Verified

    • 46 new tests — 17 pure (loop-continue-preview.test.ts), 17 route (loop-continue-preview-routes.test.ts), 6 real-schema driven end to end through the migrated SQLite (coding-resume-note-repo.test.ts, 12 → 18), 6 console-lib (loopContinue.test.ts, 13 → 21). The 24 existing POST tests are untouched and pass, which is the evidence the shared-helper refactor moved no behaviour.
    • Mutation-checked. Each of these turns tests red: dropping the continue lookback from the preview; passing 0 instead of the tree read; fixing kind to this-run; collapsing an unreadable tree to 0; having the console compose its own headline; making the note a second computation rather than a projection.
    • Full suite 12,002/12,002; e2e 493/493 including the WebKit phone geometry; pnpm -r typecheck plus the three test-config typechecks (Three tsconfigs exclude test files and CI typechecks none of them, so a type-level guard in any worker test is inert #599); biome; docs:drift; all 13 check-*.mjs.

    Not verified

    • No live run. This needs a real stopped run previewed against a real checkout — agent_trace on a subsequent continue would show whether the note that fired is the one the preview showed. All coverage is tests. Same gap the four earlier slices each record.
    • Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150's stop reason is still untraced. If that relay timeout recorded as failed, it is outside RESUMABLE_STOP_REASONS, the Continue button is not offered on it, and this preview would report it as refused.
    • other-run has not been observed in the wild. It is constructed in tests; nobody has seen the ordering that produces it on a real repo.

    Still open here — and why this commit carries no closing keyword

    (i) is an owner decision and a Pilot cannot make it. Items 1, 3(b) and the "what it still intended" third of item 2 all depend on whether the stopped run's reasoning is persisted, or whether #523's option (b) — checkpoint the objective, not the transcript — stands. A plan is not a fact the platform holds, and coding-resume-note.ts deliberately refuses to state a remainder it would have to invent; this slice does not work around that.

    Every buildable engineering slice is now done:

    Item State
    1 — keep the reasoning context addressable blocked on (i)
    2 — review before deciding shipped here, minus "what it still intended" (blocked on (i))
    3(a) — start clean works today as cancel-then-start; cancelled is outside RESUMABLE_STOP_REASONS by design
    3(b) — resume with prior context intact blocked on (i)
    3(c) — grant more iterations and continue shipped 1b8cdb44 + ca866695
    4 — works hours later shipped 1b8cdb44 (30-day continue lookback), and honoured by this preview

    If option (b) stands, items 1, 2-third and 3(b) should be closed as won't-do and this issue with them. If it is overturned, they are a separate persistence project and deserve their own issue rather than staying here.

    Leaving this issue open.

  10. serge-ivo commented on Sep 20, 2026

    @serge-ivo
    Contributor

    Final slice shipped in b2e5d3c7: a continued run inherits what the stopped Pilot had worked out, and what its owner knows now. This commit closes the issue.

    Why the "owner decision (i)" stopped blocking

    Items 1, 3(b) and the "what it still intended" third of item 2 were parked on one question: persist the stopped run's reasoning, or keep #523's option (b) — checkpoint the objective, not the transcript. Every earlier comment here is right that a Pilot cannot make that call, and this slice does not make it.

    What changed is a fact underneath it. #822 (caa79b5b, the day after the last comment's analysis) made the Pilot record what it learned as it goes, and each entry is already a brain row on the session's coding_timeline. They were added so the Pilot could remember across its own steps. They are also, exactly, the part of a run's reasoning that survives it: what it worked out, where it had got to, and very often what it meant to do next ("Fix written, tests green. Next: rebase, commit, push."). Handing them to the successor persists nothing new — the checkpoint is still composed at the next start from rows that already exist. Option (b) stands, unchanged.

    What changed

    Item What it does now
    1 — reasoning context addressable The stopped run's learned notes are read by its own session and run window (same keying as its acts, #809) and carried in ResumeCheckpoint.learned.
    2 — "what it was mid-way through, what it still intended" GET …/continue-preview returns briefing.learned — all of them, oldest first — from the same checkpoint the run is briefed from, and counts them in the headline. The console's "What carries over?" panel quotes them, attributed ("its own words, not checked").
    3(b) — resume with prior context, optionally adding instructions The resume note quotes the newest 12 notes to the new Pilot. And POST …/continue takes note — what the owner knows now that the run did not.

    The notes are QUOTED, never adopted. coding-resume-note.ts still refuses to state "what remains" in the platform's voice. The note says: "They are ITS words, not something the platform checked: each was true when it was written, and the repository may have moved since… verify any one you are about to rely on." They sit apart from the landed acts, which are asserted.

    The owner's note rides in the OBJECTIVE, not as a hint. A hint is one round's message and the workflow clears it. The objective is in every Pilot decision, on the run's row, in the timeline — and therefore in what a further continue inherits. The stopped run's words stay verbatim and first; the addition is labelled Added by the owner when continuing this run: so a correction can be told from the original. Past the column's 2000 characters it is refused with the room available, never cut, and refused before a budget is opened.

    Also:

    • A run that landed nothing on a clean tree but had worked something out now briefs its successor. It used to brief nobody — that is the Investigate iteration waste: redundant re-reads and re-verification eating step budget #822 shape (steps spent reading, nothing pushed yet).
    • No notes → every pre-existing resume note and preview headline is byte-identical (pinned by tests).
    • A failed notes read costs the notes, not the acts.
    • Console: an optional textarea inside the review panel; the one-click Continue beside the label stays one click and sends an empty body.
    • MCP: continue_instance_run gains note. Server 0.1.47 → 0.1.48, surface lock appended; registrations unchanged at 213. surface-lock.ts crossed 800 lines and takes its first size pin (an append-only ledger whose git history is the check is not split).
    • The engine's side needed nothing: a new session already adopts the repo's previous CLI conversation inside the four-day window, and is seeded from the timeline when it cannot (coding-session-continuity.ts, coding-seed-brief.ts).

    Verified

    • 30 new tests (12,076 → 12,106): 5 unit on the note's wording, 6 against the real migrated schema end to end through pendingCodingResumeCheckpoint, 5 preview-lib, 1 preview-route, 10 continue-route / continueObjective (it.each rows counted), 3 console-lib.
    • Mutation-checked. Each turns a test red: dropping type = 'brain' from the read; dropping the run-window bound (a human-opened session then hands over an earlier run's notes); rounding the window's start up instead of down (a note from the run's first second is lost — created_at is whole seconds, the run's bounds are ms).
    • One mutation did not bite and I replaced the test rather than keep a claim nothing held: I had a "last second" edge test that passed with the rounding removed, because a note written before the run finishes can never be stamped after it. The start edge is the real one.
    • Full suite 12,106/12,106; e2e 499/499 (306 chromium + 193 webkit); pnpm -r typecheck + the three test-config typechecks (Three tsconfigs exclude test files and CI typechecks none of them, so a type-level guard in any worker test is inert #599); CI's biome command; docs:drift; openapi:coverage; all 13 check-*.mjs.

    Not verified

    • No live run. Same gap every slice here records. It needs a real run stopped at max_iterations that had written learned notes, then a Continue (ideally with a note) — coding_loop_trace / agent_trace on the successor shows the briefing that fired.
    • No e2e spec drives the review panel, before or after this change; the new textarea is covered by typecheck, the control-labels source guard and the pure continueBody tests, not by a browser.
    • Agent work as first-class Tickets — granular, with reasoning + per-ticket conversation #150's stop reason is still untraced. If that relay timeout was recorded as failed it is outside RESUMABLE_STOP_REASONS and no slice here reaches it.

    What this deliberately is not

    The per-step transcript is still not persisted. "Full session/reasoning context" is answered by what the Pilot chose to record, plus the acts, the tree and the engine's own conversation — not by a replay. A run from before #822, or one whose Pilot recorded nothing, carries acts and the tree exactly as before. If a verbatim transcript is ever wanted, that is overturning option (b) and deserves its own issue, as the previous comment said. Item 3(a) "start clean" remains cancel-then-start by design.

    Item State
    1 shipped here (Pilot's recorded notes; not a transcript)
    2 8d245d57 + here
    3(a) works as cancel-then-start
    3(b) shipped here
    3(c) 1b8cdb44 + ca866695
    4 1b8cdb44 (30-day continue lookback)

    Reopen if the transcript question should be decided the other way.

  11. added a commit that references this issue on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2: correctnessReal defect, no live harm today — inert fields, miscounts, missing guardscoderThe Coder wedge agent (#68) — Engine, Pilot, Co-pilot, Loop, OverseerenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions