Skip to content

fix(state): make task and graph state updates atomic and validated — prevent lost updates and invalid persisted state - #767

Closed
unohee wants to merge 8 commits into
mainfrom
swarm/AGT-3420-fix-state-make-task-and-graph-state-upda
Closed

unohee wants to merge 8 commits into
mainfrom
swarm/AGT-3420-fix-state-make-task-and-graph-state-upda

Conversation

@unohee

@unohee unohee commented Sep 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Published because this run stopped and needs a human: autonomous execution failed 4 times

It has not been reviewed and is very likely incomplete — this PR is a draft on purpose. It exists so the work is reviewable instead of sitting on a branch that was never pushed.

Change shape

428 file(s): 211 source · 196 test · 4 docs · 17 other

Base freshness

Branch base is 172 commit(s) behind main at publication.

⚠ Conflicts with main in: src/automation/runnerState.ts, src/memory/codex.ts, src/taskState/store.ts, src/task_state_model.py
GitHub runs no pull_request workflows on a PR it cannot merge, so any green checks here are not the verification gate. Opened as a draft; rebase before review.

⚠️ File overlap with in-flight work

This branch changes files that other open PRs / active branches also touch. Coordinate before merging to avoid divergent parallel edits (INT-2388 #3):

Linear

Closes AGT-3420


🤖 Generated with OpenSwarm

unohee pushed a commit that referenced this pull request Sep 28, 2026
…hip-safe

Salvages the unique work of draft PRs #766 and #767 into one change.

- fileLock: add withFileLockSync (Atomics wait) for sync RMW callers, with the
  ownership-safe stale reclaim and ENOTEMPTY-tolerant unlink; lock waits use the
  real timer so a test that fakes setTimeout cannot freeze lock hand-off.
- pairPipeline: replace the per-instance abortSignal/stuckDetector with an
  AsyncLocalStorage RunControl, so concurrent run() calls on one instance cannot
  observe or overwrite each other's cancellation state.
- processRegistry: taskId+spawnedAt identity ownership on activity, close and
  health-check, plus a SIGKILL escalation timer that is unref'd, cancelled on
  child close, and cleared by a force kill.
- store: tryClaimTaskAdmission claims a task in one locked read-modify-write;
  stale-lock reclaim tolerates ENOTEMPTY; main's updateTaskLinearState lock
  invariant is preserved.
- runnerState: cross-process locks on the rejection, decomposition, pace,
  project-selection and pipeline-history RMW paths; main's withRunnerStateLock
  and reserveDailyCreations are kept, and the daily reset runs outside the
  decomposition lock because withFileLockSync is not reentrant.
- decisionEngine: admission claims on the auto-execute paths and an in_progress
  filter; file-locked engine state and backlog appends.
- oauthStore, dev, logRotation, codex, reembed, taskParser, gitInfo, locale:
  the locked or scoped variant of the same invariant.

Dropped: telemetry.ts (main has withTelemetryLock), workflow.ts (owned by
another salvage), prProcessor.ts (its state lock cannot fit the repo's
1500-line gate, which main's copy already sits on), task_state_model.py,
agt3420-probe.txt, and package.json/lock version churn.
unohee pushed a commit that referenced this pull request Sep 28, 2026
…hip-safe

Salvages the unique work of draft PRs #766 and #767 into one change.

- fileLock: add withFileLockSync (Atomics wait) for sync RMW callers, with the
  ownership-safe stale reclaim and ENOTEMPTY-tolerant unlink; lock waits use the
  real timer so a test that fakes setTimeout cannot freeze lock hand-off.
- pairPipeline: replace the per-instance abortSignal/stuckDetector with an
  AsyncLocalStorage RunControl, so concurrent run() calls on one instance cannot
  observe or overwrite each other's cancellation state.
- processRegistry: taskId+spawnedAt identity ownership on activity, close and
  health-check, plus a SIGKILL escalation timer that is unref'd, cancelled on
  child close, and cleared by a force kill.
- store: tryClaimTaskAdmission claims a task in one locked read-modify-write;
  stale-lock reclaim tolerates ENOTEMPTY; main's updateTaskLinearState lock
  invariant is preserved.
- runnerState: cross-process locks on the rejection, decomposition, pace,
  project-selection and pipeline-history RMW paths; main's withRunnerStateLock
  and reserveDailyCreations are kept, and the daily reset runs outside the
  decomposition lock because withFileLockSync is not reentrant.
- decisionEngine: admission claims on the auto-execute paths and an in_progress
  filter; file-locked engine state and backlog appends.
- oauthStore, dev, logRotation, codex, reembed, taskParser, gitInfo, locale:
  the locked or scoped variant of the same invariant.

Dropped: telemetry.ts (main has withTelemetryLock), workflow.ts (owned by
another salvage), prProcessor.ts (its state lock cannot fit the repo's
1500-line gate, which main's copy already sits on), task_state_model.py,
agt3420-probe.txt, and package.json/lock version churn.
@unohee

unohee commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: superseded — the salvageable work in this draft was rebased onto current main, verified, and re-published as a reviewed PR: absorbed into #791. Closing the stale draft instead of merging it, because it was 170-250 commits behind, carried scratch files, and (in several cases) reverted main's later hardening.

@unohee unohee closed this Sep 28, 2026
@unohee
unohee deleted the swarm/AGT-3420-fix-state-make-task-and-graph-state-upda branch September 28, 2026 07:02
unohee pushed a commit that referenced this pull request Sep 28, 2026
…hip-safe

Salvages the worthwhile work from draft PRs #766 and #767 (both 'failed 4
times … very likely incomplete', 174-247 commits behind) into one change,
rebased onto current main.

Kept: unified withFileLockSync (Atomics.wait + ownership-safe reclaim +
ENOTEMPTY tolerance); AsyncLocalStorage per-run RunControl in pairPipeline;
processRegistry taskId/spawnedAt ownership + cancellable unref'd timer;
taskState tryClaimTaskAdmission + ENOTEMPTY reclaim; decisionEngine admission
claims + in_progress filter; taskParser zod schemas; cross-process locks in
memoryCore/codex/reembed/runnerState; gitInfo enrichment now persists via
graph.addNode+saveGraph; locale AsyncLocalStorage scope; oauthStore refresh
serialized on a store lock with reload-under-lock.

Also updates the duck-typed AuthProfileStore double in codexResponses.test.ts
(reloadProfileFromDisk/setProfileUnlocked) so it implements the interface its
declared collaborator now requires — the adapter's tests are the only caller
that is not a real AuthProfileStore instance.
unohee added a commit that referenced this pull request Sep 28, 2026
…hip-safe (#766 + #767) (#791)

* fix(concurrency): make lifecycle and durable state transitions ownership-safe

Salvages the unique work of draft PRs #766 and #767 into one change.

- fileLock: add withFileLockSync (Atomics wait) for sync RMW callers, with the
  ownership-safe stale reclaim and ENOTEMPTY-tolerant unlink; lock waits use the
  real timer so a test that fakes setTimeout cannot freeze lock hand-off.
- pairPipeline: replace the per-instance abortSignal/stuckDetector with an
  AsyncLocalStorage RunControl, so concurrent run() calls on one instance cannot
  observe or overwrite each other's cancellation state.
- processRegistry: taskId+spawnedAt identity ownership on activity, close and
  health-check, plus a SIGKILL escalation timer that is unref'd, cancelled on
  child close, and cleared by a force kill.
- store: tryClaimTaskAdmission claims a task in one locked read-modify-write;
  stale-lock reclaim tolerates ENOTEMPTY; main's updateTaskLinearState lock
  invariant is preserved.
- runnerState: cross-process locks on the rejection, decomposition, pace,
  project-selection and pipeline-history RMW paths; main's withRunnerStateLock
  and reserveDailyCreations are kept, and the daily reset runs outside the
  decomposition lock because withFileLockSync is not reentrant.
- decisionEngine: admission claims on the auto-execute paths and an in_progress
  filter; file-locked engine state and backlog appends.
- oauthStore, dev, logRotation, codex, reembed, taskParser, gitInfo, locale:
  the locked or scoped variant of the same invariant.

Dropped: telemetry.ts (main has withTelemetryLock), workflow.ts (owned by
another salvage), prProcessor.ts (its state lock cannot fit the repo's
1500-line gate, which main's copy already sits on), task_state_model.py,
agt3420-probe.txt, and package.json/lock version churn.

* fix(concurrency): make lifecycle and durable state transitions ownership-safe

Salvages the worthwhile work from draft PRs #766 and #767 (both 'failed 4
times … very likely incomplete', 174-247 commits behind) into one change,
rebased onto current main.

Kept: unified withFileLockSync (Atomics.wait + ownership-safe reclaim +
ENOTEMPTY tolerance); AsyncLocalStorage per-run RunControl in pairPipeline;
processRegistry taskId/spawnedAt ownership + cancellable unref'd timer;
taskState tryClaimTaskAdmission + ENOTEMPTY reclaim; decisionEngine admission
claims + in_progress filter; taskParser zod schemas; cross-process locks in
memoryCore/codex/reembed/runnerState; gitInfo enrichment now persists via
graph.addNode+saveGraph; locale AsyncLocalStorage scope; oauthStore refresh
serialized on a store lock with reload-under-lock.

Also updates the duck-typed AuthProfileStore double in codexResponses.test.ts
(reloadProfileFromDisk/setProfileUnlocked) so it implements the interface its
declared collaborator now requires — the adapter's tests are the only caller
that is not a real AuthProfileStore instance.

---------

Co-authored-by: SalvageA <salvage@local>
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.

1 participant