Repository navigation
Description
Symptom
A thread round submitted via hub.StartThread(...) can wedge at Status = StartingExecution forever: the user message stays in pendingUserMessages, no cell is allocated, no agent round runs, and nothing ever recovers it. The thread only executes when its hub is explicitly re-activated (e.g. a DisposeRequest recycle of the main node followed by an access).
Production evidence (memex portal, UW Deepfield Industry News auto-update)
Two gather rounds (agent Assistant, model DeepSeek-V4-Flash, submitted through the canonical hub.StartThread surface from a layout-area click):
Thread (under UWDeepfield/IndustryNews/_Thread/) |
Submitted | Wedged state | Recovered |
|---|---|---|---|
gather-…-2958 |
2026-07-16 05:27:46 | StartingExecution, message in pendingUserMessages, thread version 1 — for 2.5 days |
2026-07-18 ~21:50, within seconds of an explicit recycle of UWDeepfield/IndustryNews + next access; thread ran to completion (v1 → v90), items landed correctly |
gather-…-b0f2 |
2026-07-18 21:41:11 | same | same moment, same way (v1 → v90) |
Trigger correlation — both wedged rounds were submitted ~60–70 s after a NodeType compile + release recycled the main-node hub:
- Release
20260716052635at 05:26:35 → thread submitted 05:27:46 → wedged. - Release
20260718214041at 21:40:41 → thread submitted 21:41:11 → wedged.
(Opening the feed triggers the stale-assembly recompile/release, which recycles the per-node hub; the user's toggle click then lands right on that boundary.)
The model was exonerated: both rounds executed quickly and correctly on DeepSeek-V4-Flash once the hub was re-activated.
Root cause
The round dispatch is a two-edge handshake:
- Claim —
InstallSubmissionWatcher(on the thread hub's action block) writesIdle → StartingExecution. - Commit — the
_Execround watcher's first emission commitsStartingExecution → Executingand launches the round.
When the commit edge is lost — here: the _Exec startup raced the release-recycle of the freshly claimed thread's hub environment — the claim survives as durable node state but no recovery path exists. InitializeThreadLifecycle (src/MeshWeaver.AI/ThreadExecution.cs) self-heals every other state on activation:
Executing+ActiveMessageId→ resume the interrupted round;Executingwithout a cell → reset toIdleso the submission watcher re-claims;Idle/Cancelled+ pending → submission watcher claims;StartingExecution→ deliberately no write: "the_Execround watcher fires on its own first emission."
That assumption doesn't hold when the claim was made by a previous hub incarnation (or _Exec never came up): nothing activates _Exec, so the "first emission" never happens, and the thread is parked in a state that ThreadStateTransitions treats as in-flight — resubmission is refused, the submission watcher won't re-claim. The cancel path already acknowledges this exact window (the "No-CTS claim-window fallback" writes terminal Cancelled directly when Status is stuck at StartingExecution), but normal resumption has no equivalent.
Needed fix (root cause — no timers/watchdogs)
On thread-hub activation, StartingExecution must not be a no-op. Activation is proof that no round is actually in flight (executions die with their hub), so the recovery can act deterministically, mirroring the existing Executing branches:
- either drive the commit: ensure the
_Exechub is started and its round watcher observes the claimed state (so the claim → commit → launch chain completes), or - roll the orphaned claim back (CAS-guarded, same idempotent shape as the
Executing-without-cell reset):StartingExecution → Idle, lettingInstallSubmissionWatcherre-claim the still-queuedpendingUserMessages.
Care needed to not reintroduce the re-dispatch ping-pong documented in ThreadStateTransitions (Executing → StartingExecution stays illegal; this is only about an orphaned claim on a freshly activated hub, before any commit).
Deterministic repro to pin the fix (per WritingTests.md): submit via StartThread, dispose the hub after the claim write lands but before _Exec commits, re-activate the hub, assert the round executes and pendingUserMessages drains — observed via GetMeshNodeStream(threadPath).Where(...).FirstAsync().Timeout(...).
Impact
Any StartThread submission racing a hub recycle (NodeType release, redeploy, grain migration) silently never runs; from the product side it reads as "the agent ignored me". In UW Deepfield the feed's 6-minute stale-guard clears the UI, so the failure is doubly silent — the round just never happens. A 2.5-day wedge was observed in production with the page being reopened repeatedly in between; only an explicit recycle recovered it.
Pointers
src/MeshWeaver.AI/ThreadExecution.cs—InitializeThreadLifecyclerecovery branch table (theStartingExecutionno-op),InstallSubmissionWatcher,_Execround-watcher commit edge, No-CTS claim-window cancel fallback.src/MeshWeaver.AI/ThreadStateTransitions.cs— legal edges;StartingExecutioncounts as in-flight, so nothing re-claims it.- Field diagnosis thread: memex
UWDeepfield/IndustryNewsauto-update, 2026-07-18/19.
Activity
rosalie1984 commentedon Jul 18, 2026
Update — the wedge is NOT a release-window race; on the distributed portal the commit edge never fires at all
New evidence from three further rounds (same feed, same
StartThreadclick path, submitted 2026-07-18 21:58:08, 22:01:11, 22:01:36 UTC):StartingExecutionwith the message inpendingUserMessages, thread version 1.memex, Orleans), every round submitted through this path wedges: the claim (Idle → StartingExecution) is written, and the_Execround watcher's commit never fires spontaneously. Not one of the ~10 rounds observed since 2026-07-15 ever executed without an external nudge.What does and doesn't recover a wedged round (all verified live):
get)DisposeRequest) aloneThe ~11-minute gap between submission and the successful poke is consistent with the thread hub having idled out and the write forcing a fresh activation, i.e. recovery-on-activation works; the spontaneous claim→commit handoff is what's broken. (A delayed self-nudge from application code would be a watchdog band-aid and is not an acceptable interim.)
Sharpened fix requirements:
InstallSubmissionWatcherwrites the claim, the_Execexecution hub must be deterministically started/driven as part of the same dispatch — or the commit must happen on the thread hub itself.StartingExecution(as originally filed): on thread-hub activation, an orphaned claim with queuedpendingUserMessagesmust be rolled forward (drive_Exec) or back (→ Idle, CAS-guarded) — activation proves nothing is in flight.Repro guidance stands, but the deterministic test should cover the distributed case: claim written,
_Execnever activated, thread hub deactivates; assert (a) a fresh dispatch commits without external writes, and (b) re-activation drains an orphaned claim.Live impact update: UW Deepfield's Industry News auto-update is effectively dead on the portal — every toggle-triggered gather wedges silently and only runs when something else happens to write to the thread node.
Merge pull request #543 from Systemorph/fix/539-startingexecution-orp…