refactor(chat): run recovery continuations on Tasks - #2194
Open
mattzcarey wants to merge 8 commits into
Open
Conversation
🦋 Changeset detectedLatest commit: 56e4635 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
agents
@cloudflare/ai-chat
@cloudflare/codemode
hono-agents
@cloudflare/shell
@cloudflare/think
@cloudflare/voice
@cloudflare/worker-bundler
commit: |
Base automatically changed from
chore/alarm-memory-limit-domain-boundaries
to
main
September 1, 2026 14:44
Two confirmed gaps (pinned red-first in memory-limit.test.ts): 1. The driver's in-process dispatch retry converted a memory-limit reset into a silent success — the retry found the half-claimed run not due, returned void, and the wake was deleted without the breaker ever engaging. Memory-limit resets now defer to the alarm boundary like code-update resets: the isolate is condemned either way. 2. The run row outlives the breaker's queue-row policy: startup reconciliation re-derives due-now wakes from it, resurrecting a doomed run through backoff and past sealing. MemoryLimitContext now carries the striking job's identity, and Tasks applies the breaker to the run itself — demoted to the backoff wake on a strike (claim stripped so reconcile honors the deadline), terminally failed (task:failed, TaskMemoryLimitSealed) when the breaker seals. Generic for every definition: the striking run is the one that exhausted memory. Claude-Session: https://claude.ai/code/session_011QZUJztM1rMTsHEC7mbcbz
Replace root-agent recovery schedule rows with chained runs of the reserved __cf_internal_chat_recovery Task definition shared by AI Chat and Think. Initial attempts deduplicate by incident, delayed retries use step.sleep, and the existing bounded callback methods still detach at model handoff. Legacy and routed dynamic-agent schedules remain as compatibility shims. Make Tasks breaker-safe for recovery definitions: condemned-isolate failures escape journal retries, flagged framework definitions carry queue membership, and onMemoryLimit aligns or seals their authoritative run rows so startup cannot resurrect purged work. Think's submission sweep now inspects Tasks rather than Scheduler rows. AI Chat and Think require agents >=0.23.1, the release containing the shared definition and internal enqueue aperture.
Apply retain: false through one terminal cleanup path shared by completed, failed, and cancelled runs. A memory-limit seal now emits task:failed, removes the run and journal, cancels its wake, and releases its idempotency key. Make the breaker regression tolerate only the intentional workerd isolate reset and use per-attempt Durable Object names so Vitest retries cannot collide with durable rows from the failed attempt.
Preserve #2192's exact atomic helper shape while checking the Task transport through a separate same-RPC method. Align chat peer ranges with the pending changesets release batch, which publishes this work in agents 0.23.0.
mattzcarey
force-pushed
the
feat/replatform-chat-recovery-on-tasks
branch
from
September 1, 2026 15:02
0dbc1a3 to
310dfce
Compare
Upgrade existing Lifecycle job tables before reading the recovery-loop flag. Let Task wakes defer after one JobDriver attempt because ReplayStep owns their durable retry budget, including reconciliation of older wake rows. Keep pre-handoff chat failures on the current Task or schedule, and enqueue a replacement only after the bounded callback has handed off to the model turn.
When bounded Task dispatch returns before an attempt, preserve a durable late-memory-limit marker if that detached attempt later OOMs. The next alarm rethrows the canonical signal inside JobDriver so existing strike, backoff, and sealing policy remains authoritative. Mark detached job outcomes so their alarm is not treated as a clean breaker cycle, preserve marker wakes through startup reconciliation, and cover queued and warm attempts plus marker cleanup.
When work previously detached from an alarm settles without a memory reset, enqueue one no-op Task wake. That wake restores any authoritative run deadline and gives the existing JobDriver a clean alarm boundary to clear stale strikes. Late OOMs keep using the existing late-memory-limit marker.
Keep Lifecycle's deadman alarm armed while it joins promises registered at bounded job handoffs. Classify the whole dynamically growing alarm batch once: all clean work clears prior strikes, while any memory reset enters the existing breaker once with its captured executing job. Tasks now only registers attempts at its five-second handoff, and AI Chat and Think register post-handoff model work so Task and Scheduler transports share the same breaker. Remove the Task-specific late marker jobs and their state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Base
Rebased onto
mainafter #2190 merged. The rebase also incorporates #2191's Tasks write optimizations and #2192's deterministic Durable Object test helpers.Why this changed
Root AI Chat and Think recovery still used Scheduler rows even though Tasks had become the durable replay primitive. Moving recovery onto Tasks exposed a broader alarm-lifetime issue: a job can return at a bounded handoff so Lifecycle can continue its due batch while the promise it started is still running. A memory reset from that promise still belongs to the alarm that started it, but it no longer reaches the alarm's call stack unless Lifecycle retains that relationship.
This is an alarm-domain concern, not Task or Scheduler policy.
Final architecture
Lifecycle owns bounded alarm work
trackAlarmWork(promise)lets a bounded job handoff register the work it started with the current alarm.Tasks is only an adapter to that boundary
retain: falseremoves completed, failed, and cancelled runs, journals, wakes, and idempotency keys.Chat recovery uses Tasks without losing Scheduler compatibility
__cf_internal_chat_recoveryTask runs._chatRecoveryContinueand_chatRecoveryRetryreturn at model handoff, preserving queue liveness.tasks.list().Upgrade and breaker correctness
cf_agents_jobstables are upgraded idempotently withrecovery_loopbefore any query references it.step.doimmediately.What we found during review
The first downstream approach used Task-specific late-OOM and clean-settlement marker jobs. It handled one detached Task, but it made Tasks responsible for alarm policy and failed when several promises belonged to the same alarm: one clean Task could clear strikes before a sibling reported OOM. It also left Scheduler-originated recovery as a separate case.
The final version removes those Task marker jobs and moves promise retention and group classification into Lifecycle. Tasks, Scheduler-driven chat recovery, and future alarm-backed capabilities now use one small core boundary instead of reimplementing breaker behavior downstream.
Compatibility and release
_chatRecoveryContinue/_chatRecoveryRetryScheduler rows still dispatch through the retained methods and drain normally.agents >=0.23.0, the pending changesets release batch containing the shared runtime support.Verification
Current head:
56e4635f.git diff --checkpasses.Regression coverage in the branch includes: