Skip to content

Fix #69: Durable mid-task resume: per-tool write-ahead checkpoints, compacted-run resume, and a persistent plan/todo tool - #76

Merged
vicgupta merged 1 commit into
mainfrom
amf/issue-69
Oct 25, 2026
Merged

vicgupta merged 1 commit into
mainfrom
amf/issue-69

Conversation

@vicgupta

Copy link
Copy Markdown
Owner

Closes #69

What

Durable checkpoints

  • With -o, the checkpoint is saved after the model response, with pending_calls, before any tool runs. It is saved again after each tool result. The once-per-step save after all tools is gone.
  • Every save fsyncs the temp file, renames it into place, then fsyncs the directory. The directory sync is skipped on Windows and on filesystems that return EINVAL/ENOTSUP.
  • On resume, each pending call gets the synthetic error result [interrupted: tool did not complete before the agent stopped; it may have partially run — verify state before retrying]. The call is never re-run.
  • Background processes are recorded as processes (id, pid, command). On resume they are reported as lost, both in the continuation notice and as resume.process_lost events.
  • Schema 2. v1 checkpoints still load and resume. A v1 file that contains v2 fields is rejected. Partial or invalid JSON is rejected with a clear error.

Compacted-run resume

  • Removed the refusal. The view is rebuilt from the last 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)

  • --plan or plan.enabled: true advertises update_plan. Each call replaces the whole list: up to 50 items, text up to 500 characters, at most one in_progress, and an optional note.
  • The plan is stored in the trajectory as plan and plan_history, never in messages. 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_version is recorded.
  • The tool is allowed with --read-only. Hooks do not run for update_plan.
  • --plan-file requires the plan tool and writes a Markdown mirror after each checkpoint save.
  • The checkpoint records plan_enabled, and resume keeps that setting. --plan that 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 --json events: plan.updated {step, version, items, note, diff}, checkpoint.saved {step, path, pending}, resume.interrupted_call {id, name}, resume.process_lost {id, pid, cmd}. The event schema_version stays 1, because these are additive.

Tests

  • cmd/tinyagent/kill_test.go builds the binary, runs it against a fake OpenAI server, sends SIGKILL during bash 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.go covers 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.go covers v1/v2 validation, pending-call matching, plan rules, compaction-record rules, and partial-file rejection.
  • cmd/tinyagent/plan_test.go covers --plan-file without the plan, the resume --plan conflict, the checkpoint.saved event and the Markdown export.

gofmt -l, go vet ./..., go build ./..., go test ./... and go test -race all pass locally.

Deferred / not in this PR

…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.
@vicgupta

Copy link
Copy Markdown
Owner Author

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 tool.result with format_error: true, well-formed calls run through the plan tool when enabled or runTool otherwise, and the consecutive-format-error cap still ends the run with RepeatedFormatError. The once-per-step save after all tools stays gone, per this PR's design. Also kept: the --output-schema submit handling and viewFromTrajectory for compacted-run resume.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Durable mid-task resume: per-tool write-ahead checkpoints, compacted-run resume, and a persistent plan/todo tool

1 participant