Skip to content

feat(workflows)!: add durable cross-session resume - #1509

Merged
flora131 merged 18 commits into
mainfrom
issue-1498-durable-workflow-resume
Jun 26, 2026
Merged

feat(workflows)!: add durable cross-session resume#1509
flora131 merged 18 commits into
mainfrom
issue-1498-durable-workflow-resume

Conversation

@flora131

@flora131 flora131 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Introduces a pluggable durable workflow backend that persists ctx.* operation checkpoints across sessions, enabling a new Atomic session to resume a workflow from the last completed checkpoint — without re-running completed work or changing workflow authoring syntax. File-backed durability is now on by default (rooted at ~/.atomic/workflow-durable). Closes #1498.

Breaking Changes

  • File backend is now the default. Workflow durability is always enabled with the zero-infrastructure file backend at ~/.atomic/workflow-durable. The previous process-local in-memory default and ATOMIC_WORKFLOW_DURABLE_DIR opt-in are removed. In-memory durability is available only as an explicit test/custom backend override, or via the ATOMIC_WORKFLOW_DURABLE=0/false/off/memory privacy opt-out.

Key Changes

Durable Backend System (packages/workflows/src/durable/)

  • Three backend implementations: file-backed (default, lock-protected, ~/.atomic/workflow-durable), DBOS-backed (Postgres via DBOS_SYSTEM_DATABASE_URL, lazily initialized), and in-memory (test/custom override only).
  • Per-workflow file storage: one JSON state file per root workflow with automatic pruning of completed/cancelled runs; secure directory and file permissions (0700 / 0600); stale lock directories from crashed processes are reclaimed automatically.
  • ctx.tool primitive: durable cached tool execution with optional exponential-backoff retry (retriesAllowed, maxAttempts, intervalMs, backoffRate). Completed side effects are never re-run on resume.
  • ctx.ui checkpointing: prompt responses cached by stable identity (method + label/message + options + call order) so resumed workflows skip already-answered prompts.
  • ctx.stage / ctx.task / ctx.chain / ctx.parallel / child ctx.workflow checkpointing: stage outputs recorded with stable ordinal replay keys; child workflow calls checkpoint the completed result at the parent boundary.
  • Scoped child backend: child workflow ctx.* side effects are keyed under the root boundary so an interrupted child does not re-execute completed work on parent resume.
  • SHA-256 replay hashing (durableHash) replacing the prior 32-bit DJB2 hash to eliminate collisions across distinct tool/stage identities.
  • DBOS structured envelopes: checkpoints stored as versioned JSON envelopes enabling full metadata reconstruction on hydration.
  • Lifecycle guarantees: workflow completion waits for pending durable writes; terminal status (completed/failed/cancelled) is flushed before the run returns; cancellation races are guarded at ctx.tool and retry boundaries.

Resume Catalog & Runtime

  • scanResumableWorkflows: scans session JSONL files for workflow.durable.checkpoint entries to build the /workflow resume selector without querying the backend.
  • DBOS read-side hydration: on fresh process start with DBOS_SYSTEM_DATABASE_URL, /workflow resume hydrates the in-memory mirror from Postgres so prior-session workflows are discoverable.
  • Backend terminal-state precedence: backend-known completed/cancelled workflows override stale session-JSONL cache entries, preventing resurrection of non-resumable runs.
  • Cross-session crash recovery: durable running handles from crashed processes remain resumable at the catalog level; same-session double-resume is blocked only when an actively-executing live run exists in the current session.

Quit vs Kill UX

  • Resumable quit/detach: pressing q (orchestrator/CLI) now pauses a workflow (durable handle → paused) rather than killing it. Background widget and status list render a quit badge with a "resumable via /workflow resume" hint.
  • /workflow kill only cancels: kill is now the only path that authoritatively cancels a workflow, preventing accidental data loss during quit/resume cycles.

/workflow resume TUI Integration

  • Combined live + durable picker: no-arg /workflow resume opens a unified selector showing live runs alongside cross-session durable workflows; dismissing returns to chat without a second prompt.
  • Durable target resolution: a target id not matching a live run falls back to the durable catalog; stale cache-only entries are refused rather than silently re-run from scratch.
  • Overlay connection on success: successful durable resume connects the graph overlay, matching live resume ergonomics.
  • @dbos-inc/dbos-sdk optional dependency added to @bastani/atomic so DBOS-backed execution is available without a separate install; the adapter is loaded lazily and the workflows package remains dependency-free.

Bug Fixes

  • Fixed empty-string stage outputs collapsing into status objects (empty text now replays as empty).
  • Fixed ScopedDurableBackend.listCheckpoints leaking sibling child-scope checkpoints sharing a common id prefix.
  • Fixed file-backed backend recovering from stale lock directories left by crashed processes.
  • Fixed schema-backed stage replay returning raw JSON strings instead of structured values.
  • Fixed parallel fail-fast skipping stages before async finalizers completed.
  • Fixed concurrency limiter/semaphore leaking on stage finalization failures.
  • Fixed mixed live+durable resume using async DBOS-hydrated listing so Postgres-persisted entries appear in the combined selector.
  • Fixed replayed durable stages registering in the graph frontier tracker so parent/lineage topology is preserved.
  • Fixed durable and combined pickers to settle selections before synchronous custom-UI disposal can dismiss them.
  • Fixed ctx.ui.custom replaying cached void/undefined responses instead of treating them as cache misses.
  • Fixed headless no-arg durable resume to print the catalog instead of awaiting unavailable picker UI.
  • Fixed in-progress LM stage-session checkpoints colliding with completed stage-output checkpoints (now stored in separate backend indexes).
  • Fixed mid-session LM resume sending Continue instead of re-sending the original prompt, preventing repeated quit/resume cycles from emptying chats.
  • Fixed stale quit snapshots being reused when a workflow id is recycled.
  • Fixed an unnecessary replayKey override in the executor stage factory that broke continuation-replay topology validation.
  • Fixed CI test isolation: tests mutating the global durable backend singleton now reset it in afterEach/afterAll.

Tests & Docs

  • Added 11 new unit test files covering backend CRUD, DBOS backend, factory opt-in, resume catalog, resume runtime, cross-session resume, stage primitives, UI primitives, root/child fixes, stage frontier fixes, and stage-session resume (~3,400+ lines of test coverage).
  • Added integration coverage for overlay entrypoints and /workflow resume dispatch; added regression suite for overlay resume edge cases; added durable-stage-session-resume unit coverage.
  • Updated packages/coding-agent/docs/workflows.md with durable resume configuration, backend selection, ctx.tool authoring guidance, and quit/kill UX.

Configuration

Backend Trigger Persistence
File-backed (default) (always on) Cross-process, lock-protected, ~/.atomic/workflow-durable
DBOS/Postgres DBOS_SYSTEM_DATABASE_URL=<url> Cross-session, full Postgres durability (falls back to file if unavailable)
In-memory (privacy opt-out) ATOMIC_WORKFLOW_DURABLE=0/false/off/memory Current process only, no plaintext disk persistence
In-memory (test/custom) Explicit backend override Current process only

Validation

bun install --frozen-lockfile --minimum-release-age 0
bun test test/unit/durable-stage-frontier-fixes.test.ts
bun test test/unit/durable-*.test.ts test/integration/overlay-entrypoints-commands.test.ts
bun test test/unit/slash-dispatch.test.ts -t "resume"
bun run typecheck
bun run lint
bun run check:file-length
bun run test:unit

QA

No QA E2E video applies: this is backend/library plus terminal/TUI command-path behavior, not a browser UI flow. The executable proof is the headless overlay command integration suite plus the durable unit coverage above.

flora131 added 11 commits June 24, 2026 23:09
Add an optional DBOS-backed durable workflow backend with file/in-memory fallbacks, plus ctx.tool, UI, and stage checkpoint primitives for resumable side effects.

Cache durable workflow metadata in session history and wire /workflow resume to a workflow-specific resume catalog while preserving live-run controls.

Cover durable state, DBOS adapter behavior, resume catalog/runtime flows, and cross-session metadata with unit tests, and document configuration and semantics.

Assistant-model: OpenAI GPT-5
Wire the lazy DBOS SDK adapter through launch, workflow control, and checkpoint hydration so fresh sessions can replay persisted state.

Add durable stage/task replay, stronger resume discovery, file backend locking and merge semantics, root workflow and failure-state filters, and coverage for the DBOS hydration and resume paths.
Sync bun.lock with the optional DBOS SDK dependency and durable workflow packages.

Scan Atomic custom JSONL entries for workflow resume metadata, hydrate DBOS workflow metadata without marking resumable roots completed, and serialize durable checkpoint writes with a flush barrier so write failures surface before completion.

Preserve graph/store visibility when replaying cached stages and tasks, and keep no-arg /workflow resume aligned with the live run picker before falling back to durable history.

Assistant-model: OpenAI Codex
Persist mutable DBOS metadata without relying on write-once helper workflow results and remove the invalid duplicationPolicy parameter from DBOS calls.

Await stage checkpoint persistence, add a DBOS initialization barrier before workflow dispatch, and make no-arg workflow resume surface the durable selector while preserving live picker behavior.

Differentiate repeated ctx.tool calls with ordinal checkpoint identity, make retry backoff cancellation-aware, and preserve first-run onboarding from origin/main.

Assistant-model: Atomic Subagent
Add durable replay coverage for ctx.chain, ctx.parallel, and child workflow calls so composite workflow operations do not re-run after resume.

Flush terminal durable workflow status before returning, only update the DBOS replay mirror after checkpoint acceptance, and keep /workflow resume durable history visible when completed local runs exist.

Assistant-model: OpenAI GPT-5
Keep the prepared durable catalog available through resume so scan-only session-cache entries can be selected and resumed.

Preserve structured schema-backed stage replay values, await parallel fail-fast finalizers, clean up sleepOrAbort listeners on normal completion, and prefer explicit stage replay keys before falling back to external lookup.

Assistant-model: OpenAI GPT-5
Stabilize child workflow replay keys so repeated child calls resume completed work without re-executing side effects.

Persist direct stages under the durable replay key, record ctx.exit terminal durable metadata, surface durable history even when live runs exist, and propagate durableBackend into child workflow runs.

Assistant-model: OpenAI GPT-5
Scope child workflow internal checkpoints to the root workflow so nested side effects replay consistently across sessions.

Refuse stale cache-only resume entries when no durable backend state exists, switch durable replay identities to SHA-256 digests, check cancellation after tool functions resolve before checkpointing or returning, and recover stale file-backend locks after crashes.
Suppress stale terminal cache entries when backend state is terminal so old session JSONL cannot resurrect completed workflows.

Open the workflow overlay after a successful durable resume and combine live and durable entries in the no-arg /workflow resume picker.

Tighten scoped checkpoint listing to exclude sibling scopes and clean up the merged run import.

Assistant-model: OpenAI GPT-5 Codex
Preserve empty string stage outputs when checkpointing durable stage results.

Release stage limiter slots when finalization fails, hydrate durable entries before mixed live/durable resume selection, preserve replayed stage parent/frontier graph state, and avoid opening a second picker after combined picker dismissal.

Assistant-model: OpenAI GPT-5
@flora131

Copy link
Copy Markdown
Collaborator Author

Implementation notes (part 1/2)

Implementation Notes

Task: Implement GitHub issue #1498 in bastani-inc/atomic: #1498 ("Add cross-session resumability for Atomic workflows with DBOS").

Decisions and Tradeoffs

1. Durable backend seam (not direct DBOS coupling)

Decision: Created a DurableWorkflowBackend interface in packages/workflows/src/durable/backend.ts with three implementations:

  • InMemoryDurableBackend — process-local; default for tests.
  • FileDurableBackend — JSON-file-backed; zero-infrastructure cross-process resume.
  • DbosDurableBackend — wraps @dbos-inc/dbos-sdk when configured.

Rationale: The issue says "integrate DBOS with the workflow backend" and "do not change the frontend workflow syntax." A backend seam lets the engine persist ctx.* checkpoints without hard-coupling to DBOS or requiring Postgres for basic operation/tests. This matches the research recommendation: "DBOS should integrate narrowly at the workflow backend."

Tradeoff: The DBOS adapter (DbosDurableBackend) currently delegates async DBOS calls fire-and-forget while keeping an in-memory mirror for synchronous queries. A production deployment would need the extension runtime to initialize DBOS via DBOS.launch() and wire DbosSdkHandle to the real SDK. This is documented but not fully wired (would require Postgres at runtime to test).

2. ctx.tool primitive

Decision: Added ctx.tool(name, args, fn, options?) to WorkflowRunContext in both shared/types.ts and shared/authoring-contract-ui.ts. It runs arbitrary TS code and caches the result via the durable backend, keyed by a deterministic content hash of name + args.

Rationale: The issue explicitly requires "ctx.tool which allows you to run any typescript code and cache the result for DBOS."

Tradeoff: The hash function (durableHash) uses a simple DJB2-style hash of canonical JSON. This is fast and deterministic but not cryptographically secure — sufficient for checkpoint identity, not for security.

3. File-backed backend as default (no Postgres required)

Decision: When DBOS_SYSTEM_DATABASE_URL is not set, the engine uses FileDurableBackend rooted at ~/.atomic/workflow-durable/state.json.

Rationale: The issue says "Save state in DBOS by caching on session file." The file backend provides cross-process resume without infrastructure, matching the zero-setup expectation. DBOS remains available as an upgrade path.

Tradeoff: File I/O is not as robust as Postgres for concurrent writes. The atomic write (temp + rename) mitigates corruption risk.

4. Session JSONL cache entries

Decision: Added workflow.durable.checkpoint session entries via persistDurableCacheEntry(). These cache top-level workflow metadata (id, name, inputs, status, checkpoint count) so a new session can discover resumable workflows via scanResumableWorkflows().

Rationale: The issue requires "durable state cached on the session file" and a /workflow resume selector.

5. Cancellation/failure/retry semantics

Decision: Mapped to existing workflow failure classification plus durable status tracking:

  • Killed/cancelled → cancelled status, not auto-resumable.
  • Recoverable failure → failed status, remains in resumable list.
  • ctx.tool retries → exponential backoff (matching DBOS step retry defaults).
  • Pending UI prompts → left unanswered on resume (user must answer).

6. Mock for builtin-workflows-helpers.ts

Decision: Added a tool stub to the test mock context that throws "should not be used by builtin workflow mocks."

Rationale: The WorkflowRunContext type now requires tool, so all mock contexts must provide it.

Deviations from Research

  • The research suggested wiring DBOS directly into run(). Instead, I created a backend seam (DurableWorkflowBackend) that abstracts DBOS, with file-backed fallback. This is more conservative and testable.
  • The research mentioned ctx.ui responses should be saved. I added DurableUiCheckpoint types and backend support (getUiResponse/recordCheckpoint for UI), but the UI context wiring in run.ts does not yet intercept ctx.ui.* calls to auto-record UI checkpoints. This is a gap — the backend supports it, but the engine doesn't yet wire it for every UI call. The ctx.tool primitive IS fully wired.
  • /workflow resume cross-session selector: the existing /workflow resume command already handles live-run resume and continuation replay. The durable catalog (scanResumableWorkflows, listResumableFromBackend, formatResumableWorkflowList) provides the discovery layer. Full TUI integration into the slash command's picker is implemented at the catalog/format level but not yet wired into the TUI overlay code (would require modifying workflow-run-control-command.ts and TUI overlays, which is a larger surface change).

Validation

  • bun run typecheck — clean (0 errors)
  • bun run lint — clean (0 errors)
  • bun run check:file-length — all files under 500 lines
  • 41 new tests pass across 4 test files:
    • durable-backend.test.ts (17 tests): checkpoint idempotency, no-duplicate side effects, file persistence, ctx.tool caching/retry/cancel
    • durable-resume-catalog.test.ts (11 tests): JSONL scanning, cache entry persistence, selector formatting
    • durable-dbos-backend.test.ts (8 tests): DBOS adapter delegation, isDbosConfigured, export/import round-trip
    • durable-cross-session-resume.test.ts (5 tests): full cross-session resume integration, failed stage resume, multi-stage pending, cancelled exclusion, selector formatting
  • Existing tests unaffected: persistence-session-entries.test.ts (58 pass), cancellation-registry.test.ts, builtin-workflows.test.ts (73 pass) all pass.

Open Gaps / Future Work

  1. ctx.ui durable checkpointing: The backend supports UI checkpoints (DurableUiCheckpoint, getUiResponse), but the engine does not yet auto-intercept ctx.ui.* calls in run.ts to record/restore UI responses. The buildExitGatedUiContext wrapper is the right place for this — a future change should wrap each UI method to check the durable backend first.

  2. ctx.stage/ctx.task durable checkpointing: Similarly, stage completion outputs are not yet auto-recorded as DurableStageCheckpoint. The stage lifecycle hooks (onStageEnd) are the right place.

  3. DBOS SDK initialization: The createDbosDurableBackend() factory throws intentionally — it's a lazy seam. A production deployment needs the extension runtime to call DBOS.launch() and construct a real DbosSdkHandle. The DbosDurableBackend class itself is fully implemented and tested with a mock SDK.

  4. /workflow resume TUI overlay integration: The durable catalog and formatting functions are ready, but the TUI picker overlay (openSessionPicker in workflow-run-control-command.ts) is not yet modified to show cross-session durable entries alongside live runs. This is a TUI surface change that requires careful interactive testing.

  5. @dbos-inc/dbos-sdk not added to package.json: The dependency is intentionally not added because the package should work without it. The DBOS adapter dynamically imports the SDK only when configured. Adding it as an optionalDependency could be considered.

QA E2E Video

No QA E2E video was produced for this implementation. The change is backend/library code (durable state management, ctx.tool primitive, resume catalog) with no user-visible UI scenario that can be driven via playwright-cli. The /workflow resume TUI overlay integration is catalog-level only (not yet wired into the interactive TUI), so there is no executable user scenario to record. Validation was performed via 41 hermetic unit/integration tests covering all acceptance criteria scenarios.

