Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
4cb0120
fix(delegation): preserve live child delegation links across extensio…
Sep 2, 2026
717e233
test(task-persistence): cover mutation edge cases for live child live…
Sep 3, 2026
289c822
test(task-persistence): kill surviving static-mutant arithmetic mutan…
Sep 3, 2026
55ab370
chore(gitignore): ignore local worktrees (.wt-*) and scratch files
Sep 3, 2026
0c35045
test(task-persistence): make liveness-boundary tests filesystem-preci…
Sep 3, 2026
85b4e16
fix(delegation): guard replayDelegationRepairIntent against live cros…
Sep 4, 2026
795da5e
fix(delegation): run delegation reconciliation on periodic reconcile …
Sep 5, 2026
d2e67f9
fix(delegation): use console.warn for live-child skip log
Sep 5, 2026
1f88b8b
test(delegation): route undefined child mtime through initialize() en…
Sep 5, 2026
7206d6f
test(lifecycle): model cross-window child liveness guard in lifecycle…
Sep 5, 2026
ccf801b
chore(gitignore): drop local worktree and scratch ignore patterns
Sep 5, 2026
df7b644
test(delegation): kill 15 surviving changed-code mutants
Sep 5, 2026
40e06ca
test(delegation): replace fixed-count timer pump with condition polling
Sep 5, 2026
d4c619b
fix(delegation): harden cross-window liveness guards per CodeRabbit r…
Sep 8, 2026
cdfc136
fix(delegation): roll back ownership claim on non-started resume path…
Sep 8, 2026
2375b10
test(delegation): add markLocallyInactive to wholesale TaskHistorySto…
Sep 8, 2026
5708994
test(delegation): kill 19 changed-code mutants in claim/rollback and …
Sep 8, 2026
05278f0
fix(delegation): close ownership race in reconciliation; CodeRabbit r…
Sep 8, 2026
1794520
test(delegation): strengthen resolved-task assertion in hookless crea…
Sep 8, 2026
8c0ff22
refactor(webview): eliminate all 12 no-explicit-any violations from C…
Sep 8, 2026
e9a1820
refactor(task): eliminate all 17 no-explicit-any violations from Task.ts
Sep 8, 2026
3f10771
test(task): kill 43 changed-code mutants surfaced by Task.ts type-cle…
Sep 8, 2026
0ce3e21
test(task): pin reasoning-block id key-absence with explicit property…
Sep 8, 2026
b9896d2
fix(delegation): release local ownership when continuation scheduling…
Sep 20, 2026
0190a54
test(delegation): cover continuation scheduling rejection and cancell…
Sep 20, 2026
ed792d3
test(task): pass threaded request model snapshot in buildCleanConvers…
Sep 20, 2026
4fc47fc
docs(lifecycle): restrict invariant 8 to boolean model behavior
Sep 20, 2026
6d05781
fix(task-persistence): drop evicted task ids from local ownership on …
Sep 22, 2026
09f7f46
fix(task-persistence): keep live delegated children during reconcile
Sep 23, 2026
5534add
Merge remote-tracking branch 'refs/remotes/upstream/main' into fix/re…
myk1yt Sep 27, 2026
623d756
Merge remote-tracking branch 'refs/remotes/upstream/main' into fix/re…
myk1yt Oct 3, 2026
64ee0a3
fix(test): import LOCK_STALE_MS from utils/fileLock after upstream move
myk1yt Oct 3, 2026
fd375d1
fix(task): keep delegated children alive and owned across the whole s…
myk1yt Oct 3, 2026
a4cdc90
fix(task-persistence): grace-guard the missing-child orphan repair an…
myk1yt Oct 3, 2026
608d913
fix(webview): claim local session ownership at every spawn path
myk1yt Oct 3, 2026
9f2acce
fix(task-persistence): re-validate child liveness under the lock befo…
myk1yt Oct 3, 2026
fe67d5c
chore(model-check): print measured action/landmark/witness counts
myk1yt Oct 3, 2026
55639e3
fix(test): stub heartbeat/ownership methods on partial Task and store…
myk1yt Oct 3, 2026
127f051
fix(task): cover the nested-delegation resume entry with the liveness…
myk1yt Oct 3, 2026
f3f7eaa
fix(task-persistence): harden local ownership claims against transien…
myk1yt Oct 3, 2026
791f724
fix(task-persistence): scope the liveness heartbeat write to live, ex…
myk1yt Oct 3, 2026
6c09dbf
fix(task-persistence): re-validate orphan repairs under the target's …
myk1yt Oct 3, 2026
a9a72c0
refactor(task-persistence): trim mutation-diff budget lines and P3 cl…
myk1yt Oct 3, 2026
9990c36
Merge remote-tracking branch 'upstream/main' into fix/returntoparent
myk1yt Oct 3, 2026
93eef1e
Merge remote-tracking branch 'upstream/main' into fix/returntoparent
myk1yt Oct 8, 2026
bc6697e
chore: retrigger CI after windows-only mutation-gate flake
myk1yt Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions docs/architecture/task-lifecycle-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,21 @@ TLA+/PlusCal or Quint with TLC becomes a better fit when the lifecycle needs tem

