new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

AI SDK · Core · all subjects

agents

279 notes in this subject, read out of this brain and free to use. This is page 1 of 5.

ToolLoopAgent class overview

The ToolLoopAgent class handles the three components of agents: LLMs, tools, and the orchestration loop. It automatically manages the loop, context management, and stopping conditions to orchestrate tool calls and generate final responses.

toolsContext for per-tool values

Use toolsContext for per-tool values such as API keys or scoped permissions. Each tool receives only its own typed context based on its contextSchema.

runtimeContext for agent shared state

Use runtimeContext as the agent's shared runtime state for server-side data that should not be placed directly in the prompt, such as tenant settings, request IDs, feature flags, credentials, or progress through a task. It flows through the agent loop, is available in prepareStep and lifecycle callbacks, and can be updated between steps.

ToolLoopAgent benefits

The ToolLoopAgent is the recommended approach for building agents with the AI SDK because it reduces boilerplate by managing loops and message arrays, improves reusability by defining the agent once and using it throughout your application, and simplifies maintenance by providing a single place to update agent configuration.

When to use ToolLoopAgent vs core functions

Start with the ToolLoopAgent for most use cases. Use core functions like generateText and streamText when you need explicit control over each step for complex structured workflows.

runAgentTUI function usage

The runAgentTUI function accepts an object with title and agent properties, where title is a string for the terminal UI title and agent is a ToolLoopAgent instance to run.

Weather agent example with ToolLoopAgent

Example showing a ToolLoopAgent with two tools: a 'weather' tool that takes a location string and returns temperature in Fahrenheit, and a 'convertFahrenheitToCelsius' tool that converts Fahrenheit to Celsius. The agent automatically calls these tools in sequence to answer the prompt 'What is the weather in San Francisco in celsius?' and returns the final answer in result.text and the steps taken in result.steps.

Structured workflows for deterministic outcomes

When you need reliable, repeatable outcomes with explicit control flow instead of non-deterministic agent behavior, use core functions with structured workflow patterns combining conditional statements for explicit branching, standard functions for reusable logic, error handling for robustness, and explicit control flow for predictability.

ToolLoopAgent.generate() method

The ToolLoopAgent.generate() method takes a prompt parameter and returns a result object with a text property containing the agent's final answer and a steps property containing the steps taken by the agent.

ToolLoopAgent constructor: model and tools parameters

The ToolLoopAgent constructor takes a model parameter and a tools parameter. The model is the LLM to use, and tools is an object containing tool definitions, where each tool is created using the tool() function with description, inputSchema, and execute properties.

Agent loop orchestration: context management and stopping conditions

The agent loop orchestrates execution through two mechanisms: context management, which maintains conversation history and decides what the model sees at each step, and stopping conditions, which determine when the loop (task) is complete.

Terminal UI with @ai-sdk/tui

Use @ai-sdk/tui to run a ToolLoopAgent in an interactive terminal UI. It is useful for local agent development, demos, and internal tools, providing prompt input, streamed responses, tool cards, reasoning sections, scrolling, and tool approval prompts without building a custom interface.

HarnessAgent class for preconfigured harnesses

Use HarnessAgent when you want to run a preconfigured established harness, such as Claude Code, Codex, or Pi, instead of building the loop yourself around a language model. Harnesses are a separate abstraction from providers and models, but stream into AI SDK-compatible result and UI primitives.

Agents definition: LLMs using tools in a loop

Agents are large language models (LLMs) that use tools in a loop to accomplish tasks. The three core components are: LLMs that process input and decide the next action, tools that extend capabilities beyond text generation (reading files, calling APIs, writing to databases), and a loop that orchestrates execution through context management and stopping conditions.

Provider-specific options example code

import { OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai'; import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: 'openai/o3', callOptionsSchema: z.object({ taskDifficulty: z.enum(['low', 'medium', 'high']), }), prepareCall: ({ options, ...settings }) => ({ ...settings, providerOptions: { openai: { reasoningEffort: options.taskDifficulty, } satisfies OpenAILanguageModelResponsesOptions, }, }), }); await agent.generate({ prompt: 'Analyze this complex scenario...', options: { taskDifficulty: 'high' }, });

RAG with call options example code

import { ToolLoopAgent } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; const ragAgent = new ToolLoopAgent({ model: __MODEL__, callOptionsSchema: z.object({ query: z.string(), }), prepareCall: async ({ options, ...settings }) => { const documents = await vectorSearch(options.query); return { ...settings, instructions: `Answer questions using the following context:\n\n${documents.map(doc => doc.content).join('\n\n')}`, }; }, }); await ragAgent.generate({ prompt: 'What is our refund policy?', options: { query: 'refund policy' }, });

Dynamic model selection with call options

You can select different models at runtime based on call options. Use prepareCall to return a different model property based on options. For example, use a faster model like 'openai/gpt-4o-mini' for simple queries and a more capable model like 'openai/o1-mini' for complex reasoning.

Basic call options example code

import { ToolLoopAgent } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; const supportAgent = new ToolLoopAgent({ model: __MODEL__, callOptionsSchema: z.object({ userId: z.string(), accountType: z.enum(['free', 'pro', 'enterprise']), }), instructions: 'You are a helpful customer support agent.', prepareCall: ({ options, ...settings }) => ({ ...settings, instructions: settings.instructions + `\nUser context:\n- Account type: ${options.accountType}\n- User ID: ${options.userId}\n\nAdjust your response based on the user's account level.`, }), }); const result = await supportAgent.generate({ prompt: 'How do I upgrade my account?', options: { userId: 'user_123', accountType: 'free', }, });

Dynamic model selection example code

import { ToolLoopAgent } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: __MODEL__, callOptionsSchema: z.object({ complexity: z.enum(['simple', 'complex']), }), prepareCall: ({ options, ...settings }) => ({ ...settings, model: options.complexity === 'simple' ? 'openai/gpt-4o-mini' : 'openai/o1-mini', }), }); await agent.generate({ prompt: 'What is 2+2?', options: { complexity: 'simple' }, }); await agent.generate({ prompt: 'Explain quantum entanglement', options: { complexity: 'complex' }, });

Dynamic tool configuration with call options

You can configure tool behavior at runtime using call options. Define tools in the agent config, then in prepareCall return a modified tools object with the same tool keys but updated configuration. For example, pass user location to web search tools based on userCity and userRegion options.

Call options for RAG pattern

For Retrieval Augmented Generation, use call options to pass a query that prepareCall can use to fetch relevant documents asynchronously. Since prepareCall can be async, you can await vectorSearch(options.query) and inject the returned documents into instructions.

Combining multiple modifications example code

import { ToolLoopAgent } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: __MODEL__, callOptionsSchema: z.object({ userRole: z.enum(['admin', 'user']), urgency: z.enum(['low', 'high']), }), tools: { readDatabase: readDatabaseTool, writeDatabase: writeDatabaseTool, }, prepareCall: ({ options, ...settings }) => ({ ...settings, model: options.urgency === 'high' ? __MODEL__ : settings.model, activeTools: options.userRole === 'admin' ? ['readDatabase', 'writeDatabase'] : ['readDatabase'], instructions: `You are a ${options.userRole} assistant.\n${options.userRole === 'admin' ? 'You have full database access.' : 'You have read-only access.'}`, }), }); await agent.generate({ prompt: 'Update the user record', options: { userRole: 'admin', urgency: 'high', }, });

prepareCall hook signature and behavior

The prepareCall hook receives an object with options (the runtime options passed in) and settings (the current agent settings). It returns modified settings. Only return the settings you want to change; the rest will be merged. The prepareCall function can be async, enabling you to fetch data before configuring the agent.

callOptionsSchema property

The callOptionsSchema property on agent configuration accepts a Zod schema that defines the shape of options that will be passed at runtime. When defined, the options parameter becomes required and type-checked on generate() and stream() calls.

