<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Agents on RockB</title><link>https://baeseokjae.github.io/tags/agents/</link><description>Recent content in Agents on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Wed, 22 Apr 2026 05:37:21 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/agents/index.xml" rel="self" type="application/rss+xml"/><item><title>Vercel AI SDK Guide 2026: Build Streaming AI Apps in TypeScript With One SDK</title><link>https://baeseokjae.github.io/posts/vercel-ai-sdk-guide-2026/</link><pubDate>Wed, 22 Apr 2026 05:37:21 +0000</pubDate><guid>https://baeseokjae.github.io/posts/vercel-ai-sdk-guide-2026/</guid><description>Complete guide to Vercel AI SDK in 2026 — streaming, tool calling, structured output, agents, and production deployment with code examples.</description><content:encoded><![CDATA[<p>The Vercel AI SDK is a unified TypeScript library that lets you build streaming AI applications across OpenAI, Anthropic, Google, and 13+ other providers without rewriting your core logic when you switch models. Install it once, pick your provider, and ship production-ready AI features in hours instead of days.</p>
<h2 id="what-is-the-vercel-ai-sdk-and-why-it-matters-in-2026">What Is the Vercel AI SDK and Why It Matters in 2026</h2>
<p>The Vercel AI SDK is an open-source TypeScript toolkit for building AI-powered web applications with a provider-agnostic API, first-class streaming support, and framework-native UI hooks. As of April 2026, it has 11.5 million weekly npm downloads, 23.7K GitHub stars, and 614+ contributors — making it the most widely adopted TypeScript AI library for web developers. The SDK is organized into three layers: AI SDK Core handles server-side text generation, object generation, and tool calling; AI SDK UI provides React/Vue/Svelte hooks like <code>useChat</code> and <code>useCompletion</code> for building chat interfaces without managing stream state; and AI SDK RSC integrates with React Server Components for edge-compatible generative UI. The SDK supports 100+ LLM models across 16+ providers via the Vercel AI Gateway, including OpenAI GPT-4o, Anthropic Claude, Google Gemini, and open models on Together/Groq. In 2026 Vercel added three major features on top: Workflows (long-running durable agents), Sandbox (secure agent code execution), and AI Elements (prebuilt UI components). OpenCode — one of the most popular open-source coding agents — is built entirely on AI SDK, which validates its production-grade viability.</p>
<h3 id="the-three-layer-architecture">The Three-Layer Architecture</h3>
<p>The SDK cleanly separates concerns: Core runs on the server or edge, UI runs on the client, and RSC bridges the two with streaming server components. This separation means you can adopt incrementally — start with Core for a simple API route, add UI hooks when you need chat state management, and layer in RSC if you need server-driven generative UI.</p>
<h3 id="how-it-fits-the-vercel-ecosystem">How It Fits the Vercel Ecosystem</h3>
<p>AI Gateway gives you one API key to access 100+ models with automatic fallbacks and rate limit management. Sandbox provides a secure Node.js environment for agents that need to execute code. Workflows lets agents suspend and resume across function invocations, solving the serverless timeout problem for long-running tasks.</p>
<h2 id="getting-started-installing-and-configuring-ai-sdk">Getting Started: Installing and Configuring AI SDK</h2>
<p>Getting started with the Vercel AI SDK requires installing the <code>ai</code> core package plus one or more provider adapters. The setup takes under five minutes for a Next.js project and works equally well in any Node.js or edge runtime environment. The provider adapter pattern is the key architectural decision: you import a model from its provider package and pass it to AI SDK functions, meaning you can swap from OpenAI to Anthropic by changing a single import and model string — your business logic stays untouched. This design was explicitly chosen to prevent vendor lock-in, and in practice it means you can A/B test models in production, build fallback chains, or migrate providers without refactoring your entire codebase. The package size is small — <code>ai</code> is under 200KB minified — and it is designed to run on Vercel Edge Functions, Cloudflare Workers, and standard Node.js without adaptation. For new projects, the recommended starting point is a Next.js App Router app with the <code>edge</code> runtime on API routes, which gives you global distribution and sub-100ms cold starts.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// .env.local
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">OPENAI_API_KEY</span><span style="color:#f92672">=</span><span style="color:#a6e22e">sk</span><span style="color:#f92672">-</span>...
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ANTHROPIC_API_KEY</span><span style="color:#f92672">=</span><span style="color:#a6e22e">sk</span><span style="color:#f92672">-</span><span style="color:#a6e22e">ant</span><span style="color:#f92672">-</span>...
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">GOOGLE_GENERATIVE_AI_API_KEY</span><span style="color:#f92672">=</span>...
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/api/chat/route.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">streamText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;ai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/openai&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">runtime</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;edge&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">messages</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">json</span>()
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#39;gpt-4o&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">messages</span>,
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">toDataStreamResponse</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="ai-gateway-one-key-for-100-models">AI Gateway: One Key for 100+ Models</h3>
<p>Vercel AI Gateway lets you use a single <code>VERCEL_API_KEY</code> to access models from OpenAI, Anthropic, Google, Mistral, and more. It handles rate limit rotation, cost tracking, and automatic retry logic. For teams that need to experiment with multiple providers without managing individual API key billing, Gateway is the fastest path to a multi-model setup.</p>
<h2 id="ai-sdk-core-text-generation-and-streaming">AI SDK Core: Text Generation and Streaming</h2>
<p>AI SDK Core is the server-side engine that converts provider-specific APIs into a consistent interface for generating text, streaming responses, and calling tools. The two primary functions are <code>generateText</code> and <code>streamText</code>. <code>generateText</code> is for synchronous operations — you send a prompt and wait for the full response, which is ideal for batch jobs, summarization pipelines, and any context where the user is not watching a UI render in real time. <code>streamText</code> is the streaming counterpart: it returns a <code>ReadableStream</code> that you can pipe directly to a <code>Response</code> object, and it integrates with UI hooks via the <code>toDataStreamResponse()</code> method. Both functions accept the same options object — <code>model</code>, <code>messages</code>, <code>system</code>, <code>tools</code>, <code>maxSteps</code>, <code>temperature</code>, and more — so switching between them is a one-word change. Provider switching is similarly simple: swapping <code>openai('gpt-4o')</code> for <code>anthropic('claude-opus-4-7')</code> is the only change needed. Retry logic and fallbacks are handled with the <code>wrapLanguageModel</code> utility and the <code>fallback</code> provider, which tries a list of models in order if the primary returns an error. The consistency across providers is the single biggest productivity gain AI SDK offers compared to using provider SDKs directly.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span>, <span style="color:#a6e22e">streamText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;ai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">anthropic</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/anthropic&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// One-shot generation
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">text</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">anthropic</span>(<span style="color:#e6db74">&#39;claude-sonnet-4-6&#39;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Summarize the key features of React 19 in 3 bullet points.&#39;</span>,
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Streaming
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">anthropic</span>(<span style="color:#e6db74">&#39;claude-sonnet-4-6&#39;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Write a guide on async/await in TypeScript.&#39;</span>,
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#66d9ef">await</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">chunk</span> <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">textStream</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">stdout</span>.<span style="color:#a6e22e">write</span>(<span style="color:#a6e22e">chunk</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="built-in-fallbacks-and-retry-logic">Built-In Fallbacks and Retry Logic</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createFallback</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/provider-utils&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/openai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">anthropic</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/anthropic&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">resilientModel</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createFallback</span>([
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">openai</span>(<span style="color:#e6db74">&#39;gpt-4o&#39;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">anthropic</span>(<span style="color:#e6db74">&#39;claude-sonnet-4-6&#39;</span>),
</span></span><span style="display:flex;"><span>])
</span></span></code></pre></div><h2 id="structured-output-with-zod-schemas">Structured Output with Zod Schemas</h2>
<p><code>generateObject</code> and <code>streamObject</code> are AI SDK&rsquo;s solution to one of the biggest pain points in production AI: getting reliable, type-safe structured data from LLMs instead of freeform text that you then parse with fragile regexes. These functions accept a Zod schema and use the model&rsquo;s native structured output mode — JSON mode for OpenAI, tool-use-based extraction for Anthropic — to guarantee the response matches the schema shape. If the model returns malformed output, AI SDK retries automatically. This is not just a developer convenience: structured output is essential for any AI pipeline where the response feeds into downstream logic, databases, or APIs. Teams using <code>generateObject</code> in production report near-zero JSON parsing errors compared to prompt-based extraction, and the Zod types flow through the entire TypeScript type system so you get autocomplete on the AI response object. The <code>streamObject</code> variant lets you stream partial structured objects, enabling progressive UI rendering as the AI fills in fields — useful for forms, dashboards, or any interface where showing partial data is better than a blank loading state. For data extraction tasks — pulling product specs from HTML, extracting entities from documents, or parsing unstructured API responses — structured output with Zod is the recommended production approach.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateObject</span>, <span style="color:#a6e22e">streamObject</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;ai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/openai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;zod&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">BlogPostSchema</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">title</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">summary</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">max</span>(<span style="color:#ae81ff">200</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tags</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>()).<span style="color:#a6e22e">max</span>(<span style="color:#ae81ff">5</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">seoScore</span>: <span style="color:#66d9ef">z.number</span>().<span style="color:#a6e22e">min</span>(<span style="color:#ae81ff">0</span>).<span style="color:#a6e22e">max</span>(<span style="color:#ae81ff">100</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">sections</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">object</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">heading</span>: <span style="color:#66d9ef">z.string</span>(),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">keyPoints</span>: <span style="color:#66d9ef">z.array</span>(<span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>()),
</span></span><span style="display:flex;"><span>  })),
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> { <span style="color:#66d9ef">object</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateObject</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#39;gpt-4o&#39;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">schema</span>: <span style="color:#66d9ef">BlogPostSchema</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Analyze this article and return structured metadata: ...&#39;</span>,
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#66d9ef">object</span>.<span style="color:#a6e22e">title</span>) <span style="color:#75715e">// TypeScript knows the full type
</span></span></span></code></pre></div><h3 id="streaming-structured-objects">Streaming Structured Objects</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">partialObjectStream</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamObject</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#39;gpt-4o&#39;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">schema</span>: <span style="color:#66d9ef">BlogPostSchema</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Generate a blog post outline for: &#34;AI agents in 2026&#34;&#39;</span>,
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> <span style="color:#66d9ef">await</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">partial</span> <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">partialObjectStream</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// partial.title appears as soon as the model generates it
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">updateUI</span>(<span style="color:#a6e22e">partial</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="building-chat-uis-with-ai-sdk-ui-hooks">Building Chat UIs with AI SDK UI Hooks</h2>
<p>AI SDK UI is the client-side complement to Core, providing React hooks that manage chat state, streaming responses, and optimistic updates without requiring a single <code>useState</code> or <code>useEffect</code> for stream handling. The primary hook is <code>useChat</code>, which gives you <code>messages</code>, <code>input</code>, <code>handleInputChange</code>, <code>handleSubmit</code>, and <code>isLoading</code> — everything needed to build a ChatGPT-like interface in under 50 lines of React. Under the hood it connects to your AI route, handles stream parsing, and appends message chunks to state as they arrive. The <code>useCompletion</code> hook handles text completion use cases — autocomplete, writing suggestions, or any single-prompt UX. <code>useObject</code> streams structured objects from a <code>streamObject</code> route and exposes the partial object as it builds, enabling progressive form filling or AI-driven dashboard updates. All three hooks work with React, Vue, Svelte, and SolidJS — the framework-agnostic design means you can share backend patterns between projects built on different frontend stacks. The hooks integrate with React Suspense and Error Boundaries for graceful loading and error states without extra wiring.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/components/Chat.tsx
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#e6db74">&#39;use client&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useChat</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;ai/react&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Chat() {</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">messages</span>, <span style="color:#a6e22e">input</span>, <span style="color:#a6e22e">handleInputChange</span>, <span style="color:#a6e22e">handleSubmit</span>, <span style="color:#a6e22e">isLoading</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">useChat</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">api</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;/api/chat&#39;</span>,
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> (
</span></span><span style="display:flex;"><span>    &lt;<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      {<span style="color:#a6e22e">messages</span>.<span style="color:#a6e22e">map</span>(<span style="color:#a6e22e">m</span> <span style="color:#f92672">=&gt;</span> (
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">key</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">id</span>} <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">role</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;user&#39;</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;user&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;assistant&#39;</span>}&gt;
</span></span><span style="display:flex;"><span>          {<span style="color:#a6e22e">m</span>.<span style="color:#a6e22e">content</span>}
</span></span><span style="display:flex;"><span>        &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>      ))}
</span></span><span style="display:flex;"><span>      &lt;<span style="color:#f92672">form</span> <span style="color:#a6e22e">onSubmit</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleSubmit</span>}&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">value</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">input</span>} <span style="color:#a6e22e">onChange</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">handleInputChange</span>} <span style="color:#a6e22e">disabled</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">isLoading</span>} /&gt;
</span></span><span style="display:flex;"><span>        &lt;<span style="color:#f92672">button</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;submit&#34;</span> <span style="color:#a6e22e">disabled</span><span style="color:#f92672">=</span>{<span style="color:#a6e22e">isLoading</span>}&gt;<span style="color:#a6e22e">Send</span>&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>      &lt;/<span style="color:#f92672">form</span>&gt;
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>  )
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="framework-support-comparison">Framework Support Comparison</h3>
<table>
  <thead>
      <tr>
          <th>Hook</th>
          <th>React</th>
          <th>Vue</th>
          <th>Svelte</th>
          <th>SolidJS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>useChat</code></td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td><code>useCompletion</code></td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td><code>useObject</code></td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
          <td>✅</td>
      </tr>
      <tr>
          <td><code>useAssistant</code></td>
          <td>✅</td>
          <td>❌</td>
          <td>❌</td>
          <td>❌</td>
      </tr>
  </tbody>
