Repository navigation
Fix #69: Durable mid-task resume: per-tool write-ahead checkpoints, compacted-run resume, and a persistent plan/todo tool - #76
Conversation
…lan tool (#69) - Checkpoint before tools run (pending_calls) and after each tool result; fsync file and directory on save. Resume closes interrupted calls with an error result instead of re-running them, and reports lost background processes. - Resume compacted runs by rebuilding the model view from the last compaction record. - Opt-in update_plan tool (--plan / plan.enabled, off by default) whose plan lives in the trajectory and is pinned into each request, surviving compaction and resume; --plan-file mirrors it as Markdown. - Trajectory schema 2 (v1 still loads); new JSON events plan.updated, checkpoint.saved, resume.interrupted_call, resume.process_lost.
e7d2e88 to
410bc4c
Compare
|
Rebased onto main by the hourly merge job (head now 410bc4c), which now includes #75 (format-error handling, merged first). The rebase had a semantic conflict in the tool-call loop: main's loop had the #66 format-error handling, this PR rewrote the loop for write-ahead checkpoint saves and the plan tool. The merged loop keeps both: pending calls are recorded and saved before any tool runs and after every result, format-error calls still skip execution and hooks and still emit |
Closes #69
What
Durable checkpoints
-o, the checkpoint is saved after the model response, withpending_calls, before any tool runs. It is saved again after each tool result. The once-per-step save after all tools is gone.[interrupted: tool did not complete before the agent stopped; it may have partially run — verify state before retrying]. The call is never re-run.processes(id,pid,command). On resume they are reported as lost, both in the continuation notice and asresume.process_lostevents.Compacted-run resume
Compaction.View, its elisions and its summary, then the later messages are replayed. Compaction records are now validated: indices must be in range and ordered, a summary is required for summarized slots, and only tool results can be elided.Plan tool (opt-in, off by default)
--planorplan.enabled: trueadvertisesupdate_plan. Each call replaces the whole list: up to 50 items, text up to 500 characters, at most onein_progress, and an optional note.planandplan_history, never inmessages. A[Current plan]block is pinned after the system prompt in every request. It counts toward the compaction size estimate and is passed to the summarizer.Compaction.plan_versionis recorded.--read-only. Hooks do not run forupdate_plan.--plan-filerequires the plan tool and writes a Markdown mirror after each checkpoint save.plan_enabled, and resume keeps that setting.--planthat conflicts with the checkpoint is an error. With the plan disabled, the tool signature is byte-identical to before, so existing checkpoints still resume. With the plan enabled, the signature is prefixed.New
--jsonevents:plan.updated{step, version, items, note, diff},checkpoint.saved{step, path, pending},resume.interrupted_call{id, name},resume.process_lost{id, pid, cmd}. The eventschema_versionstays 1, because these are additive.Tests
cmd/tinyagent/kill_test.gobuilds the binary, runs it against a fake OpenAI server, sends SIGKILL duringbash sleep, then runs--resume. It checks that the model receives the interrupted result, that no step, tokens or messages are lost, and that the events are emitted.internal/agent/durable_test.gocovers crash points before, during and after tools; a compacted resume whose view equals the uninterrupted view plus the notice; lost processes; the plan off by default; the plan surviving compaction and resume and appearing in every request; plan validation; and the plan in read-only mode.internal/trajectory/durable_test.gocovers v1/v2 validation, pending-call matching, plan rules, compaction-record rules, and partial-file rejection.cmd/tinyagent/plan_test.gocovers--plan-filewithout the plan, the resume--planconflict, thecheckpoint.savedevent and the Markdown export.gofmt -l,go vet ./...,go build ./...,go test ./...andgo test -raceall pass locally.Deferred / not in this PR
.bakcopy of the previous checkpoint. The issue marks it optional. Atomic rename plus fsync already guarantees that either the old or the new checkpoint survives intact.step.savedevent.checkpoint.savedfires on every save, including the last one in a step.-omust still differ from the source.