Developer

Designing Cloudflare Agents for Disconnects, Durable Work, and Route Failures

A production agent cannot depend on a browser tab staying open until the work is done. If a refresh, dropped connection, or temporary routing error can erase a turn—or run the same operation twice—the model is not the problem. The pipeline is.

That is what makes Cloudflare Agents SDK v0.12.4 relevant. Released on May 13, 2026, it adds or improves several reliability primitives: chat recovery, reconnect state synchronization, durable Think submissions, configurable routing retries, and Voice connection control.

The important part is not the mere presence of retries. Any team can write a retry loop. What matters is that interrupted streams, accepted work, and agent resolution can now be treated as separate lifecycle problems.

That separation is the difference between a resilient pipeline and several overlapping retry loops that nobody fully controls.

Reliability Is More Than an Open WebSocket

Take a typical long-running interaction.

A user starts an agent turn. The application resolves a route to the agent, and the server begins streaming a response. Halfway through, the browser goes offline. The server may still be working, but the client no longer knows what happened.

Now the real operational questions begin:

  • Does the server turn continue?
  • Can the client reconnect and recover partial output?
  • Was the underlying work durably accepted?
  • If the original request is retried, will it execute twice?
  • What happens if agent resolution temporarily fails?
  • Does closing the tab mean “cancel,” or only “disconnect”?
  • How does the interface distinguish running, reconnecting, cancelled, and failed?

A successful model call answers none of them.

Cloudflare’s Agents runtime provides a useful foundation. As described in the Cloudflare Agents documentation, hosted agent sessions can have durable identity, local SQL storage, real-time connections, scheduled work, and recoverable execution. The application still needs an explicit reliability policy.

The cleanest way to reason about that policy is through three layers:

  1. Transport continuity: recover a streamed conversation after connectivity is interrupted.
  2. Durable work ownership: ensure accepted work can outlive the request that initiated it.
  3. Routing resilience: handle transient failures while resolving the target agent.

These mechanisms work together, but they do not replace one another.

What Agents SDK v0.12.4 Changes

The release improves reliability across several related packages and workflows.

In @cloudflare/ai-chat, a server turn can continue after the browser or client stream is interrupted. That includes page refreshes, closed tabs, and temporary connectivity loss. The release also fixes reconnect synchronization issues that could break recovery even when the underlying server work survived.

For Think workflows, submitMessages() provides durable acceptance and operational controls around server-driven turns. Think.chat() runs turns in recovery fibers and persists streamed chunks, allowing partial output to survive interruption or Durable Object eviction.

Agent resolution now exposes configurable routing retry behavior, including a routingRetry.maxAttempts option in Cloudflare’s release material.

Voice connection control is also included in v0.12.4. For this production pipeline, however, the critical path runs from route resolution to durable execution and finally to recovered client output.

The official Agents changelog covers the release details. The implementation challenge is combining those primitives without turning every failure into an unbounded retry.

Chat Recovery Separates Work From Connectivity

The most consequential chat change is straightforward: interrupting the client stream no longer has to terminate the server turn.

That shifts the ownership model. The browser remains the viewer and control surface, but it does not need to own the lifetime of the work.

If someone refreshes during a long response, briefly loses connectivity, or closes and later reopens the interface, server processing can continue. When the client reconnects, the application can recover the turn instead of silently abandoning it.

That does not mean every disconnect should be ignored. Accidental transport interruption and intentional cancellation are different events and should stay different in the application state.

Calling stop() still cancels an active server turn. Developers can also set:

cancelOnClientAbort: true

This option makes sense when continued execution has no value after the caller leaves. A costly exploratory turn whose output will never be consumed may fit that case. A research task the user expects to revisit later probably does not.

A global default chosen for convenience is risky here. The better decision is per workflow, based on the cost and value of orphaned execution.

The release’s bug fixes also show why reconnection is more than opening another socket. One fix addresses a stream-resume race in which replay could encounter an already closed WebSocket. Another addresses recovered continuations that could leave useAgentChat stuck in a streaming state when the original socket disconnected before a terminal response.

Those are protocol-state failures. The server may finish useful work while the interface remains permanently “streaming” or reconnects into the wrong state. Application-level retry code often compounds the problem by introducing another state machine without a shared source of truth.

A clean mid-stream disconnect is not enough to validate chat recovery. Test interruptions:

  • Before the first streamed token.
  • During partial output.
  • Near the terminal response.
  • During replay after reconnection.
  • Through a full page refresh.
  • Through an offline-to-online transition.
  • After explicitly closing the socket.
  • Separately from an intentional stop() call.

The invariant should be stronger than “the socket reconnects.” Every turn should settle into one understandable state: still running, resumed, completed, cancelled, or failed.

Durable Think Submissions Give Work an Identity

Chat recovery protects continuity around a turn. It does not automatically give every background operation durable ownership.