</table>
<h2 id="tool-calling-giving-your-ai-agent-superpowers">Tool Calling: Giving Your AI Agent Superpowers</h2>
<p>Tool calling in AI SDK is the mechanism that transforms a passive text generator into an active agent — the model describes which tools it wants to invoke, AI SDK executes them server-side, and the results feed back into the next model turn automatically. Tools are defined with the <code>tool</code> helper, which takes a <code>description</code> (natural language explanation for the model), <code>parameters</code> (a Zod schema for typed inputs), and an <code>execute</code> function (the actual implementation). The SDK handles the full tool-call cycle: formatting the tool description for the provider, parsing the model&rsquo;s structured tool-call output, executing the function with validated arguments, and appending the result to the conversation context. <code>maxSteps</code> controls how many tool-call cycles the agent can run before stopping, preventing infinite loops while allowing multi-step reasoning chains of 5–10 steps. Tool results stream to the client via <code>toDataStreamResponse()</code>, so users see intermediate tool outputs in real time rather than waiting for the final answer. In production applications, tool sets commonly include database query tools, web search, calculator functions, external API calls, and file operations — anything your server-side code can do, the agent can orchestrate. The Zod parameter schemas provide input validation at zero extra cost, catching malformed tool calls before they reach your database or external services.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">streamText</span>, <span style="color:#a6e22e">tool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;ai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/openai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;zod&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#39;gpt-4o&#39;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">maxSteps</span>: <span style="color:#66d9ef">5</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">getWeather</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Get current weather for a city&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">city</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">city</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">`https://api.weather.com/</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">city</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>()
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">searchDatabase</span>: <span style="color:#66d9ef">tool</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Search the product database&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">parameters</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">z.string</span>(), <span style="color:#a6e22e">limit</span>: <span style="color:#66d9ef">z.number</span>().<span style="color:#66d9ef">default</span>(<span style="color:#ae81ff">5</span>) }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">query</span>, <span style="color:#a6e22e">limit</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">products</span>.<span style="color:#a6e22e">search</span>(<span style="color:#a6e22e">query</span>, { <span style="color:#a6e22e">limit</span> })
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    }),
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">messages</span><span style="color:#f92672">:</span> [{ <span style="color:#a6e22e">role</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;user&#39;</span>, <span style="color:#a6e22e">content</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;What is the weather in Tokyo and do we sell umbrellas?&#39;</span> }],
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">toDataStreamResponse</span>()
</span></span></code></pre></div><h3 id="multi-step-agent-loop">Multi-Step Agent Loop</h3>
<p>With <code>maxSteps: 5</code>, the model can: call <code>getWeather</code> → get Tokyo weather → call <code>searchDatabase</code> for umbrellas → combine results → return final answer. Each step is visible to the user via streaming tool call indicators rendered automatically by <code>useChat</code>.</p>
<h2 id="building-ai-agents-with-multi-step-reasoning">Building AI Agents with Multi-Step Reasoning</h2>
<p>An AI agent in the Vercel AI SDK context is a <code>streamText</code> or <code>generateText</code> call with tools enabled and <code>maxSteps</code> set above 1 — the model reasons, calls tools, observes results, and reasons again until it reaches a conclusion or exhausts its step budget. This loop pattern is what separates agents from chatbots: rather than answering from static training knowledge, the agent actively queries databases, fetches URLs, or calls APIs to gather real-time information before formulating a response. The key to a production-grade agent is memory and context management: you control what goes in <code>messages</code>, so you can implement sliding window context, summarization, or retrieval-augmented generation by fetching relevant documents before calling the model. For RAG integration, the standard pattern is to embed the user query, retrieve top-k chunks from a vector store (Pinecone, Supabase pgvector, or Upstash), and prepend them as a system message. The agent then has access to both retrieved context and its tool-calling ability, so it can fetch additional information if the retrieved chunks are insufficient. OpenCode&rsquo;s architecture demonstrates this at scale: it uses AI SDK with file system tools, runs multi-step reasoning loops to understand a codebase, and streams results back to a terminal UI — all without custom streaming infrastructure because AI SDK handles it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// Research agent with RAG
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">researchAgent</span>(<span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">string</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">relevantDocs</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">vectorStore</span>.<span style="color:#a6e22e">search</span>(<span style="color:#a6e22e">query</span>, { <span style="color:#a6e22e">topK</span>: <span style="color:#66d9ef">5</span> })
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#39;gpt-4o&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">maxSteps</span>: <span style="color:#66d9ef">8</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">system</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`You are a research agent. Use context and tools to answer thoroughly.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Context from knowledge base:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"></span><span style="color:#e6db74">${</span><span style="color:#a6e22e">relevantDocs</span>.<span style="color:#a6e22e">map</span>(<span style="color:#a6e22e">d</span> <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">content</span>).<span style="color:#a6e22e">join</span>(<span style="color:#e6db74">&#39;\n\n&#39;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">messages</span><span style="color:#f92672">:</span> [{ <span style="color:#a6e22e">role</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;user&#39;</span>, <span style="color:#a6e22e">content</span>: <span style="color:#66d9ef">query</span> }],
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">searchWeb</span>: <span style="color:#66d9ef">tool</span>({ <span style="color:#75715e">/* web search implementation */</span> }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">fetchUrl</span>: <span style="color:#66d9ef">tool</span>({ <span style="color:#75715e">/* URL fetching implementation */</span> }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">saveNote</span>: <span style="color:#66d9ef">tool</span>({ <span style="color:#75715e">/* note saving implementation */</span> }),
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">toDataStreamResponse</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="vercel-workflows-long-running-agents-that-survive">Vercel Workflows: Long-Running Agents That Survive</h2>
<p>Vercel Workflows is a 2026 addition to the AI SDK ecosystem that solves the most critical limitation of serverless AI agents: function timeout. Standard serverless functions on Vercel time out after 30 seconds (Pro plan) or 5 minutes (Enterprise), which is insufficient for agents that need to search the web, process large documents, run multi-stage pipelines, or wait for human approval. Workflows introduces durable execution — agent tasks are broken into named steps that can suspend (persist state to managed storage), wait for external events, and resume exactly where they left off across multiple function invocations without losing context. This makes genuinely complex agentic pipelines feasible on serverless infrastructure: a content generation pipeline can run for 20+ minutes as it researches, drafts, and revises content, with the agent suspending between phases. The <code>@vercel/workflows</code> package integrates directly with AI SDK&rsquo;s <code>generateText</code> and <code>streamText</code> — you wrap agent logic in a <code>workflow</code> function and use <code>step.run()</code> to define resumable checkpoints. Human-in-the-loop approval is supported via <code>step.waitForEvent()</code>, which suspends the workflow until a webhook fires. In 2026, Workflows is the recommended architecture for any AI task that may exceed 30 seconds or requires coordination between multiple agents.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">workflow</span>, <span style="color:#a6e22e">step</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@vercel/workflows&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">generateText</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;ai&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">anthropic</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#39;@ai-sdk/anthropic&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">contentPipeline</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">workflow</span>(<span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">input</span> }<span style="color:#f92672">:</span> { <span style="color:#a6e22e">input</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">topic</span>: <span style="color:#66d9ef">string</span> } }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Each step is resumable — survives function timeout
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">research</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">step</span>.<span style="color:#a6e22e">run</span>(<span style="color:#e6db74">&#39;research&#39;</span>, <span style="color:#66d9ef">async</span> () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">text</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">anthropic</span>(<span style="color:#e6db74">&#39;claude-opus-4-7&#39;</span>),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Research: </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">input</span>.<span style="color:#a6e22e">topic</span><span style="color:#e6db74">}</span><span style="color:#e6db74">. Return key facts and sources.`</span>,
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">text</span>
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">draft</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">step</span>.<span style="color:#a6e22e">run</span>(<span style="color:#e6db74">&#39;draft&#39;</span>, <span style="color:#66d9ef">async</span> () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">text</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">generateText</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">anthropic</span>(<span style="color:#e6db74">&#39;claude-sonnet-4-6&#39;</span>),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Using this research: </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">research</span><span style="color:#e6db74">}</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">n</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">nWrite a 1000-word article about </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">input</span>.<span style="color:#a6e22e">topic</span><span style="color:#e6db74">}</span><span style="color:#e6db74">.`</span>,
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">text</span>
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">research</span>, <span style="color:#a6e22e">draft</span> }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><h2 id="production-deployment-and-scaling">Production Deployment and Scaling</h2>
<p>Deploying a Vercel AI SDK application to production requires careful attention to runtime selection, cost management, and observability. For runtime selection, Edge Functions are the right choice for streaming chat routes because they have lower cold-start latency and are globally distributed across 30+ regions — users in Tokyo get a fast response without routing to a US datacenter. Node.js runtime is better for heavy tool execution, large file processing, or anything requiring Node-specific APIs. Cost management starts with the <code>maxTokens</code> parameter to cap spending per request, and AI Gateway adds team-level spend limits and per-model cost tracking with dashboards. For rate limiting on API routes, <code>@vercel/kv</code> with a sliding window counter is the standard pattern: each user or IP gets N requests per minute, excess requests return 429 with a <code>retry-after</code> header. Observability is critical for catching silent model failures: the <code>onFinish</code> callback in <code>streamText</code> and <code>generateText</code> lets you log token usage, model name, latency, and finish reason to your analytics pipeline, enabling cost attribution per feature and alerting on abnormal token consumption. Vercel&rsquo;s built-in function logs surface AI SDK error events automatically for debugging.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(<span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">Request</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">messages</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">json</span>()
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Rate limiting check
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ip</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">headers</span>.<span style="color:#66d9ef">get</span>(<span style="color:#e6db74">&#39;x-forwarded-for&#39;</span>) <span style="color:#f92672">??</span> <span style="color:#e6db74">&#39;anonymous&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">success</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">ratelimit</span>.<span style="color:#a6e22e">limit</span>(<span style="color:#a6e22e">ip</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">success</span>) <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Response</span>(<span style="color:#e6db74">&#39;Rate limit exceeded&#39;</span>, { <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">429</span> })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">streamText</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#39;gpt-4o&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">messages</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">maxTokens</span>: <span style="color:#66d9ef">2000</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">temperature</span>: <span style="color:#66d9ef">0.7</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">onFinish</span><span style="color:#f92672">:</span> ({ <span style="color:#a6e22e">usage</span>, <span style="color:#a6e22e">finishReason</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">analytics</span>.<span style="color:#a6e22e">track</span>(<span style="color:#e6db74">&#39;ai_completion&#39;</span>, {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">tokens</span>: <span style="color:#66d9ef">usage.totalTokens</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">finishReason</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">model</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;gpt-4o&#39;</span>,
</span></span><span style="display:flex;"><span>      })
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>  
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">toDataStreamResponse</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="ai-sdk-vs-langchain-vs-mastra-framework-comparison">AI SDK vs LangChain vs Mastra: Framework Comparison</h2>
<p>Choosing between Vercel AI SDK, LangChain.js, and Mastra in 2026 depends primarily on your stack, agent complexity, and how important streaming and bundle size are to your application. Vercel AI SDK is the right choice for TypeScript web developers building streaming-first applications — it is the lightest of the three (under 200KB), has the best Next.js and Edge Function integration, and provides the most seamless streaming API with minimal boilerplate. LangChain.js has the broadest ecosystem: pre-built chains, 50+ vector store integrations, document loaders, memory modules, and a large community cookbook — making it better for teams needing to quickly assemble complex RAG pipelines from components rather than writing integration code themselves. Mastra, which emerged in late 2025, sits between the two: TypeScript-native like AI SDK but with an opinionated agent framework including built-in memory, durable workflow primitives, and multi-agent coordination, targeting developers who need more structure than AI SDK provides without LangChain&rsquo;s abstraction overhead. The bundle size difference is meaningful for edge and browser deployments where LangChain.js&rsquo;s 2MB+ footprint can impact cold start times. For most Next.js applications in 2026, AI SDK is the practical default and LangChain or Mastra are reached for only when specific missing features justify the additional complexity.</p>
<table>
  <thead>
      <tr>
          <th>Feature</th>
          <th>Vercel AI SDK</th>
          <th>LangChain.js</th>
          <th>Mastra</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bundle size</td>
          <td>~200KB</td>
          <td>~2MB+</td>
          <td>~500KB</td>
      </tr>
      <tr>
          <td>Streaming</td>
          <td>First-class</td>
          <td>Good</td>
          <td>Good</td>
      </tr>
      <tr>
          <td>Tool calling</td>
          <td>Native</td>
          <td>Via chains</td>
          <td>Native</td>
      </tr>
      <tr>
          <td>Structured output</td>
          <td>Zod-native</td>
          <td>Manual</td>
          <td>Zod-native</td>
      </tr>
      <tr>
          <td>Long-running agents</td>
          <td>Via Workflows</td>
          <td>Partial</td>
          <td>Built-in</td>
      </tr>
      <tr>
          <td>Next.js/Edge</td>
          <td>Excellent</td>
          <td>Moderate</td>
          <td>Good</td>
      </tr>
      <tr>
          <td>Pre-built integrations</td>
          <td>16+ providers</td>
          <td>50+</td>
          <td>20+</td>
      </tr>
      <tr>
          <td>TypeScript types</td>
          <td>Excellent</td>
          <td>Good</td>
          <td>Excellent</td>
      </tr>
      <tr>
          <td>Learning curve</td>
          <td>Low</td>
          <td>High</td>
          <td>Medium</td>
      </tr>
  </tbody>
</table>
<h2 id="faq">FAQ</h2>
<p><strong>What is the difference between AI SDK Core and AI SDK UI?</strong>
AI SDK Core (<code>generateText</code>, <code>streamText</code>, <code>generateObject</code>) runs on the server and handles model calls. AI SDK UI (<code>useChat</code>, <code>useCompletion</code>, <code>useObject</code>) runs on the client and manages stream state, message history, and UI updates. In a Next.js app, Core lives in <code>app/api/</code> routes and UI hooks live in client components. You can use Core without UI (for backend pipelines) but UI requires a Core-powered API endpoint.</p>
<p><strong>Can I use Vercel AI SDK without Vercel hosting?</strong>
Yes. The AI SDK is a pure npm package with no dependency on Vercel&rsquo;s infrastructure. You can use it in any Node.js server, AWS Lambda, Cloudflare Workers, or on-premise environment. Vercel-specific features like Workflows and AI Gateway require Vercel hosting, but AI SDK Core and UI work on any JavaScript runtime.</p>
<p><strong>How do I switch between AI providers in Vercel AI SDK?</strong>
Change one line: swap the model import and the model string. Replace <code>openai('gpt-4o')</code> with <code>anthropic('claude-sonnet-4-6')</code>. The rest of your code — messages, tools, streaming — stays identical. This is the main design goal: provider portability without refactoring business logic.</p>
<p><strong>What is the recommended way to add memory to an AI SDK agent?</strong>
The SDK does not manage memory itself — you control <code>messages</code>. Store conversation history in a database (KV, Postgres, or Upstash), retrieve the last N turns before each request, and pass them as <code>messages</code>. For long-term memory across sessions, embed user facts and retrieve them via vector search, prepending relevant memories to the system prompt before each request.</p>
<p><strong>Does Vercel AI SDK support multi-modal inputs like images and PDFs?</strong>
Yes. Models that support vision (GPT-4o, Claude Opus 4, Gemini Pro Vision) accept <code>content</code> arrays with <code>{ type: 'image', image: url }</code> or <code>{ type: 'file', data: base64, mimeType: 'application/pdf' }</code> parts alongside text. AI SDK normalizes these into the provider&rsquo;s expected format automatically, so you write the same code regardless of which vision model you use.</p>
]]></content:encoded></item><item><title>Mastra AI: The TypeScript AI Agent Framework for 2026</title><link>https://baeseokjae.github.io/posts/mastra-ai-typescript-framework-2026/</link><pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate><guid>https://baeseokjae.github.io/posts/mastra-ai-typescript-framework-2026/</guid><description>A practical guide to Mastra AI, the TypeScript-first framework for building production AI agents. Covers setup, agents, tools, MCP, workflows, RAG, evals, deployment, and a head-to-head comparison with LangGraph and CrewAI.</description><content:encoded><![CDATA[<h2 id="introduction-why-mastra-is-the-typescript-ai-framework-to-watch-in-2026">Introduction: Why Mastra Is the TypeScript AI Framework to Watch in 2026</h2>
<p>The AI agent ecosystem has a Python problem. Not with Python itself—it works fine—but with the fact that most agents ship as web services, and the teams building those services increasingly write TypeScript. Sam Bhagwat, CEO of Mastra, noted on Hacker News that 60–70% of YC X25 agent startups are building in TypeScript, not Python. The tooling hasn&rsquo;t caught up. LangChain, CrewAI, and AutoGen all originated in Python, leaving TypeScript developers either wrapping Python services or cobbling together their own agent infrastructure.</p>
<p>Mastra was built to close that gap.</p>
<h3 id="the-shift-from-python-to-typescript-for-ai-agents">The shift from Python to TypeScript for AI agents</h3>
<p>The shift is practical, not ideological. When your production stack runs on Node.js or edge runtimes, reaching for a Python framework introduces serialization overhead, deployment complexity, and a skills mismatch. TypeScript gives you shared types between your agent logic and your API layer, native streaming support for Server-Sent Events and WebSocket responses, and a single runtime for your entire backend. The ergonomics matter: you can define a tool&rsquo;s input schema with Zod, pass that schema directly to the LLM as a function definition, and validate the LLM&rsquo;s output against the same schema—no JSON Schema translation layer required.</p>
<h3 id="what-is-mastra">What is Mastra?</h3>
<p>Mastra is an open-source (Apache 2.0) TypeScript framework for building AI agents. It was created by the team behind Gatsby, the React static-site generator that peaked at 50k+ GitHub stars. That team shipped a framework before; they understand the ergonomics of developer tooling. Mastra provides structured primitives for agents, tools, workflows, RAG pipelines, evals, and observability—all expressed as TypeScript code, not YAML DSLs or visual editors that generate unreadable files.</p>
<p>The project has accumulated 23,200+ GitHub stars, 14,334 commits, and 1,079 branches as of April 2026. The velocity is real. Mastra raised a $22M Series A led by Spark Capital in early 2026, bringing total funding to $35M.</p>
<h3 id="enterprise-adoption">Enterprise adoption</h3>
<p>The customer list is worth examining because it signals production readiness, not just developer enthusiasm:</p>
<table>
  <thead>
      <tr>
          <th>Company</th>
          <th>Use Case</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Docker</td>
          <td>Event-driven PR management agents with MCP</td>
      </tr>
      <tr>
          <td>Brex</td>
          <td>Financial agents that helped drive the $5.1B Capital One acquisition</td>
      </tr>
      <tr>
          <td>Marsh McLennan</td>
          <td>Enterprise search agent used by 100k+ people daily</td>
      </tr>
      <tr>
          <td>Elastic</td>
          <td>Agentic RAG with Elasticsearch</td>
      </tr>
      <tr>
          <td>SoftBank</td>
          <td>Enterprise productivity at scale</td>
      </tr>
      <tr>
          <td>Replit</td>
          <td>Agent 3 built on Mastra primitives</td>
      </tr>
      <tr>
          <td>MongoDB, Workday, Salesforce, Plaid</td>
          <td>Various production agent deployments</td>
      </tr>
  </tbody>
