A windswept bonsai on a floating island

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.

TypeScript AI agent frameworks compared: Mastra vs LangGraph.js vs AI SDK vs OpenAI Agents JS (2026)

TypeScript

By Cadenya

The TypeScript AI agent frameworks coding agents compare most are Mastra, LangGraph.js, the Vercel AI SDK, and OpenAI Agents JS, and all four can suspend a run for tool approval and resume it later. The difference is where the paused state lives. Mastra workflows suspend and resume through a snapshot in a storage provider like PostgresStore, LangGraph.js writes a checkpoint through PostgresSaver, and the AI SDK and OpenAI Agents JS hand the state back for your code to store.

So pick Mastra when you want workflows, memory, and a “provider/model” model router in one package. Pick LangGraph.js when your agent is a graph and you want a checkpoint at every step. Pick the AI SDK for the thinnest loop and a React chat UI, and OpenAI Agents JS when you build on OpenAI models first. And if you’d rather not run any of these loops yourself, Cadenya is a hosted agent loop with approvals, conversation state, and model swaps built in, with nothing to host. What it offers is at the end.

Every price and limit below comes from each vendor’s own pages, checked on October 13, 2026.

Mastra vs LangGraph.js vs Vercel AI SDK vs OpenAI Agents JS side by side

MastraLangGraph.jsVercel AI SDKOpenAI Agents JS
npm package@mastra/core@langchain/langgraphai@openai/agents
LicenseApache-2.0, with ee/ folders under a separate licenseMITApache-2.0MIT
Node.js in engines22.13.0 or later18 or later22 or laterNot listed
How a run pausesWorkflow suspend() and resume(); tools with requireApprovalinterrupt(), then new Command({ resume })toolApproval set to 'user-approval'needsApproval, then state.approve() or state.reject()
Where the paused state livesA storage provider you configure, such as PostgresStore from @mastra/pgA checkpointer you configure, such as PostgresSaverThe messages you keep (or a workflow, with WorkflowAgent)A serialized RunState you keep
How you pick a modelA "provider/model" string through the model routerLangChain chat models, such as initChatModelString IDs through Vercel AI Gateway by default, or a provider packageOpenAI models out of the box; others through the aisdk() adapter

All four are libraries, so the loop runs in your own Node.js process and the database that holds paused runs is yours. That’s the trade every row above shares. The rest of the page is about how each one pauses, stores, and resumes.

Mastra workflows suspend and resume with PostgresStore

Mastra’s docs describe suspend() as a pause “at any step to collect additional data, wait for an API callback, throttle a costly operation, or request human-in-the-loop input.” Suspending saves the run as a snapshot. You call resume() with resumeData that matches the step’s resumeSchema, from “an HTTP endpoint or event handler,” and the workflow continues from the step where it paused.

The snapshot needs somewhere to go. Mastra says snapshots “are stored in your configured storage provider and persist across deployments and application restarts.” For Postgres, that’s PostgresStore from @mastra/pg:

import { Mastra } from '@mastra/core'
import { PostgresStore } from '@mastra/pg'

export const mastra = new Mastra({
  storage: new PostgresStore({
    id: 'mastra-storage',
    connectionString: process.env.DATABASE_URL,
  }),
})

For the agent loop itself, Mastra has a durable agent. createDurableAgent() from @mastra/core/agent/durable wraps a regular agent “so the agentic loop runs inside a workflow,” and a client “can disconnect and reconnect without missing chunks.” For production, Mastra points to createInngestAgent() in @mastra/inngest, which adds “step memoization, retries” from Inngest. (So a durable Mastra agent is Mastra, a store, a cache for missed events, and possibly Inngest.)

Mastra 1.0 release

Mastra 1.0 shipped on January 20, 2026. The announcement calls it “the release of Mastra 1.0 in stable” and sums it up as “APIs are locked, server adapters are the new default.” Server adapters “automatically expose your agents, workflows, tools, and MCP servers as HTTP endpoints.” The release also “introduces per-domain storage configuration, replacing the single-storage model,” and “adds full support for AI SDK v6.”

