{"slug": "ai-transport-v0-6-0-mid-run-steering", "title": "AI Transport v0.6.0: mid-run steering", "summary": "AI Transport SDK v0.6.0 introduces mid-run steering, allowing clients to redirect an agent while it is still processing by sending a follow-up message into the same Run. The feature uses Ably channels to route steering messages to the exact agent process, eliminating the need to cancel and restart or wait for completion. Developers can call steer() on an active Run to send a follow-up prompt, with promises indicating whether the agent consumed the message before the Run ended.", "body_md": "Steering lets a client redirect an agent while the agent is still working, so a follow-up message reshapes the answer in flight instead of cancelling the Run and starting over, or waiting for it to finish.\n\nIn the AI Transport SDK, a Run encapsulates the agent's output for a single turn (including multiple iterations around an agentic loop). The last release, v0.5.0, made a single agent turn survive a crash by splitting a Run into re-attemptable Steps. This one is about what happens when the user changes their mind while the agent is still processing.\n\n## Changing your mind mid-run\n\nA user asks the agent to plan a 3-day trip to Lisbon. Halfway through the response they decide they want 5 days, under £800. Today you have two options.\n\nYou can cancel the Run and start a new one with the combined prompt. The agent throws away every token and every piece of reasoning and research it produced so far, and the user watches the answer restart from scratch.\n\nOr, you can wait for the Run to finish and then send the follow-up as a second turn. The user sits through a full answer they already know is wrong before they get to correct it.\n\nSteering is the third option. The client sends the follow-up into the Run that's already going, the agent picks it up on its next loop iteration, and it keeps the tokens, research, and reasoning it already has.\n\nSteering is hard to build without a transport like Ably behind the conversation.\n\nFor steering to work, the client has to get a message to the agent while the agent is still responding. Over a traditional HTTP and SSE transport, there's no way for the client to send data to the server after the initial request. There's no way for the client to send a follow-up prompt to the agent to shape its output. You can open a second HTTP request for the follow-up, but then your server has to route it to the exact process running the agent's loop, and where that process lives depends on how you deploy.\n\nUsing the AI Transport SDK the agent's loop reads its input from an Ably channel, and the client publishes the steering message onto that channel, so it stops mattering which process the loop runs in or whether the client that sent the prompt is still connected. The same client and agent code works on a serverless function, a long-lived server, or a Temporal or WDK workflow.\n\n## Send a steering message\n\nA Run you started with `session.view.send`\n\nreturns the active Run. You call `steer(...)`\n\non it to send a follow-up prompt into the same Run, which is routed to the exact same agent process that started the Run:\n\n``` js\nconst activeRun = await session.view.send(UIMessageCodec.createUserMessage({\n  id: crypto.randomUUID(),\n  role: 'user',\n  parts: [{ type: 'text', text: 'Plan a 3-day trip to Lisbon.' }],\n}));\n\nconst { published, outcome } = activeRun.steer(UIMessageCodec.createUserMessage({\n  id: crypto.randomUUID(),\n  role: 'user',\n  parts: [{ type: 'text', text: 'Make it 5 days, and keep it under £800.' }],\n}));\n\nconst { serial } = await published;\nconst { consumed, runTerminalReason } = await outcome;\n```\n\n`steer(...)`\n\ngives you back two promises. `published`\n\nresolves once the steering message reaches the channel, with the serial Ably assigned it. `outcome`\n\nresolves once you know whether the agent acted on it: `consumed`\n\nis `true`\n\nwhen the agent saw the message before it sent its response, and `false`\n\nwhen the Run ended first. If it comes back `false`\n\n, tell the user the message didn't land rather than dropping it silently.\n\n## What the agent does\n\nThe agent runs a loop. After each response it checks whether it has any new input it hasn't answered yet, and a steering message counts as input:\n\n``` js\nconst run = session.createRun(invocation, { signal: req.signal });\n\nwhile (run.view.hasOlder()) await run.view.loadOlder();\nawait run.start();\n\nwhile (run.hasInput()) {\n  const conversation = run.view.getMessages().map(({ message }) => message);\n  const result = streamText({\n    model: anthropic('claude-sonnet-4-20250514'),\n    messages: await convertToModelMessages(conversation),\n    abortSignal: run.abortSignal,\n  });\n  await run.pipe(result.toUIMessageStream());\n}\n\nawait run.end({ reason: 'complete' });\n```\n\n`run.hasInput()`\n\nreturns `true`\n\nwhile there's an unanswered message, whether that's the original prompt or a steering message that arrived while the last response was streaming. Each pass rebuilds the conversation from `run.view.getMessages()`\n\n, so the steering message is already in the list the model sees. `run.pipe`\n\nrecords which steering messages the agent consumed on that pass, which is what resolves the client's `consumed`\n\nflag.\n\n## Interrupt the model call the moment it arrives\n\nThe loop above finishes the current model call before it picks up a steering message. If you want the agent to stop generating and react straight away, pass an `onSteer(...)`\n\nhook that aborts the model call without ending the Run:\n\n``` js\nlet stepAbort = new AbortController();\n\nconst run = session.createRun(invocation, {\n  signal: req.signal,\n  onSteer: () => stepAbort.abort(),\n});\n\nwhile (run.hasInput()) {\n  stepAbort = new AbortController();\n  const result = streamText({\n    model: anthropic('claude-sonnet-4-20250514'),\n    messages: await convertToModelMessages(conversation),\n    abortSignal: AbortSignal.any([run.abortSignal, stepAbort.signal]),\n  });\n\n  const pipeResult = await run.pipe(result.toUIMessageStream());\n\n  const steerInterrupted =\n    pipeResult.reason === 'complete' &&\n    stepAbort.signal.aborted &&\n    !run.abortSignal.aborted;\n\n  if (steerInterrupted) {\n    result.finishReason.catch(() => {});\n    continue;\n  }\n}\n```\n\n`onSteer(...)`\n\ntriggers the `stepAbort`\n\ncancellation signal, which cancels only the in-flight model call. The Run's own `abortSignal`\n\nstays untouched, so the Run lives on and the loop restarts with the steering message in the conversation. The two signals do different jobs on purpose: `run.abortSignal`\n\nends the whole Run, and `stepAbort`\n\nends one model call.\n\n## Steer, cancel, or send alongside\n\nSteering is one of three ways a client can act on a Run that's already processing. Which one you want depends on whether the earlier direction still stands:\n\n**Steer** keeps the Run and its output. Use it when the new message refines where the agent is already heading, like turning a 3-day plan into a 5-day one under budget.**Cancel and re-prompt** ends the Run and starts a new one.`session.cancel(runId)`\n\nfires the Run's`abortSignal`\n\n, streaming stops, and the Run ends with`reason: 'cancelled'`\n\n. Use it when the earlier direction was wrong and you want to start over.**Send alongside** starts a second Run next to the first, with its own`runId`\n\n. Both stream at once. Use it for a quick follow-up where you want to keep both answers, like \"/btw what's the weather in Lisbon\".\n\n## What the client sees\n\nSteering travels over the Ably channel like everything else in AI Transport, so it inherits the same guarantees the rest of the conversation has:\n\n- Every tab and device on the conversation sees the steering message and the redirected response.\n- A client that reconnects after a drop or joins late catches up from channel history, steering messages included.\n- The\n`consumed`\n\nflag tells the sender whether the agent acted on the message before the Run ended, so the UI can flag a steering message that didn't land instead of dropping it, or can trigger a brand new Run for that follow-up prompt.\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)[Read the steering and interruption guide](https://ably.com/docs/ai-transport/features/interruption-and-steering)[See what AI Transport is](https://ably.com/ai-transport)[Read the source and 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/ai-transport-v0-6-0-mid-run-steering", "canonical_source": "https://ably.com/blog/ai-transport-mid-run-steering", "published_at": "2026-07-22 15:29:36+00:00", "updated_at": "2026-07-22 16:08:40.209194+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["AI Transport SDK", "Ably"], "alternates": {"html": "https://wpnews.pro/news/ai-transport-v0-6-0-mid-run-steering", "markdown": "https://wpnews.pro/news/ai-transport-v0-6-0-mid-run-steering.md", "text": "https://wpnews.pro/news/ai-transport-v0-6-0-mid-run-steering.txt", "jsonld": "https://wpnews.pro/news/ai-transport-v0-6-0-mid-run-steering.jsonld"}}