</table>
<p>That&rsquo;s not a &ldquo;coming soon&rdquo; list. Marsh McLennan&rsquo;s agent is in daily production use by over 100,000 people. Brex&rsquo;s agents contributed to a multi-billion-dollar acquisition. These are load-bearing systems.</p>
<h2 id="getting-started-setting-up-your-first-mastra-project">Getting Started: Setting Up Your First Mastra Project</h2>
<h3 id="prerequisites-and-installation">Prerequisites and installation</h3>
<p>You need Node.js 18+ and an LLM API key (OpenAI, Anthropic, or Google). Create a new project:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npm create mastra@latest
</span></span></code></pre></div><p>The scaffold prompts you for a project name, your preferred LLM provider, and whether you want the Mastra Studio dev UI included. After setup:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>cd my-mastra-app
</span></span><span style="display:flex;"><span>npm install
</span></span><span style="display:flex;"><span>npm run dev
</span></span></code></pre></div><p>The dev server starts on port 4111 by default and opens Mastra Studio.</p>
<h3 id="project-structure-overview">Project structure overview</h3>
<p>A scaffolded Mastra project looks like this:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 488 249"
      >
      <g transform='translate(8,16)'>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='0' y='36' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='52' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='84' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='100' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='116' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='132' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='148' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='164' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='180' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='0' y='196' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='0' y='212' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='0' y='228' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='8' y='196' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='8' y='212' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='8' y='228' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='196' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='212' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='16' y='228' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='32' y='196' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='32' y='212' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='32' y='228' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='40' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='40' y='212' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='40' y='228' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='48' y='36' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='48' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='48' y='212' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='48' y='228' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='56' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='212' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='56' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='64' y='84' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='64' y='116' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='64' y='132' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='64' y='148' fill='currentColor' style='font-size:1em'>├</text>