Call options configuration steps

Define call options in three steps: (1) Define the schema - specify what inputs you accept using callOptionsSchema; (2) Configure with prepareCall - use those inputs to modify agent settings; (3) Pass options at runtime - provide the options when calling generate() or stream().

Combining multiple modifications in prepareCall

You can modify multiple agent settings in a single prepareCall implementation. Common patterns include: changing model based on urgency, limiting activeTools based on user role, and adjusting instructions based on options.

Using call options with createAgentUIStreamResponse

Pass call options through createAgentUIStreamResponse by providing an options property alongside agent and messages. Options extracted from the request can be passed directly to createAgentUIStreamResponse, which will forward them to the agent.

Provider-specific options with call options

Use prepareCall to set provider-specific options dynamically. Return a providerOptions object keyed by provider name. For example, set OpenAI's reasoningEffort based on task difficulty: providerOptions: { openai: { reasoningEffort: options.taskDifficulty } }.

Call options definition and purpose

Call options allow you to pass type-safe structured inputs to your agent. Use them to dynamically modify any agent setting based on the specific request. Without call options, you would need to create multiple agents or handle configuration logic outside the agent.

Using call options with createAgentUIStreamResponse example code

import { createAgentUIStreamResponse } from 'ai'; import { myAgent } from '@/ai/agents/my-agent'; export async function POST(request: Request) { const { messages, userId, accountType } = await request.json(); return createAgentUIStreamResponse({ agent: myAgent, messages, options: { userId, accountType, }, }); }

Dynamic tool configuration example code

import { openai } from '@ai-sdk/openai'; import { ToolLoopAgent } from 'ai'; __PROVIDER_IMPORT__; import { z } from 'zod'; const newsAgent = new ToolLoopAgent({ model: __MODEL__, callOptionsSchema: z.object({ userCity: z.string().optional(), userRegion: z.string().optional(), }), tools: { web_search: openai.tools.webSearch(), }, prepareCall: ({ options, ...settings }) => ({ ...settings, tools: { web_search: openai.tools.webSearch({ searchContextSize: 'low', userLocation: { type: 'approximate', city: options.userCity, region: options.userRegion, country: 'US', }, }), }, }), }); await newsAgent.generate({ prompt: 'What are the top local news stories?', options: { userCity: 'San Francisco', userRegion: 'California', }, });

Call options use cases

Call options enable: (1) Adding dynamic context by injecting retrieved documents, user preferences, or session data into prompts; (2) Selecting models dynamically based on request complexity; (3) Configuring tools per request such as passing user location to search tools or adjusting tool behavior; (4) Customizing provider options by setting reasoning effort, temperature, or other provider-specific settings.

Custom memory tool implementation patterns

Two common patterns for building custom memory tools: (1) Structured actions — define explicit operations (view, create, update, search) and handle structured input yourself, safe by design since you control every operation. (2) Bash-backed — give the model a sandboxed bash environment to compose shell commands (cat, grep, sed, echo) for flexible memory access, more powerful but requires command validation for safety.

isLoopFinished() purpose with memory tools

isLoopFinished() lets an agent keep running until the tool loop naturally finishes, which is useful when memory tools need to read and write before the final response is generated.

MongoDB memory usage example

import { createMongoDBMemory } from '@mongodb-developer/vercel-ai-memory'; import { openai } from '@ai-sdk/openai'; import { ToolLoopAgent, isLoopFinished } from 'ai'; const mongodbMemory = createMongoDBMemory({ uri: process.env.MONGODB_URI!, embedder: openai.embedding('text-embedding-3-small'), }); const agent = new ToolLoopAgent({ model: openai('gpt-4.1'), tools: mongodbMemory({ userId: 'alice', sessionId: 'sess-001' }), stopWhen: isLoopFinished(), }); const result = await agent.generate({ prompt: 'My name is Alice and I love hiking. Remember that.', });

MongoDB memory session modes