Follow-up implementation (issue #1498, second pass)

Closed the open gaps from the first implementation pass:

  1. DBOS SDK dependency: Added @dbos-inc/dbos-sdk@4.20.11 as an optionalDependency of @bastani/atomic (packages/coding-agent/package.json). Optional so installs that cannot resolve it still succeed; the workflows package remains runtime-dependency-free and loads DBOS lazily only when DBOS_SYSTEM_DATABASE_URL is set. The bun.lock could not be regenerated in this worktree because the minimumReleaseAge bunfig gate blocks resolution of the ^0.79.10 pi packages from the registry (they are workspace-linked locally and resolve fine for typecheck/tests); CI/normal installs will produce the lock entry.

  2. Durable ctx.stage/ctx.task: New stage-primitive.ts records completed stage outputs at the stage-end lifecycle boundary (wired via a composing onStageEnd in run.ts). Skips non-completed stages; idempotent.

  3. Durable ctx.ui: New ui-primitive.ts (wrapUiWithDurable) caches completed input/confirm/select/editor/custom responses by prompt identity; resume returns cached responses without re-asking. Wired into buildExitGatedUiContext via an optional durableUi dep.

  4. /workflow resume selector integration: New resume-runtime.ts adapter (resumeDurableWorkflow) re-dispatches the workflow with the ORIGINAL workflow id and cached inputs so durable checkpoints replay (DBOS-style). Wired into handleRunControlCommand: when a resume target is not a live run, it falls back to the durable catalog; with no target it shows the durable selector. Added resumeDurableWorkflow + listDurableResumable to the ExtensionRuntime interface and proxy.

Key design decision: opt-in cross-session persistence

Changed the default durable backend from file-backed to in-memory (factory.ts). Rationale:

  • The file backend silently writes to ~/.atomic/workflow-durable/ on every run — an undesirable default side effect.
  • In-memory default keeps the session lifecycle JSONL stream clean (no workflow.durable.checkpoint entries interleaved with lifecycle events), so existing lifecycle-ordering tests pass unchanged.
  • Added a persistent: boolean flag to DurableWorkflowBackend; the engine only mirrors discovery cache entries to the session JSONL when the backend is persistent.
  • Cross-session resume is opt-in via ATOMIC_WORKFLOW_DURABLE_DIR (file) or DBOS_SYSTEM_DATABASE_URL (DBOS).

Validation

  • bun run typecheck / lint / check:file-length: clean.
  • bun run test:unit: 2617 pass, 0 fail (added durable-ui-primitive, durable-stage-primitive, durable-resume-runtime, durable-factory-optin test files; updated package-metadata.test.ts to tolerate absent optional deps).
  • End-to-end engine smoke (run.ts): fresh run records tool/ui/stage checkpoints; a second run reusing the same workflowId replays all cached checkpoints with zero side-effect re-execution.

Remaining notes

  • The DBOS SDK adapter (createDbosDurableBackend) still throws intentionally as a lazy seam; production DBOS launch wiring (calling DBOS.launch() and constructing a real DbosSdkHandle) is deferred to the extension runtime integration and is not exercisable without Postgres. The DbosDurableBackend class itself is fully implemented and mock-tested.
  • No QA E2E video: this is backend/library + command-path code with no user-visible browser UI scenario; the executable proof is the engine smoke test above.

Validation pass (independent review, issue #1498)

Reviewer independently re-ran all validation and probed the durable-replay path end-to-end.

Commands run / results

  • git status --short + git diff --stat HEAD: 14 tracked files changed (+387/-30); 11 new files under packages/workflows/src/durable/; 8 new test files.
  • bun run typecheck: clean (0 errors).
  • bun run lint (= tsc --noEmit): clean.
  • bun run check:file-length: passed (1796 files, all <=500; durable/ files max 275 lines).
  • bun test test/unit/durable-*.test.ts: 68 pass, 0 fail across 8 files.
  • bun run test:unit: 2617 pass, 0 fail (full suite, ~9s).
  • npm view @dbos-inc/dbos-sdk@4.20.11: confirmed 4.20.11 is a real published version (latest is 4.21.6; pin is valid). @dbos-inc/dbos-sdk is NOT installed in node_modules (optional dep, legitimately absent) — typecheck/lint unaffected because the adapter loads it lazily.

Independent end-to-end engine smoke (reviewer-authored)

Wrote a throwaway run()-level script: run a workflow once (records tool+ui checkpoints), then run it AGAIN with the same workflowId. Findings:

  • ctx.tool replay verified: the tool fn body executed exactly once across both runs (toolCalls stayed 1); Run2 returned the cached RESULT without re-executing.
  • ctx.ui replay verified: instrumented the REAL base ui.input (not the workflow body) — it printed only on Run1; Run2 returned the cached ANS without invoking the real base UI. (Initial smoke mis-placed a counter in the workflow body, which falsely suggested a miss; a corrected probe confirmed the cache short-circuits correctly — backend.getUiResponse returns the cached value and the real UI is never re-asked.)
  • Conclusion: durable checkpoint replay semantics are correct end-to-end through run().

Observations / minor flags (non-blocking)

  1. Root package.json reformatted: keys were alphabetically reordered (a formatter ran). Functionally equivalent JSON, but it is unrelated noise in the diff. Recommend reverting the root package.json to its original ordering before commit to keep the diff focused, or leave as-is since it is valid.
  2. DBOS launch not actually wired: createDbosDurableBackend() throws by design; the headline "with DBOS" is architecturally satisfied (backend seam + fully implemented, mock-tested DbosDurableBackend class + @dbos-inc/dbos-sdk optional dep declared), but a real Postgres-backed DBOS.launch() integration in the extension runtime is deferred. Cross-session resume today works via the opt-in file backend (ATOMIC_WORKFLOW_DURABLE_DIR) or the in-memory same-process path. This is the principal honest limitation.
  3. Default backend is in-memory (opt-in persistence): a deliberate, documented tradeoff so the default run does not write to ~/.atomic or pollute session JSONL. Cross-session persistence requires setting ATOMIC_WORKFLOW_DURABLE_DIR or DBOS_SYSTEM_DATABASE_URL.
  4. bun.lock not regenerated: blocked by repo minimumReleaseAge (3-day) gate during full bun install; the optional dep declaration is correct and CI will produce the entry.

QA E2E video

No QA E2E video produced. Rationale: this is backend/library + TUI command-path code with no user-visible browser UI scenario; /workflow resume TUI behavior requires building the atomic binary and driving an interactive session, which is not practical in this worktree. The executable proof is the 2617-passing unit suite plus the independent run() engine smoke proving durable replay (tool + ui) skips side effects on resume. The narrower validation that proves the command path is the durable-resume-runtime / resume-catalog unit tests covering selector resolution, ambiguity, not-resumable, invalid-inputs, and successful re-dispatch by original workflow id.

Safety to commit

Safe to commit. No fixes required from this review. The only recommendation is reverting the incidental root package.json key reordering (cosmetic).

Files touched (this pass)

  • New: packages/workflows/src/durable/{ui-primitive.ts, stage-primitive.ts, resume-runtime.ts}
  • New tests: test/unit/{durable-ui-primitive, durable-stage-primitive, durable-resume-runtime, durable-factory-optin}.test.ts
  • Modified: packages/workflows/src/durable/{index.ts, backend.ts, file-backend.ts, dbos-backend.ts, factory.ts}, engine/primitives/ui.ts, engine/run.ts, extension/runtime.ts, extension/extension-runtime-state.ts, extension/workflow-run-control-command.ts, packages/coding-agent/package.json, packages/coding-agent/docs/workflows.md, packages/workflows/CHANGELOG.md, test/unit/package-metadata.test.ts

Review-fixes validation pass (issue #1498)

Independent validation of the staged review-fixes (on top of commit ae7636784).

Commands / results

  • bun test test/integration/overlay-entrypoints-commands.test.ts: 12 pass, 0 fail (previously 11 pass / 1 fail; the no-runId fresh-discovery case now passes).
  • bun test test/unit/durable-*.test.ts: 71 pass, 0 fail across 8 files.
  • bun run typecheck / lint (tsc --noEmit): clean.
  • bun run check:file-length: passed (1815 files, all <=500).
  • bun run test:unit: 2620 pass, 0 fail across 288 files (~9s).

Review findings — all addressed

  • DBOS env wiring: createDbosDurableBackend now imports SDK + launch() + real workflow/control records; initializeDbosDurableBackendFromEnv() invoked from extension runtime with warning fallback.
  • Stage/task replay: createDurableStagePrimitive / createDurableTaskPrimitive read backend.getStageOutput before executing; wired into ctx.stage/ctx.task.
  • Stable checkpoint IDs: tool = tool:${argsHash}; stage = ${kind}:${replayKey} (ordinal replay keys).
  • /workflow resume no-target fresh discovery: listDurableResumable falls back to resolveDefaultStageSessionDir(); JSONL scan merged with backend catalog.
  • UI identity collisions: ordinal-aware { kind, message, details, ordinal } identity; select options + custom replayIdentity participate.
  • File backend concurrency: mkdir lock dir + read-merge-write; concurrent-merge test passes.
  • Root-only catalog: isRootWorkflow filter on both backend and JSONL scan.
  • Non-recoverable failures excluded: resumable !== false for failed/blocked; run.ts sets resumable: durableFailureResumable.

Non-blocking concern: DBOS read-side hydration

The DbosDurableBackend reads from the in-memory mirror only. On a fresh process with only DBOS configured, the mirror is empty — checkpoint replay returns undefined because checkpoints are not hydrated from Postgres at startup. File backend cross-process resume is fully functional and tested; DBOS provides write-side durability + control plane. Not a commit blocker.

TUI/tmux E2E

Not practical: CLI not built (no dist/; requires tsgo + asset copy), DBOS SDK not installed (minimumReleaseAge gate). Narrower proof: overlay integration test (12/12) exercises the /workflow resume command path headlessly including the previously-failing fresh-discovery branch; durable unit + cross-session tests (76 total) prove replay/resume/cancel/failure semantics.

QA E2E video

No QA E2E video. Backend/library + TUI command-path code; no browser UI scenario; interactive TUI blocked by unbuilt binary. Executable proof is the test suite (2620/2620 unit + 12/12 overlay + 71/71 durable).

Safety to commit

Safe to commit. No fixes required. Findings written to subagent-review-fixes-validation.md.

Commit

  • Reverted the incidental root package.json key reordering before commit to keep the diff focused.
  • Created commit ae7636784085 with message feat(workflows): add durable cross-session resume.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • No pull request was created or pushed per orchestration instructions.
  • Final working tree after commit only has untracked research artifacts under research/docs/ and research/web/; implementation files are committed.

Final independent validation after DBOS hydration follow-up

Validation performed from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume after the DBOS hydration follow-up. Reviewed the latest diff for the issue #1498 acceptance areas: lazy real DBOS SDK wiring, DBOS read-side checkpoint hydration, durable ctx.tool/ctx.ui/stage/task replay, session JSONL cache/discovery, /workflow resume durable listing/resume path, cancellation/failure/retry/root-only semantics, file backend lock+merge behavior, and docs/changelog coverage.

Commands run:

  • bun test test/unit/durable-dbos-backend.test.ts test/unit/durable-resume-runtime.test.ts — 30 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts — 83 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 12 pass, 0 fail.
  • bun run typecheck — clean.
  • bun run lint — clean.
  • bun run check:file-length — passed; 1815 tracked files checked, all <=500 lines.
  • bun run test:unit — 2632 pass, 0 fail.

Tiny validation fix made: corrected packages/coding-agent/docs/workflows.md cancellation semantics to say cancelled workflows are excluded from /workflow resume discovery and should be restarted as a new run if retry is desired. This aligns docs with the implemented durable resumability filter and tests.

Final acceptance assessment: no known unaddressed acceptance gaps remain in the implementation diff. DBOS-backed resume remains lazily initialized and mock/integration-tested without requiring a live Postgres instance in this validation environment; the real DBOS path now has read-side hydration APIs and resume/list preparation hooks.

QA E2E video note: no browser QA video applies. This is backend/library plus terminal/TUI command-path behavior. tmux is available (tmux 3.6b), but a reliable interactive TUI end-to-end run would require launching a fully configured Atomic TUI session and workflow fixture with model/runtime credentials; the narrower executable proof is the overlay command integration test plus full unit suite and durable DBOS hydration tests. No video was produced, and none is claimed.

Safe-to-commit verdict: safe to commit after including the tiny docs correction. No final blockers identified.

Review-fix implementation pass

Addressed the post-review blockers called out in the refreshed research:

  • Implemented createDbosDurableBackend() with lazy @dbos-inc/dbos-sdk loading, DBOS.setConfig(), DBOS.registerWorkflow(), DBOS.launch(), workflow start/resume/cancel/list delegation, and checkpoint persistence via DBOS-managed checkpoint workflows. The extension runtime now attempts DBOS initialization when DBOS_SYSTEM_DATABASE_URL is set and warns/falls back safely if SDK/Postgres initialization fails.
  • Added durable stage/task replay wrappers with stable ordinal replay keys and content-derived checkpoint ids so completed stages/tasks can be skipped after re-dispatch and new checkpoints do not collide with previous run counters.
  • Changed ctx.tool checkpoint ids to stable tool-hash ids, avoiding counter collisions after resume.
  • Strengthened ctx.ui prompt identity to include method, message/label, select options/custom replay identity, and call order; repeated prompts in one workflow no longer collide and replay in the same order.
  • Made /workflow resume surface durable workflow history in fresh/no-target sessions when durable entries exist, and made runtime durable listing invoke JSONL scanning using the default session directory when available.
  • Hardened FileDurableBackend with a lock-directory critical section plus read/merge/write persistence so concurrent process updates do not overwrite each other.
  • Added root-workflow metadata and resumability flags. Durable catalogs now hide child workflows and filter terminal/non-recoverable failures while retaining recoverable failed/blocked workflows.
  • Updated workflow docs and packages/workflows/CHANGELOG.md to describe real DBOS initialization, file locking, prompt identity, root-only catalog behavior, and refined failure semantics.

Review-fix validation

  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 12 pass, including the previously failing /workflow resume no-runId behavior.
  • bun test test/unit/durable-*.test.ts — 71 pass, including new collision/concurrency/filtering coverage.
  • bun run test:unit — 2620 pass, 0 fail.

Remaining limitation

DBOS checkpoint storage is now wired to real SDK lifecycle/control APIs where feasible without making DBOS a required dependency. Atomic still executes workflow code itself and mirrors checkpoints locally for synchronous replay queries; DBOS is used for durable workflow/control records and checkpoint-workflow records. A full DBOS-native execution model for arbitrary dynamically discovered Atomic workflow definitions would require a broader architecture change and a live Postgres-backed integration environment.

Review-gap preflight (delegated analysis)

  • Re-ran gh issue view 1498 --repo bastani-inc/atomic; confirmed the issue requires real DBOS TypeScript SDK integration, cross-session resume by top-level workflow id, /workflow resume selector parity with /resume, durable ctx.ui/ctx.tool/stage state, and tests/docs for cancellation/failure/retry semantics.
  • Read latest primary research and supporting post-review artifacts listed there.
  • Inspected HEAD ae7636784085ead047759d3d19dd6c721d12dcc7 and current git status. No tracked modifications were present; only untracked research/support artifacts remained.
  • Setup state: node_modules and bun.lock are present; Bun is 1.3.14. The repo bunfig.toml has minimumReleaseAge = 259200, so unnecessary lock/install churn should be avoided.
  • Reproduced the known focused failure: bun test test/integration/overlay-entrypoints-commands.test.ts => 11 pass, 1 fail (/workflow resume — overlay integration > resume with no runId prints usage).
  • Acceptance gaps to fix before claiming production readiness:
    1. DBOS env path still uses file backend; createDbosDurableBackend() throws and is not wired into runtime initialization.
    2. Durable stage checkpoints are currently recorded but not read before stage/task execution, so completed stages can re-run.
    3. Checkpoint ids restart at cp-1 on resume and can collide/drop later checkpoints.
    4. Interactive no-arg /workflow resume does not surface durable workflows in fresh sessions and the existing integration test fails.
    5. Session JSONL discovery is implemented but not invoked by default command/runtime paths.
    6. ctx.ui prompt identity can collide for same-message prompts/select/editor/custom calls.
    7. Shared file backend can lose concurrent updates due whole-file stale-memory writes.
    8. Child workflows can leak into the public root resume catalog.
    9. Non-recoverable failures are listed as resumable because durable status loses failure recoverability metadata.
    10. DBOS-backed read/replay semantics require hydration from DBOS; current adapter delegates reads to an in-memory mirror.
  • Wrote detailed findings to /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume/subagent-review-gap-preflight.md.

Follow-up: DBOS read-side hydration fix

Implemented the remaining DBOS read-side hydration gap found by validation:

  • Added packages/workflows/src/durable/dbos-envelope.ts, a structured checkpoint envelope stored as DBOS checkpoint output. Future DBOS records now preserve checkpoint kind, checkpoint id, tool args hash, UI prompt hash, stage replay key, raw output/response, and completion timestamp.
  • Extended DbosSdkHandle with async read-side methods (listAllWorkflows, listStepRecords) and implemented the real SDK handle via lazy DBOS import, listWorkflows for root workflow metadata/input hydration, checkpoint workflow prefix scans, and retrieveWorkflow().getResult() for checkpoint payload reads. No top-level DBOS import was added.
  • Added optional hydrateWorkflow(workflowId) and hydrateResumableWorkflows() hooks to DurableWorkflowBackend; DbosDurableBackend uses them to reconstruct the in-memory mirror from DBOS in a fresh process before synchronous replay reads.
  • Wired async pre-hydration into runtime list/resume paths: /workflow resume now calls prepareDurableResumable() before listing or resuming durable workflows, and resume-runtime.ts exposes prepareRuntimeDurableResumable for this path.
  • Legacy/simple DBOS payloads without the envelope are handled as generic stage checkpoints keyed by step name.
  • Added DBOS hydration tests covering fresh-process tool/UI/stage replay, resumable workflow discovery, idempotent hydration, legacy payload handling, and resume-runtime pre-hydration.

Validation for DBOS hydration fix

  • bun test test/unit/durable-dbos-backend.test.ts: 19 pass, 0 fail.
  • bun test test/unit/durable-resume-runtime.test.ts: 11 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts: 82 pass, 0 fail (before adding the resume-runtime hydration test); rerun included in final subagent report.
  • bun test test/integration/overlay-entrypoints-commands.test.ts: 12 pass, 0 fail.
  • bun run typecheck: clean after type fixes.
  • bun run lint: clean.
  • bun run check:file-length: clean, runtime.ts remains exactly 500 lines.

Remaining limitation

The real DBOS SDK handle stores Atomic checkpoints as separate atomicWorkflowCheckpoint workflows with ids prefixed by <workflowId>:checkpoint:. Hydration therefore enumerates checkpoints with DBOS.listWorkflows({ workflow_id_prefix }) and reads each checkpoint with retrieveWorkflow().getResult(). listWorkflowSteps(workflowId) is not used for those checkpoint records because the current storage model does not store Atomic checkpoints as DBOS steps within the root workflow; the async hydration seam keeps that storage choice isolated.

Final commit after review fixes

  • Created follow-up commit 800c73424 with message fix(workflows): complete durable DBOS resume wiring.
  • This commit includes the DBOS read-side hydration fix, final docs correction, and review-finding fixes for durable replay, resume discovery, file locking, root/failure filtering, and tests.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • No pull request was created or pushed per orchestration instructions.
  • Final git status after commit contains only untracked research/progress/subagent artifacts; implementation changes are committed.

Final latest validation after risk fixes

Final validation was run from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume after the latest risk fixes. No code changes were made by this validation pass.

Risk verification:

  • Stage lifecycle checkpoint writes now have a durability barrier before workflow success: run.ts awaits durableBackend.flush?.() before recording completed run state, and DbosDurableBackend.flush() awaits serialized queued writes and propagates queued DBOS errors. Covered by durable DBOS/stage tests.
  • Cached ctx.task replay now populates store/graph visibility: cached task replay calls recordCachedTask, and run.ts records a completed replayed stage snapshot. Covered by cached ctx.task replay records a completed store stage.

Commands / results:

  • bun install --frozen-lockfile --minimum-release-age 0 — passed; no lockfile changes.
  • bun test test/unit/durable-stage-primitive.test.ts test/unit/durable-dbos-backend.test.ts — 33 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts — 93 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 13 pass, 0 fail.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1816 tracked files checked, all <=500 lines.
  • bun run test:unit — 2642 pass, 0 fail.

QA E2E note: tmux is available (tmux 3.6b), but a reliable full interactive Atomic TUI E2E run remains impractical in this validation context because it requires a fully configured interactive Atomic session with model/runtime credentials and a workflow fixture. The narrower executable proof is the overlay command integration test plus durable unit/integration coverage. There is no browser UI scenario for Playwright, so no QA E2E video applies and none was produced.

Safe-to-commit verdict: safe to commit. No blockers found.

Follow-up latest risk fixes

Addressed the two validation risks found after the latest production-readiness pass:

  • Added a run-completion durable flush barrier: after workflow output validation and before recording/reporting success, run() now awaits durableBackend.flush?.(). For DBOS, queued async checkpoint writes are serialized and flush() now propagates queued write failures instead of only warning, so a workflow does not report success before lifecycle stage checkpoints are durably persisted.
  • Preserved cached ctx.task graph/store visibility: createDurableTaskPrimitive now accepts recordCachedTask, and run() records cached task replays as completed durable-replay stage nodes using the same store/graph path as cached ctx.stage replay.
  • Updated packages/coding-agent/docs/workflows.md and packages/workflows/CHANGELOG.md to document the durability barrier and cached task replay visibility.

Validation for this follow-up:

  • bun test test/unit/durable-stage-primitive.test.ts test/unit/durable-dbos-backend.test.ts — 32 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts — 92 pass, 0 fail.
  • bun run typecheck — clean.
  • bun run lint — clean.
  • bun run check:file-length — passed; 1816 tracked files checked, all <=500 lines.

Remaining note: the DBOS failure-propagation test intentionally exercises a failing mock backend and emits the expected atomic-workflows: DBOS durable write failed: dbos write failed warnings during the passing test run.

Latest production-fix pass

Implemented the latest research findings after commit 800c73424:

  • Regenerated bun.lock with bun install --minimum-release-age 0; bun install --frozen-lockfile --minimum-release-age 0 now passes and includes @dbos-inc/dbos-sdk@4.20.11.
  • Fixed DBOS helper workflow status drift by writing Atomic root metadata as a DBOS checkpoint metadata record and using that Atomic status during hydration/listing, so DBOS helper SUCCESS does not mark resumable roots completed.
  • Updated the session cache scanner to parse Atomic's actual custom JSONL entry shape (type: custom, customType: workflow.durable.checkpoint, data: ...) while retaining legacy direct-entry support.
  • Added async/serialized DBOS checkpoint persistence with recordCheckpointAsync()/flush(); ctx.tool, ctx.ui, and ctx.task await async checkpoint persistence before returning completed side-effect results.
  • Replayed durable stages now call a graph/store visibility hook; run.ts records completed durable-replay stage snapshots so UI/status views preserve progress instead of silently skipping graph nodes.
  • No-arg /workflow resume now preserves the live picker path when live/paused runs exist, avoiding the prior durable-only branch that hid live paused runs.
  • Updated docs and changelog to describe the lockfile-backed optional DBOS dependency, Atomic metadata hydration, custom-entry session cache shape, serialized writes, durable-replay graph nodes, and live/durable resume ergonomics.

Validation for latest pass:

  • bun install --frozen-lockfile --minimum-release-age 0 — passed.
  • bun test test/unit/durable-*.test.ts — 87 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 13 pass, 0 fail.
  • bun run typecheck — clean.
  • bun run lint — clean.
  • bun run check:file-length — passed (1816 tracked files checked, all <=500 lines).
  • bun run test:unit — 2636 pass, 0 fail.

No QA E2E video was produced: this pass is backend/library plus terminal/TUI command-path behavior. The executable proof is the overlay command integration test plus durable unit tests and full unit suite.

Latest preflight / review findings after commit 800c734

Performed a read-only preflight from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume and wrote detailed findings to subagent-latest-preflight-issue1498.md.

Evidence gathered:

  • Re-read gh issue view 1498 --repo bastani-inc/atomic; issue still requires real DBOS durable state, cross-session resume by top-level workflow id, /workflow resume selector parity, durable ctx.ui/ctx.tool/stage state, and tests/docs for cancellation/failure/retry.
  • Read the latest primary research report plus the newest supporting artifacts it references.
  • Inspected HEAD 800c734240f9; final tracked diff was clean after restoring a transient lockfile write from the frozen install probe.
  • Confirmed packages/coding-agent/package.json contains @dbos-inc/dbos-sdk@4.20.11, but bun.lock does not; bun install --frozen-lockfile --minimum-release-age 0 fails with lockfile had changes, but lockfile is frozen.
  • Confirmed DBOS root metadata workflow currently returns immediately (atomicWorkflowHandle returns inputs), so DBOS SUCCESS maps to durable completed during hydration and can hide otherwise-resumable workflows.
  • Confirmed scanResumableWorkflows() parses top-level { type: "workflow.durable.checkpoint" }, while Atomic persistence writes real custom entries shaped { type: "custom", customType: "workflow.durable.checkpoint", data: ... }.
  • Confirmed DBOS registerWorkflow, recordCheckpoint, and status control methods still use fire-and-forget void this.sdk... calls; this risks unhandled rejections and duplicate side effects if DBOS checkpoint persistence fails after a side effect completes.
  • Confirmed cached durable stage replay returns a synthetic StageContext without recording start/end snapshots into the workflow store/graph.
  • Confirmed no-arg interactive /workflow resume routes directly to durable listing and bypasses the live picker, which can hide live paused/resumable runs.

Recommended targeted validation after fixes:

  • bun install --frozen-lockfile --minimum-release-age 0
  • bun test test/unit/durable-resume-catalog.test.ts
  • bun test test/unit/durable-dbos-backend.test.ts test/unit/durable-resume-runtime.test.ts
  • bun test test/unit/durable-stage-primitive.test.ts test/unit/durable-ui-primitive.test.ts test/unit/durable-backend.test.ts
  • bun test test/unit/durable-*.test.ts
  • bun test test/integration/overlay-entrypoints-commands.test.ts
  • bun run typecheck
  • bun run lint
  • bun run check:file-length
  • bun run test:unit

Latest final validation pass (delegated, issue #1498)

Validation performed from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume after the latest fixes for lockfile, DBOS metadata hydration, JSONL scanning, serialized DBOS writes, cached-stage visibility, and /workflow resume live/durable behavior.

Diff review findings

Confirmed addressed by code/tests:

  • bun.lock now contains @dbos-inc/dbos-sdk@4.20.11; bun install --frozen-lockfile --minimum-release-age 0 passed with no changes.
  • DBOS hydration now reads Atomic durable metadata from __atomic_metadata records and tests assert resumable status is not taken from helper workflow SUCCESS.
  • scanResumableWorkflows() now parses Atomic custom JSONL entries with { type: "custom", customType: "workflow.durable.checkpoint", data: ... } as well as legacy direct rows.
  • DBOS write path now has serialized enqueueWrite(), recordCheckpointAsync(), and awaited durable writes for ctx.tool, ctx.ui, and cached ctx.task checkpoints; ignored sync writes have an attached catch handler to avoid unhandled rejections.
  • Cached ctx.stage replay records synthetic completed stage snapshots back into the active workflow store so graph/widget visibility is preserved.
  • No-arg /workflow resume now opens the live picker when top-level live runs exist, while falling back to durable resume when none exist; overlay integration tests cover the live-picker regression.

Potential remaining blockers / risks found by manual review:

  • recordStageCheckpoint() is still called from the stage-end lifecycle synchronously and uses backend.recordCheckpoint() rather than an awaited async write or a run-end backend.flush(). The DBOS backend catches ignored write failures, so this should not produce unhandled rejections, but stage checkpoint durability can still lag workflow completion/process exit. This means the statement "all DBOS checkpoint writes are awaited" is not fully true for lifecycle stage checkpoints.
  • Cached ctx.task replay returns the cached task result directly and does not create a synthetic stage snapshot in the workflow store. Cached ctx.stage does populate graph/store visibility; cached ctx.task graph visibility is not independently implemented or tested in the current diff.

Commands run / results

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no changes.
  • bun test test/unit/durable-resume-catalog.test.ts — 12 pass, 0 fail.
  • bun test test/unit/durable-dbos-backend.test.ts test/unit/durable-resume-runtime.test.ts — 31 pass, 0 fail.
  • bun test test/unit/durable-stage-primitive.test.ts test/unit/durable-ui-primitive.test.ts test/unit/durable-backend.test.ts — 34 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts — 87 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 13 pass, 0 fail.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1816 tracked files checked, all <=500 lines.
  • bun run test:unit — 2636 pass, 0 fail.

TUI / QA E2E assessment

tmux is available (tmux 3.6b), but a reliable full interactive Atomic TUI E2E validation was not practical in this checkout because it would require launching and driving a fully configured Atomic interactive session with workflow fixtures and model/runtime credentials. The narrower executable proof for the TUI command path is the headless overlay integration suite, including the no-arg /workflow resume live-picker regression test. No browser QA video applies and no video was produced.

Safe-to-commit verdict

Not fully safe to commit as "complete" if the latest acceptance checklist requires every DBOS checkpoint write to be awaited and cached ctx.task replay to populate graph/store visibility. All requested validation commands pass, but the two manual-review risks above remain. No code fixes were made in this validation pass.

Latest final commit

  • Created commit 37c34a0416eae2b67fe5c5af17fead1e1a3a08a2 with subject fix(workflows): harden durable resume persistence.
  • This commit includes the lockfile sync for optional DBOS SDK, Atomic custom JSONL scanner fix, DBOS metadata hydration/status fix, awaited/serialized durable writes and flush barrier, replayed stage/task graph visibility, and no-arg /workflow resume live picker behavior.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

Current preflight after commit 37c34a0

Preflight-only pass from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume; no implementation files were edited. Wrote detailed findings to subagent-current-preflight-issue1498.md.

Evidence gathered:

  • Re-ran gh issue view 1498 --repo bastani-inc/atomic; issue still requires DBOS-backed durable state, top-level workflow-id cross-session resume, /workflow resume selector parity with /resume, durable ctx.ui/ctx.tool/stage state, and cancellation/failure/retry tests/docs.
  • Read the latest primary research file and current support artifacts (subagent-review-durable-findings.md, subagent-review-ui-findings.md, subagent-review-validation.md, subagent-latest-validation-issue1498.md, subagent-latest-risk-fixes-issue1498.md).
  • Confirmed current HEAD is 37c34a0416eae2b67fe5c5af17fead1e1a3a08a2 with no tracked implementation diff; only untracked progress/research/subagent report artifacts remain.
  • bun install --frozen-lockfile --minimum-release-age 0 passes with no lockfile changes.
  • origin/main contains unmerged commit d9b1d0ca3 feat(onboarding): first-run CTA and workflow-routing handoff (#1488), so the branch must preserve first-run onboarding changes/tests when rebased/merged.

Current unresolved findings confirmed:

  1. DBOS mutable metadata is stored via fixed checkpoint workflow id <workflowId>:checkpoint:__atomic_metadata, making status/checkpoint counts effectively write-once under DBOS idempotency.
  2. DBOS real handle passes duplicationPolicy: "return-existing" to DBOS.startWorkflow; latest review flags this as invalid for the target SDK path and it needs correction/verification against installed types.
  3. Stage lifecycle checkpoint writes are synchronous (durableOnStageEnd -> recordStageCheckpoint -> backend.recordCheckpoint) and are not awaited before the engine advances to later stages.
  4. DBOS backend init is fire-and-forget in createExtensionRuntime; early dispatch can use the file fallback before async DBOS initialization installs the DBOS backend.
  5. No-arg durable /workflow resume still prints formatResumableWorkflowList(...) plus a hint instead of opening a durable selector UI.
  6. ctx.tool keys cache by durableHash({ name, args }), so distinct same-name/same-args tool calls in one workflow collapse to one side effect/result.
  7. ctx.tool retry backoff sleeps with setTimeout and does not observe cancellation during backoff or before retry attempts.
  8. The branch is behind origin/main first-run onboarding work; likely validation should include packages/coding-agent/test/first-run-onboarding*.test.ts and workflow-agent-dir-isolation.test.ts after integrating main.

Recommended targeted validation after fixes:

  • bun install --frozen-lockfile --minimum-release-age 0
  • bun test test/unit/durable-dbos-backend.test.ts
  • bun test test/unit/durable-backend.test.ts
  • bun test test/unit/durable-stage-primitive.test.ts
  • bun test test/unit/durable-resume-runtime.test.ts
  • bun test test/unit/durable-resume-catalog.test.ts
  • bun test test/unit/durable-cross-session-resume.test.ts
  • bun test test/unit/durable-*.test.ts
  • bun test test/integration/overlay-entrypoints-commands.test.ts
  • bun test test/unit/slash-dispatch.test.ts -t "resume"
  • first-run onboarding tests from packages/coding-agent/test/first-run-onboarding*.test.ts plus workflow-agent-dir-isolation.test.ts after rebase/merge
  • bun run typecheck, bun run lint, bun run check:file-length, bun run test:unit

Current unresolved findings implementation pass

Implemented the latest unresolved #1498 fixes from the current research/preflight pass:

  • Merged origin/main into the branch to preserve first-run onboarding (d9b1d0ca3) before applying the current fixes.
  • Changed DBOS Atomic metadata from a fixed idempotent checkpoint id to versioned __atomic_metadata:<timestamp>:<uuid> records; hydration chooses the latest metadata record so mutable status, checkpoint counts, pending prompts, and resumability update over time.
  • Removed invalid DBOS duplicationPolicy: "return-existing" usage from the adapter's local SDK type and real startWorkflow calls; duplicate root workflow starts are tolerated by catching DBOS duplicate/conflict errors.
  • Made live stage finalization await async onStageEnd hooks, and changed durable stage checkpoint recording to await recordCheckpointAsync/flush before returning from completed stage calls where practical.
  • Added a DBOS initialization barrier in ExtensionRuntime: dispatch, runDirect, async direct run, and durable resume preparation await the DBOS init attempt before using the backend.
  • Added an interactive durable workflow picker for no-arg /workflow resume when only durable entries exist, while preserving headless list output and live paused-run picker behavior.
  • Changed ctx.tool identity to include stable per-run ordinal order in addition to name + args, so repeated same-name/same-args calls are distinct and replay in order.
  • Made ctx.tool retry backoff cancellation-aware via AbortSignal and explicit cancellation checks before attempts and after backoff.
  • Updated docs/changelog and added targeted tests for versioned DBOS metadata hydration, distinct/replayed repeated tools, cancellation during retry backoff, awaited stage checkpoints, and durable picker mounting.

Validation run in this pass (partial so far):

  • bun run typecheck — passed.
  • bun run check:file-length — passed after keeping runtime.ts under 500 lines.
  • bun test test/unit/durable-*.test.ts — 97 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 14 pass, 0 fail.

Final validation for current unresolved findings pass:

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no lockfile changes.
  • First-run onboarding preservation after merging origin/main: bun test packages/coding-agent/test/first-run-onboarding.test.ts packages/coding-agent/test/first-run-onboarding-new-session.test.ts packages/coding-agent/test/first-run-onboarding-round4.test.ts packages/coding-agent/test/first-run-onboarding-round7.test.ts packages/coding-agent/test/first-run-onboarding-round8.test.ts packages/coding-agent/test/workflow-agent-dir-isolation.test.ts — 39 pass, 0 fail.
  • Targeted resume tests: bun test test/unit/durable-dbos-backend.test.ts test/unit/durable-backend.test.ts test/unit/durable-stage-primitive.test.ts test/unit/durable-resume-runtime.test.ts test/unit/durable-resume-catalog.test.ts test/unit/durable-cross-session-resume.test.ts test/integration/overlay-entrypoints-commands.test.ts test/unit/slash-dispatch.test.ts -t "resume" — 37 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts — 97 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 14 pass, 0 fail.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; runtime.ts kept under 500 lines.
  • bun run test:unit — 2647 pass, 0 fail.

QA E2E video note remains unchanged: no browser UI applies. Full interactive TUI E2E remains impractical without a configured Atomic interactive session and model/runtime credentials; headless overlay integration tests exercise the /workflow resume command path.

Current independent validation after unresolved-finding fixes

Validation performed from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume after the current issue #1498 fixes and merge from origin/main. No code changes were made by this validation pass.

Diff review findings:

  • DBOS mutable metadata now uses versioned __atomic_metadata:<timestamp>:<uuid> checkpoint records and hydration selects the latest metadata entry, so mutable status/checkpoint counts are not stuck behind a fixed idempotent workflow id.
  • No invalid DBOS duplicationPolicy: "return-existing" parameters remain in the workflows DBOS adapter; duplicate start conflicts are handled by catching duplicate/conflict/already errors.
  • Stage checkpoint persistence is awaited before advancing through completed stage finalization: recordStageCheckpoint is async, onStageEnd awaits it, and stage finalization awaits opts.onStageEnd where practical.
  • DBOS initialization race is addressed with an awaitable dbosReady barrier in extension runtime dispatch/runDirect/prepareDurable paths.
  • No-arg /workflow resume now opens a durable custom picker when only durable entries exist, and the live picker is preserved when paused/live runs exist.
  • ctx.tool same-name/same-args calls are distinguished by per-call ordinal and replay in order after resume.
  • Retry backoff observes cancellation via AbortSignal and throwIfCancelled checks before/after retry sleeps.
  • First-run onboarding from origin/main is preserved: origin/main is an ancestor of HEAD and onboarding test suites pass.

Commands / results:

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no lockfile changes.
  • bun test test/unit/durable-*.test.ts — 98 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 14 pass, 0 fail.
  • bun test test/unit/durable-dbos-backend.test.ts test/unit/durable-backend.test.ts test/unit/durable-stage-primitive.test.ts test/unit/durable-resume-runtime.test.ts test/unit/durable-resume-catalog.test.ts test/unit/durable-cross-session-resume.test.ts test/integration/overlay-entrypoints-commands.test.ts test/unit/slash-dispatch.test.ts -t "resume" — 37 pass, 198 filtered, 0 fail.
  • bun test packages/coding-agent/test/first-run-onboarding.test.ts packages/coding-agent/test/first-run-onboarding-new-session.test.ts packages/coding-agent/test/first-run-onboarding-round4.test.ts packages/coding-agent/test/first-run-onboarding-round7.test.ts packages/coding-agent/test/first-run-onboarding-round8.test.ts packages/coding-agent/test/workflow-agent-dir-isolation.test.ts — 39 pass, 0 fail.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1824 tracked files checked, all <=500 lines.
  • bun run test:unit — 2647 pass, 0 fail.

QA / E2E note: tmux is available (tmux 3.6b), but a reliable full interactive Atomic TUI E2E run is not practical in this validation context because it requires a fully configured interactive Atomic session with workflow fixtures and model/runtime credentials. The narrower executable proof is the overlay command integration suite plus durable and onboarding tests. There is no browser UI scenario for Playwright, so no QA E2E video applies and none was produced.

Safe-to-commit verdict: safe to commit. No blockers found in this validation pass.

Current final commit

  • Created commit d4f32a41816a7c073e4ce8fec8a6e0a69027a3c5 with subject fix(workflows): finalize DBOS resume semantics.
  • Commit includes the current unresolved-finding fixes plus the merge from origin/main preserving first-run onboarding.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

Final preflight after commit d4f32a4

Preflight-only pass from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume; no implementation files were edited. Wrote detailed findings to subagent-final-preflight-issue1498.md.

Evidence gathered:

  • Re-ran gh issue view 1498 --repo bastani-inc/atomic; issue remains open and still requires DBOS-backed durable state, top-level workflow-id resume, /workflow resume selector parity, durable ctx.ui/ctx.tool/stage state, no duplicate side effects, and cancellation/failure/retry docs/tests.
  • Read the latest primary research report.
  • Confirmed HEAD is d4f32a41816a7c073e4ce8fec8a6e0a69027a3c5 with no tracked implementation diff; only untracked progress/research/subagent artifacts remain.
  • Confirmed origin/main is an ancestor of HEAD and bun install --frozen-lockfile --minimum-release-age 0 passes with no lockfile changes.

Current unresolved findings confirmed:

  1. Durable replay wraps direct ctx.stage and ctx.task, but ctx.chain, ctx.parallel, and child ctx.workflow are still wired to non-durable paths and can re-run completed work across sessions.
  2. Terminal durable status is set after a flush in run.ts and is not flushed before return; failed/killed final durable metadata in finally is likewise not followed by a flush.
  3. DbosDurableBackend.recordCheckpointAsync() updates the in-memory replay mirror before DBOS accepts the checkpoint, so a failed DBOS write can still create same-process replay state.
  4. No-arg /workflow resume checks all top-level local runs, including completed runs, before falling back to durable history; completed local runs can hide durable resumable workflows.

Earlier resolved findings appear covered by existing tests: lockfile, DBOS versioned metadata, invalid duplicate params removal, custom JSONL cache shape, file concurrency, root/non-recoverable filtering, UI/tool ordinal identity, retry cancellation, DBOS activation/hydration, and onboarding preservation.

Recommended validation after fixes is listed in subagent-final-preflight-issue1498.md, including frozen install, durable DBOS/backend/stage/resume/catalog/cross-session/UI suites, overlay command tests, resume slash-dispatch tests, onboarding tests, typecheck/lint/file-length, and full bun run test:unit.

Final workspace recheck note for preflight: after writing subagent-final-preflight-issue1498.md, git status --short showed tracked modifications in packages/workflows/src/durable/dbos-backend.ts and packages/workflows/src/engine/run.ts (2 files, 64 insertions, 18 deletions). This preflight pass did not edit those implementation files; it only wrote the report and appended these notes. Subsequent validation should account for the now-present implementation diff.

Final unresolved findings implementation pass

Implemented the latest issue #1498 review findings after commit d4f32a41816a7c073e4ce8fec8a6e0a69027a3c5:

  • Added packages/workflows/src/durable/child-primitive.ts and wired run.ts so durable replay covers child ctx.workflow calls. Completed child workflow results are checkpointed at the parent workflow boundary and replayed as completed graph/store nodes without re-running the child workflow.
  • Rewired ctx.chain and ctx.parallel to use the durable task primitive for each item, preserving parallel fail-fast scope while checkpointing/replaying each completed item.

@flora131

Copy link
Copy Markdown
Collaborator Author

Implementation notes (part 2/2)

  • Ensured completed root workflow terminal status is flushed after setWorkflowStatus("completed") before returning. Failed/killed terminal status finalization now attempts a flush in finally and logs if a deliberately failing backend refuses the failure metadata write.
  • Changed DBOS recordCheckpointAsync() ordering so the DBOS checkpoint write succeeds before updating the in-memory replay mirror and metadata, preventing failed DBOS writes from becoming replayable in the current process.
  • Changed no-arg /workflow resume to choose the live picker only when resumable live/paused/failed-local runs exist; completed local runs no longer hide durable workflow history.
  • Updated workflow docs and packages/workflows/CHANGELOG.md for chain/parallel/child workflow durable replay, terminal status flushes, DBOS mirror ordering, and durable selector behavior.

Validation for this pass:

  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; all touched authored files remain <=500 lines (run.ts is 499 lines).
  • bun test test/unit/durable-*.test.ts — 102 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 15 pass, 0 fail.
  • bun install --frozen-lockfile --minimum-release-age 0 — passed.
  • bun run test:unit — 2651 pass, 0 fail.

QA E2E video note remains unchanged: no browser UI applies. Full interactive TUI E2E remains impractical without a configured Atomic interactive session and model/runtime credentials; headless overlay integration tests cover the /workflow resume command path.

Final validation after newest durable replay fixes

Validation performed from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume after the newest issue #1498 fixes. No code changes were made by this validation pass.

Diff review findings:

  • Durable replay now covers ctx.chain and ctx.parallel because run.ts wires both through createChainPrimitive / createParallelPrimitive using the durable ctx.task primitive. Durable task checkpoints are replayed and record cached graph/store stages.
  • Child ctx.workflow replay is covered by createDurableChildWorkflowPrimitive, wired in run.ts; completed child workflow boundary results are checkpointed as stage records and replayed by workflow boundary replay key.
  • Terminal durable status is flushed before return for completed runs (setWorkflowStatus("completed") followed by await durableBackend.flush?.()) and failure/killed paths attempt a final durable flush in finally with warning-only failure handling.
  • DbosDurableBackend.recordCheckpointAsync() now persists the DBOS checkpoint record first, then updates the in-memory replay mirror, then writes metadata, so async side-effect checkpoint callers do not observe replay state before DBOS accepts the checkpoint.
  • No-arg /workflow resume filters live local runs down to active/paused/resumable failed runs before deciding whether to open the live picker or durable resume path; completed/non-resumable local runs no longer hide durable history. Overlay tests cover durable-only, completed-local-plus-durable, and paused-live picker cases.
  • Earlier regressions remain covered by durable DBOS/backend/catalog/UI/stage tests and overlay resume tests: frozen lockfile, versioned DBOS metadata, custom JSONL scanner shape, file merge/locking, UI identity, root filtering, invalid DBOS duplicate params removal, cancellation-aware retry backoff, and first-run onboarding preservation via the full unit suite.

Commands / results:

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no lockfile changes.
  • bun test test/unit/durable-*.test.ts — 102 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 15 pass, 0 fail.
  • bun test test/unit/durable-dbos-backend.test.ts test/unit/durable-backend.test.ts test/unit/durable-stage-primitive.test.ts test/unit/durable-resume-runtime.test.ts test/unit/durable-resume-catalog.test.ts test/unit/durable-cross-session-resume.test.ts test/unit/durable-ui-primitive.test.ts test/integration/overlay-entrypoints-commands.test.ts test/unit/slash-dispatch.test.ts -t "resume" — 39 pass, 208 filtered, 0 fail.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1824 tracked files checked, all <=500 lines.
  • bun run test:unit — 2651 pass, 0 fail.

QA / E2E note: tmux is available (tmux 3.6b), but a reliable full interactive Atomic TUI E2E run is not practical in this validation context because it requires a fully configured interactive Atomic session, workflow fixtures, and model/runtime credentials. The narrower executable proof for the user-visible command path is the headless overlay integration suite. There is no browser UI scenario for Playwright, so no QA E2E video applies and none was produced.

Safe-to-commit verdict: safe to commit. No blockers found in this validation pass.

Final durable composite replay commit

  • Created commit 299c4612b609d89c8adf72853a7d2238b4a716eb with subject fix(workflows): complete durable composite replay.
  • Commit includes durable replay for ctx.chain, ctx.parallel, and child ctx.workflow, terminal durable status flushes, DBOS replay mirror update-after-acceptance, and the /workflow resume completed-local/durable selector fix.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

Scan-only resume validation pass

Final independent validation after the scan-only resume/latest issue #1498 fixes. No code changes were made in this validation pass. Findings were written to subagent-scan-resume-validation-issue1498.md.

Diff review verified:

  • Prepared durable catalogs from session scans are retained through actual resume, allowing scan-only/session-cache entries to resume with a cold backend.
  • Schema-backed stage replay returns structured values rather than raw strings.
  • Parallel fail-fast skip now awaits async finalizers/durable checkpoint persistence paths.
  • sleepOrAbort removes abort listeners on normal timer completion and abort.
  • recordStageCheckpoint prefers explicit stage.replayKey over external map lookup.
  • Earlier DBOS/resume regression areas remain covered by durable and overlay tests.

Commands / results:

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no changes.
  • bun test test/unit/durable-resume-runtime.test.ts test/unit/durable-stage-primitive.test.ts test/unit/durable-backend.test.ts — 53 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts — 106 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 15 pass, 0 fail.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1825 tracked files checked, all <=500 lines.
  • bun run test:unit — 2655 pass, 0 fail.

QA / E2E note: tmux is available (tmux 3.6b), but a reliable full interactive Atomic TUI E2E run is not practical in this checkout because it requires a fully configured Atomic interactive session, workflow fixtures, and model/runtime credentials. The narrower executable proof for the user-visible command path is the headless overlay integration suite. There is no browser UI scenario for Playwright, so no QA E2E video applies and none was produced.

Safe-to-commit verdict: safe to commit. No blockers found.

Scan/session-cache resume preflight

Preflight-only pass from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume; no implementation files were edited. Wrote detailed findings to subagent-scan-resume-preflight-issue1498.md.

Evidence gathered:

  • Re-ran gh issue view 1498 --repo bastani-inc/atomic; issue remains open and still requires DBOS-backed durable state, cross-session top-level workflow-id resume, /workflow resume selector parity, no duplicate side effects, and tests/docs for cancellation/failure/retry.
  • Read the latest primary research file.
  • Confirmed HEAD is 299c4612b609d89c8adf72853a7d2238b4a716eb.
  • git status --short and git diff --stat showed no tracked implementation diff before this report; only untracked progress/research/subagent artifacts.

Current unresolved findings confirmed:

  1. Prepared durable catalog is discarded before actual resume: handleDurableResume() prepares/merges backend + scanned JSONL catalog, but runtime.resumeDurableWorkflow(target) does not receive it, so scan-only/session-cache entries can fail to resume.
  2. Schema-backed ctx.stage replay can return raw/stringified values: durable stage replay uses StageSnapshot.result and createCachedStageContext().prompt() without applying StageOptions.schema parsing/validation.
  3. Parallel fail-fast skip path does not await async stage finalization: skipForParallelFailFast() calls void finalizeStageSnapshot(), so durable stage checkpoint writes can race after fail-fast skip.
  4. sleepOrAbort() leaks abort listeners on normal timer completion because it adds an abort listener but only clears the timer on abort, not removing the listener on successful timeout.
  5. recordStageCheckpoint() should prefer explicit stage.replayKey; current precedence lets replayKeyForCompletedStage override the snapshot replay key.

Recommended validation after fixes:

  • bun install --frozen-lockfile --minimum-release-age 0
  • bun test test/unit/durable-resume-runtime.test.ts
  • bun test test/unit/durable-resume-catalog.test.ts
  • bun test test/unit/durable-stage-primitive.test.ts
  • bun test test/unit/durable-backend.test.ts
  • bun test test/unit/durable-dbos-backend.test.ts
  • bun test test/unit/durable-ui-primitive.test.ts
  • bun test test/unit/durable-cross-session-resume.test.ts
  • bun test test/unit/durable-*.test.ts
  • bun test test/integration/overlay-entrypoints-commands.test.ts
  • bun test test/unit/slash-dispatch.test.ts -t "resume"
  • bun run typecheck, bun run lint, bun run check:file-length, bun run test:unit

Scan-only resume and structured stage replay fixes

Implemented the latest issue #1498 unresolved findings:

  • Carried the prepared durable catalog from prepareDurableResumable() through resumeDurableWorkflow() in the extension runtime, so workflows discovered only from session JSONL can resume even when the durable backend has no in-memory handle yet.
  • Preserved parsed structured values for schema-backed stage replay by checkpointing non-string ctx.stage(..., { schema }).prompt(...) results directly and replaying them as structured values.
  • Changed the parallel fail-fast skip path to await async stage finalizers before returning, so durable checkpoint persistence and graph/store visibility complete before fail-fast unwinds.
  • Exported and fixed sleepOrAbort() so abort listeners are removed on normal timer completion.
  • Changed recordStageCheckpoint() to prefer explicit stage.replayKey before falling back to the completed-stage map or generated replay key.
  • Updated packages/coding-agent/docs/workflows.md and packages/workflows/CHANGELOG.md for the user-visible durable replay behavior.

Validation:

  • bun test test/unit/durable-resume-runtime.test.ts test/unit/durable-stage-primitive.test.ts test/unit/durable-backend.test.ts — 53 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts — 106 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts — 15 pass, 0 fail.
  • bun install --frozen-lockfile --minimum-release-age 0 — passed with no changes.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed.
  • bun run test:unit — 2655 pass, 0 fail.

QA E2E video note: no browser UI applies. Full interactive Atomic TUI E2E remains impractical without a configured Atomic interactive session, workflow fixtures, and model/runtime credentials; the headless overlay integration suite covers the /workflow resume command path.

Final independent validation after child workflow/ctx.exit/resume/backend propagation fixes

Validation performed from /Users/tonystark/Documents/projects/atomic-issue-1498-workflow-dbos-resume after the latest child workflow/ctx.exit/resume/backend propagation fixes. No code changes were made in this validation pass.

Diff review — all five findings verified as resolved

  1. Repeated ctx.workflow(child) replay: Separate durableChildReplayCounts counter ensures cache hits don't desync ordinal sequencing. Test passes.
  2. Direct completed stage checkpoint key: Precedence changed to prefer durable map key (replayKeyForCompletedStage) over executor snapshot stage.replayKey, ensuring checkpoint-under-same-key-as-lookup. Test passes.
  3. ctx.exit terminal durable metadata: finally block now handles exit terminal states (cancelled/blocked/skipped) with correct durable status mapping, flush, and cache persistence. Normal completion via return value is NOT double-flushed (handled exclusively in try block). Tests pass.
  4. No-arg /workflow resume durable history with live runs: Now surfaces durable entries as a hint alongside the live picker. Tests pass.
  5. Child durableBackend propagation: Added to EngineChildRunOptions Pick and passed in child run options. Test passes.

Earlier regression areas confirmed covered by existing tests.

Commands / results

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no changes.
  • bun test test/unit/durable-*.test.ts110 pass, 0 fail.
  • bun test test/integration/overlay-entrypoints-commands.test.ts16 pass, 0 fail.
  • Targeted resume suite (9 files, -t "resume") — 41 pass, 215 filtered, 0 fail.
  • First-run onboarding + workflow-agent-dir-isolation tests — 39 pass, 0 fail.
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1825 tracked files, all <=500 lines.
  • bun run test:unit2659 pass, 0 fail.

TUI / QA E2E

tmux is available (tmux 3.6b), but a reliable full interactive Atomic TUI E2E run is not practical in this checkout because it requires a fully configured interactive Atomic session with workflow fixtures and model/runtime credentials. The narrower executable proof for the user-visible command path is the headless overlay integration suite. There is no browser UI scenario for Playwright, so no QA E2E video applies and none was produced.

Safe-to-commit verdict

Safe to commit. No blockers found. Findings written to subagent-child-exit-validation-issue1498.md.

Scan-only durable resume commit

  • Created commit 262cacabd42612fee549d0f75626219a3bb8f174 with subject fix(workflows): preserve scan-only durable resume state.
  • Commit includes prepared durable catalog preservation through actual resume, structured schema-backed stage replay, awaited parallel fail-fast finalizers, sleepOrAbort listener cleanup, and explicit stage.replayKey preference.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

Child-workflow and ctx.exit preflight (issue #1498)

Preflight-only pass after commit 262cacabd42612fee549d0f75626219a3bb8f174. No implementation files edited. Wrote detailed findings to subagent-child-exit-preflight-issue1498.md.

Evidence gathered:

  • Re-ran gh issue view 1498 --repo bastani-inc/atomic; issue remains OPEN and still requires cross-session resume, selector parity, durable ctx state, and cancellation/failure/retry tests/docs.
  • Read latest primary research file. Confirmed five current unresolved findings.
  • Confirmed HEAD is 262cacabd42612fee549d0f75626219a3bb8f174 with no tracked implementation diff; only untracked artifacts remain.
  • bun install --frozen-lockfile --minimum-release-age 0: passed, no lockfile changes.
  • bun run typecheck / bun run lint: clean.

Current unresolved findings confirmed (all five, with exact code locations):

  1. Child workflow replay-key counter double-increment/desync — CONFIRMED.

    • engine/run.ts:270-274 defines nextWorkflowBoundaryReplayKey (shared incrementing counter).
    • engine/run.ts:341 wires it as the durable child primitive's nextReplayKey.
    • durable/child-primitive.ts:33 increments once (durable key, e.g. workflow:foo:1).
    • engine/primitives/workflow.ts:55 increments AGAIN (executor key, e.g. workflow:foo:2).
    • Durable read/write under :1; executor snapshot under :2. Next call reads :3 (miss) and re-executes.
  2. Direct completed stage checkpoints under executor snapshot replay key — CONFIRMED.

    • Durable read uses ordinal stage:foo:1 (stage-primitive.ts:37 via nextReplayKey).
    • Durable write (stage-primitive.ts:23) precedence is stage.replayKey ?? deps.replayKeyForCompletedStage ?? nextReplayKey, and stage.replayKey is the executor snapshot key stage:foo (set at executor-stage-factory.ts:57), which wins over the registered ordinal.
    • Key mismatch causes completed stages to re-execute on resume.
  3. ctx.exit terminal states not persisted to durable metadata — CONFIRMED.

    • executor-run-finalizers.ts:63-95 (finalizeWorkflowExit) records signal.status (cancelled/blocked/skipped) in store + JSONL but never calls durableBackend.setWorkflowStatus.
    • executor-run-finalizers.ts:116-125 (finalizeParentWorkflowExitCancellation) same for cancelled.
    • run.ts finally block only covers failed/killed, not exit terminal states (which return earlier at run.ts:393-407).
  4. No-arg /workflow resume hides durable when live local runs exist — CONFIRMED.

    • workflow-run-control-command.ts:291-294 filter includes run.endedAt === undefined (in-progress non-paused runs).
    • Any in-progress run preempts the durable selector. Prior fix addressed completed runs but not in-progress ones.
  5. durableBackend not propagated into child workflow run options — CONFIRMED.

    • engine/run.ts:229-248 childRunOptions omits opts.durableBackend.
    • workflow.ts:91-99 spreads runtime.childRunOptions (no durableBackend).
    • Child runs fall back to getDurableBackend() at run.ts:191, causing split-brain with custom backends.

None of these five findings are covered by existing regression tests (grep confirmed).

Recommended validation after fixes is listed in subagent-child-exit-preflight-issue1498.md.

Child workflow / ctx.exit commit

  • Created commit 367c1ed3207f3eebe9e51e268be9ddc0dfeedc77 with subject fix(workflows): persist child and exit resume state.
  • Commit includes child workflow replay-key stability, direct completed stage checkpoint durable-key alignment, ctx.exit terminal durable metadata, durable history surfacing alongside live runs, and child durableBackend propagation.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

Root-child & replay-robustness fixes (latest pass)

Implemented the latest issue #1498 unresolved findings:

  1. Child side effects under root: Added packages/workflows/src/durable/scoped-backend.ts (ScopedDurableBackend) that remaps every child ctx.tool/ctx.ui/ctx.stage checkpoint identity to the parent (root) durable workflow id, prefixed by a stable child-boundary scope key. The durable child primitive publishes the scope for the next invocation; the child runner consumes it and passes it into run() as opts.durableScope. This prevents split-brain: an interrupted child no longer writes checkpoints under a fresh per-run UUID that resume never recovers — re-dispatched children replay completed side effects from the root store. Proven at both unit level (ScopedDurableBackend) and run()-level (interrupted child re-dispatch with zero re-execution).
  2. Stale cache-only resume refusal: resumeDurableWorkflow now returns { ok: false, reason: "stale" } when a workflow is present in the resume catalog (e.g. from a session-JSONL scan) but has no registered handle/checkpoint state in the durable backend, instead of silently re-running from scratch. Added stale to the result reason union and a positive resume test.
  3. Collision-resistant digest: Replaced the 32-bit DJB2 durableHash with a SHA-256 digest over canonical JSON. The old hash demonstrably collided across distinct tool/stage identities, which could cause completed side effects to be merged or skipped incorrectly on resume.
  4. ctx.tool post-resolve cancellation: ctx.tool now re-checks cancellation after the tool function resolves but BEFORE the side-effect result is durably checkpointed/returned, so a side effect completing concurrently with cancellation is not persisted as a replayable checkpoint.
  5. File stale-lock recovery: withFileLock now detects a lock directory whose mtime exceeds a 30s stale threshold and reclaims it, instead of wedging until the 5s acquire timeout after a crash.

Refactor: extracted durable terminal-status finalization from run() into packages/workflows/src/engine/run-durable-finalize.ts to keep run.ts under the 500-line gate (497 lines).

Validation

  • bun run typecheck — clean.
  • bun run lint — clean.
  • bun run check:file-length — passed (1825 files; run.ts 497).
  • bun test test/unit/durable-*.test.ts test/integration/overlay-entrypoints-commands.test.ts — 141 pass, 0 fail.
  • bun test test/unit/durable-root-child-fixes.test.ts — 14 pass, 0 fail (new file).
  • bun test test/unit/slash-dispatch.test.ts -t "resume" — 10 pass.
  • bun test packages/coding-agent/test/first-run-onboarding.test.ts packages/coding-agent/test/workflow-agent-dir-isolation.test.ts — 10 pass.
  • bun run test:unit — 2674 pass, 0 fail.

QA E2E video: no browser UI scenario applies (backend/library + TUI command-path). Full interactive TUI E2E remains impractical without a configured Atomic session/model runtime; the executable proof is the durable + overlay test suites including the run()-level interrupted-child integration test.

Root-child checkpoint & durable robustness preflight (issue #1498)

Preflight-only pass after commit 367c1ed3207f3eebe9e51e268be9ddc0dfeedc77. No implementation files edited. Wrote detailed findings to subagent-root-child-preflight-issue1498.md.

Baseline at HEAD (all clean):

  • bun install --frozen-lockfile --minimum-release-age 0 — passed (489 installs / 609 packages, no changes).
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1825 tracked files, all <=500 lines.
  • bun test test/unit/durable-*.test.ts — 110 pass, 0 fail.

Current unresolved findings confirmed (all five, with exact code locations):

  1. Child workflow internal side effects not checkpointed under root workflow id before child completion — CONFIRMED. durable/child-primitive.ts writes only a single boundary checkpoint under input.workflowId (parent/root) after the entire child completes; child-internal ctx.tool/ctx.ui/ctx.stage calls are checkpointed against the child workflow id (via propagated durableBackend in childRunOptions). A mid-child crash leaves child-internal checkpoints under the child id, invisible to root-id resume, so the child re-executes from scratch.
  2. Resume lists JSONL-only entries without backend checkpoint state as resumable — CONFIRMED. resume-runtime.ts:194-200 merges backend + scanResumableWorkflows with no backend presence check; JSONL-only entries with no backend checkpoints are surfaced as resumable and resume re-executes from scratch.
  3. 32-bit durableHash has demonstrated collisions — CONFIRMED. backend.ts:139-146 uses a DJB2-style 32-bit fold (((hash << 5) - hash) | 0); collisions produce wrong cached tool/ui results on resume.
  4. ctx.tool does not check cancellation after fn() resolves and before checkpoint/return — CONFIRMED. tool-primitive.ts:84-102 records the checkpoint immediately after executeWithRetries returns, with no throwIfCancelled() between result and checkpoint — a tool resolving concurrent with cancellation is durably recorded.
  5. File-backed durability has no stale lock recovery — CONFIRMED. file-backend.ts withFileLock uses mkdirSync(lockDir) with a 5s deadline but no mtime-based stale recovery; a crash between mkdir and finally rmSync leaves the lock dir forever, permanently blocking durable writes with Timed out acquiring durable workflow state lock.

Recommended validation after fixes is listed in subagent-root-child-preflight-issue1498.md.

Earlier resolved findings remain covered by existing regression tests.

Root-child / digest / stale validation pass (issue #1498)

Final independent validation after the latest root-child/digest/stale fixes (uncommitted diff on top of HEAD 367c1ed32). No code changes were made by this validation pass. Detailed findings written to subagent-root-child-validation-issue1498.md.

Diff review — all five findings verified as resolved

  1. Child side effects under root: ScopedDurableBackend (scoped-backend.ts) remaps child ctx.tool/ctx.ui/ctx.stage checkpoints to the root workflow id via a stable boundary scope prefix. Wired through child-primitive.tsworkflow.tsexecutor-types.tsrun.ts. run()-level tests prove the checkpoint lands under the root id and an interrupted child re-dispatch replays the child tool with zero re-execution.
  2. Stale cache-only resume refusal: resume-runtime.ts returns { ok: false, reason: "stale" } when backend.getWorkflow() is undefined (session-cache-only). Covered by durable-resume-runtime tests.
  3. Collision-resistant digest: durableHash now uses SHA-256 over canonical JSON (h + 32 hex chars). Tests cover determinism, collision distinction, and key-order canonicalization.
  4. Post-resolve cancellation check: ctx.tool calls throwIfCancelled() after executeWithRetries resolves and before checkpoint recording. Tests confirm no checkpoint is recorded on post-resolve cancellation.
  5. File backend stale lock recovery: withFileLock reclaims backdated locks (>30s) via isStaleLock/reclaimStaleLock; fresh locks are not reclaimed. Tests cover both cases.

Earlier regression areas remain covered by the full durable + overlay + slash-dispatch + onboarding suites.

Commands / results

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no changes.
  • bun test test/unit/durable-root-child-fixes.test.ts14 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts test/integration/overlay-entrypoints-commands.test.ts141 pass, 0 fail (10 files).
  • bun test test/unit/slash-dispatch.test.ts -t "resume"10 pass, 125 filtered, 0 fail.
  • Onboarding + workflow-agent-dir-isolation tests — 39 pass, 0 fail (6 files).
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1825 tracked files, all ≤500 lines.
  • bun run test:unit2674 pass, 0 fail (289 files, ~9s).

TUI / QA E2E

tmux 3.6b is available but dist/cli.js is not built in this worktree; a full interactive Atomic TUI E2E run requires model/runtime credentials and workflow fixtures not available in this context. The narrower executable proof is the headless overlay integration suite (16/16). No browser UI scenario applies. No QA E2E video was produced.

Safe-to-commit verdict

Safe to commit. No blockers found.

Root-child durable checkpoint commit

  • Created commit 535e288bee6f28e7a7deacb88e49804821c69e46 with subject fix(workflows): scope child durable checkpoints to roots.
  • Commit includes root-scoped child internal checkpoints, stale cache-only resume refusal, SHA-256 replay digest, post-resolve ctx.tool cancellation checks, stale file-lock recovery, and supporting docs/tests.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

Terminal-cache / overlay / latest preflight (issue #1498)

Preflight-only pass after commit 535e288bee6f28e7a7deacb88e49804821c69e46. No implementation files edited. Wrote detailed findings to subagent-terminal-overlay-preflight-issue1498.md.

Baseline at HEAD (all clean):

  • bun install --frozen-lockfile --minimum-release-age 0 — passed (489 installs / 609 packages, no changes).
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun test test/unit/durable-*.test.ts test/integration/overlay-entrypoints-commands.test.ts — 141 pass, 0 fail.

Current unresolved findings confirmed (all five, with exact code locations):

  1. Stale session-cache entries can resurrect terminal workflows — CONFIRMED. scanResumableWorkflows() trusts the JSONL cache status field; a workflow terminal in the backend (completed/cancelled) but stale-running in JSONL is merged back by prepareRuntimeDurableResumable because it is absent from the backend resumable list. Fix: suppress scanned entries whose backend getWorkflow(id)?.status is terminal.

  2. Successful durable /workflow resume <id> prints but does not open/connect the overlay — CONFIRMED. handleDurableResume calls print(result.message) on success but never deps.overlay.open(runId, ...), unlike the live resume path. Fix: call deps.overlay.open(result.runId, overlaySurfaceFromContext(ctx)) (gated on policy.allowInputPicker) on success.

  3. No-arg /workflow resume does not make durable workflows selectable when live runs exist — CONFIRMED. When live runs exist, the live picker (openSessionPicker) only iterates store.runs(); durable-only entries are printed as a hint but not selectable. Existing test only asserts the durable name appears in printed messages. Fix: merge durable entries into the picker or present a combined selector.

  4. Scoped checkpoint listing can include sibling scopes due to double-prefix filtering — CONFIRMED. listCheckpoints() in scoped-backend.ts filters with scopedCheckpointId(cp, scope).startsWith(prefix) where scopedCheckpointId re-prefixes an already-scoped id, so all root checkpoints leak into every child scope. Additionally numeric-ordinal scope prefixes (workflow:foo:1 vs :10) collide under startsWith. Fix: exact-segment scope membership match, no double-prefix.

  5. Cosmetic merged import in run.ts — CONFIRMED. Line 42 concatenates import { isWorkflowDefinition, ... };import { getDurableBackend } on one line (missing newline). Valid TS but cosmetic diff noise. Fix: insert newline.

Recommended validation after fixes is listed in subagent-terminal-overlay-preflight-issue1498.md.

Earlier resolved findings remain covered by existing regression tests.

Terminal-cache / overlay / scoped-listing validation pass (issue #1498)

Final independent validation after the terminal-cache/overlay/scoped-listing fixes (uncommitted diff on top of HEAD 535e288be). No code changes were made by this validation pass. Detailed findings written to subagent-terminal-overlay-validation-issue1498.md.

Diff review — all five findings verified as resolved

  1. Stale session-cache terminal resurrection — RESOLVED. New isBackendTerminal(backend, workflowId) returns true only when the backend has a registered handle whose status is completed, cancelled, or failed/blocked with resumable === false. Both prepareRuntimeDurableResumable and runtime.listDurableResumable filter scanned JSONL entries with this check so stale cache entries cannot resurrect terminal workflows.
  2. Successful durable resume opens overlay — RESOLVED. handleDurableResume now calls deps.overlay.open(result.runId, overlaySurfaceFromContext(ctx)) on result.ok (both targeted and picker paths), gated by policy.allowInputPicker.
  3. No-arg /workflow resume combined selector — RESOLVED. New openCombinedResumePicker shows live + durable items together; picking durable delegates to durable resume + overlay open; picking live resumes directly. Falls through to normal live picker when no durable entries.
  4. ScopedDurableBackend sibling exclusion — RESOLVED. listCheckpoints filters by storedScopeId(cp) (raw stored id) rather than re-prefixing, so sibling scopes (workflow:child:1 vs :2) are correctly excluded. getWorkflow return type corrected from never to undefined.
  5. run.ts import cosmetic — RESOLVED. Merged import line split into two separate statements.

Earlier regression areas remain covered by the full durable + overlay + slash-dispatch + onboarding suites.

Commands / results

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no changes.
  • bun test test/unit/durable-resume-runtime.test.ts test/unit/durable-root-child-fixes.test.ts35 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts test/integration/overlay-entrypoints-commands.test.ts152 pass, 0 fail (10 files).
  • bun test test/unit/slash-dispatch.test.ts -t "resume"10 pass, 125 filtered, 0 fail.
  • Onboarding + workflow-agent-dir-isolation tests — 39 pass, 0 fail (6 files).
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1828 tracked files, all <=500 lines.
  • bun run test:unit2682 pass, 0 fail (289 files, ~9s).

TUI / QA E2E

tmux 3.6b is available but dist/cli.js is not built in this worktree; a full interactive Atomic TUI E2E run requires model/runtime credentials and workflow fixtures not available in this validation context. The narrower executable proof for the user-visible command path is the headless overlay integration suite (19 tests including combined picker, durable resume overlay, and live-only fallback). No browser UI scenario applies for Playwright. No QA E2E video was produced.

Safe-to-commit verdict

Safe to commit. No blockers found.

Terminal-cache / durable overlay commit

  • Created commit 7a4c2876f3ceb0c6488d4169e1d99a469d5d3891 with subject fix(workflows): connect durable resume selections.
  • Commit includes stale terminal cache suppression, overlay connection on durable resume success, combined live+durable /workflow resume selector behavior, scoped checkpoint sibling filtering, and the run.ts import cleanup.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

Stage/frontier/selector preflight (issue #1498)

Preflight-only pass after commit 7a4c2876f3ceb0c6488d4169e1d99a469d5d3891. No implementation files edited. Detailed findings written to subagent-stage-frontier-preflight-issue1498.md.

Baseline at HEAD (all clean):

  • bun install --frozen-lockfile --minimum-release-age 0 — passed (489 installs / 609 packages, no changes).
  • bun run typecheck / lint — passed.
  • bun run check:file-length — passed; 1828 tracked files, all <=500 lines.
  • bun test test/unit/durable-*.test.ts test/integration/overlay-entrypoints-commands.test.ts — 152 pass, 0 fail.

Current unresolved findings confirmed (all five, with exact code locations):

  1. Empty string stage outputs not preserved — CONFIRMED. stage-primitive.ts:179-182 stageOutput() uses stage.result.length > 0, so a completed stage with result === "" is checkpointed as { status, stageId } and replays as a JSON object, not "". Fix: guard on stage.result !== undefined.
  2. Limiter leak on durable finalize throw — CONFIRMED. executor-stage-call.ts:184-192 final finally calls await runtime.finalizeStageSnapshot() (which awaits the durable onStageEnd checkpoint write) BEFORE input.limiter.release(). A throw from the durable write permanently leaks the concurrency slot. Fix: release limiter before the awaited durable finalization, or wrap finalize in an inner try/finally.
  3. Sync durable listing in combined picker — CONFIRMED. workflow-run-control-command.ts:366 uses runtime.listDurableResumable() (sync) in the combined-picker branch; DBOS-backed fresh processes need await runtime.prepareDurableResumable() (which hydrates) first. The targeted handleDurableResume path already awaits it.
  4. Replay loses graph parent/frontier — CONFIRMED. stage-primitive.ts:171-188 recordCachedStageIntoStore hardcodes parentIds: [] and never calls scheduler parent/frontier APIs. Live stages use scheduler.tracker.currentParents()/replaceParents (executor-stage-call.ts:87-92); replayed stages do not, so the replayed node is detached from the DAG and the frontier does not advance.
  5. Dismissed combined picker opens second picker — CONFIRMED. workflow-run-control-command.ts:377-387: when openCombinedResumePicker returns undefined (dismissed), control falls through the if (durableOnly.length > 0) block and unconditionally opens openSessionPicker, presenting a second live-only picker.

Earlier resolved findings remain covered by the durable + overlay + slash-dispatch + onboarding suites.

Recommended validation after fixes is listed in subagent-stage-frontier-preflight-issue1498.md, including frozen install, durable DBOS/backend/stage/resume/catalog/cross-session/UI suites, overlay command tests, resume slash-dispatch tests, onboarding tests, typecheck/lint/file-length, and full bun run test:unit.

Stage/frontier/selector validation pass (issue #1498)

Final independent validation after the stage/frontier/selector fixes (uncommitted diff on top of HEAD 7a4c2876f3ceb0c6488d4169e1d99a469d5d3891). No code changes were made in this validation pass. Detailed findings written to subagent-stage-frontier-validation-issue1498.md.

Diff review — all five findings verified as resolved

  1. Empty string stage outputs preserved — RESOLVED. stageOutput() guards on stage.result !== undefined (not length > 0); wrapSchemaStageForDurability also checkpoints empty string schema results. Tests: 4 empty/undefined/distinction cases pass.
  2. Limiter release on finalization failure — RESOLVED. executor-stage-call.ts finally block wraps finalizeStageSnapshot() and handle release in independent try/catch blocks so limiter release always executes.
  3. Async hydrated mixed resume listing — RESOLVED. Combined picker now calls await runtime.prepareDurableResumable(undefined) instead of sync listDurableResumable(). Test: mixed live+durable uses prepareDurableResumable (async hydration).
  4. Replayed stages preserve graph lineage — RESOLVED. New recordCachedStageWithTracker() registers stages in GraphFrontierTracker; run.ts uses it. Test: second replayed stage has first as parent.
  5. Dismissed combined picker no second picker — RESOLVED. Dismiss path now return true; after printing summary. Test: exactly 1 custom call after dismissal.

Earlier regression areas remain covered: durable-resume-runtime/root-child/dbos-backend/stage-primitive (80 pass), overlay (19 tests), slash-dispatch resume (10 pass), onboarding (39 pass).

Commands / results

  • bun install --frozen-lockfile --minimum-release-age 0 — passed, no changes.
  • bun test test/unit/durable-stage-frontier-fixes.test.ts6 pass, 0 fail.
  • bun test test/unit/durable-*.test.ts test/integration/overlay-entrypoints-commands.test.ts160 pass, 0 fail (11 files).
  • bun test test/unit/slash-dispatch.test.ts -t "resume"10 pass, 125 filtered, 0 fail.
  • Onboarding + workflow-agent-dir-isolation tests — 39 pass, 0 fail (6 files).
  • bun run typecheck — passed.
  • bun run lint — passed.
  • bun run check:file-length — passed; 1828 tracked files, all ≤500 lines.
  • bun run test:unit2688 pass, 0 fail (290 files, ~9s).

TUI / QA E2E

tmux 3.6b is available but dist/cli.js is not built in this worktree; a full interactive Atomic TUI E2E run requires model/runtime credentials and workflow fixtures not available in this context. The narrower executable proof is the headless overlay integration suite (19 tests including combined picker, durable resume overlay, async hydration, dismissal no-second-picker, and live-only fallback). No browser UI scenario applies. No QA E2E video was produced.

Safe-to-commit verdict

Safe to commit. No blockers found.

Stage/frontier durable replay commit

  • Created commit 50194e9d52c6fdc4c096564c757b374939142b8a with subject fix(workflows): preserve replayed stage graph state.
  • Commit includes empty string stage output preservation, limiter release on durable finalization failure, async hydrated mixed live+durable resume listing, replayed stage parent/frontier state, and no second picker after combined picker dismissal.
  • Pre-commit hooks passed, including bun run lint, bun run check:file-length, and bun run test:unit.
  • Final tracked implementation changes are clean; only untracked progress.md, research artifacts, and subagent report files remain.
  • No pull request was created or pushed per orchestration instructions.

@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown

PR Review: feat(workflows): add durable cross-session resume

Reviewed all 14 files under packages/workflows/src/durable/, the engine/run.ts wiring, primitives, and the test suite. This is a large, well-structured feature: the backend seam is clean, the in-memory/file/DBOS layering is sensible, replay identities moved to SHA-256, and test coverage is genuinely good (~130 tests across 10 durable suites covering backends, DBOS hydration, resume catalog/runtime, child/root scoping, and stage-frontier replay). Docs and changelog are updated. Nice work.

Findings grouped by severity — most are scaling/edge-cases rather than correctness blockers.

Potential bugs / correctness

  1. Schema-backed stage returning a non-empty string is never checkpointed — durable/stage-primitive.ts:107. The guard if (typeof result !== 'string' || result.length === 0) records everything except a non-empty string. For options.schema where Static<TSchemaDef> resolves to a plain string (e.g. Type.String()), WorkflowStageResult is string (authoring-contract-stage.ts:58-60), so a non-empty schema result falls through and no durable checkpoint is written — the stage re-runs on resume. The non-schema path (recordStageCheckpoint) handles string stages, but this wrapper is the only path for schema stages. Either always record (the replay-cache lookup already guards re-recording) or invert the condition to also cover non-empty strings.

  2. ctx.tool re-executes its side effect on resume after a cancellation race — durable/tool-primitive.ts:88-95. The post-fn() throwIfCancelled() intentionally skips checkpointing when cancellation lands during execution; the side effect already ran once and re-runs on resume (no checkpoint to replay), so ctx.tool is at-least-once, not exactly-once, under cancellation. Documented as deliberate and reasonable, but worth calling out in user docs so authors keep ctx.tool bodies idempotent.

Performance / scaling

  1. Default file backend serializes ALL workflows into one file and rewrites it on every checkpoint — durable/factory.ts:78-86, durable/file-backend.ts:45-64. createDefaultFileBackend() points at a single state.json; every recordCheckpoint/registerWorkflow/setWorkflowStatus does lock + read-entire-state + merge + write-entire-state. For a workflow with N checkpoints that's O(N^2) IO, and the file grows with all workflows the user has ever run. A per-workflow file (createWorkflowFileBackend/durableStateFileFor already exist but are unused outside index.ts) would bound this — worth considering before this becomes the default fallback.

  2. withFileLock blocks the event loop — durable/file-backend.ts:152. Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25) is a synchronous sleep that stalls the whole event loop up to 25ms per spin under contention. File checkpoint writes are fully synchronous (no flush), so each ctx.* checkpoint can block the loop. An async lock (or async recordCheckpointAsync for the file backend) would avoid starving in-flight stages.

  3. DBOS metadata records grow unboundedly — durable/dbos-backend.ts:336-340, 369-371. writeMetadata runs on every checkpoint and status change, each creating a new DBOS workflow keyed by metadataStepName(ts) with a crypto.randomUUID() suffix (step outputs are write-once). A workflow with K checkpoints accumulates ~K metadata records that are never reclaimed, and applyMetadata lists/decodes/sorts all of them every resume. The write-once constraint is understood, but the unbounded growth + O(K) hydration cost deserves a TODO or compaction strategy.

  4. DBOS resumable hydration is fully sequential — durable/dbos-backend.ts:301-324, 143-157. hydrateResumableWorkflows loops every workflow and calls listStepRecords, which itself sequentially awaits retrieveWorkflow(wid).getResult() per step — O(workflows x steps) serial round-trips before the resume selector renders. Parallelizing getResult would cut discovery latency.

Robustness / minor

  1. Over-broad duplicate-workflow detection — durable/dbos-backend.ts:164-167. /duplicate|conflict|already/i against err.message also swallows unrelated failures like 'connection already closed'. Prefer a DBOS error code/class if the SDK exposes one.

  2. applyMetadata overwrites createdAt with the metadata timestamp — durable/dbos-backend.ts:350-357. createdAt: entry.ts uses the latest metadata write time, so displayed creation time drifts forward after each hydration. Affects resume-selector ordering/labels.

  3. Misleading comment in createDefaultFileBackenddurable/factory.ts:80-85. Comment claims per-workflow files but the implementation passes a single shared state.json. Ties into (3).

  4. Replayed multi-prompt stages return the same cached output for every prompt()durable/stage-primitive.ts:124-151. createCachedStageContext().prompt() ignores its args and returns the single cached value, so a stage that called prompt() more than once replays identical output each call. Likely fine today, but a latent surprise.

Security / privacy

  1. Durable checkpoints persist tool outputs and UI responses verbatim to ~/.atomic/workflow-durable (plaintext JSON) or Postgres — these can contain secrets/PII returned by ctx.tool or typed into ctx.ui, and the state file is created under HOME without a restrictive mode. Recommend documenting that the durable opt-in implies on-disk persistence of side-effect payloads, and consider mode: 0o600 on the state file.

Design note (not a blocker)

The replay model relies on deterministic call ordering — ctx.tool/ctx.ui ordinals (tool-primitive.ts:79, ui-primitive.ts:25) and stage replay-key counters reset to 0 each run and re-derive identities positionally. Non-deterministic branching before a ctx.* call (e.g. random/time-based) will desync replay keys and re-execute completed work. Standard durable-execution constraint, but it should be prominent in the authoring docs.

Overall: solid, cohesive implementation with strong tests and good adherence to repo conventions (file-length gate respected, raw-TS no-build-step preserved, optional DBOS dep correctly under optionalDependencies). The schema-string checkpoint gap (1) is the one I'd want addressed (or confirmed unreachable) before merge; the rest are scaling/hardening follow-ups.

@mintlify

mintlify Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
bastani 🟢 Ready View Preview Jun 25, 2026, 2:03 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown

PR Review — Durable cross-session workflow resume (#1509)

Thorough, well-documented change. The backend seam is cleanly abstracted, checkpoints are idempotent, cancellation is re-checked at the ctx.tool boundary, the scoped child-backend sibling-prefix fix is correct, and the test/docs/changelog coverage is excellent. SHA-256 replay hashing and the versioned DBOS envelope are the right calls. A few things worth addressing before merge.

Performance / scalability

  1. File backend rewrites the entire shared state file on every checkpoint (file-backend.ts). getDurableBackend()createDefaultFileBackend() binds to a single shared ${dir}/state.json holding all workflows. Every ctx.tool/ctx.stage/ctx.ui checkpoint calls persist(), which locks → reads → merges → rewrites that whole file. That's O(checkpoints × total_state) and serializes all concurrent in-process workflows on one lock. createWorkflowFileBackend(workflowId) (per-workflow files) already exists and is exported but is never used — and the createDefaultFileBackend doc comment claims per-workflow handling that doesn't actually happen. Either route the factory through the per-workflow backend or fix the comment to match behavior.

  2. withFileLock blocks the event loop (file-backend.ts:152). It waits via Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25), which synchronously parks the thread. Because persist() runs synchronously inside recordCheckpoint, a parallel() workflow with several in-process runs contending on the lock stalls the whole loop (up to the 5s deadline). The call sites are already async, so an async backoff (await new Promise(r => setTimeout(r, 25))) would avoid freezing unrelated work.

  3. DBOS metadata grows unbounded (dbos-backend.ts). writeMetadata runs after every checkpoint/status write and each call creates a new step via metadataStepName(ts) with a crypto.randomUUID() suffix. You accumulate one extra DBOS workflow record per checkpoint op, and hydrateWorkflow/applyMetadata read them all back — each via a retrieveWorkflow(...).getResult() round-trip in listStepRecords. For long workflows both storage and resume-time hydration grow linearly purely for metadata mirroring. Worth a compaction strategy or at least a tracking TODO.

Maintainability

  1. Resumability predicate is triplicated. isResumableHandle (backend.ts:294), isResumableEntry/isResumableStatus (resume-catalog.ts:150-159), and isResumableEntry (resume-runtime.ts:173) all encode the same running/paused + failed/blocked-with-resumable !== false rule. Extract one shared helper so it can't drift across catalog, runtime, and backend.

  2. run.ts is at 498 lines — one under the 500 gate. Fine now, but the next edit there will trip check:file-length; consider extracting a bit more proactively.

Minor

  • crypto.randomUUID() is used as a global in dbos-backend.ts while backend.ts imports createHash from node:crypto. Prefer an explicit import { randomUUID } from "node:crypto".
  • decodeLegacy (dbos-envelope.ts:151) stamps completedAt: Date.now(), losing original ordering for legacy step outputs on hydration (listCheckpoints sorts by completedAt). Acceptable for the legacy path, but worth a comment.
  • console.warn is used directly in enqueueWrite and finalizeDurableTerminalStatus — confirm there isn't a structured logger the engine uses elsewhere.
  • Stale-lock reclaim keys off the lock directory mtime with a 30s threshold; a legitimate holder blocked >30s mid-write could be reclaimed by a peer. Low risk given fast writes, but a note on the assumption would help.
  • createCachedStageContext returns cached as never as StageContext and throws on most live ops — pragmatic, but the double cast is a smell.

Nothing here blocks correctness for the documented opt-in paths — items 1–3 are the ones I'd most want addressed (or explicitly deferred with a tracking note) since they affect behavior under real load.

@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown

PR Review: feat(workflows): add durable cross-session resume

Thorough, well-documented work — the backend seam is clean, the discriminated-union checkpoint model is sound, the cross-references to #1498 are helpful, and test coverage (~10 new unit files + integration) is genuinely comprehensive. The changelog is exemplary. Most of the trickier replay/idempotency edge cases already have a dedicated fix + test. Below are the things worth addressing before merge, ordered by impact.

🟠 Potential issues / correctness

  1. Synchronous, event-loop-blocking IO in FileDurableBackend (file-backend.ts). FileDurableBackend does not implement recordCheckpointAsync, so recordCheckpointDurably falls through to the synchronous recordCheckpoint(), which calls persist()withFileLock()readFileSync + writeFileSync + renameSync on every checkpoint. Under lock contention it also spins on Atomics.wait(...) for up to 5s (file-backend.ts:135,152). All of this runs on the main thread. In an interactive TUI session, a ctx.tool/ctx.stage-heavy workflow will block rendering/input on each checkpoint, and a contended lock can freeze the UI for seconds. Consider an async write path (or offloading to a worker) for the file backend, mirroring the DBOS recordCheckpointAsync design.

  2. O(N²) rewrite cost for the file backend. persist() reads the entire state file, merges, and rewrites it on every single checkpoint (file-backend.ts:45-52). For a workflow that accumulates many checkpoints, total IO over its lifetime is quadratic in checkpoint count, and the file holds all workflows' state in one state.json. createWorkflowFileBackend (per-workflow file) exists but createDefaultFileBackend — the one the factory actually wires — uses the single shared file. Worth at least a comment on the expected scale ceiling, or switching the default to per-workflow files.

  3. Unbounded DBOS metadata accumulation + N+1 hydration reads (dbos-backend.ts). writeMetadata() creates a new DBOS step workflow with a UUID-suffixed name (metadataStepNamecrypto.randomUUID(), line 369-371) on every registerWorkflow / recordCheckpoint / setWorkflowStatus. So a workflow with N status transitions leaves N metadata rows that are never reclaimed; applyMetadata lists them all just to pick .at(-1). Worse, listStepRecords issues a separate retrieveWorkflow(wid).getResult() round-trip per step (line 152-153) even though listWorkflows was already called with loadOutput: true — so hydration is N+1 sequential Postgres queries over both real checkpoints and all the redundant metadata rows. This will degrade for long-running workflows. Suggest reusing the already-loaded output from listWorkflows and bounding/compacting metadata.

  4. Broad duplicate-error regex can swallow real failures. isDbosDuplicateWorkflowError matches /duplicate|conflict|already/i (dbos-backend.ts:164-167) and is used to silently ignore startWorkflow errors. Messages like "connection already closed" or "table ... in conflict" would be misclassified as benign duplicates, masking a genuine dispatch failure. Tightening to DBOS's specific duplicate-workflow error code/class would be safer.

🟡 Minor / polish

  1. Stale doc comment in factory.ts. The file header says "File-backed fallback (default; zero infrastructure)" (line 6), but getDurableBackend() defaults to InMemoryDurableBackend unless ATOMIC_WORKFLOW_DURABLE_DIR/DBOS_SYSTEM_DATABASE_URL is set (line 42-45, and the getDurableBackend JSDoc correctly says so). The header should be corrected to avoid confusion.

  2. Inconsistent crypto usage. dbos-backend.ts:370 uses the global crypto.randomUUID(), while the rest of the durable code imports from node:crypto (e.g. backend.ts:24). It works on Bun/Node ≥19, but importing randomUUID from node:crypto would match the surrounding convention.

  3. undefined round-tripping for ctx.ui.custom. The void/undefined cache-miss fix is good, but encodeCheckpoint stores output: undefined for such a checkpoint (dbos-envelope.ts), and the envelope interface declares output as required. JSON-serializing an object with output: undefined drops the key; decodeEnvelope then yields response: undefined, which happens to be correct here — but it relies on a non-obvious coincidence. A short comment (or an explicit null sentinel) would make the intent durable.

🔒 Security

  1. Durable state files have default permissions. writeState (file-backend.ts:123-129) writes state.json under ~/.atomic/workflow-durable with default mode (umask-dependent, typically 0644). These files persist arbitrary ctx.tool outputs and ctx.ui responses, which can plausibly contain secrets (tokens fetched by a tool, answers typed into a prompt). On shared/multi-user hosts this is readable by other users. Consider creating the directory 0700 and writing files 0600. Same applies to the lock directory.

🧪 Tests

Coverage is strong and targeted (backend CRUD, DBOS adapter, scoped-backend sibling-leak, stale-lock recovery, frontier tracker, cross-session resume). Two gaps worth filling:

  • No test asserts the file backend is non-blocking / bounded under repeated checkpoints (ties to add agent instructions #1 and updates to readme and instructions #2).
  • A determinism caveat is worth documenting: replay correctness depends on ctx.* call order being stable across runs (ordinals in tool-primitive.ts:79 / ui-primitive.ts:25 are positional). If author code branches on time/randomness, replay keys desync. The docs update should call this out explicitly so authors don't get silent re-execution or mismatched cache hits.

File length

packages/workflows/src/engine/run.ts is now 498 lines — one edit away from the 500-line gate. The run-durable-finalize.ts extraction was the right move; keep an eye on this file in follow-ups.


Overall this is high-quality, defensively-written code with excellent documentation. None of the above blocks the opt-in default (in-memory) path; items #1, #2, #3, and #8 matter most once users actually enable the file or DBOS backends in production.

🤖 Generated with Claude Code

…rkflow-resume

# Conflicts:
#	packages/workflows/CHANGELOG.md
@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review: durable cross-session workflow resume (issue 1509)

Reviewed the durable backend system, resume catalog/runtime, and the engine/TUI wiring. This is a well-architected feature: the backend seam is clean, the in-memory/file/DBOS implementations share an honest interface, the changelog and workflows.md docs are genuinely comprehensive, and the test surface (~10 new unit files + integration suites) is strong. The commit history shows many of the tricky edge cases (child scoping, empty-string outputs, cancellation races, sibling-scope leakage) were already found and fixed. Nice work.

Findings grouped by severity. Most are performance/scalability concerns rather than correctness blockers.

Performance / scalability (the main theme)

  1. createDefaultFileBackend uses a single shared state.json with full read-merge-write per checkpoint (factory.ts:78-86, file-backend.ts:45-52). Every recordCheckpoint / registerWorkflow / setWorkflowStatus reads the entire file, merges all workflows, rewrites the whole file, and re-imports — all under a global lock. With many checkpoints or concurrent workflows this is quadratic write amplification and fully serializes durable writes across unrelated workflows. There is already a per-workflow createWorkflowFileBackend(workflowId) / durableStateFileFor that would scope this better — but it is dead code (exported, never called). Either wire the per-workflow backend in, or document why the shared file is acceptable. The inline comment in createDefaultFileBackend actually claims per-workflow files ("The FileDurableBackend handles per-workflow files internally"), which contradicts the code and is misleading.

  2. withFileLock blocks the event loop synchronously (file-backend.ts:142-154). It spins with Atomics.wait(...) for up to a 5s deadline. Because recordCheckpoint is synchronous and on the workflow hot path, lock contention freezes the entire process (and the TUI) for up to 5 seconds. For an interactive agent this is a noticeable hang under contention. Consider an async lock or moving file persistence off the critical path.

  3. DBOS metadata records grow unbounded (dbos-backend.ts:336-363, 369-371). writeMetadata is invoked after every checkpoint and status change, and metadataStepName(ts) appends crypto.randomUUID() so each write creates a brand-new DBOS step/workflow row. applyMetadata then reads all of them and sorts to find the latest. A long workflow with hundreds of checkpoints accumulates hundreds of metadata rows in Postgres, and every hydration re-reads and sorts the full set. Worth a compaction strategy or a single mutable metadata key.

  4. hydrateResumableWorkflows is N x M round-trips (dbos-backend.ts:301-324 + dbos-backend.ts:143-157). For each workflow it calls listStepRecords, which itself does a listWorkflows prefix query and then a separate retrieveWorkflow(wid).getResult() per step. On a fresh process, /workflow resume issues O(workflows x checkpoints) Postgres calls before the picker can render. Could be slow enough to feel broken with real history. Consider batching the output reads.

Correctness — worth a second look

  1. Schema-stage checkpoint skips non-empty string results (stage-primitive.ts:107-117). The condition if (typeof result !== "string" || result.length === 0) records structured values and empty strings, but a schema-backed stage that returns a non-empty string is silently not checkpointed and will re-execute on resume. In practice schema stages return structured objects so this is rare, but the asymmetry is surprising and undocumented — a stray non-empty string result becomes a hidden replay gap. Suggest always checkpointing the result, or a comment/test pinning the intended behavior.

  2. mergeRecords does not reconcile completedCheckpoints (file-backend.ts:181-191). It picks the newer handle by updatedAt but unions checkpoints from both records; the chosen handle completedCheckpoints count can then disagree with the actual merged checkpoint Map. Only affects the count shown in the selector (formatResumableWorkflowList), not replay correctness, but it can mislead users.

  3. DBOS duplicate-detection is string-matching (dbos-backend.ts:164-167). isDbosDuplicateWorkflowError swallows any error whose message matches /duplicate|conflict|already/i. That is brittle across SDK versions and could mask unrelated failures (e.g. a connection error mentioning "already closed"). If the SDK exposes a typed error or code, prefer that.

Minor / nits

  1. getDurableBackend swap window with DBOS (factory.ts:33-47, 64-73): when DBOS_SYSTEM_DATABASE_URL is set, the factory hands out a file backend until initializeDbosDurableBackendFromEnv() resolves and calls setDurableBackend. A workflow that registers during that window writes to the file backend, then later reads go to DBOS which never saw it. Likely fine given the runtime awaits init before dispatch (per the changelog), but worth a comment noting that invariant.

  2. cachedCustom does a linear listCheckpoints scan per ctx.ui.custom call (ui-primitive.ts:47-51). Fine for small workflows; noting it is O(checkpoints) per custom prompt where the other UI kinds use the O(1) getUiResponse map.

  3. console.warn for durable write failures (dbos-backend.ts:331, run-durable-finalize.ts:46). If the package has a structured logger, prefer it so these surface consistently in the TUI/log sinks.

  4. Files at the 500-line gate: extension/runtime.ts is exactly 500 and run.ts (498) / workflow-run-control-command.ts (493) are right at the edge. They pass today, but any follow-up will immediately trip check:file-length — good candidates for proactive extraction.

Security / data handling

No credential leakage spotted — DBOS_SYSTEM_DATABASE_URL is passed to setConfig and never logged; error paths log SDK messages, not the URL. One docs callout worth adding: durable persistence writes workflow inputs and tool/stage outputs in plaintext to ~/.atomic/workflow-durable/*.json (and to Postgres). Since workflow args can contain sensitive data, the opt-in docs should mention that enabling durability persists those values at rest.

Tests

Coverage is strong and maps clearly to the fixes. Two gaps to consider: a test pinning the schema-stage non-empty-string behavior (item 5), and a file-backend concurrency/contention test exercising the lock path.

Overall: solid, careful implementation. None of the above is a hard blocker, but the file-backend write amplification (1) + event-loop-blocking lock (2) and the unbounded DBOS metadata (3) are the ones I would most want addressed before this sees heavy real-world use.

Automated review by Claude (Opus 4.8)

Default workflow durability now uses the lock-protected file backend under ~/.atomic/workflow-durable so cross-session /workflow resume works without opt-in environment variables. DBOS remains an optional upgrade when DBOS_SYSTEM_DATABASE_URL is configured, with file-backed durability as the fallback.

BREAKING CHANGE: workflow durability no longer defaults to a process-local in-memory backend and the ATOMIC_WORKFLOW_DURABLE_DIR opt-in path is removed; in-memory durability is only for explicit test/custom backend overrides.

Assistant-model: GPT-5.5
@claude claude Bot changed the title feat(workflows): add durable cross-session resume feat(workflows)!: add durable cross-session resume Jun 26, 2026
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review: durable cross-session workflow resume (issue 1498)

Thorough, well-structured PR. The backend seam is a clean abstraction, the durable subsystem is nicely modularized (every file under the 500-line gate), typing is strict with no any/unknown slop, the changelog is detailed, and test coverage is substantial (10 durable unit files + integration suites). The reasoning around scoped child checkpoints, post-resolution cancellation re-checks, and the empty-string-vs-status-object distinction is excellent. Findings below, ordered by impact.

🔴 Performance — the default file backend blocks the event loop (highest impact)

This commit makes FileDurableBackend the default for every user (breaking change), so its perf characteristics now matter for everyone.

  1. Atomics.wait busy-waits on the main thread. file-backend.ts:153 spins Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25) while waiting for the lock dir. On a single-threaded JS runtime this blocks the entire event loop (TUI, timers, other async work) per 25ms spin, up to a 5s deadline. Under multi-session lock contention the UI will visibly freeze.
  2. Every checkpoint does a full read-merge-write of one shared file. getDurableBackend() -> createDefaultFileBackend() uses a single state.json for all workflows. persist() (called synchronously on every ctx.tool/ctx.ui/ctx.stage checkpoint and every status change) re-reads the whole file, merges all workflows, then does mem.reset() + importAll(merged) and rewrites the entire file. That is O(total-state) per checkpoint, ~O(N^2) for an N-checkpoint workflow, all synchronous/inline (recordCheckpointDurably -> recordCheckpoint, no async flush).
  3. No retention/GC. The shared state.json accumulates every workflow ever run (completed records + checkpoints are never pruned), compounding point 2 over time.

Suggestions: use the already-written per-workflow createWorkflowFileBackend (see below), make persistence async/debounced rather than synchronous-per-checkpoint, drop the full reset()+importAll() rebuild, and add retention for terminal workflows.

🟠 Dead / contradictory code

  • createWorkflowFileBackend (per-workflow file) is exported but never used — the default uses the single-shared-file backend, ironically the variant that would largely fix concern 2.
  • nextCheckpointId / checkpointIdGenerator is dead. Threaded from run.ts:207/337/347 into tool/stage/ui deps, but no primitive reads it — checkpoint ids come from content hashes (tool:argsHash, ui:identity.hash, stableCheckpointId(replayKey)). Wire it in or remove it.
  • Misleading comment in createDefaultFileBackend (factory.ts:69-74) claims the backend handles per-workflow files internally via the workflowId in the file path, but it passes a single state.json and stores all workflows in one file.

🟠 DBOS metadata grows unbounded

writeMetadata (dbos-backend.ts:336) creates a new DBOS workflow row per call via metadataStepName(ts) = ts:crypto.randomUUID(), invoked on every checkpoint and status change. applyMetadata reads all metadata rows on hydration, sorts by ts, keeps only the last. Long-lived workflows leave an unbounded metadata trail in Postgres and pay O(metadata-writes) on hydrate. Consider a deterministic single metadata step id (overwrite-in-place) or pruning.

🟡 Smaller correctness/robustness notes

  • Brittle duplicate detection. isDbosDuplicateWorkflowError (dbos-backend.ts:164) matches /duplicate|conflict|already/i on the error message and swallows the error on startWorkflow. Any unrelated DBOS error containing "already" is silently treated as benign. Prefer a DBOS error code/type if the SDK exposes one.
  • Terminal-status gate may miss non-exited cancellations. finalizeDurableTerminalStatus (run-durable-finalize.ts:36) only persists when status is failed/killed or (exited and status not running). A cancelled run that did not set exited is gated out even though toDurableStatus handles cancelled. Confirm all cancellation paths set exited, ideally with a regression test.
  • Stale-lock TOCTOU. isStaleLock + reclaimStaleLock (file-backend.ts:163-180) can let two processes both decide a lock is stale, one rmSync-ing the others fresh lock. The tmp+rename write avoids file corruption, but two concurrent writers in the critical section could lose a checkpoint — real now that this is the shared default across sessions.

Nits

  • ui-primitive select returns the cached string verbatim even if it is no longer a valid option after a workflow edit (only matters when options are unchanged but the stored value is stale). Low priority.

Verification

I could not run bun run typecheck / the durable suites in this sandboxed environment (commands gated), so this is a static review. Recommend confirming the PR-description validation block passes in CI and adding a focused test for the cancelled-without-exited terminal-persistence path.

Overall strong, careful work — the main ask before merge is the default-backend event-loop blocking and single-shared-file write amplification, since the breaking change puts that path in front of all users.

🤖 Generated with Claude Code

Resolve the failing CI test and harden the durable workflow resume feature
landed in #1498.

CI fix (the failing `executor.run — lifecycle persistence` test):
- The `workflow.durable.checkpoint` cache-entry append is gated on a
  *persistent* durable backend. Several test files mutate the global durable
  backend singleton (bun shares process state across files) and never reset
  it, leaking an in-memory backend that made this assertion flaky. Every test
  that calls `setDurableBackend(...)` now resets it in `afterEach`, and the
  lifecycle test resets the singleton in `beforeAll`/`afterAll` for defense in
  depth.

Durable resume correctness:
- A durable `running` handle may belong to a crashed process, so it stays
  resumable at the backend/catalog level (cross-session crash recovery). Same-
  session double-resume is prevented session-aware: the selector hides and
  resume refuses only when there is an *actively-executing live run* in this
  session, with an intuitive error pointing at `/workflow connect`/`/workflow kill`.
- In-progress LM stage-session checkpoints no longer collide with completed
  stage-output checkpoints (separate backend indexes), DBOS records persist by
  checkpoint id, stale quit snapshots are removed before reusing the workflow
  id, and mid-session LM resume sends `Continue` instead of re-sending the
  original prompt — fixing repeated quit/resume cycles that emptied chats.
- Removed an unnecessary `replayKey` override in the executor stage factory
  that broke continuation-replay topology validation.

Quit vs kill UX:
- Orchestrator/CLI `q` is now a resumable quit/detach (durable handle →
  `paused`), not a kill. Only `/workflow kill` authoritatively cancels a
  workflow. The background widget/status list render a `quit` badge with a
  "resumable via /workflow resume" note.

Tests: 2716 unit + 247 integration passing; typecheck, lint, file-length clean.

Refs #1498
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review: durable cross-session workflow resume (#1509)

Reviewed the durable backend system, resume catalog/runtime, and the ctx.* checkpoint primitives. This is a large, well-organized change — the backend seam is clean, the module-level docblocks are genuinely helpful, cancellation/crash-recovery edge cases are handled thoughtfully, and the ~3,400 lines of new unit coverage are excellent. Below are the things worth addressing before merge, ordered by impact.

🔴 Performance — default file backend rewrites all state on every checkpoint

createDefaultFileBackend() points every workflow at a single shared ~/.atomic/workflow-durable/state.json, and FileDurableBackend.recordCheckpoint() calls persist() synchronously on every checkpoint. persist() does a full read → mergeRecords → write of the entire file under a lock (file-backend.ts:46-53, :61-65).

Consequences now that durability is on by default:

  • A workflow with N checkpoints does O(N) full-file rewrites, each O(total persisted state) → roughly O(N²) growth in write cost for a single run.
  • The shared file is never prunedmergeRecords keeps every workflow forever, including completed/cancelled ones. The file (and therefore every write) grows monotonically across a user's entire history.
  • withFileLock + persist() are fully synchronous and block the event loop (the lock spin uses Atomics.wait for 25 ms slices, up to a 5 s deadline — file-backend.ts:148-160). Parallel stages each recording checkpoints serialize through this synchronous disk path.

You already wrote createWorkflowFileBackend(workflowId) (per-workflow file, factory.ts:81-84) but it's unused — switching the default to per-workflow files would bound write size dramatically. Beyond that, consider pruning terminal workflows and/or batching/debouncing writes (or an async write path like the DBOS backend's recordCheckpointAsync/flush). A TTL or cap would also keep the on-disk store from growing without limit.

Minor but related: the comment in createDefaultFileBackend ("The FileDurableBackend handles per-workflow files internally via the workflowId in the file path") is inaccurate — it uses a single shared file. Please fix the comment to avoid misleading future readers.

🟠 Stale-lock reclaim has a TOCTOU race

withFileLock checks isStaleLock() then reclaimStaleLock() as separate steps (file-backend.ts:153-156, 168-185). Two processes can both observe the same lock as stale and both rmSync it — and one can delete a lock the other just legitimately recreated, allowing two concurrent writers and a lost update. The window is small (30 s threshold, fast writes), but this is exactly the crash-recovery scenario the lock exists to protect. Consider re-stating after reclaim to confirm the mtime is unchanged, or writing an owner/pid marker inside the lock dir and only reclaiming locks you can prove are abandoned.

🟠 Privacy/security — sensitive data persisted to disk by default

Durability is now always on and writes workflow inputs, ctx.tool outputs, and ctx.ui responses as plaintext JSON to ~/.atomic/workflow-durable/state.json. These frequently contain secrets/PII (API responses, user-entered input). Since this is a breaking default change and the old ATOMIC_WORKFLOW_DURABLE_DIR opt-in was removed, please consider:

  • Restrictive permissions on the dir and file (e.g. mode 0700/0600) — currently they inherit the process umask.
  • Documenting clearly (in workflows.md) that durable state now persists potentially-sensitive workflow data to disk, plus an opt-out.
  • defaultDurableStateDir() falls back to /tmp when HOME/USERPROFILE are both unset (file-backend.ts:199-202) — that writes durable state to a world-readable location. Prefer failing closed (in-memory) over /tmp.

🟡 Replay identity has no workflow-version guard

Tool/UI/stage replay keys are (content hash, call ordinal) (tool-primitive.ts:78-81, ui-primitive.ts:23-29, stage-primitive.ts:215-222). If the workflow source changes between sessions — a ctx.* call inserted or reordered ahead of others — cached results can replay against the wrong call. The args content-hash mitigates the common case (changed args → cache miss → safe re-run), but same-name/same-args-at-a-shifted-ordinal is a genuine hazard. Worth either a definition-hash guard on resume or an explicit docs note on resume-after-edit semantics.

🟡 Minor

  • isDbosDuplicateWorkflowError matches error message text against /duplicate|conflict|already/i (dbos-backend.ts:164-167). This is brittle and could swallow unrelated errors whose message happens to contain "already". If the SDK exposes an error code/class, prefer that.
  • DbosDurableBackend.enqueueWrite swallows write failures into writeErrors + console.warn, surfacing them only on flush() (dbos-backend.ts:326-334). That's reasonable given finalizeDurableTerminalStatus calls flush() in a finally — but it means a mid-run recordCheckpoint (sync, fire-and-forget) failure is invisible until terminal flush. Fine as designed; flagging so it's intentional.
  • Nice touch: getToolOutput's cached !== undefined check is safe because WorkflowSerializableValue excludes top-level undefined (only null), and the ctx.ui.custom void/undefined case is correctly handled via the separate found flag. Good attention to that distinction.

Test coverage

Strong — 11 durable unit files plus integration coverage for the overlay/resume dispatch paths. If you address the file-backend points above, please add a test for (a) the shared-file growth/pruning behavior and (b) concurrent-process lock contention / stale-lock reclaim, since those are the riskiest paths and currently the least exercised.

Overall this is solid, careful work. The performance of the default file backend is the one item I'd consider a merge blocker given it's now always-on; the rest are hardening/correctness follow-ups.

🤖 Generated with Claude Code

Store default durable workflow state in per-workflow files, prune terminal file-backed state, and avoid /tmp fallback when no home directory is available.

Add restrictive file permissions, owner-marked stale-lock reclaim, a documented in-memory privacy opt-out, and coverage for default backend persistence.

Assistant-model: GPT-5.5
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review: feat(workflows)!: add durable cross-session resume

Reviewed the durable backend system, primitives, resume runtime/catalog, and engine wiring. This is a large, well-organized change — the backend seam is clean, files stay under the 500-line gate, security hygiene on persisted state is genuinely good (0o600/0o700, atomic temp+rename writes, PID/host/token-validated lock reclamation), and the test coverage is broad with proper afterEach singleton resets and tmp-HOME isolation. Failure directions are mostly chosen safely (re-run rather than skip a side effect on uncertainty). Nice work.

Below is feedback organized by severity. Most are worth a look but none are blockers on their own.

Performance

  1. WorkflowFileDurableBackend.lookupBackends scans every workflow file in the durable dir on every cache miss (file-backend.ts:244-261, used by getToolOutput/getUiResponse/getStageOutput/getStageSession). During normal forward execution every ctx.* lookup misses (nothing cached yet), so each one does a readdirSync and pulls all sibling workflow files into memory. As ~/.atomic/workflow-durable accumulates files across sessions, every workflow run gets progressively slower and holds more state in memory. Since checkpoints for a workflow are always written to workflow-<id>.json via backendFor(checkpoint.workflowId), the cross-file fallback looks unnecessary for point reads — could you confirm what case requires scanning siblings rather than just the primary file? If it's only for scoped children, note those remap to rootWorkflowId and still land in the root's file.

  2. persist() rewrites the entire workflow state file under a lock on every checkpoint (file-backend.ts:67-86). A workflow with K checkpoints performs O(K^2) total bytes written, plus a full lock dance (mkdir + owner write + read + merge + write + rename + rmdir) per recordCheckpoint/setWorkflowStatus. For high-frequency checkpointing this is a lot of syscalls. Worth considering append-style or batched persistence if checkpoint volume is expected to be high.

  3. ctx.ui.custom does a full listCheckpoints(...).find(...) per call (ui-primitive.ts:47-51), which on the file backend triggers the all-files scan in (1). Minor, but compounds.

Correctness / robustness

  1. No retention/cleanup for non-terminal abandoned workflows. isPrunableTerminalStatus prunes only completed/cancelled (and non-resumable failed/blocked) (file-backend.ts:430-433). running/paused/resumable-failed handles persist indefinitely, so a user who frequently quits or crashes workflows accumulates unbounded files under ~/.atomic/workflow-durable. Consider a TTL/max-count sweep or at least documenting manual cleanup.

  2. Privacy of on-by-default persistence (breaking change). Stage outputs, ctx.tool results, and UI responses — potentially sensitive LM-generated content — are now written to disk in plaintext JSON by default. 0o600 perms mitigate cross-user exposure, and the ATOMIC_WORKFLOW_DURABLE=0 opt-out is a good escape hatch, but this is a meaningful default-behavior shift. Please make sure docs/workflows.md calls out what gets persisted and where, prominently, so users with sensitive workflows know to opt out.

  3. Changelog vs. code on mid-session resume prompt. The changelog says you "Fixed mid-session LM resume sending Continue instead of re-sending the original prompt," but stage-primitive.ts:56-74 still overrides the prompt to send the constant "Continue" (MID_SESSION_RESUME_PROMPT) on isMidSessionResume. These read as contradictory — worth double-checking the changelog wording matches the intended behavior so the next reader isn't misled.

  4. WorkflowFileDurableBackend.reset() does rmSync(this.dir, { recursive: true, force: true }) (file-backend.ts:227-230) — i.e. it wipes the whole durable root. Tests correctly override HOME to a tmp dir first, but this is a sharp edge: any future test (or caller) that does getDurableBackend().reset() against the real default would delete a user's entire durable store. A guard or a more scoped reset would be safer.

Minor / questions

  1. Scratch fixtures committed? .atomic/workflows/dummy-resume-tool.ts, dummy-resume-custom-ui.ts, and stress-resume-*.ts look like manual QA/stress scripts (they use new Date().toISOString() markers) rather than referenced test fixtures. Are these intended to ship in the repo, or leftover dev scratch? If intentional, a short comment on their purpose would help.

  2. dbos-backend.ts error handling: registerWorkflow/setWorkflowStatus enqueue writes whose failures only surface via console.warn + flush() (dbos-backend.ts:326-334). The mem mirror is updated optimistically, so a silently-failed DBOS write means the in-process view diverges from Postgres until a flush throws. The failure direction (re-run on a fresh process) is safe, but the divergence is worth a comment.

  3. isDbosDuplicateWorkflowError matches on /duplicate|conflict|already/i against the message string (dbos-backend.ts:164-167). String-matching SDK error messages is brittle across SDK versions; an error-code/class check would be more durable if the SDK exposes one.

Overall this is a solid, carefully-built feature. My main asks before merge: confirm/resolve the lookupBackends all-files scan (#1) since it affects the steady-state hot path, add a retention story for #4, and make the persistence privacy implications explicit in the docs (#5).

Reviewed by Claude (Opus 4.8) — automated PR review.

Avoid per-workflow backend all-file scans on point lookups, document plaintext durability retention, and make file permission assertions portable on Windows.

Keep reset scoped to workflow state files and remove scratch durable-resume QA workflows from the project workflow catalog.

Assistant-model: GPT-5.5
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review: feat(workflows)! add durable cross-session resume (PR 1509)

Reviewed the durable backend system, primitives, resume catalog/runtime, and the file/DBOS adapters. This is a large, well-structured change — the backend seam is a clean abstraction, the changelog and docs/workflows.md are genuinely thorough, and the test surface (11 new unit files + integration coverage) is excellent. Most of the edge cases that bite checkpoint/replay systems (empty-string outputs, ordinal collisions on repeated ctx.tool, sibling child-scope leakage, cancellation-vs-checkpoint races, stale lock reclaim) are explicitly handled and tested. Nice work.

Feedback below, roughly ordered by impact. Nothing here is a correctness blocker; the top item is a performance concern worth weighing before this lands as an always-on default.

1. Performance — synchronous full-file rewrite on every checkpoint (now default-on).
FileDurableBackend.persist() runs on every recordCheckpoint, registerWorkflow, and setWorkflowStatus (file-backend.ts:67-74, called from :79/:85/:121). Each call acquires the lock dir (potentially busy-waiting), re-reads + JSON.parses the entire per-workflow state file, merges, then re-serializes and rewrites the whole file (writeState). So a workflow recording N checkpoints rewrites the full file N times, roughly O(N^2) bytes written, all synchronous on the event loop. Since file-backed durability is now on by default for every workflow, a checkpoint-heavy run (many ctx.tool/ctx.stage/ctx.ui calls) repeatedly does blocking disk I/O on the main thread of an interactive TUI. Worth considering a debounce/batch (coalesce writes, or flush on an interval / at stage boundaries) or moving the serialize+write off the hot path.

2. Atomics.wait blocks the event loop during lock contention.
withFileLock (file-backend.ts:294) spins with Atomics.wait(...25) when the lock is held. This is a synchronous sleep — if another Atomic process holds the lock, the current process UI freezes in 25 ms increments up to the 5 s deadline. Combined with item 1 (lock taken on every checkpoint), two concurrent Atomic sessions touching the same workflow file can stall each other event loops. An async lock (retry via setTimeout/await) would avoid freezing the loop.

3. Unbounded metadata-step growth in the DBOS backend.
writeMetadata writes a new step every time, and metadataStepName makes each one unique via crypto.randomUUID() (dbos-backend.ts:336-340, 369-371). applyMetadata then reads them all and keeps only the latest by ts (:342-349). Functionally correct, but metadata steps accumulate without pruning — for a long-lived or frequently-updated workflow this is unbounded growth in Postgres and an ever-growing listStepRecords scan on hydrate. Consider overwriting a single metadata step id (or periodic compaction).

4. isDbosDuplicateWorkflowError matches on a loose message regex.
/duplicate|conflict|already/i over the error message (dbos-backend.ts:164-167) is brittle — it swallows any unrelated SDK error whose message happens to contain "already". If the SDK exposes an error code/class, prefer matching on that; otherwise at least tighten the pattern.

5. wrapSchemaStageForDurability silently skips non-empty string results.
The condition if (typeof result !== "string" || result.length === 0) (stage-primitive.ts:160) checkpoints structured + empty-string results but not non-empty strings, relying on a different code path to checkpoint those. The comment explains the empty-string motivation but not where a non-empty string result from a schema stage gets persisted. This is subtle and easy to regress — worth an explicit inline note pointing at the other checkpoint path, or a dedicated test for a schema stage returning a non-empty string and replaying correctly.

6. Minor / nits.

  • initializeDbosDurableBackendFromEnv caches the promise via dbosInit ??= (factory.ts:66); a transient DBOS init failure is then permanent for the process (no retry). The caller correctly catches it and falls back to the file backend (runtime.ts:164), so this is acceptable — just flagging the no-retry semantics.
  • durableStateFileFor builds the filename from encodeURIComponent(workflowId) (file-backend.ts:431). For normal UUID ids this is fine; an unusually long id could exceed the 255-char filename limit. Probably not reachable in practice, but a length guard / hash-fold would be defensive.
  • ui-primitive custom cachedCustom does a linear listCheckpoints(...).find(...) per custom-prompt call (ui-primitive.ts:47-51). Fine given prompt counts are small and it is the documented fix for distinguishing void responses from cache misses — noting the O(n) for completeness.

Strengths worth calling out.

  • The cancellation re-check in ctx.tool after fn() resolves but before checkpointing (tool-primitive.ts:88-95) is exactly right, and sleepOrAbort cleans up its abort listener on both paths.
  • ScopedDurableBackend.listCheckpoints filtering on the stored prefixed id rather than re-prefixing (scoped-backend.ts:93-102) is a genuinely easy mistake to get wrong, and the explanatory comment is great.
  • Failing closed to in-memory when no HOME resolves (factory.ts:77-81) rather than writing checkpoints to /tmp, plus the 0700/0600 perms, is the right security posture for persisting potentially sensitive prompt/tool data.

Overall this looks solid and carefully tested. The main thing I would want resolved before shipping as always-on is the per-checkpoint synchronous-write cost (items 1 and 2) under realistic workflow sizes.

@flora131
flora131 merged commit 411e72d into main Jun 26, 2026
11 checks passed
@flora131
flora131 deleted the issue-1498-durable-workflow-resume branch June 26, 2026 06:25
lavaman131 pushed a commit that referenced this pull request Jun 29, 2026
* feat(workflows): add durable cross-session resume

Add an optional DBOS-backed durable workflow backend with file/in-memory fallbacks, plus ctx.tool, UI, and stage checkpoint primitives for resumable side effects.

Cache durable workflow metadata in session history and wire /workflow resume to a workflow-specific resume catalog while preserving live-run controls.

Cover durable state, DBOS adapter behavior, resume catalog/runtime flows, and cross-session metadata with unit tests, and document configuration and semantics.

Assistant-model: OpenAI GPT-5

* fix(workflows): complete durable DBOS resume wiring

Wire the lazy DBOS SDK adapter through launch, workflow control, and checkpoint hydration so fresh sessions can replay persisted state.

Add durable stage/task replay, stronger resume discovery, file backend locking and merge semantics, root workflow and failure-state filters, and coverage for the DBOS hydration and resume paths.

* fix(workflows): harden durable resume persistence

Sync bun.lock with the optional DBOS SDK dependency and durable workflow packages.

Scan Atomic custom JSONL entries for workflow resume metadata, hydrate DBOS workflow metadata without marking resumable roots completed, and serialize durable checkpoint writes with a flush barrier so write failures surface before completion.

Preserve graph/store visibility when replaying cached stages and tasks, and keep no-arg /workflow resume aligned with the live run picker before falling back to durable history.

Assistant-model: OpenAI Codex

* fix(workflows): finalize DBOS resume semantics

Persist mutable DBOS metadata without relying on write-once helper workflow results and remove the invalid duplicationPolicy parameter from DBOS calls.

Await stage checkpoint persistence, add a DBOS initialization barrier before workflow dispatch, and make no-arg workflow resume surface the durable selector while preserving live picker behavior.

Differentiate repeated ctx.tool calls with ordinal checkpoint identity, make retry backoff cancellation-aware, and preserve first-run onboarding from origin/main.

Assistant-model: Atomic Subagent

* fix(workflows): complete durable composite replay

Add durable replay coverage for ctx.chain, ctx.parallel, and child workflow calls so composite workflow operations do not re-run after resume.

Flush terminal durable workflow status before returning, only update the DBOS replay mirror after checkpoint acceptance, and keep /workflow resume durable history visible when completed local runs exist.

Assistant-model: OpenAI GPT-5

* fix(workflows): preserve scan-only durable resume state

Keep the prepared durable catalog available through resume so scan-only session-cache entries can be selected and resumed.

Preserve structured schema-backed stage replay values, await parallel fail-fast finalizers, clean up sleepOrAbort listeners on normal completion, and prefer explicit stage replay keys before falling back to external lookup.

Assistant-model: OpenAI GPT-5

* fix(workflows): persist child and exit resume state

Stabilize child workflow replay keys so repeated child calls resume completed work without re-executing side effects.

Persist direct stages under the durable replay key, record ctx.exit terminal durable metadata, surface durable history even when live runs exist, and propagate durableBackend into child workflow runs.

Assistant-model: OpenAI GPT-5

* fix(workflows): scope child durable checkpoints to roots

Scope child workflow internal checkpoints to the root workflow so nested side effects replay consistently across sessions.

Refuse stale cache-only resume entries when no durable backend state exists, switch durable replay identities to SHA-256 digests, check cancellation after tool functions resolve before checkpointing or returning, and recover stale file-backend locks after crashes.

* fix(workflows): connect durable resume selections

Suppress stale terminal cache entries when backend state is terminal so old session JSONL cannot resurrect completed workflows.

Open the workflow overlay after a successful durable resume and combine live and durable entries in the no-arg /workflow resume picker.

Tighten scoped checkpoint listing to exclude sibling scopes and clean up the merged run import.

Assistant-model: OpenAI GPT-5 Codex

* fix(workflows): preserve replayed stage graph state

Preserve empty string stage outputs when checkpointing durable stage results.

Release stage limiter slots when finalization fails, hydrate durable entries before mixed live/durable resume selection, preserve replayed stage parent/frontier graph state, and avoid opening a second picker after combined picker dismissal.

Assistant-model: OpenAI GPT-5

* fix(workflows): harden durable resume review blockers

Assistant-model: GPT-5.5

* fix(workflows): avoid headless durable resume picker

Assistant-model: GPT-5.5

* feat(workflows)!: enable durable resume by default

Default workflow durability now uses the lock-protected file backend under ~/.atomic/workflow-durable so cross-session /workflow resume works without opt-in environment variables. DBOS remains an optional upgrade when DBOS_SYSTEM_DATABASE_URL is configured, with file-backed durability as the fallback.

BREAKING CHANGE: workflow durability no longer defaults to a process-local in-memory backend and the ATOMIC_WORKFLOW_DURABLE_DIR opt-in path is removed; in-memory durability is only for explicit test/custom backend overrides.

Assistant-model: GPT-5.5

* fix(workflows): stabilize durable resume, quit/kill UX, and CI isolation

Resolve the failing CI test and harden the durable workflow resume feature
landed in #1498.

CI fix (the failing `executor.run — lifecycle persistence` test):
- The `workflow.durable.checkpoint` cache-entry append is gated on a
  *persistent* durable backend. Several test files mutate the global durable
  backend singleton (bun shares process state across files) and never reset
  it, leaking an in-memory backend that made this assertion flaky. Every test
  that calls `setDurableBackend(...)` now resets it in `afterEach`, and the
  lifecycle test resets the singleton in `beforeAll`/`afterAll` for defense in
  depth.

Durable resume correctness:
- A durable `running` handle may belong to a crashed process, so it stays
  resumable at the backend/catalog level (cross-session crash recovery). Same-
  session double-resume is prevented session-aware: the selector hides and
  resume refuses only when there is an *actively-executing live run* in this
  session, with an intuitive error pointing at `/workflow connect`/`/workflow kill`.
- In-progress LM stage-session checkpoints no longer collide with completed
  stage-output checkpoints (separate backend indexes), DBOS records persist by
  checkpoint id, stale quit snapshots are removed before reusing the workflow
  id, and mid-session LM resume sends `Continue` instead of re-sending the
  original prompt — fixing repeated quit/resume cycles that emptied chats.
- Removed an unnecessary `replayKey` override in the executor stage factory
  that broke continuation-replay topology validation.

Quit vs kill UX:
- Orchestrator/CLI `q` is now a resumable quit/detach (durable handle →
  `paused`), not a kill. Only `/workflow kill` authoritatively cancels a
  workflow. The background widget/status list render a `quit` badge with a
  "resumable via /workflow resume" note.

Tests: 2716 unit + 247 integration passing; typecheck, lint, file-length clean.

Refs #1498

* fix(workflows): harden file-backed durability

Store default durable workflow state in per-workflow files, prune terminal file-backed state, and avoid /tmp fallback when no home directory is available.

Add restrictive file permissions, owner-marked stale-lock reclaim, a documented in-memory privacy opt-out, and coverage for default backend persistence.

Assistant-model: GPT-5.5

* fix(workflows): address durable backend review feedback

Avoid per-workflow backend all-file scans on point lookups, document plaintext durability retention, and make file permission assertions portable on Windows.

Keep reset scoped to workflow state files and remove scratch durable-resume QA workflows from the project workflow catalog.

Assistant-model: GPT-5.5
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.

Add cross-session resumability for Atomic workflows with DBOS

1 participant