For server-driven work that needs to continue after the initiating caller returns, @cloudflare/think provides submitMessages(). According to Cloudflare’s documentation, durable Think submissions support:

  • Durable acceptance.
  • Idempotent retries.
  • Status inspection.
  • Cancellation.
  • Cleanup.

Each capability addresses a separate operational failure.

Durable acceptance separates “the system accepted this work” from “the initiating request remained open.” That distinction matters whenever the request ends before processing does.

Idempotent retries deal with uncertainty. If the caller loses the response after submitting work, it may not know whether the system accepted it. Repeating the same HTTP request blindly can create duplicate work. An idempotent submission lets the caller retry without converting uncertainty into duplicate execution.

Status inspection lets the interface report an actual state instead of guessing from elapsed time.

Cancellation gives the user or operator a deliberate way to stop work.

Cleanup keeps abandoned submission records from accumulating indefinitely.

The submission identifier belongs in application state. Retain it, associate it with the user’s initiating intent, and expose its status in the interface. Burying it inside a request handler and later trying to reconstruct the job from conversation text is not a reliable design.

The state model can remain plain:

pending -> running -> completed
                   -> failed
                   -> cancelled

The UI may add states such as reconnecting or resumed, but it should not report completion until the durable operation has a terminal result.

Think’s chat path introduces another recovery layer. The Think documentation in the Cloudflare Agents repository says Think.chat() turns run in recoverable fibers and persist stream chunks. If a Durable Object is evicted during a stream, Think can reconstruct buffered chunks, preserve partial output, and schedule continuation of the assistant turn or retry an unanswered user turn.

That is materially safer than restarting every interrupted turn from the beginning. A full restart can waste computation, produce inconsistent output, or repeat tool-backed actions.

The same documentation states that chat recovery is always enabled. Recovery configuration remains tunable, but chatRecovery = false is no longer supported. Recovery is therefore part of the execution model, not an optional client feature.

One boundary still needs explicit enforcement: idempotent submission does not make every business action idempotent. If an agent can send a message, update a record, or trigger another external side effect, the action needs its own deterministic identifier and duplicate protection. Durable acceptance protects the job lifecycle. It does not make ambiguous side effects safe.

Routing Retries Need a Defined Boundary

Before a turn can stream or a job can run, the application has to resolve the relevant agent. Agents SDK v0.12.4 makes routing retry behavior configurable, including a maximum-attempt setting:

routingRetry: {
  maxAttempts: /* bounded value */
}

There is no useful universal number. The value should fit the workflow’s latency budget.

Routing retries are appropriate for transient resolution failures. They do not fix persistent configuration errors, invalid agent identities, or unavailable dependencies.

More attempts may improve the chance of surviving a brief failure, but each attempt adds latency. The larger risk is retry multiplication across layers:

  • An outer API client retries the request.
  • The application handler retries the operation.
  • Agent resolution performs routing retries.
  • A downstream call retries again.

One user action can then generate far more attempts than anyone intended. If the operation is not idempotent, that can be worse than a visible failure.

Define one bounded retry policy at each failure boundary. Record every attempt, the final outcome, and the latency introduced by retries. When the budget is exhausted, return a clear terminal error instead of leaving the interface in an endless loading state.

The rule is simple: retry failures that are plausibly transient and only within a known budget. Refuse retry loops that mask persistent errors or make user-facing latency impossible to predict.

How the Reliability Layers Fit Together

A resilient agent workflow may use all three mechanisms in sequence.

First, the application resolves the target agent. The initial route resolution fails transiently, so the configured routing policy makes another bounded attempt. Resolution succeeds.

Next, the application submits a long-running Think turn. The system durably accepts the submission, assigns it an identity, and the application stores that identity.

The browser disconnects while output is streaming. Server-side work continues. Stream chunks remain recoverable, and the application can inspect the submission independently of the original request.

When the browser returns, it reconnects, synchronizes state, and recovers the available output. If the user deliberately cancels, the application sends an explicit cancellation rather than treating every transport interruption as intent to stop.

Each layer has a narrow responsibility:

Reliability layer Failure handled What it should not replace
Chat recovery Interrupted client streams and reconnects Durable job ownership
Durable submissions Work that must outlive the initiating request Transport synchronization
Routing retries Transient agent-resolution failures Persistent-error diagnosis

Calling all three “retries” hides the architecture. One restores a stream, another preserves accepted work, and the third repeats a bounded resolution attempt.

SDK Defaults Cannot Make Product Decisions

SDK primitives cannot decide whether abandoned work remains useful, how much latency users should tolerate, or which side effects can safely repeat. Those decisions belong to the product and its operators.

Start with user intent.

If a user launches a report and expects to return later, leaving the client should probably not cancel the work. If the operation is expensive, speculative, and useful only while the user is watching, cancelOnClientAbort: true may be the right choice.

