{"slug": "introducing-ai-transport-v0-5-0-durable-execution-with-steps", "title": "Introducing AI Transport v0.5.0: durable execution with Steps", "summary": "AI Transport v0.5.0 introduces Steps, a feature that enables durable execution of agent turns by allowing retries without duplicate output, integrating with frameworks like Temporal and Vercel WDK to ensure reliable streaming to clients.", "body_md": "AI Transport v0.5.0 is now available. It adds first-class support for running an agent turn inside a durable execution framework, such as [Temporal](https://temporal.io/) or Vercel's [Workflow Development Kit](https://workflow-sdk.dev/) (WDK), while every client watching the conversation still sees one clean, resumable stream.\n\nThe last release, [v0.4.0](https://ably.com/blog/ai-transport-database-hydration), let an agent hydrate its history from your own database. This one is about what happens when the process running the agent isn't around for the whole turn.\n\n## Two kinds of durability\n\nA durable execution framework solves one half of running an agent reliably. Temporal and WDK break an agent turn into retryable units of work: an LLM call, a tool execution, a follow-up call. Each unit runs in its own process, and the framework retries the ones that fail. If a process crashes, a deploy cycles the container, or a spot instance gets reclaimed halfway through, the framework replays from the last unit that completed.\n\nThat leaves the other half: the conversation the user is watching. The agent's output has to reach the browser, survive a dropped connection, and stay in sync across the laptop and the phone they left the tab open on.\n\nTemporal shipped Workflow Streams and WDK has resumable streams, so the agent's output is durable right up to the edge of the framework. The gap is the step from that edge to the devices a person is watching from. Temporal's Workflow Streams emit to a Temporal SDK client, not to a browser, so you still run a web server that subscribes to the stream and relays it over SSE or WebSocket, and you own the reconnect: each client has to track the offset it resumes from. A Vercel WDK stream does reach the browser, but as a single HTTP response replayed from a position, so a second tab opens its own connection with its own cursor rather than joining one shared view. Getting a single agent turn to every device on the conversation, holding it open across a reconnect, and doing that without hand-rolling per-client offsets, is still your problem.\n\nDelivering an agent's output to clients reliably and at scale is what AI Transport is for. It carries the conversation over an Ably channel, so the stream persists on the channel, a reconnecting or late-joining client catches up from history, and every tab and device subscribed to the channel sees the same turn as it streams. v0.5.0 makes that transport safe to drive from a framework that has automatic retries.\n\n## The problem retries create\n\nWhen a durable framework retries a unit of work, it runs your code again. If that code already published output to the user on the first attempt, a plain retry publishes it a second time, and the user sees the reply twice. Keeping external side effects idempotent under retry is left to you; [Temporal's own guidance](https://temporal.io/blog/idempotency-and-durable-execution) is to build an idempotency key from the run and activity ids and dedupe against it.\n\nPublishing to a live conversation is exactly that kind of side effect. v0.5.0 handles it with Steps.\n\n## Steps, the re-attemptable unit of output\n\nA [Step](https://ably.com/docs/ai-transport/concepts/steps) is a bracket around one publishable unit of a run's output. You open it, stream through it, and close it, and it carries a stable `stepId`\n\n:\n\n``` js\nconst step = run.createStep({ stepId });\nawait step.start();\nawait step.pipe(llmStream);\nawait step.end();\n```\n\nThe `stepId`\n\nis what makes a retry safe. Two `ai-step-start`\n\nevents under the same `stepId`\n\ncoalesce on the channel: the later attempt supersedes the earlier one's output instead of appending beside it. A retried unit of work re-emits the same step, its output replaces the dead attempt's, and the reply settles once. No duplicate bubble, no orphaned half-stream.\n\n`run.pipe()`\n\nstill works as before and now brackets its output in an implicit step, so a single-process agent needs no changes. You reach for `createStep`\n\nwith an explicit `stepId`\n\nwhen a retry might re-run the same work in a different process.\n\n## Adopt an open run from a fresh process\n\nA run's lifecycle can now span several processes under one stable `runId`\n\n. The process that opens the run calls `run.start()`\n\nas usual. A later process, a retried activity or a follow-up invocation, adopts the run instead of opening a new one:\n\n``` js\nconst run = session.adoptRun({ runId, invocationId, triggerEventId });\nawait run.load(); // resolves the open run from the channel; publishes no new run-start\n\nconst step = run.createStep({ stepId });\nawait step.start();\nawait step.pipe(llmStream);\nawait step.end();\n```\n\n`load()`\n\nwaits for the run's `ai-run-start`\n\nto be observed on the channel and adopts it for publishing without republishing an opening event, so clients never see the turn restart. Combined with a stable `stepId`\n\n, a fresh process can pick up an in-flight turn and continue it as if it had been there the whole time.\n\n## Run it on Temporal\n\nTemporal reruns a failed activity under the same `activityId`\n\n. The new `@ably/ai-transport/temporal`\n\nentry point ships `stepIdFor`\n\n, which composes the run's invocation id with the current Temporal `activityId`\n\n:\n\n``` js\nimport { stepIdFor } from '@ably/ai-transport/temporal';\n\nconst run = session.adoptRun({ runId, invocationId, triggerEventId });\nawait run.load();\nconst step = run.createStep({ stepId: stepIdFor(invocationId) });\n```\n\nBecause the `activityId`\n\nis identical on a retry, the derived `stepId`\n\nmatches, and the retried activity's output supersedes the failed attempt's on the channel. Each temporal activity is a single SDK Step. The [runnable Temporal demo](https://github.com/ably/ably-ai-transport-js/tree/main/demo/temporal/use-client-session-temporal) has a deliberately flaky tool so you can watch a retried activity working with Steps in the Temporal Web UI and in the chat at the same time.\n\nWhen running on Temporal, use `stepIdFor(invocationId)`\n\nto get unique identifiers in the format `${invocationId}-${activityId}`\n\nso that Temporal can safely re-use an `activityId`\n\nacross different workflow runs without those Steps colliding on the Ably channel. A Temporal `activityId`\n\nis guaranteed to be unique within a single workflow run only, the composite of `invocationId`\n\nand `activityId`\n\nmakes them safe to use for Steps.\n\n## Run it on Vercel Workflows\n\nWDK gives each step an id that is stable across retries, so it passes straight into `createStep`\n\nwith no composition:\n\n``` js\nimport { getStepMetadata } from 'workflow';\n\nconst { stepId } = getStepMetadata(); // stable across retries of this activity\n\nconst run = session.adoptRun({ runId, invocationId, triggerEventId });\nawait run.load();\nconst step = run.createStep({ stepId });\n```\n\nThere's no separate worker or account to run: `withWorkflow`\n\ncompiles the workflow and its steps into your Next.js app's own routes, and in development WDK runs on a local backend with no extra infrastructure. The [use-chat-wdk demo](https://github.com/ably/ably-ai-transport-js/tree/main/demo/vercel/react/use-chat-wdk) has a \"Fail once\" toggle and a \"Crash\" button: arm one, send a prompt, and the reply still settles exactly once while the process panel shows the retry.\n\n## What the client sees\n\nNone of this changes the client. The same `useChat`\n\nor `useView`\n\ncode from a single-process agent works unchanged against a durable one, because the durability lives in how the agent's output is published, not in what the client subscribes to. Over the Ably channel the client gets:\n\n- A stream that persists on the channel, so refreshing the page mid-turn rehydrates the conversation and picks the stream back up.\n- Catch-up from history for a client that joins late or reconnects after a drop.\n- The same turn on every subscribed tab and device, in sync as it streams.\n- Cancellation that travels over the channel: a client's stop publishes\n`ai-cancel`\n\n, the in-flight activity's`abortSignal`\n\nfires, and the run ends cleanly.\n\nThe framework keeps the execution alive across a crash. AI Transport keeps the conversation alive across a reconnect. The two compose because the framework's retryable unit and AI Transport's supersedable unit share one id.\n\n## Get started\n\nInstall the SDK:\n\n```\nnpm install @ably/ai-transport\n```\n\n[Read the docs](https://ably.com/docs/ai-transport)[See what AI Transport is](https://ably.com/ai-transport)[Read the source, including the Temporal and WDK demos](https://github.com/ably/ably-ai-transport-js)[Sign up free](https://ably.com/sign-up): you need an Ably account and an API key to run a session, and the free tier covers everything you need to start.", "url": "https://wpnews.pro/news/introducing-ai-transport-v0-5-0-durable-execution-with-steps", "canonical_source": "https://ably.com/blog/ai-transport-durable-execution-steps", "published_at": "2026-07-10 11:02:46+00:00", "updated_at": "2026-07-10 11:37:55.723356+00:00", "lang": "en", "topics": ["ai-tools", "ai-infrastructure", "ai-agents"], "entities": ["AI Transport", "Temporal", "Vercel", "Workflow Development Kit", "Ably"], "alternates": {"html": "https://wpnews.pro/news/introducing-ai-transport-v0-5-0-durable-execution-with-steps", "markdown": "https://wpnews.pro/news/introducing-ai-transport-v0-5-0-durable-execution-with-steps.md", "text": "https://wpnews.pro/news/introducing-ai-transport-v0-5-0-durable-execution-with-steps.txt", "jsonld": "https://wpnews.pro/news/introducing-ai-transport-v0-5-0-durable-execution-with-steps.jsonld"}}