You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
Zoo (VP)
committed
fix(task-persistence): keep live delegated children during reconcile
A delegated child streaming a long turn can go minutes without writing
anything to its history file, so startup/periodic reconciliation in
another window — or this window after an extension-host restart, before
any local-ownership claim — misjudged the live child as a crash orphan
(mtime > 5 min), repaired it to interrupted, and severed the delegation
link so the child's completion was discarded (AttemptCompletionTool:
'Skipping delegation ... childStatus: interrupted, parentStatus: active').
The owning session now persists a throttled liveness heartbeat
(lastActivityAt, 60s interval, child tasks only) while streaming, and
reconcile's cross-instance liveness guard treats a child as live when
EITHER the history-file mtime OR the heartbeat is fresh within the
5-minute threshold (shared predicate isDelegatedChildLive). A child with
both signals stale — the genuine crash orphan — is still repaired.
- packages/types: optional lastActivityAt on HistoryItem
- TaskHistoryStore: recordTaskActivity heartbeat write; reconcile and
repair-intent replay use isDelegatedChildLive; skip log names the
signal that kept the child alive
- Task: start/stop liveness heartbeat around streaming for child tasks;
stopped on dispose so a trailing beat never claims life for an orphan
- taskLifecycle: isLivenessSignalFresh / isDelegatedChildLive reducers
- check-task-lifecycle model: heartbeat/expireHeartbeat actions,
heartbeat-protected landmark, invariant 8 covers either signal
- docs: task-lifecycle-model.md mapping/landmarks/invariant updated
|`markLiveElsewhere(child)` / `expireLiveElsewhere(child)`| child history-file mtime recent vs stale past `LIVE_CHILD_MTIME_THRESHOLD_MS` (abstracted; no wall clock in model) |
53
+
|`heartbeat(child)` / `expireHeartbeat(child)`| persisted `lastActivityAt` liveness heartbeat fresh vs stale past `LIVE_CHILD_MTIME_THRESHOLD_MS` (production: the owning session's throttled heartbeat during a long streaming turn, shared predicate `isDelegatedChildLive`) |
| Event interleaving | Competing completion, cancellation, abandonment, and new delegation calls |
55
56
56
-
The model has three fixed task slots, enough to cover competing siblings and a nested parent-child-grandchild chain, plus one abstract boolean per slot recording whether an active child's session is owned by another window (recent history-file mtime). 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, and a stale-mtime (crash-orphan) active child being repaired to `interrupted` with the parent returned to `active` only through `reconcileStartup`.
57
+
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`.
57
58
58
59
Production completion also accepts a recovery-compatible `active` parent that still awaits the returning child, then clears the stale pointers. Normal model transitions never create that intermediate state, so it is covered by a focused reducer test rather than admitted as a generally valid reachable state.
59
60
@@ -136,7 +137,7 @@ The task delegation checker currently enforces:
136
137
5. Parent-child lineage is acyclic.
137
138
6. Completed task records cannot be changed by later lifecycle events.
138
139
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.
139
-
8. No transition may clear a delegated parent's link to a child that is active and marked live-elsewhere (`ModelState.liveElsewhere` true); reconciliation repairs the link only when that flag is false. This encodes the PR #1495 cross-window misrepair bug class, which broke delegation links so subtask completion could not return to the parent.
140
+
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.
140
141
141
142
The completion persistence checker additionally enforces:
0 commit comments