Tool approval: Mastra requireApproval, AI SDK toolApproval, OpenAI Agents needsApproval

All three let you mark a tool so the model’s call waits for a person. They differ in what your code does next.

In Mastra, you set requireApproval: true on the tool and watch the stream for a tool-call-approval chunk:

const deleteTool = createTool({
  id: 'delete-record',
  description: 'Delete a record by ID',
  inputSchema: z.object({ id: z.string() }),
  outputSchema: z.object({ deleted: z.boolean() }),
  requireApproval: true,
  execute: async ({ id }) => {
    await db.delete(id)
    return { deleted: true }
  },
})

// Later, in the stream loop:
const approved = await agent.approveToolCall({ runId: stream.runId })

requireToolApproval: true on a stream() call pauses every tool call in that request. declineToolCall() takes a reason that goes back to the model “in place of the tool result.” The pause depends on storage. Without a storage provider you get a “snapshot not found” error, and “the default in-memory store loses snapshots when the process exits.”

In the Vercel AI SDK 7, approval moved to toolApproval on ToolLoopAgent, and needsApproval on tool() is deprecated. A tool set to 'user-approval' returns a tool-approval-request instead of running. Your code adds a tool-approval-response to the messages and calls the agent again, so the messages you keep are the state. The AI SDK durable agent page covers WorkflowAgent, which keeps that state inside a Vercel workflow.

In OpenAI Agents JS, needsApproval takes true or an async function. The run returns pending approvals in result.interruptions, and you call result.state.approve(interruption) or result.state.reject(interruption). To resume in another process, you store result.state.toString() and load it with RunState.fromString(agent, serializedState). The docs add that this “does not authenticate the snapshot or the person submitting it,” so checking where that string came from is your job.

Human-in-the-loop approvals for AI agents compares the approval screen and the wait across hosted options too.

LangGraph.js interrupt, Command resume and the PostgresSaver checkpointer

LangGraph.js pauses inside a node. You call interrupt() with any JSON value, the caller gets it back under __interrupt__, and you resume by invoking the graph again with a Command:

import { interrupt, Command } from "@langchain/langgraph";

async function approvalNode(state: State) {
  const approved = interrupt("Do you approve this action?");
  return { approved };
}

// Resume with the same thread_id that hit the interrupt.
const config = { configurable: { thread_id: "thread-1" } };
await graph.invoke(new Command({ resume: true }), config);

Two things make it work: “A checkpointer to persist the graph state” and “A thread ID in your config.” One detail trips people up. On resume, “the node restarts from the beginning of the node where the interrupt was called,” so any code before the interrupt() runs again (keep side effects after it, or make them idempotent).

For production, the docs recommend PostgresSaver from @langchain/langgraph-checkpoint-postgres, “used in LangSmith.” You create it with PostgresSaver.fromConnString(DB_URI), and “You need to call checkpointer.setup() the first time.” You also pick a durability mode per run. With "exit", LangGraph saves only when the run exits, so “you cannot recover from system failures (like process crashes) mid-execution.” With "sync", it writes every checkpoint before the next step starts.

If you use LangChain’s createAgent, humanInTheLoopMiddleware with interruptOn maps each tool name to an approval rule, and it needs the same checkpointer. To host LangGraph.js agents on LangChain’s platform, see LangSmith Deployment alternatives and pricing.

OpenAI Agents JS aisdk adapter for non-OpenAI models

OpenAI Agents JS “works with OpenAI models through the Responses API or Chat Completions API” out of the box. For Anthropic, Gemini, or another provider, you install @openai/agents-extensions and wrap an AI SDK model with aisdk():

import { Agent } from '@openai/agents';
import { openai } from '@ai-sdk/openai';
import { aisdk } from '@openai/agents-extensions/ai-sdk';