Define idempotency around that intent, not around a socket connection. When appropriate, repeated clicks, browser retries, and caller retries should refer to the same intended operation. A submission ID should map to one durable piece of work. External side effects still need separate, deterministic duplicate controls.

Keep deterministic lifecycle decisions outside model output. A model should not decide whether two submissions are operationally identical, whether the retry budget is exhausted, or whether cancellation was acknowledged. Put those decisions in schemas, stored state, and application code.

Human control remains important wherever consequences are meaningful. Long-running work needs visible cancellation. Failure states should be inspectable. A “reconnecting” label should never conceal a job that has no terminal state.

Cleanup also needs a policy. Durable work is useful precisely because it survives the caller, but abandoned submissions cannot remain forever without review. Track active jobs, terminal jobs, and records waiting for cleanup.

Test Failure Boundaries Before Release

Happy-path integration tests do not validate these mechanisms. Reliability testing requires interruptions at specific boundaries.

Interrupt the chat stream before the first token, during output, and immediately before completion. Refresh the page while a turn is active. Close the tab, reconnect, and verify that client state matches the server instead of starting a second turn.

Simulate Durable Object eviction during a Think stream. Confirm that buffered chunks and partial output recover as documented. Test an unanswered user turn separately so the retry path does not duplicate a completed assistant turn.

Resubmit the same durable operation through the intended idempotent path. Verify that it does not create unintended duplicate work. Then repeat the scenario with an external side effect and confirm that business-level duplicate protection still holds independently of submission idempotency.

Force both transient and persistent routing failures. The transient case should recover within the configured attempt budget. The persistent case should stop at the limit and surface a useful terminal error.

Finally, test accidental interruption separately from explicit control:

  • Network loss should follow the recovery policy.
  • stop() should cancel the active server turn.
  • Submission cancellation should reach a terminal cancelled state.
  • cancelOnClientAbort should only be enabled where departure genuinely means cancellation.

A suite that collapses all four paths into “the request ended” is not exercising the real lifecycle.

Observability Should Expose Limbo

Recovery without observability creates invisible limbo.

At minimum, structured lifecycle events should include:

  • Turn ID.
  • Submission ID.
  • Agent identity.
  • Reconnect event.
  • Routing attempt number.
  • Cancellation source and reason.
  • Current and terminal status.

Useful operational signals include recovery success, suspected duplicate work, time spent streaming without progress, latency added by route retries, submissions without terminal states, and cleanup backlog.

Avoid logging full conversation content when identifiers and structured events are sufficient. The goal is to reconstruct the execution lifecycle, not copy sensitive prompts and responses into every diagnostic stream.

Alerts should focus on broken invariants:

  • Repeated recovery loops.
  • Exhausted routing retries.
  • Jobs that never reach a terminal state.
  • Clients stuck in streaming after the server has finished.
  • A growing backlog of abandoned submissions awaiting cleanup.

These signals expose orchestration failures that request-level success rates can miss.

What I Would Require Before Shipping

Upgrade to Agents SDK v0.12.4 or a later compatible release, then read the official release notes rather than assuming the new defaults fit every workflow.

Audit custom WebSocket recovery and retry patches before removing them. Home-grown logic may now conflict with SDK recovery, but deletion should follow failure testing—not optimism.

For every chat workflow, make an explicit decision about whether client abort cancels server work. Applying cancelOnClientAbort indiscriminately is not a reliability policy.

Use durable Think submissions when work needs to survive beyond the initiating caller. Persist submission identifiers, expose status, support cancellation, and define cleanup.

Configure bounded routing retries against user-facing latency limits. Check the full call path so outer client and application retries do not amplify them.

Before rollout, test disconnects, reconnects, Durable Object eviction, duplicate submission, cancellation, transient routing failure, and persistent routing failure.

The final invariant is the one worth enforcing: every accepted operation eventually reaches one understandable terminal outcome without disappearing or executing twice.

The Skeptical Takeaway

Agents SDK v0.12.4 is a meaningful reliability release. It does not, by itself, make an agent application production-ready.

Cloudflare now provides stronger primitives for continuing turns across client interruption, durably accepting server-driven work, preserving partial Think output, synchronizing reconnect state, and retrying transient routing failures. That removes a substantial amount of fragile recovery logic from the application layer.

The difficult policies still belong to builders: retry budgets, cancellation semantics, business-level idempotency, user-facing states, cleanup, and monitoring.

As we have put it before: “If an agent cannot survive a network blip without losing its place in the conversation, it isn’t production-ready; it’s a demo.”

There is a second test. If interrupted work cannot reach one clear outcome—completed, failed, or cancelled—without vanishing or running twice, the pipeline is not durable yet.

Sources and further reading

Back to top ↑

Rody

Founder & CEO · RodyTech LLC

Founder of RodyTech LLC in Iowa. I write practical notes on automation, infrastructure, security, and software decisions for builders and business operators.

No comments yet

Leave a comment

Your email address will not be published. Required fields are marked *