## Production mapping

| Model concept | Production concept |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Task record and status | `HistoryItem` persisted by `TaskHistoryStore` |
| `delegate(parent, child)` | `ClineProvider.delegateParentAndOpenChild` |
| `interrupt(child)` | cancellation or eviction through `markDelegatedChildInterrupted` |
| `complete(child)` | `ClineProvider.reopenParentFromDelegation` |
| `abandon(child)` | `ClineProvider.abandonSubtask` |
| Pending-action settlement | `TaskHistoryStore.clearPendingActionIfMatching` compare-and-clear in the rejected-delegation settlement path (#1714) |
| Atomic event step | `atomicReadAndUpdate`, `atomicUpdatePair`, and per-parent delegation transition lock |
| Event interleaving | Competing completion, cancellation, abandonment, and new delegation calls |

The model has three fixed task slots, enough to cover competing siblings and a nested parent-child-grandchild chain. It explores every reachable interleaving through depth 12, deduplicating canonical states. Representative checks also exercise rejected operations that do not create a new state: a second concurrent delegation while the first child is active, stale completion after re-delegation, late completion after abandonment, completion after interruption, and nested completion. Named semantic landmarks require the graph to retain interrupted-child re-delegation and nested delegation even when the raw state total changes.
| Model concept | Production concept |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Task record and status | `HistoryItem` persisted by `TaskHistoryStore` |
| `delegate(parent, child)` | `ClineProvider.delegateParentAndOpenChild` |
| `interrupt(child)` | cancellation or eviction through `markDelegatedChildInterrupted` |
| `complete(child)` | `ClineProvider.reopenParentFromDelegation` |
| `abandon(child)` | `ClineProvider.abandonSubtask` |
| Pending-action settlement | `TaskHistoryStore.clearPendingActionIfMatching` compare-and-clear in the rejected-delegation settlement path (#1714) |
| `reconcileStartup(parent)` | startup/periodic `TaskHistoryStore.reconcileDelegationStateCore` orphan repair |
| `markLiveElsewhere(child)` / `expireLiveElsewhere(child)` | child history-file mtime recent vs stale past `LIVE_CHILD_MTIME_THRESHOLD_MS` (abstracted; no wall clock in model) |
| `heartbeat(child)` / `expireHeartbeat(child)` | persisted `lastActivityAt` liveness heartbeat fresh vs stale past `LIVE_CHILD_MTIME_THRESHOLD_MS` (production: the owning session's throttled heartbeat across the child's whole active lifetime — long streaming turns, long tool calls, and ask-idle periods alike — shared predicate `isDelegatedChildLive`) |
| Atomic event step | `atomicReadAndUpdate`, `atomicUpdatePair`, and per-parent delegation transition lock |
| Event interleaving | Competing completion, cancellation, abandonment, and new delegation calls |

The model has three fixed task slots, enough to cover competing siblings and a nested parent-child-grandchild chain, plus two abstract booleans per slot recording whether an active child's session shows life: one for ownership by another window (recent history-file mtime) and one for a fresh persisted `lastActivityAt` heartbeat from the owning session. It explores every reachable interleaving through depth 12, deduplicating canonical states. Representative checks also exercise rejected operations that do not create a new state: a second concurrent delegation while the first child is active, stale completion after re-delegation, late completion after abandonment, completion after interruption, and nested completion. Named semantic landmarks require the graph to retain interrupted-child re-delegation and nested delegation even when the raw state total changes, a delegated parent whose active child is live in another window surviving startup reconciliation unchanged, a delegated parent whose active child heartbeats through a long streaming turn (stale mtime, fresh `lastActivityAt`) likewise surviving unchanged, and a stale-mtime, stale-heartbeat (crash-orphan) active child being repaired to `interrupted` with the parent returned to `active` only through `reconcileStartup`.

Each task slot can also hold one of two pending `create_subtask` actions. A `stage` action mirrors `setPendingTaskAction` overwrite semantics, delegation clears the action its request carried, completion clears the child's action only when its event carries the matching action ID, and a `settle-rejected` action models the settlement that follows an authoritative delegation rejection (#1714). Production settles through the typed `LifecycleTransitionError` from the shared guards: the provider calls the disk-authoritative `TaskHistoryStore.clearPendingActionIfMatching` compare-and-clear under the per-file lock, then propagates the original rejection. Six named witnesses must remain reachable: settlement from an interrupted record after rejection, settlement through a successful active delegation, unrelated-action preservation during completion, stale-action protection where a settlement targeting one action ID leaves a replacement action intact, matching-ID completion clearing, and replacement-ID completion preservation. A mismatched pending-action request keeps its production behavior: the atomic update throws before any transition, and no settlement runs.

Expand Down Expand Up @@ -140,8 +143,9 @@ The task delegation checker currently enforces:
5. Parent-child lineage is acyclic.
6. Completed task records cannot be changed by later lifecycle events.
7. Active-child re-delegation, stale completion after ownership moves to another child, duplicate/late completion, and abandonment of a live child are rejected by the shared production guards.
8. A rejected delegation settles only the exact matching pending `create_subtask` action. Settlement preserves status, lineage, and accounting, never clears a replacement or different-kind action, and never mutates a completed record. The settlement compare-and-clear reads the persisted record under the per-file lock, so it never clears from a stale host cache.
9. A completion clears the completing child's pending action only when the completion event carries the exact matching action ID. A completion with no action ID or a different ID preserves the pending action.
8. No transition may clear a delegated parent's link to a child that is active and live, where live means either marked live-elsewhere (`ModelState.liveElsewhere` true, recent history-file mtime) or heartbeat-alive (`ModelState.heartbeatAlive` true, fresh persisted `lastActivityAt`); reconciliation repairs the link only when both flags are false. This encodes the PR #1495 cross-window misrepair bug class — which broke delegation links so subtask completion could not return to the parent — extended by the liveness-heartbeat fix so a child streaming a long turn (minutes without any other history-file write) is likewise preserved.
9. A rejected delegation settles only the exact matching pending `create_subtask` action. Settlement preserves status, lineage, and accounting, never clears a replacement or different-kind action, and never mutates a completed record. The settlement compare-and-clear reads the persisted record under the per-file lock, so it never clears from a stale host cache.
10. A completion clears the completing child's pending action only when the completion event carries the exact matching action ID. A completion with no action ID or a different ID preserves the pending action.

The completion persistence checker additionally enforces:

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const historyItemSchema = z.object({
awaitingChildId: z.string().optional(), // Child currently awaited (set when delegated)
completedByChildId: z.string().optional(), // Child that completed and resumed this parent
completionResultSummary: z.string().optional(), // Summary from completed child
lastActivityAt: z.number().optional(), // Liveness heartbeat: last time the owning session persisted activity
pendingAction: pendingTaskActionSchema.optional(),
})

Expand Down
Loading
Loading