<text text-anchor='middle' x='64' y='164' fill='currentColor' style='font-size:1em'>│</text>
<text text-anchor='middle' x='64' y='180' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='64' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='64' y='212' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='64' y='228' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='84' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='116' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='180' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='72' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='212' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='72' y='228' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='84' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='116' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='148' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='180' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='80' y='196' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='80' y='212' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='228' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='88' y='196' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='88' y='212' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='88' y='228' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='96' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='96' y='100' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='96' y='116' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='96' y='132' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='96' y='148' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='96' y='164' fill='currentColor' style='font-size:1em'>└</text>
<text text-anchor='middle' x='96' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='212' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='96' y='228' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='104' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='104' y='68' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='84' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='104' y='132' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='148' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='104' y='164' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='104' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='104' y='212' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='104' y='228' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='112' y='36' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='112' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='68' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='84' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='112' y='100' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='116' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='112' y='132' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='148' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='112' y='164' fill='currentColor' style='font-size:1em'>─</text>
<text text-anchor='middle' x='112' y='180' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='112' y='196' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='112' y='212' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='112' y='228' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='120' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='120' y='84' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='120' y='116' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='120' y='148' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='120' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='120' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='120' y='212' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='120' y='228' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='128' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='128' y='84' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='128' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='128' y='116' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='128' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='128' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='128' y='180' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='128' y='196' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='128' y='228' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='136' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='136' y='84' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='136' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='136' y='116' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='136' y='132' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='136' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='136' y='180' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='136' y='196' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='144' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='144' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='132' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='144' y='164' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='144' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='144' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='152' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='116' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='152' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='152' y='180' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='152' y='196' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='160' y='68' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='160' y='100' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='160' y='116' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='160' y='132' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='160' y='164' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='168' y='68' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='168' y='100' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='168' y='116' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='168' y='132' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='168' y='164' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='176' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='176' y='164' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='184' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='184' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='184' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='184' y='164' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='248' y='180' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='256' y='68' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='256' y='132' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='256' y='164' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='256' y='196' fill='currentColor' style='font-size:1em'>#</text>
<text text-anchor='middle' x='264' y='180' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='272' y='132' fill='currentColor' style='font-size:1em'>W</text>
<text text-anchor='middle' x='272' y='164' fill='currentColor' style='font-size:1em'>R</text>
<text text-anchor='middle' x='272' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='272' y='196' fill='currentColor' style='font-size:1em'>F</text>
<text text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='280' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='280' y='164' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='280' y='180' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='280' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='132' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='288' y='164' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='288' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='288' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='296' y='132' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='296' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='296' y='196' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='304' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='304' y='132' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='304' y='164' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='304' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='304' y='196' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='100' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='312' y='132' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='312' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='312' y='196' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='320' y='100' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='320' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='320' y='164' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='320' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='320' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='328' y='100' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='328' y='132' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='328' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='328' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='328' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='336' y='68' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='336' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='336' y='164' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='336' y='180' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='336' y='196' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='344' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='344' y='132' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='344' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='344' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='352' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='352' y='132' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='352' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='352' y='180' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='352' y='196' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='360' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='360' y='100' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='360' y='132' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='360' y='164' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='360' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='360' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='368' y='68' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='368' y='100' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='368' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='368' y='180' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='368' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='376' y='68' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='376' y='100' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='376' y='132' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='376' y='164' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='376' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='376' y='196' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='384' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='384' y='100' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='384' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='384' y='164' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='384' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='392' y='68' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='392' y='100' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='392' y='132' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='392' y='164' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='392' y='180' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='392' y='196' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='400' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='400' y='132' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='400' y='164' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='400' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='400' y='196' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='408' y='132' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='408' y='164' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='408' y='180' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='408' y='196' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='416' y='132' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='416' y='164' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='416' y='180' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='416' y='196' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='424' y='132' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='424' y='180' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='424' y='196' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='432' y='196' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='440' y='180' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='440' y='196' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='448' y='180' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='448' y='196' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='456' y='180' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='464' y='180' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='472' y='180' fill='currentColor' style='font-size:1em'>t</text>
</g>

    </svg>
  