MongoDB memory session supports two modes: tool-driven (the LLM decides when to read/write, good for prototypes) and hook-driven (the runtime persists every turn via prepareCall + onEnd hooks, recommended for production). Other memory tiers (semantic, procedural, episodic, scratchpad) are always LLM-controlled and selective by design.

Hindsight bankId configuration

In Hindsight, the bankId parameter identifies the memory store and is typically a user ID. In multi-user applications, call createHindsightTools inside the request handler so each request gets the correct bank/memory store.

Hindsight usage example

import { HindsightClient } from '@vectorize-io/hindsight-client'; import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; import { ToolLoopAgent } from 'ai'; const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL }); const agent = new ToolLoopAgent({ model: __MODEL__, tools: createHindsightTools({ client, bankId: 'user-123' }), instructions: 'You are a helpful assistant with long-term memory.', }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

MongoDB memory provider features

@mongodb-developer/vercel-ai-memory provides MongoDB Atlas-backed persistent memory with five structured tiers: Session, Semantic, Procedural, Episodic, and Scratchpad. Retrieval is powered by Atlas Vector Search using any AI SDK embedding model. Features include automatic index creation and per-type retention policies. Targets AI SDK v6 and requires peer dependencies: ai ^6.0.0, mongodb ^6.0.0, zod ^3.0.0.

Supermemory usage example

import { supermemoryTools } from '@supermemory/tools/ai-sdk'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: __MODEL__, tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY!), }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Hindsight memory provider installation and setup

Install Hindsight with: pnpm add @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client. Hindsight provides agents with persistent memory through five tools: retain, recall, reflect, getMentalModel, and getDocument. Can be self-hosted with Docker or used as a cloud service. Works with any AI SDK provider with no vendor lock-in beyond the service itself.

MongoDB memory provider installation

Install MongoDB memory with: pnpm add @mongodb-developer/vercel-ai-memory.

Mem0 memory provider installation and setup

Install Mem0 provider with: pnpm add @mem0/vercel-ai-provider. Mem0 adds a memory layer on top of any supported LLM provider. It automatically extracts memories from conversations, stores them, and retrieves relevant ones for future prompts. Works across multiple LLM providers including OpenAI, Anthropic, Google, Groq, and Cohere.

Letta memory tools integration example

import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: lettaCloud(), tools: { core_memory_append: lettaCloud.tool('core_memory_append'), memory_insert: lettaCloud.tool('memory_insert'), memory_replace: lettaCloud.tool('memory_replace'), }, providerOptions: { letta: { agent: { id: 'your-agent-id' }, }, }, }); const stream = agent.stream({ prompt: 'What do you remember about me?', });

Mem0 basic usage example

import { createMem0 } from '@mem0/vercel-ai-provider'; import { ToolLoopAgent } from 'ai'; const mem0 = createMem0({ provider: 'openai', mem0ApiKey: process.env.MEM0_API_KEY, apiKey: process.env.OPENAI_API_KEY, }); const agent = new ToolLoopAgent({ model: mem0('gpt-4.1', { user_id: 'user-123' }), }); const { text } = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Letta memory provider installation and basic setup

Install Letta provider with: pnpm add @letta-ai/vercel-ai-sdk-provider. Configure a ToolLoopAgent with lettaCloud as the model and set providerOptions with agent id. Letta handles memory management (core memory, archival memory, recall) transparently. The agent is created on Letta's platform (cloud or self-hosted) before using the AI SDK provider.

Anthropic Memory Tool usage example