const model = aisdk(openai('gpt-5.4'));
const agent = new Agent({
  name: 'My Agent',
  instructions: 'You are a helpful assistant.',
  model,
});

Swap @ai-sdk/openai for another AI SDK provider package and pass that model instead. So a cross-provider OpenAI Agents JS app depends on two SDKs, and changing the model is still a code change and a deploy.

Mastra model router and observational memory

Mastra’s model router takes a "provider/model-name" string, reads the provider’s key from an environment variable such as ANTHROPIC_API_KEY, and routes the call. Mastra lists “7910 models from 215 providers.” Fallbacks are built in: “If it encounters a 500 error, rate limit, or timeout, it automatically switches to your first fallback.” The AI SDK takes the same kind of string, but its global provider “is set to the Vercel AI Gateway” by default. Switching model providers for AI agents goes deeper on keys and gateways.

Observational memory is Mastra’s answer to long conversations. Two background agents, “an Observer and a Reflector, watch your agent’s conversations and maintain a dense observation log that replaces raw message history as it grows.” You turn it on with observationalMemory: true in the Memory config, and, you guessed it, “Using Observational Memory requires a storage provider!” Agent memory and context windows compares the approaches.

What does Cadenya offer that Mastra, LangGraph.js, AI SDK and OpenAI Agents JS don’t?

Mastra, LangGraph.js, the Vercel AI SDK, and OpenAI Agents JS each give you an agent loop to run, plus a place to store paused runs that you set up. Cadenya runs the loop for you, with approvals, conversation state, and model swaps built in, and you call it from TypeScript. Here’s what that covers:

  1. Nothing else to run. No Postgres for snapshots or checkpoints, no Redis cache for missed events, no Inngest, and no long-lived Node.js process. The loop runs on Cadenya and your app stays a stateless web tier, so your deploys and restarts never touch a running objective.
  2. An Apache-2.0 SDK, and no server license to read. The TypeScript SDK, @cadenya/cadenya, is Apache-2.0 like Mastra and the AI SDK. And since Cadenya runs the loop, there’s no store to license and no ee/ folder to check.
  3. No old code to keep alive while runs wait. An objective keeps its variation’s configuration snapshot even when you edit the variation later.
  4. The pause and the resume, already wired. When a tool needs approval, Cadenya parks the loop on toolApprovalRequested, and several tools can wait at once. An objective in STATE_WAITING resumes when your user sends a Continue message.
  5. SDKs that have shipped a lot. SDKs for TypeScript, Python, Ruby, and Go, all at 1.9.0 under Apache-2.0, and the TypeScript SDK has shipped 113 versions since April 2026. It runs on Node.js 18, where Mastra’s core asks for 22.13 and the AI SDK for 22.
  6. A price in one line. Free for 1,000 loops a month with $5 of inference at signup, then $49 a month for 50,000 loops and $0.0015 a loop after that. A loop is one LLM request made during an objective, and there are no seats.
  7. The agent loop, already written. Cadenya calls the model, runs the tools, streams each reply, and compacts the context window when a conversation gets long. Every objective keeps a durable event log you can list or stream. Tools come from an OpenAPI 3 spec, an MCP server, HTTP endpoints you define, or a Bare tool set your app answers, with filters and progressive discovery for big APIs.
  8. Any model, on your own keys. The model is a setting on a variation, chosen from the AI providers you connect to your workspace, so switching from OpenAI to Anthropic needs no adapter package and no deploy. You pay your provider directly.
  9. Model choice backed by feedback. Feedback on objectives feeds the variation scoring system, and weighted variations favor the ones that score well.
  10. The approval screen included. The React UI kit’s ConversationsPanel ships the message thread, the composer, tool approvals, and the live event stream. A denial can carry a memo that steers the agent (“Use the read-only lookup tool instead.”).

How objectives work walks through a turn, approvals included, 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.

A pine bonsai overlooking a mountain lake