</div>
<p>All agent configuration lives in TypeScript files under <code>src/mastra/</code>. The framework discovers and registers agents, tools, and workflows based on exports from these files. No YAML, no code generation.</p>
<h3 id="mastra-studio-the-interactive-dev-ui">Mastra Studio: the interactive dev UI</h3>
<p>Mastra Studio runs locally at <code>http://localhost:4111</code> and provides:</p>
<ul>
<li><strong>Agent playground</strong>: chat with any defined agent, inspect tool calls, and trace token usage in real time</li>
<li><strong>Workflow visualizer</strong>: see step DAGs, run workflows step by step, and inspect intermediate state</li>
<li><strong>RAG testing</strong>: query your knowledge base and verify retrieval quality</li>
<li><strong>Eval runner</strong>: execute model-graded and rule-based evaluations against agent outputs</li>
<li><strong>Logs and traces</strong>: structured view of every LLM call, tool invocation, and workflow transition</li>
</ul>
<p>Studio is not required in production—it&rsquo;s a dev-time tool. But it replaces the ad-hoc <code>console.log</code>-driven debugging loop that most agent developers fall into.</p>
<h2 id="building-your-first-ai-agent-with-mastra">Building Your First AI Agent with Mastra</h2>
<h3 id="defining-an-agent-with-system-prompts-and-tools">Defining an agent with system prompts and tools</h3>
<p>An agent in Mastra is a typed object with a system prompt, a model reference, and a set of tools. Here&rsquo;s a minimal research agent:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Agent</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/core/agent&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createTool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/core/tools&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/openai&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">searchTool</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createTool</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;web-search&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Search the web for information about a topic&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">inputSchema</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">query</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">describe</span>(<span style="color:#e6db74">&#34;The search query&#34;</span>),
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">context</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">results</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetchSearchResults</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">query</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">results</span> };
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">researchAgent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Agent</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;research-agent&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">instructions</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`You are a research assistant. When asked about a topic:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">1. Search the web for relevant information
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">2. Synthesize the findings into a concise summary
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">3. Cite your sources
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Always use the search tool before answering factual questions.`</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">searchTool</span> },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Key design decisions here: tools use Zod schemas for both input validation and LLM function-calling definition. The <code>instructions</code> field replaces the informal system-prompt string with a structured prompt that Mastra can version, evaluate against, and refactor across deployments.</p>
<h3 id="adding-memory">Adding memory</h3>
<p>Mastra supports two memory primitives: <strong>working memory</strong> and <strong>semantic recall</strong>.</p>
<p>Working memory is a short-term scratchpad that persists within a conversation thread. It stores structured state the agent can read and write:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Memory</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/memory&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">memory</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Memory</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">options</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">lastMessages</span>: <span style="color:#66d9ef">10</span>,        <span style="color:#75715e">// Include last 10 messages in context
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">workingMemory</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">enabled</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">template</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"># User Profile
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- Name: unknown
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- Preferences: unknown
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">- Current Task: none
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      `</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">semanticRecall</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">topK</span>: <span style="color:#66d9ef">3</span>,               <span style="color:#75715e">// Recall 3 most relevant past messages
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>      <span style="color:#a6e22e">messageRange</span>: <span style="color:#66d9ef">2</span>,       <span style="color:#75715e">// Include 2 messages around each match
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">contextualAgent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Agent</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;contextual-agent&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">instructions</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are a helpful assistant that remembers user context.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">memory</span>,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Semantic recall embeds past conversation turns and retrieves the top-K most relevant ones when a new message arrives. This means the agent can reference a preference mentioned 50 turns ago without loading the entire history into the context window. Working memory lets the agent maintain structured state—user profile, task progress, preferences—that persists across messages in the same thread.</p>
<h3 id="connecting-llm-providers">Connecting LLM providers</h3>
<p>Mastra uses the Vercel AI SDK model interface, so any provider that implements that interface works:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/openai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">anthropic</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/anthropic&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">google</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/google&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Swap models by changing one line
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">agent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Agent</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;flexible-agent&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">instructions</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are a versatile assistant.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">anthropic</span>(<span style="color:#e6db74">&#34;claude-sonnet-4-20250514&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// model: openai(&#34;gpt-4o&#34;),
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#75715e">// model: google(&#34;gemini-2.0-flash&#34;),
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>});
</span></span></code></pre></div><p>Being model-agnostic matters operationally: you can run evals across models, fall back from one provider to another, and choose cost-effective models per task without rewriting agent logic.</p>
<h2 id="tools-and-mcp-connecting-your-agent-to-the-real-world">Tools and MCP: Connecting Your Agent to the Real World</h2>
<h3 id="built-in-tool-types-in-mastra">Built-in tool types in Mastra</h3>
<p>Mastra&rsquo;s <code>createTool</code> API is the foundational primitive. Every tool has an <code>id</code>, a <code>description</code> (used in the LLM&rsquo;s function-calling prompt), an <code>inputSchema</code> (Zod), and an <code>execute</code> function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createTool</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/core/tools&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">calculateTool</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createTool</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;calculate&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">description</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Evaluate a mathematical expression&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">inputSchema</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">expression</span>: <span style="color:#66d9ef">z.string</span>().<span style="color:#a6e22e">describe</span>(<span style="color:#e6db74">&#34;Math expression to evaluate, e.g. &#39;2 + 2&#39;&#34;</span>),
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">outputSchema</span>: <span style="color:#66d9ef">z.object</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">result</span>: <span style="color:#66d9ef">z.number</span>(),
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">context</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Safe evaluation — no eval()
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">safeMathEval</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">expression</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">result</span> };
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>The <code>outputSchema</code> is optional but recommended. When provided, Mastra validates the tool&rsquo;s output against it before returning the result to the agent. This catches malformed tool outputs early and prevents cascading errors.</p>
<h3 id="mcp-model-context-protocol-integration">MCP (Model Context Protocol) integration</h3>
<p>MCP is Anthropic&rsquo;s open protocol for connecting LLMs to external tools and data sources. Mastra implements both the client and server sides. As a client, Mastra can connect to any MCP server and expose its tools to agents:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">MCPClient</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/mcp&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">mcp</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MCPClient</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">servers</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">github</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">command</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;npx&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">args</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#34;-y&#34;</span>, <span style="color:#e6db74">&#34;@modelcontextprotocol/server-github&#34;</span>],
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">env</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">GITHUB_PERSONAL_ACCESS_TOKEN</span>: <span style="color:#66d9ef">process.env.GITHUB_TOKEN</span><span style="color:#f92672">!</span>,
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// MCP tools are automatically available to agents
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tools</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">mcp</span>.<span style="color:#a6e22e">tools</span>();
</span></span></code></pre></div><p>This is how Docker connected their agents to GitHub. The GitHub MCP server provides tools for listing PRs, reading diffs, posting comments, and managing labels—all without writing custom API integration code.</p>
<h3 id="real-example-github-mcp-server-for-pr-automation">Real example: GitHub MCP server for PR automation</h3>
<p>Docker&rsquo;s architecture is instructive. They built three sub-agents, each with a narrow responsibility:</p>
<ol>
<li><strong>Analyze PR agent</strong>: Reads the PR diff and generates a structured analysis</li>
<li><strong>Generate comment agent</strong>: Takes the analysis and writes a review comment</li>
<li><strong>Post and close agent</strong>: Posts the comment and manages PR labels</li>
</ol>
<p>These agents are orchestrated by a Mastra workflow that triggers on a GitHub webhook event. The key insight: rather than one monolithic agent trying to do everything, each agent has a focused system prompt and minimal tool access. This reduces error rates and makes the system auditable—if the posted comment is wrong, you check agent 2, not the entire pipeline.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// Simplified Docker-style PR automation workflow trigger
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#34;/webhook/github&#34;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">action</span>, <span style="color:#a6e22e">pull_request</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">action</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;opened&#34;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">action</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;synchronize&#34;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">prReviewWorkflow</span>.<span style="color:#a6e22e">run</span>({
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">prNumber</span>: <span style="color:#66d9ef">pull_request.number</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">repo</span>: <span style="color:#66d9ef">pull_request.base.repo.full_name</span>,
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">200</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;ok&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="workflows-orchestrating-complex-agent-tasks">Workflows: Orchestrating Complex Agent Tasks</h2>
<h3 id="when-to-use-workflows-vs-agents">When to use workflows vs agents</h3>
<p>An agent with tools handles simple request-response patterns well. But when your task involves multiple sequential steps, conditional branching, parallel execution, or retry logic, you need a workflow. The distinction:</p>
<ul>
<li><strong>Agent</strong>: LLM decides what to do next based on context. Good for open-ended reasoning.</li>
<li><strong>Workflow</strong>: You define the control flow. Good for deterministic multi-step processes.</li>
</ul>
<p>If you can draw a flowchart for your process, use a workflow. If the process requires the LLM to decide its own path, use an agent. Many production systems combine both: a workflow orchestrates high-level steps, and agents handle LLM-driven reasoning within individual steps.</p>
<h3 id="building-step-based-workflows">Building step-based workflows</h3>
<p>Mastra workflows are defined as a series of steps, each with an input schema, an output schema, and an execute function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Workflow</span>, <span style="color:#a6e22e">Step</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/core/workflows&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">fetchContentStep</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Step</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;fetch-content&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">inputSchema</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">url</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">outputSchema</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">content</span>: <span style="color:#66d9ef">z.string</span>(), <span style="color:#a6e22e">title</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">context</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">page</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">url</span>).<span style="color:#a6e22e">then</span>((<span style="color:#a6e22e">r</span>) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">text</span>());
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">title</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">extractTitle</span>(<span style="color:#a6e22e">page</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">content</span>: <span style="color:#66d9ef">page</span>, <span style="color:#a6e22e">title</span> };
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">summarizeStep</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Step</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;summarize&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">inputSchema</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">content</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">outputSchema</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">summary</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">execute</span>: <span style="color:#66d9ef">async</span> ({ <span style="color:#a6e22e">context</span>, <span style="color:#a6e22e">mastra</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">agent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">mastra</span>.<span style="color:#a6e22e">getAgent</span>(<span style="color:#e6db74">&#34;research-agent&#34;</span>)<span style="color:#f92672">!</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">agent</span>.<span style="color:#a6e22e">generate</span>(
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">`Summarize this content concisely:</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">n</span><span style="color:#960050;background-color:#1e0010">\</span><span style="color:#e6db74">n</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">content</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> { <span style="color:#a6e22e">summary</span>: <span style="color:#66d9ef">result.text</span> };
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">contentWorkflow</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Workflow</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;content-summarizer&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">triggerSchema</span>: <span style="color:#66d9ef">z.object</span>({ <span style="color:#a6e22e">url</span>: <span style="color:#66d9ef">z.string</span>() }),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">contentWorkflow</span>
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">step</span>(<span style="color:#a6e22e">fetchContentStep</span>)
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">summarizeStep</span>)
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">commit</span>();
</span></span></code></pre></div><p>Steps can reference the Mastra instance to use agents, tools, or other workflows. The <code>context</code> object carries the output of the previous step(s).</p>
<h3 id="parallel-execution-conditional-branching-and-nesting">Parallel execution, conditional branching, and nesting</h3>
<p>Workflows support <code>branch</code> for conditional paths and <code>parallel</code> for concurrent execution:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#a6e22e">contentWorkflow</span>
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">step</span>(<span style="color:#a6e22e">fetchContentStep</span>)
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">branch</span>([
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">condition</span><span style="color:#f92672">:</span> ({ <span style="color:#a6e22e">context</span> }) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">content</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">5000</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">then</span>: <span style="color:#66d9ef">longContentStep</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">condition</span><span style="color:#f92672">:</span> ({ <span style="color:#a6e22e">context</span> }) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">context</span>.<span style="color:#a6e22e">content</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">5000</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">then</span>: <span style="color:#66d9ef">shortContentStep</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  ])
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">summarizeStep</span>)
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">commit</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Parallel execution
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">analysisWorkflow</span>
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">parallel</span>([<span style="color:#a6e22e">sentimentStep</span>, <span style="color:#a6e22e">entityStep</span>, <span style="color:#a6e22e">keywordStep</span>])
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">mergeResultsStep</span>)
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">commit</span>();
</span></span></code></pre></div><p>Workflows can also nest: a step can invoke another workflow as a sub-routine. This lets you compose complex processes from smaller, testable workflow units. Each nested workflow maintains its own state and can be run and debugged independently.</p>
<h2 id="rag-with-mastra-giving-your-agent-knowledge">RAG with Mastra: Giving Your Agent Knowledge</h2>
<h3 id="embedding-and-vector-search-support">Embedding and vector search support</h3>
<p>Mastra includes built-in embedding and vector search through its RAG module. You define an embedder and a vector store, then index documents:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">MastraRAG</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/rag&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">openai</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@ai-sdk/openai&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">PineconeVector</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/pinecone&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">rag</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MastraRAG</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">embedder</span>: <span style="color:#66d9ef">openai.embedding</span>(<span style="color:#e6db74">&#34;text-embedding-3-small&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">vectorStore</span>: <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">PineconeVector</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">indexName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;knowledge-base&#34;</span>,
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Index documents
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">await</span> <span style="color:#a6e22e">rag</span>.<span style="color:#a6e22e">index</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">docs</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;doc-1&#34;</span>, <span style="color:#a6e22e">text</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Mastra supports multiple LLM providers...&#34;</span> },
</span></span><span style="display:flex;"><span>    { <span style="color:#a6e22e">id</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;doc-2&#34;</span>, <span style="color:#a6e22e">text</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Workflows enable conditional branching...&#34;</span> },
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Query
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">results</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">rag</span>.<span style="color:#a6e22e">retrieve</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">query</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;How do I branch in a workflow?&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">topK</span>: <span style="color:#66d9ef">5</span>,
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Mastra handles chunking, embedding, and storage. You control the chunk size, overlap, and embedding model.</p>
<h3 id="mastra-built-in-rag-capabilities">Mastra built-in RAG capabilities</h3>
<p>Beyond basic retrieval, Mastra provides:</p>
<ul>
<li><strong>Query transformation</strong>: Automatically rewrites user queries for better retrieval</li>
<li><strong>Hybrid search</strong>: Combines vector similarity with keyword matching for improved recall</li>
<li><strong>Re-ranking</strong>: Applies a second-pass relevance model to filter and reorder results</li>
</ul>
<p>These are not external integrations—they ship with the framework and are configurable via the RAG constructor options.</p>
<h3 id="integration-with-elasticsearch-and-other-vector-stores">Integration with Elasticsearch and other vector stores</h3>
<p>Elastic published a detailed walkthrough of building agentic RAG with Mastra and Elasticsearch. The pattern:</p>
<ol>
<li>Index documents into Elasticsearch with dense vector fields</li>
<li>Use Mastra&rsquo;s RAG module with the Elasticsearch vector store adapter</li>
<li>Define an agent that retrieves context and generates answers</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">MastraRAG</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/rag&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">ElasticsearchVector</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/elasticsearch&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">rag</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">MastraRAG</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">embedder</span>: <span style="color:#66d9ef">openai.embedding</span>(<span style="color:#e6db74">&#34;text-embedding-3-small&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">vectorStore</span>: <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">ElasticsearchVector</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">indexName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;docs-index&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">client</span>: <span style="color:#66d9ef">esClient</span>,
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ragAgent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Agent</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;rag-agent&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">instructions</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Answer questions based on the retrieved context.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">If the context doesn&#39;t contain enough information, say so.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Always cite the source document.`</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">tools</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">retrieve</span>: <span style="color:#66d9ef">rag.asTool</span>(),
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>The <code>rag.asTool()</code> method wraps the RAG pipeline as a Mastra tool, making it available to any agent. The Elastic integration demonstrates that Mastra&rsquo;s RAG layer is vendor-agnostic—you can swap Pinecone for Elasticsearch for pgvector without changing agent code.</p>
<h2 id="productionizing-evals-observability-and-guardrails">Productionizing: Evals, Observability, and Guardrails</h2>
<h3 id="running-model-graded-and-rule-based-evals">Running model-graded and rule-based evals</h3>
<p>Mastra includes an evaluation framework that runs LLM outputs against criteria. Two eval types:</p>
<ul>
<li><strong>Model-graded</strong>: An LLM judges the output against a rubric. Useful for open-ended quality assessment.</li>
<li><strong>Rule-based</strong>: Deterministic checks on output structure, content, or behavior. Useful for guardrails.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Eval</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/evals&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">z</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;zod&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">relevanceEval</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Eval</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;relevance&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">type</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;model-graded&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">prompt</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`Rate the relevance of the answer to the question on a scale of 1-5.
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Question: {{input}}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Answer: {{output}}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">Respond with a number only.`</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o-mini&#34;</span>),
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">noPIIEval</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Eval</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;no-pii&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">type</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;rule-based&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">check</span><span style="color:#f92672">:</span> ({ <span style="color:#a6e22e">output</span> }) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">piiPatterns</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">/\b\d{3}-\d{2}-\d{4}\b/</span>, <span style="color:#e6db74">/\b[A-Z]\d{8}\b/</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">piiPatterns</span>.<span style="color:#a6e22e">some</span>((<span style="color:#a6e22e">p</span>) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">p</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">output</span>));
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>You run evals against datasets in Mastra Studio or programmatically. The results give you a quantifiable quality signal before deploying changes.</p>
<h3 id="tracing-agent-calls-and-token-usage">Tracing agent calls and token usage</h3>
<p>Every LLM call, tool invocation, and workflow step is automatically traced. In Mastra Studio, you see:</p>
<ul>
<li>Total latency per request</li>
<li>Token usage breakdown (prompt vs. completion)</li>
<li>Tool call sequences and their outputs</li>
<li>Memory retrieval operations and their relevance scores</li>
</ul>
<p>For production monitoring, Mastra integrates with OpenTelemetry. You can export traces to any OTel-compatible backend (Datadog, Grafana, Honeycomb, etc.):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// mastra.config.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">defineConfig</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">observability</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">otel</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">enabled</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">serviceName</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;my-mastra-app&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">traceExporter</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;otlp&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">endpoint</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;http://localhost:4318/v1/traces&#34;</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>This is a meaningful differentiator. Most agent frameworks leave observability as an exercise for the developer. Mastra wires it into every primitive.</p>
<h3 id="guardrails-for-prompt-injection-prevention">Guardrails for prompt injection prevention</h3>
<p>Mastra provides input and output guardrails as middleware on agent calls:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">guardrail</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/guardrails&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">agent</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Agent</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;safe-agent&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">instructions</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;You are a helpful assistant.&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">model</span>: <span style="color:#66d9ef">openai</span>(<span style="color:#e6db74">&#34;gpt-4o&#34;</span>),
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">guardrails</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">input</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">guardrail</span>.<span style="color:#a6e22e">injectionDetection</span>(), <span style="color:#75715e">// Detect common injection patterns
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">output</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">guardrail</span>.<span style="color:#a6e22e">lengthLimit</span>({ <span style="color:#a6e22e">maxTokens</span>: <span style="color:#66d9ef">500</span> }),
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">guardrail</span>.<span style="color:#a6e22e">piiDetection</span>(),       <span style="color:#75715e">// Block outputs containing PII
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    ],
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Input guardrails run before the LLM call. Output guardrails run after. If a guardrail triggers, the agent returns a structured error instead of the raw LLM output. This is not a complete security solution—you still need proper access controls and sandboxing—but it adds a structured layer of defense.</p>
<h3 id="mastra-studio-metrics-logs-and-datasets">Mastra Studio metrics, logs, and datasets</h3>
<p>Studio aggregates eval results, trace data, and token usage into dashboards. You can:</p>
<ul>
<li>Compare eval scores across model versions</li>
<li>Track token cost trends over time</li>
<li>Build evaluation datasets from production traces</li>
<li>Replay failed conversations to diagnose issues</li>
</ul>
<p>The dataset feature is particularly useful: you can capture production agent interactions, annotate them, and use them as regression test suites when you change prompts, models, or tools.</p>
<h2 id="deployment-from-dev-to-production">Deployment: From Dev to Production</h2>
<h3 id="mastra-server-deploying-as-a-rest-api">Mastra Server: deploying as a REST API</h3>
<p>Mastra generates a server that exposes your agents, tools, and workflows as REST endpoints:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">Mastra</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/core&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">createServer</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@mastra/server&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">mastra</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Mastra</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">agents</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">researchAgent</span>, <span style="color:#a6e22e">contextualAgent</span> },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">workflows</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">contentWorkflow</span> },
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">server</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createServer</span>(<span style="color:#a6e22e">mastra</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#ae81ff">3000</span>);
</span></span></code></pre></div><p>This gives you REST endpoints like:</p>
<ul>
<li><code>POST /api/agents/researchAgent/generate</code> — single-turn generation</li>
<li><code>POST /api/agents/researchAgent/stream</code> — streaming generation</li>
<li><code>POST /api/workflows/contentWorkflow/run</code> — trigger a workflow</li>
<li><code>GET /api/workflows/contentWorkflow/runs/{runId}</code> — check workflow status</li>
</ul>
<p>The API is auto-generated from your Mastra instance definition. No manual route wiring.</p>
<h3 id="framework-integration">Framework integration</h3>
<p>Mastra integrates with popular Node.js frameworks as middleware:</p>
<p><strong>Next.js (App Router)</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#75715e">// app/api/agents/[agentId]/route.ts
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">mastra</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/mastra&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">NextRequest</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;next/server&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">POST</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">req</span>: <span style="color:#66d9ef">NextRequest</span>,
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">params</span> }<span style="color:#f92672">:</span> { <span style="color:#a6e22e">params</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">agentId</span>: <span style="color:#66d9ef">string</span> } }
</span></span><span style="display:flex;"><span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">agent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">mastra</span>.<span style="color:#a6e22e">getAgent</span>(<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">agentId</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> { <span style="color:#a6e22e">message</span> } <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">json</span>();
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">agent</span>.<span style="color:#a6e22e">generate</span>(<span style="color:#a6e22e">message</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">Response</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">text</span>: <span style="color:#66d9ef">result.text</span> });
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><strong>Express</strong>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-typescript" data-lang="typescript"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">express</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;express&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">mastra</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./mastra&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">app</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">express</span>();
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#a6e22e">express</span>.<span style="color:#a6e22e">json</span>());
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#34;/api/agents/:agentId/generate&#34;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">agent</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">mastra</span>.<span style="color:#a6e22e">getAgent</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">params</span>.<span style="color:#a6e22e">agentId</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">agent</span>.<span style="color:#a6e22e">generate</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">message</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">text</span>: <span style="color:#66d9ef">result.text</span> });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#ae81ff">3000</span>);
</span></span></code></pre></div><p><strong>Hono and SvelteKit</strong> are similarly supported with adapter packages. The pattern is the same: import your Mastra instance, call <code>getAgent</code> or <code>getWorkflow</code>, and handle the request.</p>
<h3 id="mastra-platform-studio--server--memory-gateway">Mastra Platform: Studio + Server + Memory Gateway</h3>
<p>For teams that don&rsquo;t want to manage their own infrastructure, Mastra offers a hosted platform:</p>
<ul>
<li><strong>Mastra Studio (cloud)</strong>: Same dev UI, hosted and shared across your team</li>
<li><strong>Mastra Server (cloud)</strong>: Managed deployment of your agents and workflows</li>
<li><strong>Memory Gateway</strong>: Hosted memory service with persistent storage, semantic recall, and cross-session state</li>
</ul>
<h3 id="pricing-and-tiers">Pricing and tiers</h3>
<table>
  <thead>
      <tr>
          <th>Tier</th>
          <th>Price</th>
          <th>Key Limits</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Starter</td>
          <td>Free</td>
          <td>3 agents, 10k memory records, community support</td>
      </tr>
      <tr>
          <td>Teams</td>
          <td>$250/team/month</td>
          <td>Unlimited agents, 1M memory records, priority support</td>
      </tr>
      <tr>
          <td>Enterprise</td>
          <td>Custom</td>
          <td>Dedicated infra, SLA, SSO, custom integrations</td>
      </tr>
  </tbody>
</table>
<p>The free tier is genuinely usable for prototyping and personal projects. The Teams tier is where production deployments land.</p>
<h2 id="mastra-vs-other-ai-frameworks-typescript-first-comparison">Mastra vs Other AI Frameworks: TypeScript-First Comparison</h2>
<h3 id="mastra-vs-langgraph-vs-crewai">Mastra vs LangGraph vs CrewAI</h3>
<table>
  <thead>
      <tr>
          <th>Feature</th>
          <th>Mastra</th>
          <th>LangGraph</th>
          <th>CrewAI</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Language</td>
          <td>TypeScript</td>
          <td>Python</td>
          <td>Python</td>
      </tr>
      <tr>
          <td>Agent abstraction</td>
          <td><code>Agent</code> class with tools + memory</td>
          <td><code>StateGraph</code> with nodes/edges</td>
          <td><code>Crew</code> with <code>Agent</code> roles</td>
      </tr>
      <tr>
          <td>Workflow model</td>
          <td>Step-based with branch/parallel</td>
          <td>State graph with conditional edges</td>
          <td>Sequential/hierarchical process</td>
      </tr>
      <tr>
          <td>Memory</td>
          <td>Built-in (working + semantic recall)</td>
          <td>Manual (checkpointer interface)</td>
          <td>Short-term + long-term memory</td>
      </tr>
      <tr>
          <td>Observability</td>
          <td>Built-in OTel + Studio</td>
          <td>LangSmith (separate product)</td>
          <td>Manual or LangSmith</td>
      </tr>
      <tr>
          <td>Eval framework</td>
          <td>Built-in</td>
          <td>LangSmith (separate product)</td>
          <td>Not included</td>
      </tr>
      <tr>
          <td>MCP support</td>
          <td>Client + server</td>
          <td>Client (via langchain-mcp)</td>
          <td>Not native</td>
      </tr>
      <tr>
          <td>RAG</td>
          <td>Built-in module</td>
          <td>Manual (LangChain retrieval)</td>
          <td>Manual</td>
      </tr>
      <tr>
          <td>Deployment</td>
          <td>Built-in server + Platform</td>
          <td>Manual or LangServe</td>
          <td>Manual</td>
      </tr>
      <tr>
          <td>License</td>
          <td>Apache 2.0</td>
          <td>MIT</td>
          <td>MIT</td>
      </tr>
  </tbody>
</table>
<p>The core distinction: LangGraph and CrewAI are Python frameworks that require the Python ecosystem for production deployment. If your stack is TypeScript, you&rsquo;ll write a Python service, wrap it in a Docker container, and communicate via HTTP. That works, but it adds operational overhead and prevents you from sharing types, tests, and utilities across your codebase.</p>
<h3 id="mastra-vs-vercel-ai-sdk">Mastra vs Vercel AI SDK</h3>
<p>The Vercel AI SDK focuses on LLM integration at the transport layer: streaming responses, managing tool calls, and providing React hooks for chat UIs. Mastra operates at a higher level:</p>
<ul>
<li><strong>Vercel AI SDK</strong>: &ldquo;How do I call an LLM and stream the response to a React component?&rdquo;</li>
<li><strong>Mastra</strong>: &ldquo;How do I build an agent with memory, tools, and guardrails, evaluate it, and deploy it as an API?&rdquo;</li>
</ul>
<p>They&rsquo;re complementary. Mastra uses the Vercel AI SDK&rsquo;s model interface under the hood for LLM calls. You can use the Vercel AI SDK for your frontend chat UI and Mastra for your backend agent logic.</p>
<h3 id="when-to-choose-mastra-and-when-not-to">When to choose Mastra (and when not to)</h3>
<p>Choose Mastra when:</p>
<ul>
<li>Your backend is TypeScript/Node.js</li>
<li>You need structured agents with memory, tools, and guardrails</li>
<li>You want built-in evals and observability without extra tooling</li>
<li>You&rsquo;re building multi-step workflows with conditional logic</li>
</ul>
<p>Skip Mastra when:</p>
<ul>
<li>Your team is Python-first and you&rsquo;re happy with LangGraph or CrewAI</li>
<li>You only need raw LLM streaming (use Vercel AI SDK directly)</li>
<li>You need capabilities Mastra doesn&rsquo;t support yet (e.g., specialized multimodal agent patterns)</li>
</ul>
<h2 id="real-world-examples-and-case-studies">Real-World Examples and Case Studies</h2>
<h3 id="docker-event-driven-pr-management-agent">Docker: Event-driven PR management agent</h3>
<p>Docker built an event-driven agent system that responds to GitHub webhooks. When a PR is opened, their Mastra workflow:</p>
<ol>
<li>Triggers on the webhook payload</li>
<li>Routes through the Docker MCP Gateway to the GitHub MCP server</li>
<li>Runs the analyze PR agent on the diff</li>
<li>Passes the analysis to the generate comment agent</li>
<li>Posts the comment via the post and close agent</li>
</ol>
<p>This is not a chatbot. There&rsquo;s no human in the loop. The entire pipeline runs in response to an event, with no user interaction. That&rsquo;s a different deployment model from most demo agents, and it&rsquo;s where Mastra&rsquo;s workflow primitives matter—the pipeline is deterministic, observable, and can fail gracefully at any step.</p>
<h3 id="elastic-agentic-rag-with-elasticsearch">Elastic: Agentic RAG with Elasticsearch</h3>
<p>Elastic built a RAG assistant combining a Vite + React frontend, a Mastra backend, and Elasticsearch as the vector store. Their writeup highlights the developer experience of staying in a single language stack: the same TypeScript types that define the search index schema also define the agent&rsquo;s tool interface and the frontend&rsquo;s API contract.</p>
<h3 id="softbank-enterprise-productivity-at-scale">SoftBank: Enterprise productivity at scale</h3>
<p>SoftBank deployed Mastra-based agents internally for productivity tools. Scale is the notable aspect—this isn&rsquo;t a prototype serving 10 users. Mastra&rsquo;s memory gateway and observability infrastructure handle the traffic.</p>
<h3 id="replit-agent-3-building-mastra-agents">Replit: Agent 3 building Mastra agents</h3>
<p>Replit&rsquo;s Agent 3 can scaffold and deploy Mastra agents. This meta-pattern—an AI agent building other AI agents—validates Mastra&rsquo;s code-first API design. If an LLM can generate valid Mastra code from a description, the abstractions are well-defined enough to be machine-writable.</p>
<h2 id="conclusion-and-next-steps">Conclusion and Next Steps</h2>
<p>Mastra addresses a real gap in the AI agent tooling ecosystem: a production-grade, TypeScript-native framework with first-class support for the primitives that matter—agents, tools, workflows, RAG, evals, and observability. The enterprise adoption numbers (100k+ daily users at Marsh McLennan, Brex&rsquo;s $5.1B acquisition involvement) confirm that it&rsquo;s not just developer-friendly but production-ready.</p>
<h3 id="key-takeaways">Key takeaways</h3>
<ol>
<li><strong>TypeScript-first is now a viable choice for AI agents.</strong> With 60–70% of YC X25 agent startups choosing TypeScript, the ecosystem demand is clear. Mastra provides the framework primitives that Python alternatives have had for years.</li>
<li><strong>MCP integration is a differentiator.</strong> Mastra&rsquo;s ability to connect to any MCP server as a tool source gives agents access to external systems without custom integration code. Docker&rsquo;s PR automation demonstrates this in production.</li>
<li><strong>Built-in evals and observability are not optional extras.</strong> If you can&rsquo;t measure agent quality, you can&rsquo;t improve it. Mastra&rsquo;s eval framework and OpenTelemetry integration give you measurement from day one.</li>
<li><strong>Workflows complement agents.</strong> Not every problem needs an LLM deciding what to do next. Mastra&rsquo;s workflow engine handles the structured part of your pipeline while agents handle the reasoning part.</li>
<li><strong>The Gatsby team&rsquo;s framework experience shows.</strong> The DX decisions—Zod schemas, code-first configuration, Studio as a dev tool—reflect experience shipping a framework used by tens of thousands of developers.</li>
</ol>
<h3 id="resources">Resources</h3>
<ul>
<li><strong>Mastra docs</strong>: <a href="https://mastra.ai/docs">mastra.ai/docs</a></li>
<li><strong>Mastra GitHub</strong>: <a href="https://github.com/mastra-ai/mastra">github.com/mastra-ai/mastra</a></li>
<li><strong>Mastra templates</strong>: <a href="https://mastra.ai/templates">mastra.ai/templates</a></li>
<li><strong>Agent Book</strong>: <a href="https://mastra.ai/agentbook">mastra.ai/agentbook</a> — community-contributed agent examples</li>
<li><strong>Community Discord</strong>: <a href="https://mastra.ai/community">mastra.ai/community</a></li>
</ul>
<h3 id="the-future-of-typescript-ai-development">The future of TypeScript AI development</h3>
<p>The trajectory is clear. As agent deployments move from demos to production, the operational requirements—evals, observability, guardrails, memory management, workflow orchestration—become the differentiating factors. Mastra builds these into the framework rather than leaving them as integration exercises. For TypeScript teams building AI agents in 2026, Mastra is the framework that matches the language, runtime, and operational demands of the job.</p>
]]></content:encoded></item></channel></rss>