import { anthropic } from '@ai-sdk/anthropic'; import { ToolLoopAgent } from 'ai'; const memory = anthropic.tools.memory_20250818({ execute: async action => { // action contains command, path, and other fields // Implement your storage backend here. // Return the result as a string. }, }); const agent = new ToolLoopAgent({ model: 'anthropic/claude-haiku-4.5', tools: { memory }, }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Letta basic usage example

import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: lettaCloud(), providerOptions: { letta: { agent: { id: 'your-agent-id' }, }, }, }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', });

Mem0 explicit memory management functions

Mem0 provides addMemories and retrieveMemories functions for explicit memory management. addMemories(messages, { user_id: 'user-123' }) stores memories. retrieveMemories(prompt, { user_id: 'user-123' }) retrieves relevant memories for a prompt.

Three approaches to add memory to agents

Memory can be added to an agent using three approaches: (1) Provider-Defined Tools (low effort, medium flexibility, yes provider lock-in), (2) Memory Providers (low effort, low flexibility, depends on memory provider for lock-in), (3) Custom Tool (high effort, high flexibility, no provider lock-in).

Supermemory provider installation and usage

Install Supermemory with: pnpm add @supermemory/tools. Supermemory is a long-term memory platform providing tools that handle saving and retrieving memories automatically through semantic search. Works with any AI SDK provider. Tools provide addMemory and searchMemories operations that handle storage and retrieval.

Agent loop stops when

The agent loop continues until one of these conditions is met: a finish reasoning other than tool-calls is returned, a tool that is invoked does not have an execute function, a tool call needs approval, or a stop condition is met.

stopWhen parameter for loop control

The stopWhen parameter controls when to stop execution when there are tool results in the last step. By default, agents stop after 20 steps using isStepCount(20). When you provide stopWhen, the agent continues executing after tool calls until a stopping condition is met. When the condition is an array, execution stops when any of the conditions are met.

prepareStep callback for loop modification

The prepareStep callback runs before each step in the loop and defaults to the initial settings if you don't return any changes. Use it to modify settings, manage context, or implement dynamic behavior based on execution history. It receives messages for the current step, plus initialMessages and responseMessages when you need to distinguish the original input from assistant/tool messages accumulated in earlier steps.

Combine multiple stopping conditions example

Example showing how to combine multiple stopping conditions with an array, where the loop stops when any condition is met: ```ts import { ToolLoopAgent, isStepCount, hasToolCall } from 'ai'; __PROVIDER_IMPORT__; const agent = new ToolLoopAgent({ model: __MODEL__, tools: { // your tools }, stopWhen: [ isStepCount(20), // Maximum 20 steps hasToolCall('someTool', 'done'), // Stop after calling either tool ], }); const result = await agent.generate({ prompt: 'Research and analyze the topic', }); ```

isLoopFinished stopping condition example

Example showing how to use isLoopFinished() to allow the agent to run until it naturally stops making tool calls with no maximum step limit: ```ts import { ToolLoopAgent, isLoopFinished } from 'ai'; __PROVIDER_IMPORT__; const agent = new ToolLoopAgent({ model: __MODEL__, tools: { // your tools }, stopWhen: isLoopFinished(), // No maximum step limit. }); const result = await agent.generate({ prompt: 'Analyze this dataset and create a summary report', }); ```

isStepCount stopping condition example

Example showing how to use isStepCount to increase the default step limit from 20 to 50 in a ToolLoopAgent: ```ts import { ToolLoopAgent, isStepCount } from 'ai'; __PROVIDER_IMPORT__; const agent = new ToolLoopAgent({ model: __MODEL__, tools: { // your tools }, stopWhen: isStepCount(50), // Increasing the default of 20 to 50. }); const result = await agent.generate({ prompt: 'Analyze this dataset and create a summary report', }); ```

Manual loop control with generateText

For scenarios requiring complete control over the agent loop, you can use AI SDK Core functions (generateText and streamText) to implement your own loop management instead of using stopWhen and prepareStep. This approach provides maximum flexibility for complex workflows and gives complete control over message history management, step-by-step decision making, custom stopping conditions, dynamic tool and model selection, and error handling and recovery.

Accessing forced tool calling results

When using forced tool calling with a done tool that has no execute function, the final answer is available in result.staticToolCalls, which contains tool calls that weren't executed.

Default step limit safety measure

The default step limit of 20 steps using isStepCount(20) is a safety measure to prevent runaway loops that could result in excessive API calls and costs.

Give your agent this brain