Get to know Cadenya
We’re developers who love to build. We set out to create a yes-code platform that makes building agents feel like the best parts of building software.
A Vercel AI SDK agent gets durable in two layers. Vercel AI SDK resumable streams (useChat resume, the resumable-stream package and Redis) keep a reply flowing after a closed tab or a reload. A durable runtime keeps the agent itself alive through a crash, a tool approval, and a human in the loop wait: WorkflowAgent on Vercel Workflow, or the Temporal, DBOS, Inngest and Restate integrations.
So use resumable streams alone when a turn is short and a crash can cost you that turn. Pick WorkflowAgent when you deploy on Vercel and want needsApproval right on the tool. Pick Temporal, DBOS, Inngest or Restate when your team already runs one of them. And if you’d rather run none of them, Cadenya runs the agent loop for you, so the stream and the approval survive your deploys. What it offers is at the end.
Every price and limit below comes from each vendor’s own pages, checked on October 13, 2026.
Durable agent options for the Vercel AI SDK side by side
Coding agents that pick the AI SDK usually pair it with a durable runtime: Vercel Workflow most often, then Temporal, DBOS, and Inngest. Here’s what each one gives you.
| What you add | A crash mid-turn | Tool approval | Resuming the stream | |
|---|---|---|---|---|
| AI SDK resumable streams | Redis, a store for the active stream ID, and POST and GET routes | ”Lost on crash” (the AI SDK’s words for ToolLoopAgent) | toolApproval returns a request, and the next call carries the answer | useChat with resume, plus a GET route |
| WorkflowAgent on Vercel Workflow | @ai-sdk/workflow and workflow@5 | ”Survives restarts” | needsApproval on the tool | WorkflowChatTransport and a GET route per run |
Temporal (@temporalio/ai-sdk) | A worker, plus a Temporal Service or Temporal Cloud | Model calls run as Activities and replay | Signals or Updates, written by you | Workflow Streams to subscribers outside the workflow |
DBOS (@dbos-inc/vercel-ai) | Postgres and a worker | Replays from checkpoints in Postgres | DBOS.recv and DBOS.send, written by you | readDurableStream emits UIMessageChunk |
| Inngest | Inngest Cloud or the self-hosted binary, with step.run() around each call | Retries only the step that failed | step.waitForEvent() with a timeout | Realtime channels |
Restate (@restatedev/vercel-ai-middleware) | A Restate server or Restate Cloud | durableCalls replays saved model responses | Awakeables (durable promises) | The pubsub library over SSE with an offset |
Vercel AI SDK resumable streams: useChat resume and persistence
Resumable streams fix the closed tab. The AI SDK docs say it plainly: “Closing a tab, refreshing the page, or calling stop() only closes the current HTTP connection and should not cancel the underlying generation.”
On the client, you turn on resume:
const { messages, sendMessage, status } = useChat({
id: chatData.id,
messages: chatData.messages,
resume, // Enable automatic stream resumption
transport: new DefaultChatTransport({ /* send the chat id */ }),
});
“When you enable resume, the useChat hook makes a GET request to /api/chat/[id]/stream on mount to check for and resume any active streams.” The rest is yours to build. The docs list it: storage that tracks which stream belongs to each chat, Redis to hold the UIMessage stream, a POST route that creates streams through consumeSseStream, and a GET route that calls resumeExistingStream. A real stop button needs a third route, because stop() now means “disconnect.”
Resumable streams keep the reply, not the agent. The generation still runs inside your function. A ToolLoopAgent “runs entirely in memory”, and if the process crashes, “all progress is lost.”
Vercel AI SDK durable agent: WorkflowAgent on Vercel Workflow
WorkflowAgent is the AI SDK’s own answer. It “provides the same agent loop as the ToolLoopAgent” and runs it inside a Vercel Workflow, where each tool marked 'use step' becomes a durable step with three retries by default. (Tools without 'use step' still work, “without durability guarantees.”)
You install @ai-sdk/workflow and workflow@5, start the run from your route, and hand the client a transport that reconnects:
const transport = useMemo(
() =>
new WorkflowChatTransport({
api: '/api/chat',
maxConsecutiveErrors: 5,
}),
[],
);
The transport “detects when a stream ends without a finish event and reconnects to resume from where it left off.” Your POST route returns an x-workflow-run-id header, and a GET route at {api}/{runId}/stream replays the run’s stream. No Redis is involved, since the workflow stores the stream.
Workflow pricing is $0.02 per 1,000 events after 50,000 included, and “Functions invoked by Workflows continue to be charged at the existing compute rates.”
AI SDK tools: tool calling, multi-step and tool approval
AI SDK tools are plain objects you pass to generateText, streamText or ToolLoopAgent. The docs say they run in the “same process as your request,” so whatever a tool does, it does inside your function.
Tool calling with tool(). A tool has a description, an inputSchema (a Zod or JSON schema), and an optional execute function, and the tool() helper infers the types of the execute parameters. The model’s request to use a tool is a tool call, and what the tool returns is a tool result. execute is optional “because you might want to forward tool calls to the client or to a queue instead of executing them in the same process.” Set toolChoice to auto, required, none, or one named tool. And strict: true makes providers that support it “only generate tool calls that are valid according to your defined inputSchema.”
import { z } from 'zod';
import { generateText, tool, isStepCount } from 'ai';
const result = await generateText({
model: "anthropic/claude-sonnet-5.5",
tools: {
weather: tool({
description: 'Get the weather in a location',
inputSchema: z.object({
location: z.string().describe('The location to get the weather for'),
}),
execute: async ({ location }) => ({
location,
temperature: 72 + Math.floor(Math.random() * 21) - 10,
}),
}),
},
stopWhen: isStepCount(5),
prompt: 'What is the weather in San Francisco?',
});
Multi-step calls with stopWhen. By default, generateText and streamText run one generation. “When stopWhen is set and the model generates a tool call, the AI SDK will trigger a new generation passing in the tool result until there are no further tool calls or the stopping condition is met.” The built-in conditions are isStepCount(count), hasToolCall(...toolNames) and isLoopFinished(), and an array of them stops on the first one that matches. prepareStep changes the model, tools or messages before a step. onStepEnd runs after each step, and the docs suggest it “for saving the chat history or recording usage.” So the loop’s progress lives in memory until your own code saves it.
Tool approval with toolApproval. By default, “tools with an execute function run automatically as the model calls them.” toolApproval sets one of four statuses per tool: 'not-applicable' (the default), 'approved', 'denied', or 'user-approval', which emits an approval request. A function can decide from the input, so a payment tool can ask only above $1,000, and a status can carry a reason to show the approver. “The older needsApproval property on tool() definitions is deprecated,” apart from WorkflowAgent. An approval request also stops the loop: “A tool call needs approval” is one of the four ways it ends. In useChat, the tool part moves to the approval-requested state, and your UI answers with addToolApprovalResponse.
Tool approval: toolApproval in AI SDK 7 and needsApproval on WorkflowAgent
AI SDK 7 has two tool approval APIs, and the docs keep them apart: needsApproval “is specific to WorkflowAgent; for generateText, streamText, and ToolLoopAgent, use toolApproval instead.”
With toolApproval, a rule such as runCommand: 'user-approval' makes the agent return a tool-approval-request instead of running the tool. Approval takes two calls: you add the user’s answer to the messages and call the agent again. No process waits in between. But the docs warn that “the message history is client-controlled input”, so set experimental_toolApprovalSecret to sign approvals before a sensitive tool trusts them.
With WorkflowAgent, approval lives on the tool:
cancelBooking: tool({
description: 'Cancel a booking',
inputSchema: z.object({ bookingId: z.string() }),
// Conditional approval based on input
needsApproval: async input => {
return input.bookingId.startsWith('VIP-');
},
execute: cancelBookingStep,
}),
“The workflow suspends until the user approves or denies”, and since the request “survives process restarts”, the agent picks up where it stopped once the user decides.
Human in the loop: waiting for a person across deploys
Every durable option parks the run without compute while it waits for a person, and they differ on which code it resumes.
Vercel Workflow pins each run to the deployment that started it: a run waiting on a hook “still resumes on the original deployment.” Temporal waits on a Signal or an Update that your code sends. Inngest’s step.waitForEvent() takes a timeout. DBOS stores the deadline and the message in Postgres. Restate suspends a handler that waits on an awakeable.
With plain toolApproval, the wait lives in the messages your app stores. That costs nothing to keep, and your own database and routes hold the state.
Temporal TypeScript SDK Vercel AI SDK integration, DBOS, Inngest and Restate
Each of these keeps your AI SDK code and makes the model and tool calls durable steps.
Temporal. @temporalio/ai-sdk adds an AiSdkPlugin to your worker, and you pass temporalProvider.languageModel('gpt-4o-mini') as the model. The plugin runs generateText() and streamText() calls as Activities. Your tools “must call Activities or Child Workflows” for API calls. Tokens reach your UI through a Workflow Stream that clients subscribe to by workflow ID. You run the worker, and the four-service Temporal Server or Temporal Cloud. Inngest vs Temporal compares the two engines.
DBOS. @dbos-inc/vercel-ai wraps the model in durableCalls and the tools in durableTools, and needs “AI SDK v7+, and a Postgres database for DBOS.” readDurableStream emits AI SDK UIMessageChunk objects, so it fits createUIMessageStreamResponse. On Vercel, DBOS recommends a worker in a Vercel Function that a cron job starts to poll for work. DBOS vs Temporal covers the tradeoffs.
Inngest. There’s no AI SDK package to install. You “wrap every LLM call in step.run()”, and Inngest resumes the agent after a crash or a deploy. Realtime streams tokens to the browser. Set historyLimit: null, since “the default limit of 100 would drop the start of a long answer.”
Restate. @restatedev/vercel-ai-middleware adds durableCalls(ctx), which saves “every LLM response” in the Restate Server, and tools use ctx.run(). Streaming to the UI goes through Restate’s pubsub library over SSE. Your services can deploy on Vercel, and the Restate server runs as one binary or on Restate Cloud. Restate vs Temporal has the details.
What does Cadenya offer that a durable AI SDK agent doesn’t?
With the AI SDK, durability is a stack you assemble: Redis for the stream, a workflow engine for the loop, and routes for the approval. Cadenya runs the agent loop for you, so the stream, the approval and the wait survive your deploys with nothing extra to run. Here’s what that covers:
- Nothing else to run. There’s no Redis, no workflow engine, no worker and no Postgres to add. The loop runs on Cadenya and your Next.js app stays a stateless web tier, so a deploy or a restart never touches a running objective.
- SDKs at 1.9.0 in four languages. TypeScript (
@cadenya/cadenya, Node 18 and up), Python, Ruby, and Go all ship at 1.9.0 under Apache-2.0, plus a CLI and a plain HTTP API. - No server license to read. You host no engine, so there’s no server license to check and no cluster to size.
- Deploy whenever you want. Each objective keeps its variation’s configuration snapshot even when you edit the variation, so there’s no old deployment to keep alive.
- Your keys and your API. You connect your own AI provider. Your app talks to Cadenya over HTTP or the SDKs, and your tools stay your own OpenAPI, MCP, or HTTP endpoints.
- Approvals that wait outside your function. A tool that needs approval parks the loop on
toolApprovalRequested, and several tools can wait at once. - Resumable streaming without Redis. The event stream uses server-sent events with durable event IDs as
Last-Event-IDcheckpoints. The TypeScript SDK reconnects on its own and resumes “from the last received event id.” - Tool approval with the screen included. Approve or deny through the API, from a signed webhook handler, or in the React widget, where
ConversationsPanelsupplies “tool approvals, and live event stream.” A denial can carry a memo that steers the agent to another action. - A price in one line. Free for 1,000 loops a month, $49 a month for 50,000, then $0.0015 a loop. A loop is one LLM request.
- Any model, switched without a deploy. The model is a setting on a variation, so you change providers without touching code. Use any model on OpenRouter or your own endpoint that speaks the OpenAI chat format.
How objectives work walks through events, approvals, and resuming a stream, and the free plan needs no card.
Create an account at app.cadenya.com/signup, then copy your API key from the API keys page.
Grow wherever AI goes next.
Start shipping agents that are equipped to evolve.