Milestone 3: persistence, checkpoint/resume, stuck watchdog (Tasks 16-18)#6
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (26)
📝 WalkthroughWalkthroughThis PR implements session persistence with resumable checkpoints, stuck task detection via timeout watchdog, observable loop events, dependency-aware task scheduling, git worktree-based integration, and comprehensive observability instrumentation. It adds ChangesPersistence, Resume, and Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/storage/store.ts (1)
142-154: 💤 Low valuePremature
verifiedassignment inrecordTransition.Line 150 sets
verified: rec.to === "GREEN", but according to theTaskRecordcomment (line 38-39),verifiedshould only be true for a verified GREEN that mirrorsTaskOutcome.verified. The transition alone doesn't indicate verification status—that comes from the critic review result stored in the outcome.While
recordOutcomelater overwrites this with the correct value, there's a brief window where the task record would reportverified: truesimply from entering GREEN state. If any reader (e.g., resume logic) queries the task between transition and outcome recording, they could get incorrect data.Consider explicitly using
falsehere and lettingrecordOutcomeset the authoritative value:Suggested fix
this.db.tasks[key(rec.sessionId, rec.taskId)] = { sessionId: rec.sessionId, taskId: rec.taskId, state: rec.to, - verified: rec.to === "GREEN", + verified: false, // authoritative value set by recordOutcome updatedAt: rec.at, };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/storage/store.ts` around lines 142 - 154, The recordTransition method is prematurely setting TaskRecord.verified from the transition (using rec.to === "GREEN"); change recordTransition to always set verified: false when updating this.db.tasks so the transition only updates state/updatedAt, and let recordOutcome (which already writes the authoritative verification value) set the correct verified flag; update the recordTransition implementation (function name: recordTransition) accordingly and leave recordOutcome and TaskRecord semantics unchanged.src/session.ts (1)
43-44: 💤 Low valueDocumentation comment may be misleading.
The comment says
sessionIdis "present when a store was provided", but per line 110,sessionIdis also present when the caller explicitly providessessionIdOpt(even without a store). Consider updating to: "present when a store was provided or sessionId was explicitly passed".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/session.ts` around lines 43 - 44, Update the JSDoc for the sessionId property to accurately reflect when it can be present: change the comment on sessionId to say it is "present when a store was provided or when sessionId was explicitly passed (sessionIdOpt)". Ensure the updated comment references the same property name sessionId and, if helpful, mention sessionIdOpt to link the behavior to the caller-provided option.test/store.test.ts (1)
61-66: 💤 Low valueTest records attempts without creating a session first.
This test calls
recordAttemptwithout first creating a session. While the currentBaseStoreimplementation doesn't enforce session existence, this could mask bugs if session validation is added later. Consider addingawait store.createSession(...)for consistency with other tests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/store.test.ts` around lines 61 - 66, The test for MemoryStore currently calls recordAttempt without creating a session first; to make it consistent with other tests and avoid masking future session-validation bugs, call await store.createSession({ sessionId: "s", createdAt: "<any-valid-timestamp>" }) (or the appropriate createSession signature used elsewhere) before the two recordAttempt calls, then keep the existing expect(await store.listAttempts("s")).toHaveLength(2); reference MemoryStore, recordAttempt, createSession, and listAttempts when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/run.ts`:
- Around line 23-30: The code currently allows "--resume" with no session id
(resumeId undefined) which is silently ignored; change the parsing logic around
resumeIdx/resumeId so that if resumeIdx !== -1 and there is no following
non-flag token (i.e., args[resumeIdx + 1] is undefined or startsWith("-")), you
surface an error and exit (for example console.error("Missing session id for
--resume") and process.exit(1) or throw), otherwise keep assigning resumeId =
args[resumeIdx + 1] and splice the two entries; update the block referencing
resumeIdx/resumeId accordingly to validate presence of a real session id before
continuing.
In `@src/storage/store.ts`:
- Around line 236-245: persist() currently assigns this.writeChain =
this.writeChain.then(async () => { mkdir, writeFile, rename }) so if any of
those ops throws the writeChain becomes rejected and all future .then() calls
short-circuit; fix persist() so the chain is made healthy on failure while still
letting the caller observe the error: wrap the async callback so failures reset
this.writeChain to a resolved Promise (e.g. set this.writeChain =
Promise.resolve() or reassign to a fresh resolved chain) before rethrowing, or
attach a .catch(err => { this.writeChain = Promise.resolve(); throw err; }) to
the Promise assigned to this.writeChain; reference symbols: persist,
this.writeChain, this.filePath, mkdir, writeFile, rename — ensure errors are
either rethrown to the caller or logged, but always restore this.writeChain to a
non-rejected state so subsequent persist() calls execute.
---
Nitpick comments:
In `@src/session.ts`:
- Around line 43-44: Update the JSDoc for the sessionId property to accurately
reflect when it can be present: change the comment on sessionId to say it is
"present when a store was provided or when sessionId was explicitly passed
(sessionIdOpt)". Ensure the updated comment references the same property name
sessionId and, if helpful, mention sessionIdOpt to link the behavior to the
caller-provided option.
In `@src/storage/store.ts`:
- Around line 142-154: The recordTransition method is prematurely setting
TaskRecord.verified from the transition (using rec.to === "GREEN"); change
recordTransition to always set verified: false when updating this.db.tasks so
the transition only updates state/updatedAt, and let recordOutcome (which
already writes the authoritative verification value) set the correct verified
flag; update the recordTransition implementation (function name:
recordTransition) accordingly and leave recordOutcome and TaskRecord semantics
unchanged.
In `@test/store.test.ts`:
- Around line 61-66: The test for MemoryStore currently calls recordAttempt
without creating a session first; to make it consistent with other tests and
avoid masking future session-validation bugs, call await store.createSession({
sessionId: "s", createdAt: "<any-valid-timestamp>" }) (or the appropriate
createSession signature used elsewhere) before the two recordAttempt calls, then
keep the existing expect(await store.listAttempts("s")).toHaveLength(2);
reference MemoryStore, recordAttempt, createSession, and listAttempts when
making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b1a16b86-c221-475d-8e7f-b4f4b3961ea4
📒 Files selected for processing (11)
.kiro/specs/loop-engine/tasks.mdsrc/domain/stateMachine.tssrc/engine/loop.tssrc/engine/watchdog.tssrc/run.tssrc/session.tssrc/storage/checkpoint.tssrc/storage/store.tstest/resume.test.tstest/store.test.tstest/watchdog.test.ts
…-18) Task 16 - storage/store.ts: dependency-free Store for sessions, tasks, attempts, transitions, and outcomes. MemoryStore (ephemeral) and JsonFileStore (durable, atomic temp+rename writes serialized through a promise chain) share one in-memory model; openStore() picks by dbPath. Task 17 - checkpointing + resume: the loop gained an optional LoopObserver (transition/attempt/outcome hooks); fire() is now async so each transition is checkpointed before the loop advances. storage/checkpoint.ts bridges the observer to the store (+ combineObservers for fan-out). session.runGoal bootstraps/persists a session, composes the checkpointer, and on resume reuses completed (GREEN/unverified) tasks while re-running unfinished ones. run.ts gains --resume <sessionId>. Task 18 - engine/watchdog.ts: guardProgress races a step against a no-progress threshold; a hung build/review aborts the task to NEEDS_HUMAN (new STUCK_ABORTED transition) instead of hanging. ProgressWatchdog tracks time-since-progress for a supervisor. Threshold overridable per-run via LoopDeps.stuckThresholdMs. Adds 15 tests (106 total); typecheck clean.
…a failed persist - run.ts: '--resume' with no following session id (or another flag) now exits with an error instead of being silently ignored and starting a fresh session. - JsonFileStore.persist: recover from a previously rejected writeChain before chaining the next write, so one failed disk write (e.g. ENOSPC) no longer permanently short-circuits every subsequent checkpoint. The current write's own outcome is still returned to its caller.
…asks 19-21) Task 19 - engine/scheduler.ts: dependency-graph scheduler. Runs tasks as a DAG with bounded concurrency (config.maxParallel), starts a task only once deps are unblocking, and skips dependents (transitively) of unsatisfied prerequisites. Validates the graph up front (duplicate/missing/self refs, cycles). Seams for per-task workspaces and resume reuse. session.runGoal now delegates ordering + parallelism to it (replacing the sequential pass). Task 20 - workspace/worktrees.ts: GitWorktreeManager gives each concurrent task an isolated git worktree on its own branch (cut from a base ref), commits its changes for integration, and tears the worktree down. git is injected (workspace/git.ts) for testing. Task 21 - engine/integrator.ts: merges the per-task branches in a dedicated integration worktree, aborts + surfaces conflicts (with file paths) instead of force-resolving, and runs full verification on the integrated tree. Wired into runGoal behind opts.repoDir (+ config.useWorktrees). Adds 13 tests (119 total), incl. real-git worktree + integrator suites; typecheck clean.
…-24) Task 22 - structured event log: Store gains a generic event stream (recordEvent/listEvents); observability/instrument.ts wraps each runner so every invocation emits a role-attributed runner_call event (chars, duration, quota, normalized token usage). createRoles takes an onRunnerCall hook; session.runGoal records runner calls plus session_started / plan_reviewed / session_finished lifecycle markers to the store. Task 23 - observability/usage.ts: computeUsage aggregates runner calls per role and per run (calls, tokens, duration, quota hits), with optional per-1k-token rates yielding a cost estimate. normalizeUsage handles OpenAI- and input/output-style usage objects. Task 24 - observability/trace.ts: buildTrace assembles a session's lifecycle, per-task state history, attempts, outcomes, events, and usage ledger; formatTrace renders it. New 'npm run trace -- <sessionId>' CLI. Adds 6 tests (125 total); typecheck clean.
Select the first usage key that is actually present rather than the first truthy one. The previous '||' chain discarded a legitimate 0 (e.g. an empty prompt) in favor of a later alias, corrupting usage aggregation.
Milestone 4: parallel execution - scheduler, worktrees, integrator (Tasks 19-21)
Milestone 5: observability - event log, usage ledger, trace (Tasks 22-24)
This pull request was created by @kiro-agent on behalf of @inhaq 👻
Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro Web
Summary
Completes Milestone 3 (Persistence & resilience) — Tasks 16, 17, 18.
Task 16 — Local store
storage/store.ts: aStorefor sessions, tasks, attempts, transitions, and outcomes. Two implementations over one in-memory model:MemoryStore(ephemeral) andJsonFileStore(durable). Dependency-free — no native SQLite build step; run state is small (a diff + gate output per task).JsonFileStorewrites atomically (temp file + rename) and serializes writes through a promise chain, so transitions from parallel tasks (Milestone 4) can't interleave a half-written file.openStore(dbPath)picks memory vs file.Task 17 — Checkpoint + resume
LoopObserver(transition/attempt/outcome);fire()is now async so every transition is checkpointed before the loop advances. With no observer, behavior is unchanged.storage/checkpoint.tsbridges the observer to the store, pluscombineObserversfor fan-out (the Milestone 5 event log composes here).session.runGoalbootstraps/persists a session, and onresumereuses completed (GREEN/unverified) tasks while re-running unfinished ones.run.tsadds--resume <sessionId>.Task 18 — Stuck watchdog
engine/watchdog.ts:guardProgressraces a step against a no-progress threshold; a hung build/review aborts the task toNEEDS_HUMANvia a newSTUCK_ABORTEDtransition instead of hanging forever.ProgressWatchdogtracks time-since-progress for a supervisor (used by the parallel scheduler next). Threshold overridable per-run viaLoopDeps.stuckThresholdMs.Testing
npm run typecheckclean.npm test: 106 passing (+15:store,watchdog,resume). Covers store CRUD + lockstep task state, durable reopen, concurrent-write integrity, checkpoint-during-run, resume-reuses-completed / re-runs-unfinished, and a hung build aborting to NEEDS_HUMAN.Summary by CodeRabbit
New Features
--resume <sessionId>flag to continue interrupted worktracecommandBug Fixes
STUCK_ABORTEDevent handlingChores