Durable-Object boot wiring, in two phases with different lifetimes.
REGISTRATION (memoized for the life of the instance): validate the storage domains and re-register the static executors. Nothing here subscribes, claims, or recovers, so it is safe in every fence state — and it must be, because every host boots before routing and the read routes hang off this memo.
DISPATCHING (fence-gated, re-attempted on every boot until it is admitted):
startWorkers() plus manager.init(pubsub). Those two are what make this
instance CLAIM work — see #ensureDispatching for why they cannot run behind
a closed fence. A refused attempt is deliberately not memoized, so the next
boot() from a request or an alarm starts dispatching once the fence
reopens, with no operator action.
A registration that FAILED is memoized only when it can only fail again: the same non-memoized-refusal reasoning #ensureDispatching applies to the fence. A transient storage fault on the first request into a fresh isolate would otherwise be the permanent answer for the life of that isolate — the read routes hang off this memo, so the host would keep 500ing a queue it can now reach, until an eviction nobody can schedule.
Enqueue a background task through the fence.
A drain deliberately still ACCEPTS enqueues: the caller is an agent's tool call inside a run the drain is trying to finish, and refusing would fail exactly the runs that are draining. Locked and proof-only refuse, because a row written then is work the migration would have to carry.
The per-task executor is wrapped with the same gate as the static ones, so a task enqueued while open cannot execute its body after a transition.
Optionalcontext: TaskContextOne task by id, or null.
Tasks matching a filter. The route adapter re-checks scope per row.
Optionalfilter: TaskFilterDO-alarm duty: ensure the DO is booted (a FRESH post-eviction instance
recovers HERE, since the alarm is what woke it — see the recovery seam
above), then run the manager's TTL cleanup. The hosting DO re-arms the alarm
after this returns. Cleanup is core's own cleanup() — the belt to the
storage-layer purgeExpiredBackgroundTasks maintenance duty (which needs no
live manager); running both is harmless (each only deletes rows past the
TTL).
Lifecycle-event stream, for the route adapter's SSE response.
Optionaloptions: {}
A BackgroundTaskManager bound to a hosting DO's Mastra + pubsub, with the boot/alarm lifecycle that makes DO eviction survivable. The hosting DO owns alarm arming (it needs
ctx.storage.setAlarm); this class owns the manager wiring, the recovery-firingboot(), and the alarmcleanup()duty.The manager itself is PRIVATE. It carries
enqueue,registerTaskContext,registerStaticExecutor,resume, andrestart— every one of which puts a task body on this deployment WITHOUT passing the fence, because the gate that stops a locked deployment executing is the#gatedwrapper this host puts around an executor on the way in. A caller holding the manager could enqueue an unwrapped executor and defeat both. What this class forwards instead is the fence-gatedenqueueand the three READS the host route adapter serves.