From 7c7a171c8d8a5af7d3cccb186614830d214dd85f Mon Sep 17 00:00:00 2001 From: Story Crater Bot <19826264+Riotpiaole@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:16:03 -0700 Subject: [PATCH] fix(agent-pod): committed progress ledger so resume skips done tasks Resuming the phase branch alone only recovers the code -- the task loop still walked from the first task, re-verifying every already-done one through a full planner call before reaching the first task that actually needed work. .agent-progress is committed (not gitignored) and appended per completed task, so a resumed run reads it once and skips straight past known-done tasks with zero LLM calls. Validated locally against a throwaway repo: second run skipped both tasks instantly (resumed: true) instead of re-running planner on them. --- k8s/apps/agent-pod/coordinator-configmap.yaml | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/k8s/apps/agent-pod/coordinator-configmap.yaml b/k8s/apps/agent-pod/coordinator-configmap.yaml index 2eb5069..04324da 100644 --- a/k8s/apps/agent-pod/coordinator-configmap.yaml +++ b/k8s/apps/agent-pod/coordinator-configmap.yaml @@ -469,6 +469,36 @@ data: } } + // Committed (never gitignored) so it survives a resumed phase branch -- + // one task id per line, appended as each task resolves. This is what lets + // a resumed run skip straight past already-resolved tasks instead of + // re-running planner's judgeOnly decision on every one of them again: + // resuming the git branch alone only recovers the CODE, not "which tasks + // are already settled," and re-deciding that from scratch for every task + // burns a full LLM call per already-done task before ever reaching the + // first one that actually needs work. + function progressLedgerPath(cwd) { + return path.join(cwd, ".agent-progress"); + } + + function readCompletedTasks(cwd) { + const file = progressLedgerPath(cwd); + if (!fs.existsSync(file)) return new Set(); + return new Set( + fs + .readFileSync(file, "utf8") + .split("\n") + .map((line) => line.trim()) + .filter(Boolean) + ); + } + + async function recordTaskComplete(cwd, task) { + fs.appendFileSync(progressLedgerPath(cwd), `${task}\n`); + await runGit(cwd, ["add", path.basename(progressLedgerPath(cwd))]); + await runGit(cwd, ["commit", "-m", `chore: mark ${task} complete in progress ledger`]); + } + // Runs every task in a phase (no declared dependency between them) strictly // one at a time against the repo's shared role pool -- only one implementer/ // judge/etc. exists per repo, so there is no per-task concurrency to have @@ -485,9 +515,21 @@ data: // from a clone that never saw any of that work. async function runPhase(cwd, baseBranch, phaseBranch, phaseTasks, pool, repoId, pipelineSession) { const entries = phaseTasks.map((t) => (typeof t === "string" ? { id: t, judgeOnly: false } : t)); + const completed = readCompletedTasks(cwd); + for (const entry of entries) { + if (completed.has(entry.id)) { + const result = { task: entry.id, status: "done", resumed: true }; + pipelineSession.taskResults.push(result); + logProgress(pipelineSession); + continue; + } + const result = await runTaskOnPool(cwd, baseBranch, entry.id, pool, repoId, pipelineSession, entry.judgeOnly); pipelineSession.taskResults.push(result); + if (result.status === "done" || result.status === "done-with-concerns") { + await recordTaskComplete(cwd, entry.id); + } await runGit(cwd, ["push", "-u", "origin", phaseBranch]); logProgress(pipelineSession); }