{"owner":"VoltAgent","repo":"voltagent","hasSkills":true,"totalSkillsCount":78,"totalTokensCount":146437,"categories":["cursor-rule","root-instruction","claude-rule","plugin-manifest","anthropic-skill","subagent-persona"],"hasMcp":false,"mcpConfig":null,"found":[".cursor/rules/agents.mdc","AGENTS.md","CLAUDE.md","examples/with-client-side-tools/README.md","examples/with-tools/README.md","examples/with-workspace/workspace/skills/data-analysis/SKILL.md","examples/with-workspace/workspace/skills/data-analysis/references/schema.md","examples/with-workspace/workspace/skills/playwright-cli/SKILL.md","examples/with-workspace/workspace/skills/playwright-cli/references/request-mocking.md","examples/with-workspace/workspace/skills/playwright-cli/references/running-code.md","examples/with-workspace/workspace/skills/playwright-cli/references/session-management.md","examples/with-workspace/workspace/skills/playwright-cli/references/storage-state.md","examples/with-workspace/workspace/skills/playwright-cli/references/test-generation.md","examples/with-workspace/workspace/skills/playwright-cli/references/tracing.md","examples/with-workspace/workspace/skills/playwright-cli/references/video-recording.md","examples/with-xquik-tools/README.md","packages/a2a-server/README.md","packages/ag-ui/README.md","packages/cli/README.md","packages/cloudflare-d1/README.md","packages/core/README.md","packages/create-voltagent-app/README.md","packages/docs-mcp/README.md","packages/evals/README.md","packages/internal/README.md","packages/langfuse-exporter/README.md","packages/libsql/README.md","packages/logger/README.md","packages/mcp-server/README.md","packages/postgres/README.md","packages/sandbox-blaxel/README.md","packages/sandbox-daytona/README.md","packages/sandbox-e2b/README.md","packages/scorers/README.md","packages/sdk/README.md","packages/server-core/README.md","packages/server-elysia/README.md","packages/serverless-hono/README.md","packages/supabase/README.md","packages/vercel-ai-exporter/README.md","packages/voice/README.md","packages/voltagent-memory/README.md","tools/README.md","tools/core/README.md","website/docs/agents/a2a/a2a-server.md","website/docs/agents/cancellation.md","website/docs/agents/context.md","website/docs/agents/dynamic-agents.md","website/docs/agents/hooks.md","website/docs/agents/mcp/authorization.md","website/docs/agents/mcp/mcp-server.md","website/docs/agents/mcp/mcp.md","website/docs/agents/memory.md","website/docs/agents/memory/cloudflare-d1.md","website/docs/agents/memory/in-memory.md","website/docs/agents/memory/libsql.md","website/docs/agents/memory/managed-memory.md","website/docs/agents/memory/overview.md","website/docs/agents/memory/postgres.md","website/docs/agents/memory/semantic-search.md","website/docs/agents/memory/supabase.md","website/docs/agents/memory/working-memory.md","website/docs/agents/message-types.md","website/docs/agents/middleware.md","website/docs/agents/multi-modal.md","website/docs/agents/overview.md","website/docs/agents/plan-agent.md","website/docs/agents/prompts.md","website/docs/agents/providers.md","website/docs/agents/resumable-streaming.md","website/docs/agents/retries-fallback.md","website/docs/agents/subagents.md","website/docs/agents/summarization.md","website/docs/agents/tools.md","website/docs/agents/voice.md","website/docs/agents/voltagent-instance.md","website/docs/tools/overview.md","website/docs/tools/tool-routing.md"],"skills":{".cursor/rules/agents.mdc":"---\ndescription: \nglobs: \nalwaysApply: true\n---\nALWAYS read the `AGENTS.md` in the root of the repository and apply the instructions to the current task.\n","AGENTS.md":"# VoltAgent\n\nVoltAgent is an open-source TypeScript framework for building and orchestrating AI agents.\n\n## What is VoltAgent?\n\nVoltAgent is a comprehensive framework that enables developers to build sophisticated AI agents with memory, tools, observability, and sub-agent capabilities. It provides direct AI SDK integration with comprehensive OpenTelemetry tracing built-in.\n\n## Overview\n\n- View [`docs/structure.md`](./docs/structure.md) for the repository structure and organization\n- View [`docs/tooling.md`](./docs/tooling.md) for development tools and utilities\n- View [`docs/testing.md`](./docs/testing.md) for testing guidelines and commands\n- View [`docs/linting.md`](./docs/linting.md) for code formatting and linting\n\n## Validating Changes\n\nTo validate your changes you can run the following commands:\n\n```bash\npnpm test:all        # Run all tests\npnpm build:all       # Build all packages\npnpm lint            # Run linting checks\npnpm lint:fix        # Auto-fix linting issues\n```\n\n## Important Notes for AI Agents\n\n1. **Always check existing patterns** before implementing new features\n2. **Use the established registry patterns** for agent and tool management\n3. **Maintain type safety** - this is a TypeScript-first codebase\n4. **Follow the monorepo structure** - changes may impact multiple packages\n5. **Test your changes** - ensure all tests pass before committing\n\n## Gotchas\n\n- **JSON.stringify** SHOULD NEVER BE USED, used the `safeStringify` function instead, imported from `@voltagent/internal`\n","CLAUDE.md":"AGENTS.md","examples/with-client-side-tools/README.md":"# VoltAgent Client-Side Tools Example\n\nThis Next.js example demonstrates how to use client-side tools with VoltAgent, providing a smooth developer experience similar to Vercel AI SDK.\n\n## Features\n\n- 🎨 **UI Tools**: Change theme, show notifications\n- 📊 **Data Tools**: Get location, read clipboard\n- 🤝 **Interactive Tools**: Ask for user confirmation\n- ⚡ **Smooth DX**: Uses Vercel AI SDK's `useChat` hook\n- 🔄 **Automatic Handling**: Tools without `execute` are automatically client-side\n\n## Running the Example\n\n1. Set your OpenAI API key:\n\n```bash\nexport OPENAI_API_KEY=\"your-api-key\"\n```\n\n2. Install dependencies:\n\n```bash\npnpm install\n```\n\n3. Run the development server:\n\n```bash\npnpm dev\n```\n\n4. Start the VoltAgent built-in server in a separate terminal:\n\n```bash\npnpm voltagent:run\n```\n\n5. Open [http://localhost:3000](http://localhost:3000)\n\nTo inspect runs, open `https://console.voltagent.dev` and connect it to `http://localhost:3141`.\n\n### How It Works\n\n### 1. Define Tools (No Execute = Client-Side)\n\n```typescript\n// Client-side tool (no execute function)\nconst getLocationTool = createTool({\n  name: \"getLocation\",\n  description: \"Get user's current location\",\n  parameters: z.object({}),\n  // No execute = automatically client-side\n});\n```\n\n### 2. Handle Client-Side Tools with useChat\n\n```typescript\nconst [result, setResult] = useState<ClientSideToolResult | null>(null);\n\nconst { messages, input, handleSubmit, addToolResult } = useChat({\n  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,\n  // Automatic client-side tool execution\n  async onToolCall({ toolCall }) {\n    if (toolCall.toolName === \"getLocation\") {\n      navigator.geolocation.getCurrentPosition(\n        (position) => {\n          const payload: ClientSideToolResult = {\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            output: {\n              latitude: position.coords.latitude,\n              longitude: position.coords.longitude,\n              accuracy: position.coords.accuracy,\n            },\n          };\n          setResult(payload);\n        },\n        (error) => {\n          const payload: ClientSideToolResult = {\n            state: \"output-error\",\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            errorText: error.message,\n          };\n          setResult(payload);\n        }\n      );\n    }\n  },\n});\n```\n\n### 2. Sending back tool results to the model\n\nWhen a client-side tool is executed, you **MUST** call `addToolResult` to provide the tool result to the model. The model will then use the result to update the conversation state.\nOtherwise, the model will consider the tool call as a failure.\n\n```ts\nuseEffect(() => {\n  if (!result) return;\n  addToolResult(result);\n}, [result, addToolResult]);\n```\n\n## Tool Types\n\n1. **Client-side Tools**: Execute client-side, don't return data\n   - Change theme\n   - Show notifications\n   - Get location\n   - Read clipboard\n   - Ask for confirmation\n\n2. **Server Tools**: Traditional server-side execution\n   - Get weather\n   - Database queries\n\n## Developer Experience\n\nThe integration is designed to be as simple as possible:\n\n1. Tools without `execute` are automatically client-side\n2. `useChat` hook handles all the streaming complexity\n3. `onToolCall` callback invoked when a tool call is received\n4. You **must** call `addToolResult` to send back the tool result.\n5. Full TypeScript support\n\n## Try These Examples\n\n- \"What's my current location?\" - Data tool\n- \"What's in my clipboard?\" - Data tool\n- \"What's the weather in San Francisco?\" - Server tool\n\n## Security Considerations\n\nClient-side tools have access to browser APIs, so:\n\n- Always validate tool arguments\n- Request user permission for sensitive operations\n- Use the interactive pattern for destructive actions\n- Consider rate limiting tool executions\n","examples/with-tools/README.md":"<div align=\"center\">\n<a href=\"https://voltagent.dev/\">\n<img width=\"1800\" alt=\"435380213-b6253409-8741-462b-a346-834cd18565a9\" src=\"https://github.com/user-attachments/assets/452a03e7-eeda-4394-9ee7-0ffbcf37245c\" />\n</a>\n\n<br/>\n<br/>\n\n<div align=\"center\">\n    <a href=\"https://voltagent.dev\">Home Page</a> |\n    <a href=\"https://voltagent.dev/docs/\">Documentation</a> |\n    <a href=\"https://github.com/voltagent/voltagent/tree/main/examples\">Examples</a> |\n    <a href=\"https://s.voltagent.dev/discord\">Discord</a> |\n    <a href=\"https://voltagent.dev/blog/\">Blog</a>\n</div>\n</div>\n\n<br/>\n\n<div align=\"center\">\n    <strong>VoltAgent is an open source TypeScript framework for building and orchestrating AI agents.</strong><br>\nEscape the limitations of no-code builders and the complexity of starting from scratch.\n    <br />\n    <br />\n</div>\n\n<div align=\"center\">\n    \n[![npm version](https://img.shields.io/npm/v/@voltagent/core.svg)](https://www.npmjs.com/package/@voltagent/core)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CODE_OF_CONDUCT.md)\n[![Discord](https://img.shields.io/discord/1361559153780195478.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://s.voltagent.dev/discord)\n[![Twitter Follow](https://img.shields.io/twitter/follow/voltagent_dev?style=social)](https://twitter.com/voltagent_dev)\n    \n</div>\n\n<br/>\n\n<div align=\"center\">\n<a href=\"https://voltagent.dev/\">\n<img width=\"896\" alt=\"VoltAgent Schema\" src=\"https://github.com/user-attachments/assets/f0627868-6153-4f63-ba7f-bdfcc5dd603d\" />\n</a>\n\n</div>\n\n## VoltAgent: Build AI Agents Fast and Flexibly\n\nVoltAgent is an open-source TypeScript framework for creating and managing AI agents. It provides modular components to build, customize, and scale agents with ease. From connecting to APIs and memory management to supporting multiple LLMs, VoltAgent simplifies the process of creating sophisticated AI systems. It enables fast development, maintains clean code, and offers flexibility to switch between models and tools without vendor lock-in.\n\n## Try Example\n\n```bash\nnpm create voltagent-app@latest -- --example with-tools\n```\n","examples/with-workspace/workspace/skills/data-analysis/SKILL.md":"---\nname: Workspace Data Analyst\ndescription: Analyze CSV files in the workspace and summarize insights.\nversion: \"1.0.0\"\ntags:\n  - data\n  - csv\n  - analysis\nreferences:\n  - references/schema.md\nassets:\n  - assets/sample.csv\n---\n\nWhen analyzing CSV data:\n\n1. Load the file and inspect headers.\n2. Summarize totals, averages, and outliers.\n3. Provide a short insight summary and recommended next steps.\n","examples/with-workspace/workspace/skills/data-analysis/references/schema.md":"# CSV Schema\n\nExpected columns:\n\n- customer_id: unique identifier\n- segment: smb | mid-market | enterprise\n- mrr: monthly recurring revenue (number)\n- region: NA | EU | APAC\n- plan: Starter | Pro | Enterprise\n","examples/with-workspace/workspace/skills/playwright-cli/SKILL.md":"---\nname: playwright-cli\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(playwright-cli:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\nplaywright-cli fill e5 \"user@example.com\"\nplaywright-cli drag e2 e8\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\nplaywright-cli snapshot --filename=after-click.yaml\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli network\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start\nplaywright-cli video-stop video.webm\n```\n\n### Install\n\n```bash\nplaywright-cli install --skills\nplaywright-cli install-browser\n```\n\n### Configuration\n\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n# Connect to browser via extension\nplaywright-cli open --extension\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n### Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli network\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Specific tasks\n\n- **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n- **Running Playwright code** [references/running-code.md](references/running-code.md)\n- **Browser session management** [references/session-management.md](references/session-management.md)\n- **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n- **Test generation** [references/test-generation.md](references/test-generation.md)\n- **Tracing** [references/tracing.md](references/tracing.md)\n- **Video recording** [references/video-recording.md](references/video-recording.md)\n","examples/with-workspace/workspace/skills/playwright-cli/references/request-mocking.md":"# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/running-code.md":"# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.loading', { state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.result', { timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const [download] = await Promise.all([\n    page.waitForEvent('download'),\n    page.click('a.download-link')\n  ]);\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.click('.maybe-missing', { timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.fill('input[name=email]', 'user@example.com');\n  await page.fill('input[name=password]', 'secret');\n  await page.click('button[type=submit]');\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/session-management.md":"# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/storage-state.md":"# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1735689600,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1735689600\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n","examples/with-workspace/workspace/skills/playwright-cli/references/test-generation.md":"# Test Generation\n\nGenerate Playwright test code automatically as you interact with the browser.\n\n## How It Works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into your test files.\n\n## Example Workflow\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n## Building a Test File\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"login flow\", async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto(\"https://example.com/login\");\n  await page.getByRole(\"textbox\", { name: \"Email\" }).fill(\"user@example.com\");\n  await page.getByRole(\"textbox\", { name: \"Password\" }).fill(\"password123\");\n  await page.getByRole(\"button\", { name: \"Sign In\" }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n## Best Practices\n\n### 1. Use Semantic Locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator(\"#submit-btn\").click();\n```\n\n### 2. Explore Before Recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### 3. Add Assertions Manually\n\nGenerated code captures actions but not assertions. Add expectations in your test:\n\n```typescript\n// Generated action\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Manual assertion\nawait expect(page.getByText(\"Success\")).toBeVisible();\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/tracing.md":"# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category        | Details                                            |\n| --------------- | -------------------------------------------------- |\n| **Actions**     | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM**         | Full DOM snapshot before/after each action         |\n| **Screenshots** | Visual state at each step                          |\n| **Network**     | All requests, responses, headers, bodies, timing   |\n| **Console**     | All console.log, warn, error messages              |\n| **Timing**      | Precise timing for each operation                  |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature                 | Trace       | Video       | Screenshot       |\n| ----------------------- | ----------- | ----------- | ---------------- |\n| **Format**              | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection**      | Yes         | No          | No               |\n| **Network details**     | Yes         | No          | No               |\n| **Step-by-step replay** | Yes         | Continuous  | Single frame     |\n| **File size**           | Medium      | Large       | Small            |\n| **Best for**            | Debugging   | Demos       | Quick capture    |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n","examples/with-workspace/workspace/skills/playwright-cli/references/video-recording.md":"# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Start recording\nplaywright-cli video-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop demo.webm\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-stop recordings/login-flow-2024-01-15.webm\nplaywright-cli video-stop recordings/checkout-test-run-42.webm\n```\n\n## Tracing vs Video\n\n| Feature  | Video                | Tracing                                  |\n| -------- | -------------------- | ---------------------------------------- |\n| Output   | WebM file            | Trace file (viewable in Trace Viewer)    |\n| Shows    | Visual recording     | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis                      |\n| Size     | Larger               | Smaller                                  |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n","examples/with-xquik-tools/README.md":"# VoltAgent with Xquik Tools\n\nThis example shows how to add Xquik REST API tools to a VoltAgent research agent for public X/Twitter data.\n\n## Features\n\n- Search public posts with X query operators\n- Look up posts by ID with author, metrics, and media\n- Look up public user profiles by username or ID\n- Fetch recent public posts from a user\n- Retrieve public X/Twitter trends by WOEID region\n\n## Prerequisites\n\n1. **Xquik API Key**: Create an API key from the [Xquik dashboard](https://dashboard.xquik.com)\n2. **OpenAI API Key**: Used by the example agent model\n\n## Setup\n\n1. Install dependencies:\n\n   ```bash\n   pnpm install\n   ```\n\n2. Configure environment variables:\n\n   ```bash\n   cp .env.example .env\n   ```\n\n   Then edit `.env`:\n\n   ```env\n   OPENAI_API_KEY=your_openai_api_key_here\n   XQUIK_API_KEY=your_xquik_api_key_here\n   XQUIK_BASE_URL=https://xquik.com/api/v1\n   ```\n\n3. Run the example:\n\n   ```bash\n   pnpm dev\n   ```\n\nThe agent runs on the default VoltAgent server port and exposes one agent named `xquikResearchAgent`.\n\n## Tools Available\n\n### 1. Search X Posts\n\n- **Purpose**: Search public X/Twitter posts with X query operators\n- **Endpoint**: `GET /x/tweets/search`\n- **Parameters**:\n  - `query`: Search query string\n  - `queryType`: `Latest` or `Top`\n  - `limit`: Maximum posts to return\n  - `cursor`: Optional pagination cursor\n  - `sinceTime`, `untilTime`: Optional ISO 8601 time bounds\n\n### 2. Get X Post\n\n- **Purpose**: Look up a public post by ID\n- **Endpoint**: `GET /x/tweets/{id}`\n\n### 3. Get X User\n\n- **Purpose**: Look up a public user profile by username or ID\n- **Endpoint**: `GET /x/users/{id}`\n\n### 4. Get X User Posts\n\n- **Purpose**: Fetch recent public posts from a user\n- **Endpoint**: `GET /x/users/{id}/tweets`\n- **Parameters**:\n  - `user`: Username without `@`, or numeric user ID\n  - `cursor`: Optional pagination cursor\n  - `includeReplies`: Include replies\n  - `includeParentTweet`: Include parent posts for replies\n\n### 5. Get X Trends\n\n- **Purpose**: Fetch public trends by WOEID region\n- **Endpoint**: `GET /x/trends`\n- **Parameters**:\n  - `woeid`: Region WOEID, with `1` for worldwide\n  - `count`: Number of trends to return\n\n## Example Queries\n\n- \"Search recent posts about AI agent frameworks and summarize the top themes.\"\n- \"Look up @voltagent_dev and summarize recent public posts.\"\n- \"Find worldwide X trends and explain which ones relate to developer tools.\"\n- \"Get this post by ID and extract the author, timestamp, and engagement metrics.\"\n\n## API Integration\n\nThis example uses the Xquik public REST API with the `x-api-key` header and the `xquik-api-contract` header set to `2026-04-29`.\n\nSee the [Xquik API reference](https://docs.xquik.com/api-reference/overview) for endpoint details.\n\n## Troubleshooting\n\n- **Missing API key**: Set `XQUIK_API_KEY` in `.env`.\n- **Authentication errors**: Create a fresh API key from the Xquik dashboard.\n- **Empty results**: Try a broader query, switch between `Latest` and `Top`, or remove time bounds.\n- **Regional trends**: Use WOEID `1` for worldwide, `23424977` for the US, or another supported region.\n","packages/a2a-server/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fa2a-server%2FREADME.md -->","packages/ag-ui/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fag-ui%2FREADME.md -->","packages/cli/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcli%2FREADME.md -->","packages/cloudflare-d1/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcloudflare-d1%2FREADME.md -->","packages/core/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcore%2FREADME.md -->","packages/create-voltagent-app/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcreate-voltagent-app%2FREADME.md -->","packages/docs-mcp/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fdocs-mcp%2FREADME.md -->","packages/evals/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fevals%2FREADME.md -->","packages/internal/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Finternal%2FREADME.md -->","packages/langfuse-exporter/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flangfuse-exporter%2FREADME.md -->","packages/libsql/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flibsql%2FREADME.md -->","packages/logger/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flogger%2FREADME.md -->","packages/mcp-server/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fmcp-server%2FREADME.md -->","packages/postgres/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fpostgres%2FREADME.md -->","packages/sandbox-blaxel/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-blaxel%2FREADME.md -->","packages/sandbox-daytona/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-daytona%2FREADME.md -->","packages/sandbox-e2b/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-e2b%2FREADME.md -->","packages/scorers/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fscorers%2FREADME.md -->","packages/sdk/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsdk%2FREADME.md -->","packages/server-core/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserver-core%2FREADME.md -->","packages/server-elysia/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserver-elysia%2FREADME.md -->","packages/serverless-hono/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserverless-hono%2FREADME.md -->","packages/supabase/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsupabase%2FREADME.md -->","packages/vercel-ai-exporter/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvercel-ai-exporter%2FREADME.md -->","packages/voice/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvoice%2FREADME.md -->","packages/voltagent-memory/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvoltagent-memory%2FREADME.md -->","tools/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=tools%2FREADME.md -->","tools/core/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=tools%2Fcore%2FREADME.md -->","website/docs/agents/a2a/a2a-server.md":"---\ntitle: A2A Server\ndescription: Step-by-step guide for exposing VoltAgent over the Agent-to-Agent protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent A2A Server Quick Start\n\nFollow the steps below to let other agents talk to your VoltAgent application over the Agent-to-Agent (A2A) protocol.\n\n## Install Packages\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n</Tabs>\n\n`@voltagent/a2a-server` exposes VoltAgent agents via JSON-RPC. `@voltagent/server-hono` wires the routes into the default Hono server (feel free to swap it out for another framework later).\n\n## Create An A2A Server Instance\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  description: \"VoltAgent A2A example\",\n  provider: {\n    organization: \"Acme\",\n    url: \"https://acme.example\",\n  },\n});\n```\n\nThe server metadata feeds the discovery card served from `/.well-known/{serverId}/agent-card.json`. The card's `url` field points to `/a2a/{serverId}`, and when the card is fetched over HTTP VoltAgent returns that URL as an absolute address.\n\n## Register The Server With VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { a2aServer } from \"./a2a/server\";\nimport { assistant } from \"./agents/assistant\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  a2aServers: {\n    a2aServer,\n  },\n  server: honoServer({ port: 3141 }),\n});\n```\n\nWith this in place, VoltAgent automatically exposes:\n\n- `GET /.well-known/{serverId}/agent-card.json`\n- `POST /a2a/{serverId}`\n\nThe JSON-RPC handler accepts `message/send`, `message/stream`, `tasks/get`, and `tasks/cancel` requests.\n\n## Available Endpoints\n\n| Method | Path                                      | Description                                                                                                      |\n| ------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |\n| `GET`  | `/.well-known/{serverId}/agent-card.json` | Returns the discovery card for the specified A2A server. The card's `url` points to `/a2a/{serverId}`.           |\n| `POST` | `/a2a/{serverId}`                         | Accepts JSON-RPC 2.0 requests. Supported methods: `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`. |\n\nExample JSON-RPC payload for `message/send`:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    \"message\": {\n      \"kind\": \"message\",\n      \"role\": \"user\",\n      \"messageId\": \"msg-1\",\n      \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }]\n    }\n  }\n}\n```\n\nYou can include an optional `context` object alongside the JSON-RPC payload to forward metadata such as `userId`, `sessionId`, or arbitrary `metadata` fields:\n\n```json\n{\n  \"context\": {\n    \"userId\": \"user-42\",\n    \"sessionId\": \"chat-1\",\n    \"metadata\": { \"tenant\": \"acme\" }\n  },\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    /* ... */\n  }\n}\n```\n\nThe context values surface inside the agent call so you can track callers or reuse an existing conversation identifier. `context.userId` is forwarded directly as the VoltAgent `userId` option, while `sessionId` and any nested `metadata` keys are merged into the agent's `options.context` map together with the request-level `params.metadata` and per-message `message.metadata` fields.\n\nIf you cannot modify the JSON body, send the same object as a JSON-encoded query parameter. VoltAgent reads `context` (or the legacy `runtimeContext`) from the query string and merges it with the body-provided values, letting the body override duplicated keys while metadata objects are combined.\n\n```bash\ncurl \\\n  -X POST \"http://localhost:3141/a2a/support-agent?context=%7B%22userId%22%3A%22user-42%22%7D\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": { \"message\": { \"kind\": \"message\", \"role\": \"user\", \"messageId\": \"msg-1\", \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }] } }\n}'\n```\n\n## Configure Which Agents Are Exposed (Optional)\n\nYou can publish agents that are not part of the primary VoltAgent registry or filter the combined list before answering A2A requests:\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\nimport { assistant } from \"./agents/assistant\";\nimport { shadowAgent } from \"./agents/shadow\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  agents: {\n    \"shadow-agent\": shadowAgent,\n  },\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n});\n```\n\nConfigured agents appear alongside registry entries. The filter receives the merged list and can drop or reorder entries before discovery and JSON-RPC calls run.\n\nVoltAgent already formats Server-Sent Events when you use `@voltagent/server-hono`, so no extra work is needed for streaming clients. If you forward chunks manually, serialize each payload with `safeStringify` and prepend `\\x1E` before the JSON string.\n\n## Provide A Different Task Store (Optional)\n\nBy default, `A2AServer` uses an in-memory store. Provide your own implementation (Redis, Postgres, KV…) by implementing the `TaskStore` interface:\n\n```ts title=\"src/a2a/redis-store.ts\"\nimport { createClient } from \"redis\";\nimport type { TaskRecord, TaskStore } from \"@voltagent/a2a-server\";\n\nexport class RedisTaskStore implements TaskStore {\n  private client = createClient({ url: process.env.REDIS_URL });\n\n  async load({ agentId, taskId }: { agentId: string; taskId: string }): Promise<TaskRecord | null> {\n    const raw = await this.client.get(`${agentId}::${taskId}`);\n    return raw ? (JSON.parse(raw) as TaskRecord) : null;\n  }\n\n  async save({ agentId, data }: { agentId: string; data: TaskRecord }): Promise<void> {\n    await this.client.set(`${agentId}::${data.id}`, JSON.stringify(data));\n  }\n}\n```\n\nPass the store during initialisation:\n\n```ts title=\"src/a2a/server.ts\"\nimport { RedisTaskStore } from \"./redis-store\";\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { assistant } from \"../agents/assistant\";\n\nconst redisTaskStore = new RedisTaskStore();\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n});\n\nexport const voltAgent = new VoltAgent({\n  agents: { assistant },\n  a2aServers: { a2aServer },\n  server: honoServer({ port: 3141 }),\n});\n\na2aServer.initialize({\n  agentRegistry: voltAgent.agentRegistry,\n  taskStore: redisTaskStore,\n});\n```\n\nRemember to implement TTL or pruning inside your custom store if you need to cap history growth.\n\n## Run The Example Smoke Test\n\nCreate the example project and run its smoke test to ensure everything is wired correctly:\n\n```bash\nnpm create voltagent-app@latest -- --example with-a2a-server\ncd with-a2a-server\npnpm test:smoke\n```\n\nThe test sends a `message/send`, streams a `message/stream`, and exercises `tasks/cancel` to verify streaming cancellation propagation.\n\n## Troubleshooting checklist\n\n- **404 for discovery card**: ensure the `serverId` in `/.well-known/{serverId}/agent-card.json` matches `A2AServer({ id })`, or the normalized `name` when `id` is omitted. It does not come from `VoltAgent({ agents: { ... } })` or the `a2aServers` map key.\n- **Unexpected JSON in SSE**: confirm you are stripping the `\\x1E` prefix before parsing the JSON payload.\n- **Cancellation not propagating**: verify you call `tasks/cancel` with the task ID from the stream and that your TaskStore preserves the `activeCancellations` set.\n","website/docs/agents/cancellation.md":"---\ntitle: Cancellation\nslug: /agents/cancellation\n---\n\n# Cancellation\n\nVoltAgent implements cancellation through the standard `AbortController` API. Pass an `AbortController` instance to agent methods to stop LLM generation, tool execution, and multi-agent workflows.\n\n## Basic Cancellation\n\nCreate an `AbortController` and pass it to agent methods:\n\n```typescript\nimport { Agent, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst abortController = new AbortController();\n\n// Cancel after 5 seconds\nsetTimeout(() => {\n  abortController.abort(\"Timeout: Operation took too long\");\n}, 5000);\n\ntry {\n  const response = await agent.generateText(\"Write a detailed analysis...\", {\n    abortController,\n  });\n  console.log(response.text);\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Operation cancelled:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## How Cancellation Works\n\nWhen you provide an `AbortController` to an agent method:\n\n1. The signal is passed to the LLM provider\n2. Tools receive the `OperationContext` containing the `abortController`\n3. Subagents inherit the parent's `abortController`\n4. All operations check the signal state before proceeding\n\nCancellation propagates through the entire operation chain for clean shutdown at every level.\n\n:::note Parameter Deprecation\nThe `signal` parameter is deprecated. Use `abortController` instead. The `signal` parameter will be removed in a future version.\n:::\n\n## Streaming Cancellation\n\nCancellation works with streaming responses:\n\n```typescript\nconst abortController = new AbortController();\n\n// User can cancel anytime\ndocument.getElementById(\"stop-btn\")?.addEventListener(\"click\", () => {\n  abortController.abort(\"User requested stop\");\n});\n\nconst response = await agent.streamText(\"Generate a long report...\", {\n  abortController,\n});\n\ntry {\n  for await (const chunk of response.textStream) {\n    process.stdout.write(chunk);\n  }\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"\\nStream cancelled\");\n  }\n}\n```\n\nWith `fullStream`, you receive detailed cancellation feedback:\n\n```typescript\nconst response = await agent.streamText(\"Complex task...\", {\n  abortController,\n});\n\nfor await (const event of response.fullStream) {\n  if (event.type === \"error\" && isAbortError(event.error)) {\n    console.log(\"Cancelled during:\", event.context);\n    break;\n  }\n  // Process other events\n}\n```\n\n## Tool Cancellation\n\nTools receive the `OperationContext` as the second parameter. Access `abortController` through the context to respond to or trigger cancellation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst dataProcessingTool = createTool({\n  name: \"process_data\",\n  description: \"Process large datasets\",\n  parameters: z.object({\n    dataset: z.string(),\n    operation: z.string(),\n  }),\n  execute: async (args, context) => {\n    const abortController = context?.abortController;\n    const signal = abortController?.signal;\n\n    // Check if already aborted\n    if (signal?.aborted) {\n      return { error: \"Operation was already cancelled\" };\n    }\n\n    // Tool can trigger abort based on conditions\n    if (args.dataset === \"restricted\") {\n      abortController?.abort(\"Access to restricted dataset denied\");\n      return { error: \"Dataset access denied\" };\n    }\n\n    // Pass signal to async operations\n    try {\n      const response = await fetch(`/api/process/${args.dataset}`, {\n        method: \"POST\",\n        body: JSON.stringify({ operation: args.operation }),\n        signal,\n      });\n\n      return await response.json();\n    } catch (error) {\n      if (error.name === \"AbortError\") {\n        return { error: \"Processing cancelled\" };\n      }\n      throw error;\n    }\n  },\n});\n```\n\n## Multi-Agent Cancellation\n\nIn supervisor-subagent architectures, the abort signal propagates to all subagents:\n\n```typescript\nconst researcher = new Agent({\n  name: \"Researcher\",\n  instructions: \"Research topics thoroughly\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst writer = new Agent({\n  name: \"Writer\",\n  instructions: \"Write detailed content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate research and writing\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researcher, writer],\n});\n\nconst abortController = new AbortController();\n\n// Cancel supervisor and any active subagent operations\nsetTimeout(() => {\n  abortController.abort(\"Deadline reached\");\n}, 10000);\n\nconst response = await supervisor.streamText(\"Research and write about quantum computing\", {\n  abortController,\n});\n```\n\nWhen the supervisor is cancelled:\n\n- Any active `delegate_task` operations stop\n- Subagents inherit the parent's `abortController`\n- All tool executions within subagents are cancelled\n- The entire workflow stops\n\n## Timeout Implementation\n\nImplement timeouts for operations:\n\n```typescript\nconst abortController = new AbortController();\n\n// Set a timeout to abort after 30 seconds\nconst timeoutId = setTimeout(() => {\n  abortController.abort(\"Operation timeout - exceeded 30 seconds\");\n}, 30000);\n\ntry {\n  const response = await agent.generateText(\"Complex analysis...\", {\n    abortController,\n  });\n\n  // Clear timeout if operation completes\n  clearTimeout(timeoutId);\n  console.log(response.text);\n} catch (error) {\n  clearTimeout(timeoutId); // Always clear the timeout\n\n  if (isAbortError(error)) {\n    console.log(\"Operation timed out:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## Error Detection with Hooks\n\nAgent hooks can detect and handle abort errors:\n\n```typescript\nimport { createHooks, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ error, context }) => {\n      if (isAbortError(error)) {\n        // Handle abort - error.name === \"AbortError\"\n        console.log(\"Operation aborted:\", error.message);\n\n        // Cleanup resources\n        await cleanup(context);\n\n        // Log metrics\n        await logAbortMetrics({\n          operationId: context.operationId,\n          reason: error.message,\n          duration: Date.now() - context.startTime,\n        });\n      } else if (error) {\n        // Handle other errors\n        console.error(\"Operation failed:\", error);\n      }\n    },\n  }),\n});\n```\n\n## REST API Cancellation\n\nClients can cancel REST API requests by closing the connection:\n\n```typescript\n// Client-side cancellation\nconst controller = new AbortController();\n\nconst response = await fetch(\"http://localhost:3141/agents/my-agent/stream\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    input: \"Generate a comprehensive report\",\n  }),\n  signal: controller.signal,\n});\n\n// Cancel button\ndocument.getElementById(\"cancel\")?.addEventListener(\"click\", () => {\n  controller.abort();\n});\n\n// Process stream\nconst reader = response.body?.getReader();\nif (reader) {\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      // Process chunk\n    }\n  } catch (error) {\n    if (error.name === \"AbortError\") {\n      console.log(\"Stream cancelled\");\n    }\n  }\n}\n```\n\nThe server detects client disconnection and aborts the agent operation.\n\n## Cancellation States\n\nOperations can be in different states regarding cancellation:\n\n```typescript\nconst abortController = new AbortController();\n\n// Check if aborted\nif (abortController.signal.aborted) {\n  console.log(\"Already aborted\");\n}\n\n// Listen for abort\nabortController.signal.addEventListener(\"abort\", () => {\n  console.log(\"Just aborted:\", abortController.signal.reason);\n});\n\n// Trigger abort with reason\nabortController.abort(\"User cancelled\");\n```\n\n## Implementation Patterns\n\n### Graceful Shutdown\n\nFor individual agent operations, manually handle shutdown signals:\n\n```typescript\nasync function processWithGracefulShutdown(agent: Agent, input: string) {\n  const abortController = new AbortController();\n\n  // Handle shutdown signals\n  const shutdownHandler = () => {\n    console.log(\"Shutting down...\");\n    abortController.abort(\"System shutdown\");\n  };\n\n  process.on(\"SIGINT\", shutdownHandler);\n  process.on(\"SIGTERM\", shutdownHandler);\n\n  try {\n    const response = await agent.generateText(input, {\n      abortController,\n    });\n    return response.text;\n  } finally {\n    process.off(\"SIGINT\", shutdownHandler);\n    process.off(\"SIGTERM\", shutdownHandler);\n  }\n}\n```\n\n### VoltAgent-Level Shutdown\n\nWhen using the `VoltAgent` class with a server, shutdown is handled automatically:\n\n```typescript\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst voltAgent = new VoltAgent({\n  agents: { myAgent },\n  server: honoServer({ port: 3141 }),\n});\n\n// VoltAgent automatically registers SIGINT/SIGTERM handlers\n// For programmatic shutdown:\nawait voltAgent.shutdown(); // Stops server, workflows, and telemetry\n```\n\nVoltAgent automatically registers SIGINT/SIGTERM handlers that clean up the server, suspend workflows, and shutdown telemetry. See [Server Architecture - Graceful Shutdown](../api/server-architecture.md#graceful-shutdown) for details.\n\n### Concurrent Operations with Cancellation\n\n```typescript\nasync function processMultiple(agent: Agent, inputs: string[]) {\n  const abortController = new AbortController();\n\n  // Cancel all if any fails\n  const promises = inputs.map(async (input) => {\n    try {\n      return await agent.generateText(input, { abortController });\n    } catch (error) {\n      abortController.abort(\"One operation failed\");\n      throw error;\n    }\n  });\n\n  return Promise.all(promises);\n}\n```\n\n## Next Steps\n\n- Learn about [Tools](./tools.md) and how they handle cancellation\n- Explore [Sub-Agents](./subagents.md) for multi-agent cancellation patterns\n- See [Hooks](./hooks.md) for cancellation detection and handling\n","website/docs/agents/context.md":"---\ntitle: Operation Context (context)\nslug: /agents/context\ndescription: Pass custom data through agent operations using context.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Operation Context (`context`)\n\nThe `context` field in `OperationContext` is a Map that passes custom data throughout a single agent operation. All components (hooks, tools, retrievers, sub-agents) can read and write to this shared data structure during execution.\n\n## Basic Concept\n\nHere's how `context` flows through an agent operation:\n\n```\nYou → Agent → Hooks → Tools → Retrievers → Memory → Sub-Agents\n     ↑                                                        ↓\n     ← ← ← ← ← ← context flows everywhere ← ← ← ← ← ← ← ← ←\n```\n\n## Initialize context\n\nYou can provide initial data in two ways:\n\n### Method 1: Set Default Context in Constructor\n\nSet default `context` when creating the agent. This context is used for all operations unless overridden at execution time:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\n// Set default context at agent creation\nconst defaultContext = new Map();\ndefaultContext.set(\"environment\", \"production\");\ndefaultContext.set(\"projectId\", \"my-project\");\n\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  context: defaultContext, // Default context for all operations\n});\n\n// Uses default context automatically\nconst response1 = await agent.generateText(\"Hello!\");\nconsole.log(\"Environment:\", response1.context?.get(\"environment\")); // \"production\"\n\n// Override with execution context (replaces default completely)\nconst response2 = await agent.generateText(\"Debug this\", {\n  context: new Map([\n    [\"environment\", \"development\"],\n    [\"projectId\", \"my-project\"],\n  ]),\n});\nconsole.log(\"Environment:\", response2.context?.get(\"environment\")); // \"development\"\nconsole.log(\"Project ID:\", response2.context?.get(\"projectId\")); // \"my-project\"\n```\n\n### Method 2: Pass Context During Execution\n\nPass `context` when calling the agent to provide operation-specific data:\n\n```typescript\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Pass context when calling the agent\nconst executionContext = new Map();\nexecutionContext.set(\"language\", \"English\");\n\nconst response = await agent.generateText(\"Hello!\", {\n  context: executionContext,\n});\n// Now you can access the data from the response\nconsole.log(\"Language:\", response.context?.get(\"language\"));\n```\n\n## Sending User Information\n\nYou can pass user information to the agent using the `context`. This information is automatically picked up by the tracing system and associated with the trace.\n\nSupported fields:\n\n- `user.name`: User's full name\n- `user.email`: User's email address\n- `user.avatar`: User's avatar URL (optional, falls back to Gravatar using email if not provided)\n- `user.metadata`: Additional metadata object\n\n<Tabs>\n  <TabItem value=\"sdk\" label=\"SDK\" default>\n\nWhen using the SDK, pass user information in the `context` Map:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({ ... });\n\nconst context = new Map();\ncontext.set(\"user.name\", \"John Doe\");\ncontext.set(\"user.email\", \"john@example.com\");\ncontext.set(\"user.avatar\", \"https://example.com/avatar.jpg\");\ncontext.set(\"user.metadata\", { plan: \"pro\" });\n\nawait agent.generateText(\"Hello\", { userId: \"user_123\", context });\n```\n\n  </TabItem>\n  <TabItem value=\"api\" label=\"REST API\">\n\nWhen using the REST API, you can pass user information in the `context` object. You can use either a nested `user` object or flat keys.\n\n**Option 1: Nested User Object**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user\": {\n      \"name\": \"John Doe\",\n      \"email\": \"john@example.com\",\n      \"avatar\": \"https://example.com/avatar.jpg\",\n      \"metadata\": { \"plan\": \"pro\" }\n    }\n  }\n}\n```\n\n**Option 2: Flat Keys**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user.name\": \"John Doe\",\n    \"user.email\": \"john@example.com\",\n    \"user.avatar\": \"https://example.com/avatar.jpg\",\n    \"user.metadata\": { \"plan\": \"pro\" }\n  }\n}\n```\n\n  </TabItem>\n</Tabs>\n\n## Hooks Access context\n\nHooks can read and write to `context`:\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"HookAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Read data that was passed in\n      const language = context.context.get(\"language\");\n      console.log(`Starting operation for language: ${language}`);\n\n      // Add new data\n      context.context.set(\"requestId\", `req-${Date.now()}`);\n      context.context.set(\"startTime\", new Date().toISOString());\n    },\n    onEnd: (context) => {\n      // Read data from context\n      const requestId = context.context.get(\"requestId\");\n      const startTime = context.context.get(\"startTime\");\n      console.log(`Request ${requestId} completed (started at ${startTime})`);\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\n\nawait agent.generateText(\"Hello!\", { context });\n```\n\n## Tools Access Context\n\nTools receive the full operation context through the second parameter (`options`), which includes all `OperationContext` fields directly accessible:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst loggerTool = createTool({\n  name: \"log_message\",\n  description: \"Logs a message with user context\",\n  parameters: z.object({\n    message: z.string(),\n  }),\n  execute: async ({ message }, options) => {\n    // Access user-defined context directly\n    const language = options?.context?.get(\"language\");\n    const requestId = options?.context?.get(\"requestId\");\n\n    // Access operation metadata\n    const userId = options?.userId;\n    const operationId = options?.operationId;\n\n    // Use the operation-scoped logger\n    options?.logger?.info(`[${requestId}] User ${userId}: ${message}`);\n\n    console.log(`[${requestId}] Language ${language}: ${message}`);\n\n    // Write to context\n    const ctx = options?.context;\n    if (ctx) {\n      const logs = ctx.get(\"logs\") || [];\n      logs.push({\n        message,\n        timestamp: new Date().toISOString(),\n        userId,\n        operationId,\n      });\n      ctx.set(\"logs\", logs);\n    }\n\n    return `Message logged for language ${language}`;\n  },\n});\n\nconst agentWithTool = new Agent({\n  name: \"ToolAgent\",\n  model: \"openai/gpt-4o\",\n  tools: [loggerTool],\n  instructions: \"Use the log_message tool to log what the user says.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\ncontext.set(\"requestId\", \"req-456\");\n\nconst response = await agentWithTool.generateText(\"Log this: Hello world!\", {\n  context,\n  userId: \"user123\",\n});\n\n// Check what was logged\nconst logs = response.context?.get(\"logs\");\nconsole.log(\"All logs:\", logs);\n```\n\n### What's Available in Tool Options?\n\nThe `options` parameter in tool `execute` functions combines `ToolExecuteOptions` and `Partial<OperationContext>`:\n\n**From OperationContext:**\n\n- `operationId` - Unique operation identifier\n- `userId` - User identifier (if provided)\n- `conversationId` - Conversation identifier (if provided)\n- `workspace` - Workspace instance configured on the agent (if available)\n- `context` - User-defined context Map (read/write)\n- `systemContext` - Internal system context Map\n- `logger` - Operation-scoped logger with full context\n- `abortController` - For operation cancellation\n- `traceContext` - OpenTelemetry trace context\n- `input` - Original input to the agent\n- `isActive` - Whether operation is still active\n\n**Tool-specific context (from `toolContext?` - optional):**\n\n> **Note:** `toolContext` is always populated when your tool is called from a VoltAgent agent. It may be `undefined` when called externally.\n\n- `toolContext?.name` - Name of the tool being executed\n- `toolContext?.callId` - Unique identifier for this tool call (from AI SDK)\n- `toolContext?.messages` - Message history at tool call time (from AI SDK)\n- `toolContext?.abortSignal` - Abort signal for cancellation (from AI SDK)\n\n```typescript\n// Example: Authorization check using context\nconst sensitiveDataTool = createTool({\n  name: \"get_sensitive_data\",\n  description: \"Retrieves sensitive user data\",\n  parameters: z.object({\n    dataType: z.string(),\n  }),\n  execute: async ({ dataType }, options) => {\n    // Check user role from context\n    const userRole = options?.context?.get(\"userRole\");\n\n    if (userRole !== \"admin\") {\n      throw new Error(\"Unauthorized: Admin role required\");\n    }\n\n    // Log access with full context including tool name\n    const toolName = options?.toolContext?.name;\n    options?.logger?.warn(`${toolName}: Sensitive data accessed: ${dataType}`, {\n      userId: options?.userId,\n      operationId: options?.operationId,\n    });\n\n    return await fetchSensitiveData(dataType);\n  },\n});\n```\n\n## Access Input and Output in Context\n\nThe `OperationContext` provides access to both the input and output of the current operation, making debugging and observability much easier.\n\n### Accessing Input\n\nThe `input` field contains the original input provided to the agent operation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst logTool = createTool({\n  name: \"log_input\",\n  description: \"Logs the original user input\",\n  parameters: z.object({}),\n  execute: async (args, options) => {\n    const input = options?.operationContext?.input;\n    console.log(\"Original input:\", input);\n    // input can be: string, UIMessage[], or BaseMessage[]\n\n    return \"Input logged successfully\";\n  },\n});\n```\n\n### Accessing Output\n\nThe `output` field contains the generated response (only available after generation completes, mainly useful in hooks):\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onEnd: async ({ context }) => {\n    // Access both input and output\n    console.log(\"User asked:\", context.input);\n    console.log(\"Agent responded:\", context.output);\n\n    // Output type depends on the method used:\n    // - generateText/streamText: string\n    // - generateObject/streamObject: object\n\n    // Log complete interaction\n    const interaction = {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    };\n\n    console.log(\"Complete interaction:\", interaction);\n  },\n});\n```\n\n### Use Cases\n\nThe `input` and `output` fields are particularly useful for:\n\n- **Audit Logging**: Track what users asked and what the agent responded\n- **Analytics**: Analyze input/output patterns across conversations\n- **Debugging**: Trace issues by comparing input to output\n- **Quality Assurance**: Monitor agent responses for quality control\n- **Custom Metrics**: Calculate response time, token efficiency, etc.\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst auditHooks = createHooks({\n  onEnd: async ({ context, output }) => {\n    // Log complete audit trail\n    await auditLog.save({\n      timestamp: context.startTime,\n      operationId: context.operationId,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      input: context.input,\n      output: context.output,\n      finalStepUsage: output?.usage,\n      totalUsage: output?.totalUsage,\n    });\n  },\n});\n\nconst auditedAgent = new Agent({\n  name: \"Audited Assistant\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  hooks: auditHooks,\n});\n```\n\n## OperationContext Properties\n\nThe `OperationContext` type (defined in [types.ts](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/types.ts)) contains multiple properties beyond the user-managed `context` Map. Understanding these properties helps with debugging, observability, and advanced use cases.\n\n### User-Managed Fields\n\nThese fields are set by you and used to identify and track operations:\n\n```typescript\ninterface OperationContext {\n  // User data storage - read and write custom values\n  readonly context: Map<string | symbol, unknown>;\n\n  // Optional user identifier\n  userId?: string;\n\n  // Optional conversation identifier\n  conversationId?: string;\n\n  // Workspace configured on the agent (if any)\n  workspace?: Workspace;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst response = await agent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n  context: new Map([[\"language\", \"en\"]]),\n});\n\n// Access in hooks or tools\nconsole.log(operationContext.userId); // \"user-123\"\nconsole.log(operationContext.conversationId); // \"conv-456\"\nconsole.log(operationContext.context.get(\"language\")); // \"en\"\nconsole.log(operationContext.workspace?.id); // e.g. \"demo-workspace\"\n```\n\n### Operation Metadata (Read-Only)\n\nThese fields are automatically set by the framework and provide metadata about the current operation:\n\n```typescript\ninterface OperationContext {\n  // Unique identifier for this operation\n  readonly operationId: string;\n\n  // When the operation started\n  startTime: Date;\n\n  // Whether the operation is still active\n  isActive: boolean;\n\n  // Parent agent ID if this is a sub-agent execution\n  parentAgentId?: string;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst agent = new Agent({\n  name: \"TrackedAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ context }) => {\n      const duration = Date.now() - context.startTime.getTime();\n      console.log(`Operation ${context.operationId} took ${duration}ms`);\n\n      if (context.parentAgentId) {\n        console.log(`Executed as sub-agent of ${context.parentAgentId}`);\n      }\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n```\n\n### System Fields (Advanced)\n\nThese fields are used internally by the framework. Most users don't need to interact with them directly:\n\n```typescript\ninterface OperationContext {\n  // Internal state management (used by framework)\n  readonly systemContext: Map<string | symbol, unknown>;\n\n  // Scoped logger with operation context\n  logger: Logger;\n\n  // OpenTelemetry span management\n  traceContext: AgentTraceContext;\n\n  // Full conversation history including tool calls\n  conversationSteps?: StepWithContent[];\n\n  // Cancellation control\n  abortController: AbortController;\n\n  // Error to throw when operation is aborted\n  cancellationError?: CancellationError;\n\n  // Bridge for requesting user input (human-in-the-loop)\n  elicitation?: (request: unknown) => Promise<unknown>;\n}\n```\n\n**System Context vs User Context:**\n\n- **`context`**: Your data - use this for sharing information between components\n- **`systemContext`**: Framework internals - stores stream writers, internal state, etc.\n\nThe framework uses `systemContext` to pass internal state between components. For example, when a sub-agent streams data, the parent agent's stream writer is stored in `systemContext` (see [subagent/index.ts:145-148](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/subagent/index.ts#L145-L148)).\n\n### Input/Output (Automatically Set)\n\nThese fields capture the operation's input and output:\n\n```typescript\ninterface OperationContext {\n  // Original input provided to the agent\n  input?: string | UIMessage[] | BaseMessage[];\n\n  // Generated response (text or object)\n  output?: string | object;\n}\n```\n\nThese fields are documented in detail in the \"Access Input and Output in Context\" section below.\n\n### Operation Lifecycle Example\n\nHere's how to use metadata fields for observability:\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst observableAgent = new Agent({\n  name: \"ObservableAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: ({ context }) => {\n      // Use the scoped logger\n      context.logger.info(\"Operation started\", {\n        operationId: context.operationId,\n        userId: context.userId,\n        conversationId: context.conversationId,\n      });\n\n      // Store start metrics\n      context.context.set(\"metrics\", {\n        startTime: context.startTime,\n        operationId: context.operationId,\n      });\n    },\n    onEnd: ({ context, output }) => {\n      const metrics = context.context.get(\"metrics\") as any;\n      const duration = Date.now() - context.startTime.getTime();\n\n      context.logger.info(\"Operation completed\", {\n        operationId: context.operationId,\n        duration,\n        inputType: typeof context.input,\n        outputType: typeof context.output,\n        isActive: context.isActive,\n      });\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// The logger automatically includes userId, conversationId, and operationId\nconst response = await observableAgent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n});\n```\n\n### Cancellation with AbortController\n\nThe `abortController` field allows operation cancellation:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"CancellableAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Create an abort controller\nconst abortController = new AbortController();\n\n// Pass the signal to the agent\nconst responsePromise = agent.generateText(\"Write a long story\", {\n  abortSignal: abortController.signal,\n});\n\n// Cancel after 1 second\nsetTimeout(() => {\n  abortController.abort(\"User cancelled\");\n}, 1000);\n\ntry {\n  const response = await responsePromise;\n} catch (error) {\n  if (error.name === \"AbortError\") {\n    console.log(\"Operation was cancelled\");\n  }\n}\n```\n\nThe agent creates an internal `AbortController` and cascades the external signal to it (see [agent.ts:3385-3395](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3385-L3395)). Sub-agents automatically inherit the parent's abort controller.\n\n## Retrievers Store References\n\nRetrievers can store source information in `context`:\n\n```typescript\nimport { BaseRetriever } from \"@voltagent/core\";\n\nclass SimpleRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    // Simulate finding documents\n    const foundDocs = [\n      { title: \"VoltAgent Guide\", url: \"https://docs.example.com\" },\n      { title: \"Agent Tutorial\", url: \"https://tutorial.example.com\" },\n    ];\n\n    // Store references in context\n    if (options?.context) {\n      options.context.set(\"references\", foundDocs);\n      options.context.set(\"searchQuery\", input);\n    }\n\n    // Return content for LLM\n    return foundDocs.map((doc) => `${doc.title}: Some helpful content...`).join(\"\\n\");\n  }\n}\n\nconst agentWithRetriever = new Agent({\n  name: \"RetrievalAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new SimpleRetriever(),\n  instructions: \"Answer using retrieved information.\",\n});\n\n// Usage\nconst response = await agentWithRetriever.generateText(\"How do I use VoltAgent?\");\n\nconsole.log(\"Answer:\", response.text);\nconsole.log(\"Search query:\", response.context?.get(\"searchQuery\"));\nconsole.log(\"References:\", response.context?.get(\"references\"));\n```\n\n## Sub-Agents Automatically Inherit Context\n\nWhen a supervisor delegates to sub-agents, the parent's `OperationContext` is passed down. The sub-agent receives the same `context` Map reference (not a copy), so modifications are visible to both parent and sub-agent.\n\n```typescript\n// Worker agent - automatically receives supervisor's context\nconst workerAgent = new Agent({\n  name: \"WorkerAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Automatically gets context from supervisor\n      const projectId = context.context.get(\"projectId\");\n      const language = context.context.get(\"language\");\n      console.log(`Worker starting for project ${projectId}, language ${language}`);\n\n      // Can add its own data too\n      context.context.set(\"workerStartTime\", new Date().toISOString());\n    },\n  }),\n  instructions: \"You are a worker that processes tasks.\",\n});\n\n// Supervisor agent\nconst supervisorAgent = new Agent({\n  name: \"SupervisorAgent\",\n  model: \"openai/gpt-4o\",\n  subAgents: [workerAgent],\n  hooks: createHooks({\n    onStart: (context) => {\n      // Set up project context\n      context.context.set(\"projectId\", `project-${Date.now()}`);\n      context.context.set(\"supervisorId\", \"supervisor-001\");\n    },\n  }),\n  instructions: \"You supervise tasks. Delegate work to WorkerAgent when needed.\",\n});\n\n// Usage\nconst initialContext = new Map();\ninitialContext.set(\"language\", \"English\");\ninitialContext.set(\"priority\", \"high\");\n\nconst response = await supervisorAgent.generateText(\"Please delegate this task to the worker\", {\n  context: initialContext,\n});\n\n// Final context includes data from both supervisor and worker\nconsole.log(\"Project ID:\", response.context?.get(\"projectId\"));\nconsole.log(\"Worker start time:\", response.context?.get(\"workerStartTime\"));\n```\n\n### Context Inheritance Priority\n\nWhen creating an `OperationContext`, the framework follows this priority (see [agent.ts:3343-3371](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3343-L3371)):\n\n1. **Parent context** (if sub-agent): Use parent's context Map reference\n2. **Runtime context** (if provided at execution): Use the provided Map\n3. **Agent-level context** (if set in constructor): Use the agent's default Map\n4. **New Map**: Create an empty Map if none exists\n\nThe framework then fills in missing keys from lower-priority sources without overriding existing values. This means:\n\n- Sub-agents see all parent context data\n- Parent's values take precedence over runtime and agent-level defaults\n- Runtime values take precedence over agent-level defaults\n\n**Important**: The context Map reference is preserved, not cloned. This enables bidirectional updates - changes made by sub-agents are visible to the parent.\n\nFor more details on sub-agent architecture, see the [Sub-Agents guide](./subagents.md).\n\n## Complete Flow Example\n\nHere's how all pieces work together:\n\n```typescript\nimport { Agent, createHooks, createTool, BaseRetriever } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Simple retriever\nclass BasicRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    if (options.context) {\n      options.context.set(\"references\", [{ title: \"Document 1\", source: \"knowledge-base\" }]);\n    }\n    return \"Retrieved content about the topic\";\n  }\n}\n\n// Simple tool\nconst counterTool = createTool({\n  name: \"increment_counter\",\n  description: \"Increments a counter\",\n  parameters: z.object({}),\n  execute: async (_, options) => {\n    const ctx = options?.context;\n    if (ctx) {\n      const count = (ctx.get(\"counter\") || 0) + 1;\n      ctx.set(\"counter\", count);\n      return `Counter is now: ${count}`;\n    }\n    return \"Counter incremented\";\n  },\n});\n\n// Agent with everything\nconst fullAgent = new Agent({\n  name: \"FullAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new BasicRetriever(),\n  tools: [counterTool],\n  hooks: createHooks({\n    onStart: (context) => {\n      console.log(\"🚀 Operation started\");\n      context.context.set(\"operationId\", `op-${Date.now()}`);\n    },\n    onEnd: (context) => {\n      const opId = context.context.get(\"operationId\");\n      const counter = context.context.get(\"counter\");\n      const references = context.context.get(\"references\");\n\n      console.log(\"✅ Operation completed\");\n      console.log(`Operation ID: ${opId}`);\n      console.log(`Counter final value: ${counter}`);\n      console.log(`References found: ${references?.length || 0}`);\n    },\n  }),\n  instructions: \"Use tools and retrieval to help users. Always increment the counter.\",\n});\n\n// Usage showing the complete flow\nasync function demonstrateFlow() {\n  const initialContext = new Map();\n  initialContext.set(\"language\", \"English\");\n\n  const response = await fullAgent.generateText(\n    \"Use the increment tool and search for information\",\n    { context: initialContext }\n  );\n\n  console.log(\"Text:\", response.text);\n  const finalContext = response.context;\n  for (const [key, value] of finalContext.entries()) {\n    console.log(`${String(key)}: ${JSON.stringify(value)}`);\n  }\n}\n```\n\n## Key Points\n\n### Context Access by Component\n\n1. **Hooks**: Access via `context.context` in `onStart`/`onEnd` callbacks\n2. **Tools**: Access via `options.operationContext.context` in `execute` function\n3. **Retrievers**: Access via `options.context` in `retrieve` method\n4. **Sub-Agents**: Automatically receive parent's `context` Map reference\n5. **Response**: Access final state via `response.context`\n6. **Dynamic Values**: Constructor context is available in dynamic instructions, model, and tools functions\n\n### OperationContext Fields\n\nThe `OperationContext` contains:\n\n- **User-managed**: `context` (Map), `userId`, `conversationId`, `workspace`\n- **Metadata**: `operationId`, `startTime`, `isActive`, `parentAgentId`\n- **System**: `systemContext`, `logger`, `traceContext`, `conversationSteps`, `abortController`, `cancellationError`, `elicitation`\n- **Input/Output**: `input`, `output` (automatically set)\n\n### Context Initialization Priority\n\n1. Parent context (for sub-agents) - highest priority\n2. Runtime context (execution parameter)\n3. Agent-level context (constructor)\n4. New empty Map - lowest priority\n\nLower-priority sources only fill in missing keys; they don't override existing values. The Map reference is preserved (not cloned) to enable bidirectional updates between parent and sub-agents.\n\n### Tips\n\n**Extend rather than replace default context:**\n\n```typescript\nconst agent = new Agent({\n  context: new Map([[\"env\", \"production\"]]),\n  // ...\n});\n\n// Extend the default context\nconst extendedContext = new Map(agent.context);\nextendedContext.set(\"requestId\", \"req-123\");\n\nawait agent.generateText(\"Hello\", { context: extendedContext });\n```\n\n**Use metadata fields for observability:**\n\n```typescript\nhooks: createHooks({\n  onEnd: ({ context }) => {\n    console.log(\n      `Operation ${context.operationId} completed in ${Date.now() - context.startTime.getTime()}ms`\n    );\n  },\n});\n```\n\n## Memory Adapters\n\nCustom memory adapters can access `OperationContext` to implement cross-cutting concerns like multi-tenancy, logging, and access control without modifying core adapter logic.\n\n### Example: Multi-Tenant Data Isolation\n\nCustom memory adapters can read user-provided context to implement tenant-scoped data access:\n\n```typescript\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext, GetMessagesOptions, UIMessage } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant ID from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID is required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log access for audit trail\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n      traceId: context.traceContext.getTraceId(),\n    });\n\n    // Use scoped user ID to isolate tenant data\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Tenant A: user-123's data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-abc\"]]), // Stores as \"company-abc:user-123\"\n});\n\n// Tenant B: same user ID, but different tenant = different data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-xyz\"]]), // Stores as \"company-xyz:user-123\"\n});\n```\n\n**What this enables:**\n\n- **Data isolation** between tenants using composite keys\n- **Per-request tenant context** without adapter reconfiguration\n- **Audit logging** with full operation context\n- **Flexible access control** based on runtime values\n\nAll memory adapters (InMemory, Postgres, LibSQL, Supabase) support `OperationContext` as an optional parameter.\n","website/docs/agents/dynamic-agents.md":"---\ntitle: Dynamic Agents\nslug: /agents/dynamic-agents\n---\n\n# Dynamic Agents\n\nDynamic agents use functions instead of static values for `instructions`, `model`, and `tools`. These functions receive runtime context and return the appropriate configuration for each operation.\n\nWhen you call `generateText()` or `streamText()`, the agent resolves dynamic values using the `context` map you provide. This lets you configure agent behavior per-request based on user roles, subscription tiers, language preferences, or any custom logic.\n\n## Use Cases\n\nDynamic agents work well for:\n\n- Multi-tenant applications where different users need different capabilities\n- Role-based access control where admins get different tools than regular users\n- Subscription tiers where premium users get access to different models\n- Internationalization where responses adapt to user's language\n- A/B testing where different users get different configurations\n\n## How Dynamic Resolution Works\n\nWhen you provide a function instead of a static value, the agent calls it during each operation:\n\n1. You call `agent.generateText(input, { context })`\n2. Agent invokes your function with `DynamicValueOptions`\n3. Function receives `{ context, prompts }` and returns the value\n4. Agent uses the resolved value for that operation\n\nThe resolution happens in [agent.ts:2583-2606](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L2583-L2606) via the `resolveValue()` method. Dynamic functions are called on every operation, so keep them synchronous or fast.\n\n### Type System\n\nDynamic values use the `DynamicValue<T>` type:\n\n```typescript\ntype DynamicValue<T> = (options: DynamicValueOptions) => Promise<T> | T;\n\ninterface DynamicValueOptions {\n  context: Map<string | symbol, unknown>; // Runtime context you provide\n  headers?: Record<string, string>; // HTTP request headers, when called through a server adapter\n  prompts: PromptHelper; // VoltOps prompt management\n}\n```\n\nThe agent defines three specific dynamic types:\n\n- `InstructionsDynamicValue`: `string | DynamicValue<string | PromptContent>`\n- `ModelDynamicValue<T>`: `T | DynamicValue<T>`\n- `ToolsDynamicValue`: `(Tool | Toolkit)[] | DynamicValue<(Tool | Toolkit)[]>`\n\n## Basic Example\n\nHere's a dynamic agent that changes instructions based on user role:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst dynamicAgent = new Agent({\n  name: \"Adaptive Assistant\",\n\n  // Dynamic instructions based on user context\n  instructions: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n    const language = (context.get(\"language\") as string) || \"English\";\n\n    if (role === \"admin\") {\n      return `You are an admin assistant with special privileges. Respond in ${language}.`;\n    } else {\n      return `You are a helpful assistant. Respond in ${language}.`;\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Dynamic Properties\n\nYou can make `instructions`, `model`, and `tools` dynamic. Each is resolved independently during operation execution.\n\n### Dynamic Instructions\n\nInstructions can be a string or a function that returns a string (or `PromptContent` when using VoltOps).\n\n```ts\nconst agent = new Agent({\n  name: \"Multilingual Support Agent\",\n\n  instructions: ({ context }) => {\n    const language = (context.get(\"language\") as string) || \"English\";\n    const supportTier = (context.get(\"supportTier\") as string) || \"basic\";\n\n    let baseInstructions = `You are a customer support agent. Respond in ${language}.`;\n\n    if (supportTier === \"premium\") {\n      baseInstructions += \" You can offer advanced troubleshooting and priority support.\";\n    } else {\n      baseInstructions += \" Provide standard support within basic guidelines.\";\n    }\n\n    return baseInstructions;\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n### Request Headers\n\nWhen an agent is called through the built-in HTTP endpoints, VoltAgent exposes the request headers to dynamic `instructions`, `model`, and `tools` functions as `headers`. Header names are normalized to lowercase.\n\n```ts\nconst agent = new Agent({\n  name: \"Tenant Agent\",\n  instructions: \"You are a tenant-aware assistant.\",\n  model: ({ headers }) => {\n    const tenantId = headers?.[\"x-tenant-id\"];\n\n    return tenantId === \"enterprise\" ? \"openai/gpt-4o\" : \"openai/gpt-4o-mini\";\n  },\n  tools: ({ headers }) => {\n    const token = headers?.authorization;\n\n    return token ? [createTenantTool(token)] : [];\n  },\n});\n```\n\nFor direct in-process calls, pass `requestHeaders`:\n\n```ts\nawait agent.generateText(\"Hello\", {\n  requestHeaders: {\n    authorization: \"Bearer token\",\n    \"x-tenant-id\": \"tenant-1\",\n  },\n});\n```\n\n### Dynamic Models\n\nThe model can be a static `LanguageModel`, a `provider/model` string, or a function returning either. The agent resolves the model at [agent.ts:1672](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1672) before each generation call.\n\n```ts\nconst agent = new Agent({\n  name: \"Tier-Based Assistant\",\n  instructions: \"You are a helpful assistant.\",\n\n  // Different models for different subscription tiers\n  model: ({ context }) => {\n    const tier = (context.get(\"tier\") as string) || \"free\";\n\n    switch (tier) {\n      case \"premium\":\n        return \"openai/gpt-4o\";\n      case \"pro\":\n        return \"openai/gpt-4o-mini\";\n      default:\n        return \"openai/gpt-3.5-turbo\";\n    }\n  },\n});\n```\n\n### Dynamic Tools\n\nTools can be a static array or a function returning an array. The agent stores dynamic tool functions separately in the `dynamicTools` property ([agent.ts:337](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L337)) and resolves them at [agent.ts:1673](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1673).\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Regular user tool\nconst basicTool = createTool({\n  name: \"get_help\",\n  description: \"Get basic help information\",\n  parameters: z.object({\n    topic: z.string().describe(\"Help topic\"),\n  }),\n  execute: async ({ topic }) => {\n    return `Here's basic help about ${topic}`;\n  },\n});\n\n// Admin-only tool\nconst adminTool = createTool({\n  name: \"admin_action\",\n  description: \"Perform administrative actions\",\n  parameters: z.object({\n    action: z.string().describe(\"Admin action to perform\"),\n  }),\n  execute: async ({ action }) => {\n    return `Admin action performed: ${action}`;\n  },\n});\n\nconst agent = new Agent({\n  name: \"Role-Based Agent\",\n  instructions: \"You are an assistant with role-based capabilities.\",\n\n  // Different tools based on user role\n  tools: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n\n    if (role === \"admin\") {\n      return [basicTool, adminTool]; // Admins get both tools\n    } else {\n      return [basicTool]; // Regular users get basic tools only\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Execution\n\nPass `context` as an option to any generation method. The agent uses this context to resolve dynamic values:\n\n```ts\n// Create context with relevant information\nconst context = new Map<string, unknown>();\ncontext.set(\"role\", \"admin\");\ncontext.set(\"language\", \"Spanish\");\ncontext.set(\"tier\", \"premium\");\ncontext.set(\"company\", \"TechCorp\");\n\n// Generate response with context\nconst response = await dynamicAgent.generateText(\"Help me manage the system settings\", {\n  context,\n});\n\nconsole.log(response.text);\n// The agent will respond in Spanish, with admin capabilities,\n// using the premium model, and have access to admin tools\n```\n\nYou can also use it with streaming:\n\n```ts\nconst streamResponse = await dynamicAgent.streamText(\"What products are available?\", {\n  context: new Map([\n    [\"role\", \"customer\"],\n    [\"language\", \"French\"],\n    [\"tier\", \"basic\"],\n  ]),\n});\n\nfor await (const chunk of streamResponse.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n## REST API Usage\n\nYou can pass `context` via the REST API to trigger dynamic resolution over HTTP.\n\n### API Request Format\n\nPass the `context` object within the `options` field of your API request:\n\n```json\n{\n  \"input\": \"Your message to the agent\",\n  \"options\": {\n    \"context\": {\n      \"role\": \"admin\",\n      \"language\": \"Spanish\",\n      \"tier\": \"premium\"\n    },\n    \"temperature\": 0.7,\n    \"maxTokens\": 500\n  }\n}\n```\n\n### REST API Examples\n\n**Basic Text Generation:**\n\n```bash\n# Admin user requesting help in Spanish\ncurl -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/text \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"I need to update system settings\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"admin\",\n           \"language\": \"Spanish\",\n           \"tier\": \"enterprise\"\n         }\n       }\n     }'\n```\n\n**Streaming with Real-time Context:**\n\n```bash\n# Stream response for different user roles\ncurl -N -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/stream \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"Write a summary of our quarterly results\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"manager\",\n           \"department\": \"finance\",\n           \"accessLevel\": \"confidential\",\n           \"language\": \"English\"\n         }\n       }\n     }'\n```\n\n## VoltOps Integration\n\nThe VoltOps dashboard lets you test dynamic agents by setting context values through a visual interface.\n\n<!-- GIF PLACEHOLDER: VoltOps Dynamic Agents Demo -->\n\n![VoltOps Dynamic Agents Demo](https://cdn.voltagent.dev/docs/user-context-demo.gif)\n\nYou can:\n\n- Set context values through a form\n- Test different user roles and configurations\n- Monitor how dynamic properties change based on context\n- Debug dynamic agent logic\n\n## Best Practices\n\n### Context Validation\n\nValidate context values and provide defaults:\n\n```ts\ninstructions: ({ context }) => {\n  // Always provide defaults and validate types\n  const role = (context.get(\"role\") as string) || \"user\";\n  const language = (context.get(\"language\") as string) || \"English\";\n\n  // Validate known values\n  const validRoles = [\"admin\", \"support\", \"customer\"];\n  const actualRole = validRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${actualRole} assistant. Respond in ${language}.`;\n};\n```\n\n### Performance\n\nDynamic functions run on every operation. Avoid async operations or heavy computation:\n\n```ts\n// ✅ Good - Fast synchronous logic\nmodel: ({ context }) => {\n  const tier = context.get('tier') as string;\n  return tier === 'premium' ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n},\n\n// ❌ Avoid - API calls or database queries\nmodel: async ({ context }) => {\n  const userProfile = await fetchUserFromDatabase(context.get('userId'));\n  return determineModelFromProfile(userProfile);\n}\n```\n\n### Security\n\nUse allowlists for security-sensitive values:\n\n```ts\ninstructions: ({ context }) => {\n  const role = context.get(\"role\") as string;\n\n  // ✅ Use allowlists for security-sensitive operations\n  const allowedRoles = [\"admin\", \"support\", \"customer\"];\n  const safeRole = allowedRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${safeRole} assistant.`;\n};\n```\n\n### Context Structure\n\nUse consistent context key naming across your application:\n\n```ts\n// ✅ Consistent naming convention\nconst createUserContext = (user: User) => {\n  const context = new Map<string, unknown>();\n  context.set(\"user.id\", user.id);\n  context.set(\"user.role\", user.role);\n  context.set(\"user.tier\", user.subscriptionTier);\n  context.set(\"user.language\", user.preferredLanguage);\n  context.set(\"request.timestamp\", Date.now());\n  return context;\n};\n```\n\n## Advanced Patterns\n\n### Conditional Resolution\n\nMake properties dynamic only when context requires it:\n\n```ts\nconst agent = new Agent({\n  name: \"Conditionally Dynamic Agent\",\n\n  // Static instructions most of the time\n  instructions: \"You are a helpful assistant.\",\n\n  // But dynamic model when user context is provided\n  model: ({ context }) => {\n    // If no tier specified, use default static model\n    if (!context.has(\"tier\")) {\n      return \"openai/gpt-4o-mini\"; // Default model\n    }\n\n    // Otherwise, choose based on tier\n    const tier = context.get(\"tier\") as string;\n    return tier === \"premium\" ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n  },\n\n  // model is selected dynamically above\n});\n```\n\n### Context Between Operations\n\nPass the same context to related operations for consistency:\n\n```ts\n// Main agent with context\nconst parentResponse = await dynamicAgent.generateText(\"Create a summary\", {\n  context: myContext,\n});\n\n// Sub-agent inherits the same context\nconst detailResponse = await detailAgent.generateText(\n  \"Provide more details\",\n  { context: myContext } // Same context for consistency\n);\n```\n\n## Error Handling\n\nWrap dynamic logic in try-catch and provide fallbacks:\n\n```ts\nmodel: ({ context }) => {\n  try {\n    const tier = context.get(\"tier\") as string;\n\n    if (tier === \"premium\") {\n      return \"openai/gpt-4o\";\n    }\n    return \"openai/gpt-3.5-turbo\";\n  } catch (error) {\n    console.warn(\"Error in dynamic model selection:\", error);\n    // Fallback to safe default\n    return \"openai/gpt-3.5-turbo\";\n  }\n};\n```\n","website/docs/agents/hooks.md":"---\ntitle: Hooks\nslug: /agents/hooks\n---\n\n# Agent Hooks\n\nHooks intercept specific points in the agent execution pipeline. Use them to observe or modify behavior during agent operations: before a request starts, when preparing messages for the LLM, before and after tool execution, or after completion.\n\n## Defining Hooks\n\nDefine hooks using the `createHooks` helper function. Pass the resulting object to the Agent constructor, or to individual method calls like `generateText` and `streamText`.\n\n```ts\nimport {\n  Agent,\n  createHooks,\n  messageHelpers,\n  type AgentTool,\n  type AgentOperationOutput,\n  type VoltAgentError,\n  type OnStartHookArgs,\n  type OnEndHookArgs,\n  type OnPrepareMessagesHookArgs,\n  type OnPrepareModelMessagesHookArgs,\n  type OnToolStartHookArgs,\n  type OnToolEndHookArgs,\n  type OnToolErrorHookArgs,\n  type OnHandoffHookArgs,\n} from \"@voltagent/core\";\n\n// Define a collection of hooks using the helper\nconst myAgentHooks = createHooks({\n  /**\n   * Called before the agent starts processing a request.\n   */\n  onStart: async (args: OnStartHookArgs) => {\n    const { agent, context } = args;\n    console.log(`[Hook] Agent ${agent.name} starting interaction at ${new Date().toISOString()}`);\n    console.log(`[Hook] Operation ID: ${context.operationId}`);\n  },\n\n  /**\n   * Called after VoltAgent sanitizes UI messages but before the LLM receives them.\n   * `rawMessages` contains the unsanitized list for inspection or metadata recovery.\n   */\n  onPrepareMessages: async (args: OnPrepareMessagesHookArgs) => {\n    const { messages, rawMessages, context } = args;\n    console.log(`Preparing ${messages.length} sanitized messages for LLM`);\n\n    // Add timestamp to each message\n    const timestamp = new Date().toLocaleTimeString();\n    const enhanced = messages.map((msg) => messageHelpers.addTimestampToMessage(msg, timestamp));\n\n    if (rawMessages) {\n      // Access raw message structure for audit logging\n      console.debug(`First raw message parts:`, rawMessages[0]?.parts);\n    }\n\n    return { messages: enhanced };\n  },\n\n  /**\n   * Called after UI messages are converted into provider-specific ModelMessage objects.\n   */\n  onPrepareModelMessages: async (args: OnPrepareModelMessagesHookArgs) => {\n    const { modelMessages, uiMessages } = args;\n    console.log(`Model payload contains ${modelMessages.length} messages`);\n\n    // Inject a system message if none exists\n    if (!modelMessages.some((msg) => msg.role === \"system\")) {\n      return {\n        modelMessages: [\n          {\n            role: \"system\",\n            content: [{ type: \"text\", text: \"Operate within safety budget\" }],\n          },\n          ...modelMessages,\n        ],\n      };\n    }\n\n    return {};\n  },\n\n  /**\n   * Called after the agent completes a request (success or failure).\n   */\n  onEnd: async (args: OnEndHookArgs) => {\n    const { agent, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Agent ${agent.name} finished with error:`, error.message);\n      console.error(`[Hook] Error Details:`, JSON.stringify(error, null, 2));\n    } else if (output) {\n      console.log(`[Hook] Agent ${agent.name} finished successfully.`);\n      // Log aggregate usage or inspect output type\n      if (\"totalUsage\" in output && output.totalUsage) {\n        console.log(`[Hook] Total Token Usage: ${output.totalUsage.totalTokens}`);\n      }\n      if (\"text\" in output && output.text) {\n        console.log(`[Hook] Final text length: ${output.text.length}`);\n      }\n      if (\"object\" in output && output.object) {\n        console.log(`[Hook] Final object keys: ${Object.keys(output.object).join(\", \")}`);\n      }\n    }\n  },\n\n  /**\n   * Called before a tool executes.\n   */\n  onToolStart: async (args: OnToolStartHookArgs) => {\n    const { agent, tool, context, args: toolArgs } = args;\n    console.log(`[Hook] Agent ${agent.name} starting tool: ${tool.name}`);\n    console.log(`[Hook] Tool arguments:`, toolArgs);\n  },\n\n  /**\n   * Called after a tool completes or throws an error.\n   */\n  onToolEnd: async (args: OnToolEndHookArgs) => {\n    const { agent, tool, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Tool ${tool.name} failed:`, error.message);\n      console.error(`[Hook] Tool Error Details:`, JSON.stringify(error, null, 2));\n    } else {\n      console.log(`[Hook] Tool ${tool.name} completed with result:`, output);\n    }\n  },\n\n  /**\n   * Called only when a tool throws, before VoltAgent serializes the error payload.\n   */\n  onToolError: async (args: OnToolErrorHookArgs) => {\n    const { tool, originalError } = args;\n    console.error(`[Hook] Raw tool error from ${tool.name}:`, originalError.message);\n\n    // Return { output } to customize what gets sent back to the model.\n    return {\n      output: {\n        error: true,\n        message: \"External API request failed\",\n      },\n    };\n  },\n\n  /**\n   * Called when a task is handed off from a source agent to this agent.\n   */\n  onHandoff: async (args: OnHandoffHookArgs) => {\n    const { agent, sourceAgent } = args;\n    console.log(`[Hook] Task handed off from ${sourceAgent.name} to ${agent.name}`);\n  },\n});\n\n// Pass hooks to the Agent constructor\nconst agentWithHooks = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\n// Or define hooks inline\nconst agentWithInlineHooks = new Agent({\n  name: \"Inline Hooks Agent\",\n  instructions: \"Another assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: {\n    onStart: async ({ agent, context }) => {\n      /* ... */\n    },\n    onEnd: async ({ agent, output, error, context }) => {\n      /* ... */\n    },\n  },\n});\n```\n\n## Passing Hooks to Methods\n\nPass hooks to `generateText`, `streamText`, `generateObject`, or `streamObject` to run hooks for that specific invocation only.\n\n:::warning\nMethod-level hooks do not override agent-level hooks. Both will execute. For most hooks, the method-level hook runs first, then the agent-level hook. For `onPrepareMessages` and `onPrepareModelMessages`, the method-level hook replaces the agent-level hook entirely.\n:::\n\n```ts\nconst agent = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\nawait agent.generateText(\"Hello, how are you?\", {\n  hooks: {\n    onEnd: async ({ context }) => {\n      console.log(\"End of generation for this invocation!\");\n    },\n  },\n});\n```\n\nFor example, store conversation history only for specific endpoints:\n\n```ts\nconst agent = new Agent({\n  name: \"Translation Agent\",\n  instructions: \"A translation agent that translates text from English to French\",\n  model: \"openai/gpt-4o\",\n});\n\n// for the translate endpoint, we don't want to store the conversation history\napp.post(\"/api/translate\", async (req, res) => {\n  const result = await agent.generateText(req.body.text);\n  return result;\n});\n\n// for the chat endpoint, we want to store the conversation history\napp.post(\"/api/translate/chat\", async (req, res) => {\n  const result = await agent.streamText(req.body.text, {\n    hooks: {\n      onEnd: async ({ context }) => {\n        await chatStore.save({\n          conversationId: context.conversationId,\n          messages: context.steps,\n        });\n      },\n    },\n  });\n\n  return result.textStream;\n});\n```\n\n## Available Hooks\n\nAll hooks receive a single argument object containing relevant information.\n\n### Choosing the right message hook\n\n| Hook                     | Stage                                                              | When to use                                                                                           |\n| ------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `onPrepareMessages`      | Operates on sanitized `UIMessage[]` built from memory + user input | Transform messages using helper functions, redact content, or access `rawMessages` for logging.       |\n| `onPrepareModelMessages` | Runs after `convertToModelMessages` on `ModelMessage[]`            | Apply provider-specific adjustments, inject system directives, or modify the final payload structure. |\n\nUse `onPrepareMessages` for general transformations and `onPrepareModelMessages` for provider-specific adjustments. Both hooks can be used together; VoltAgent applies them in that order.\n\n### `onStart`\n\n- **Triggered:** Before the agent begins processing a request (`generateText`, `streamText`, etc.).\n- **Argument Object (`OnStartHookArgs`):** `{ agent: Agent, context: OperationContext }`\n- **Use Cases:** Initialization logic, request logging, setting up request-scoped resources.\n\n```ts\n// Example: Log the start of an operation\nonStart: async ({ agent, context }) => {\n  console.log(`Agent ${agent.name} starting operation ${context.operationId}`);\n};\n```\n\n### `onPrepareMessages`\n\n- **Triggered:** After VoltAgent assembles conversation history and sanitizes UI messages, before conversion to provider-specific payloads.\n- **Argument Object (`OnPrepareMessagesHookArgs`):** `{ messages: UIMessage[], rawMessages?: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Transform sanitized messages (add timestamps, redact content), or access `rawMessages` for logging or metadata recovery.\n- **Return:** `{ messages: UIMessage[] }` to replace the sanitized list, or an empty object to keep the original.\n- **Notes:**\n  - `messages` contains sanitized UI messages safe for the LLM.\n  - `rawMessages` contains the full structure before sanitization, useful for logging or metadata reconstruction.\n\n```ts\nonPrepareMessages: async ({ messages, rawMessages }) => {\n  const tagged = messages.map((msg) =>\n    messageHelpers.addTimestampToMessage(msg, new Date().toISOString())\n  );\n\n  if (rawMessages) {\n    auditTrail.write(rawMessages); // your own analytics sink\n  }\n\n  return { messages: tagged };\n};\n```\n\n### `onPrepareModelMessages`\n\n- **Triggered:** After UI messages are converted via `convertToModelMessages`, immediately before the provider receives them.\n- **Argument Object (`OnPrepareModelMessagesHookArgs`):** `{ modelMessages: ModelMessage[], uiMessages: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Apply provider-specific transformations, inject final system messages, compress conversation length, or add structured metadata.\n- **Return:** `{ modelMessages: ModelMessage[] }` with a replacement list, or an empty object to keep the original.\n- **Tip:** Use `onPrepareMessages` for general transformations, and this hook for provider-specific adjustments.\n\n```ts\nonPrepareModelMessages: async ({ modelMessages }) => {\n  // Force the last message to include a \"speak clearly\" reminder for voice models\n  const last = modelMessages.at(-1);\n  if (last && last.role === \"user\" && Array.isArray(last.content)) {\n    last.content.push({ type: \"text\", text: \"Please answer succinctly.\" });\n  }\n\n  return { modelMessages };\n};\n```\n\n### `onEnd`\n\n- **Triggered:** After the agent completes processing a request (success or failure).\n- **Argument Object (`OnEndHookArgs`):** `{ agent: Agent, output: AgentOperationOutput | undefined, error: VoltAgentError | undefined, conversationId: string, context: OperationContext }`\n- **Use Cases:** Cleanup, logging, analyzing output or errors, recording usage statistics, storing conversation history.\n- **Note:** The `output` structure depends on the method called. Check for `text` or `object` fields. `error` contains the structured `VoltAgentError` on failure.\n\n```ts\n// Example: Log the outcome of an operation and store conversation history\nonEnd: async ({ agent, output, error, conversationId, context }) => {\n  if (error) {\n    console.error(`Agent ${agent.name} operation ${context.operationId} failed: ${error.message}`);\n    console.log(`User input: \"${context.historyEntry.input}\"`);\n    // Only user input available on error (no assistant response)\n  } else {\n    // Check output type if needed\n    if (output && \"text\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with text output.`\n      );\n    } else if (output && \"object\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with object output.`\n      );\n    } else {\n      console.log(`Agent ${agent.name} operation ${context.operationId} succeeded.`);\n    }\n\n    // Access input and output directly from context\n    console.log(\"Request input:\", context.input);\n    console.log(\"Generated output:\", context.output);\n\n    // Log the complete conversation flow\n    console.log(`Conversation flow:`, {\n      user: context.historyEntry.input,\n      assistant: context.steps, // the assistant steps\n      totalMessages: context.steps.length,\n      toolInteractions: context.steps.flatMap((s) => s.toolInvocations || []).length,\n      toolsUsed: context.steps.flatMap((s) => s.toolInvocations || []).map((t) => t.toolName),\n    });\n\n    // Log complete interaction using input/output\n    console.log(\"Full interaction:\", {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    });\n\n    // Log usage if available\n    if (output?.totalUsage) {\n      console.log(`  Total usage: ${output.totalUsage.totalTokens} tokens`);\n    }\n  }\n};\n```\n\n### `onRetry`\n\n- **Triggered:** When VoltAgent schedules a retry for an LLM attempt or a middleware abort.\n- **Argument Object (`OnRetryHookArgs`):** Includes `source`, `operation`, `context`, and retry details.\n- **Use Cases:** Retry metrics, alerts, tracking retry sources.\n\n```ts\nonRetry: async (args) => {\n  if (args.source === \"llm\") {\n    console.log(\n      `LLM retry ${args.nextAttempt}/${args.maxRetries + 1} for ${args.modelName} (${args.operation})`\n    );\n  } else {\n    console.log(\n      `Middleware retry ${args.retryCount + 1}/${args.maxRetries + 1} for ${args.middlewareId ?? \"unknown\"}`\n    );\n  }\n};\n```\n\n### `onFallback`\n\n- **Triggered:** When VoltAgent selects the next model candidate.\n- **Argument Object (`OnFallbackHookArgs`):** Includes `stage`, `fromModel`, `fromModelIndex`, `nextModel`, and `error`.\n- **Use Cases:** Tracking model failover, audit logs, fallback diagnostics.\n\n```ts\nonFallback: async ({ stage, fromModel, nextModel, operation }) => {\n  console.warn(\n    `Fallback (${stage}) from ${fromModel} to ${nextModel ?? \"next\"} during ${operation}`\n  );\n};\n```\n\n### `onToolStart`\n\n- **Triggered:** Before an agent executes a tool.\n- **Argument Object (`OnToolStartHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool being executed\n  - `args`: Tool input arguments\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Use Cases:** Logging tool usage, inspecting tool arguments, or validating inputs before execution.\n\n```ts\n// Example: Log tool invocation with arguments\nonToolStart: async ({ agent, tool, args, context }) => {\n  console.log(`Agent ${agent.name} invoking tool '${tool.name}'`);\n  console.log(`Tool arguments:`, args);\n};\n```\n\n### `onToolEnd`\n\n- **Triggered:** After a tool execution completes or throws an error.\n- **Argument Object (`OnToolEndHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that completed\n  - `output`: Tool output (undefined on error)\n  - `error`: VoltAgentError when tool throws (undefined on success)\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** `{ output }` to replace the tool result. The replacement is validated again if the tool has an `outputSchema`.\n- **Use Cases:** Logging tool results or errors, post-processing output, triggering actions based on success or failure.\n\n```ts\n// Example: Log the result or error of a tool execution\nonToolEnd: async ({ agent, tool, output, error, context }) => {\n  if (error) {\n    console.error(\n      `Tool '${tool.name}' failed in operation ${context.operationId}: ${error.message}`\n    );\n  } else {\n    console.log(\n      `Tool '${tool.name}' succeeded in operation ${context.operationId}. Result:`,\n      output\n    );\n  }\n};\n```\n\n### `onToolError`\n\n- **Triggered:** Only when a tool throws, before VoltAgent builds its default serialized error result.\n- **Argument Object (`OnToolErrorHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that failed\n  - `args`: Tool input arguments\n  - `error`: Structured `VoltAgentError` for the tool failure\n  - `originalError`: Original thrown error normalized to an `Error` instance\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** Optional `{ output }` replacement payload. If omitted, VoltAgent returns its default serialized error payload.\n- **Use Cases:** Centralized error normalization (for example Axios), redacting sensitive fields, shrinking error payload size before it is sent to the model.\n\n```ts\nonToolError: async ({ tool, error, originalError }) => {\n  const maybeAxios = (originalError as any).isAxiosError === true;\n  if (!maybeAxios) {\n    return;\n  }\n\n  return {\n    output: {\n      error: true,\n      name: error.name,\n      message: originalError.message,\n      code: (originalError as any).code,\n      status: (originalError as any).response?.status,\n    },\n  };\n};\n```\n\n### Tool-level hooks (per tool)\n\nTool hooks run for a specific tool instance and are called before/after execution. Tool-level `onEnd` runs before agent-level `onToolEnd`. If both return `{ output }`, the agent hook wins. Any override is re-validated when `outputSchema` is present.\n\n**Tool hook parameters:**\n\n- `onStart`: `{ tool, args, options }`\n- `onEnd`: `{ tool, args, output, error, options }` (return `{ output }` to override)\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst normalizeTool = createTool({\n  name: \"normalize_text\",\n  description: \"Normalize and trim text\",\n  parameters: z.object({ text: z.string() }),\n  execute: async ({ text }) => text,\n  hooks: {\n    onStart: ({ tool }) => {\n      console.log(`[tool] ${tool.name} starting`);\n    },\n    onEnd: ({ output }) => {\n      if (typeof output === \"string\") {\n        return { output: output.trim() };\n      }\n    },\n  },\n});\n```\n\n### `onHandoff`\n\n- **Triggered:** When one agent delegates a task to another agent via the `delegate_task` tool.\n- **Argument Object (`OnHandoffHookArgs`):** `{ agent: Agent, sourceAgent: Agent }`\n- **Use Cases:** Tracking workflow in multi-agent systems, logging agent collaboration.\n\n```ts\n// Example: Log agent handoffs\nonHandoff: async ({ agent, sourceAgent }) => {\n  console.log(`Task handed off from agent '${sourceAgent.name}' to agent '${agent.name}'`);\n};\n```\n\n## Hook Execution and Error Handling\n\n- **Async Execution:** Hooks can be `async` functions. VoltAgent awaits completion before proceeding. Long-running operations in hooks add latency to agent response time.\n- **Error Handling:** Errors thrown inside hooks may interrupt agent execution. Use `try...catch` within hooks or design them to be reliable.\n- **Hook Merging:** When hooks are passed to both the Agent constructor and a method call:\n  - Most hooks (`onStart`, `onEnd`, `onError`, `onHandoff`, `onToolStart`, `onToolEnd`, `onToolError`, `onStepFinish`, `onRetry`, `onFallback`) execute both: method-level first, then agent-level.\n  - Message hooks (`onPrepareMessages`, `onPrepareModelMessages`) do not merge: method-level replaces agent-level entirely.\n\n## Additional Hooks\n\n- **`onError`**: Called when an error occurs during agent execution. Receives `{ agent: Agent, error: Error, context: OperationContext }`.\n- **`onToolError`**: Called when a tool throws, before default tool error serialization. Receives `{ agent, tool, args, error, originalError, context }` and can return `{ output }`.\n- **`onStepFinish`**: Called after each step in multi-step agent execution. Receives `{ agent: Agent, step: any, context: OperationContext }`.\n- **`onRetry`**: Called when VoltAgent schedules a retry. Receives `{ source, operation, ... }` with retry metadata.\n- **`onFallback`**: Called when VoltAgent selects the next model candidate. Receives `{ stage, fromModel, nextModel, error, ... }`.\n\n## Common Use Cases\n\n1.  **Logging & Observability**: Track execution steps, timings, inputs, outputs, and errors.\n2.  **Analytics**: Collect usage data (token counts, tool usage frequency, success/error rates).\n3.  **Message Transformation**: Modify messages before they reach the LLM or provider.\n4.  **State Management**: Initialize or clean up request-specific resources.\n5.  **Workflow Orchestration**: Trigger external actions based on agent events.\n6.  **UI Integration**: Convert `OperationContext` to messages for the Vercel AI SDK using `@voltagent/vercel-ui`.\n\n## Examples\n\n### Message Transformation with onPrepareMessages\n\nTransform messages before they reach the LLM using `onPrepareMessages` and message helper functions:\n\n```ts\nimport { Agent, createHooks, messageHelpers } from \"@voltagent/core\";\n\nconst enhancedHooks = createHooks({\n  onPrepareMessages: async ({ messages, context }) => {\n    const enhanced = messages.map((msg) => {\n      // Add timestamps to user messages\n      if (msg.role === \"user\") {\n        const timestamp = new Date().toLocaleTimeString();\n        msg = messageHelpers.addTimestampToMessage(msg, timestamp);\n      }\n\n      // Redact sensitive data\n      msg = messageHelpers.mapMessageContent(msg, (text) => {\n        text = text.replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, \"[SSN-REDACTED]\");\n        text = text.replace(/\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g, \"[CC-REDACTED]\");\n        return text;\n      });\n\n      return msg;\n    });\n\n    // Add context based on user\n    if (context.context?.get && context.context.get(\"userId\")) {\n      const systemContext = {\n        role: \"system\" as const,\n        content: `User ID: ${context.context.get(\"userId\")}. Provide personalized responses.`,\n      };\n      enhanced.unshift(systemContext);\n    }\n\n    return { messages: enhanced };\n  },\n\n  onEnd: async ({ output, context }) => {\n    console.log(`Messages processed for operation ${context.operationId}`);\n    if (output?.totalUsage) {\n      console.log(`Tokens used: ${output.totalUsage.totalTokens}`);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Privacy-Aware Assistant\",\n  instructions: \"A helpful assistant that protects user privacy\",\n  model: \"openai/gpt-4o-mini\",\n  hooks: enhancedHooks,\n});\n\n// User message: \"My SSN is 123-45-6789\"\n// LLM receives: \"[10:30:45] My SSN is [SSN-REDACTED]\"\n```\n\n### Inspecting Output in onEnd\n\nThe `output` parameter structure depends on the agent method called. Check for `text` or `object` fields:\n\nFor token accounting, VoltAgent exposes the AI SDK usage contract directly:\n\n- `output.usage` is the final step usage, matching AI SDK `result.usage`.\n- `output.totalUsage` is the aggregate usage across all steps, matching AI SDK `result.totalUsage`.\n- `output.steps?.[index].usage` is the per-step usage, matching AI SDK `result.steps[].usage`.\n\nIn a single-step run, `usage` and `totalUsage` are usually the same. In a multi-step tool run, use `totalUsage` for total cost or billing, `usage` for the final model step, and `steps[].usage` for step-level analysis.\n\n```ts\nconst hooks = createHooks({\n  onEnd: async ({ output }) => {\n    if (!output) return; // operation failed or was aborted\n\n    const finalStepUsage = output.usage;\n    const aggregateUsage = output.totalUsage;\n    const perStepUsage = output.steps?.map((step) => step.usage);\n\n    console.log({\n      finalStepTokens: finalStepUsage?.totalTokens,\n      aggregateTokens: aggregateUsage?.totalTokens,\n      stepTokens: perStepUsage?.map((usage) => usage.totalTokens),\n    });\n\n    // Handle text results\n    if (\"text\" in output && output.text) {\n      console.log(\"Final text:\", output.text);\n      return;\n    }\n\n    // Handle object results\n    if (\"object\" in output && output.object) {\n      console.log(\"Final object keys:\", Object.keys(output.object));\n    }\n  },\n});\n```\n","website/docs/agents/mcp/authorization.md":"---\ntitle: MCP Authorization\ndescription: Add access control to MCP tools with VoltAgent's authorization layer\n---\n\n# MCP Authorization\n\nVoltAgent provides an authorization layer for MCP (Model Context Protocol) tools. This allows you to control which tools users can see and execute based on roles, permissions, or any custom logic.\n\n## Overview\n\nThe MCP authorization layer supports two modes:\n\n1. **Tool Discovery Filtering** (`filterOnDiscovery`) - Hide tools from users who don't have permission to use them\n2. **Execution-Time Checks** (`checkOnExecution`) - Verify permissions before each tool call\n\nYou can enable either or both modes depending on your security requirements.\n\n## Quick Start\n\nAdd authorization with the `can` function:\n\n```typescript\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    myServer: {\n      type: \"http\",\n      url: \"http://localhost:3000/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, action, userId, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      // Admin-only tools\n      if (toolName === \"delete_item\" && !roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete items\" };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n\n// Get tools with authorization context\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"], department: \"engineering\" },\n});\n```\n\n## The `can` Function\n\nThe `can` function receives authorization parameters and returns whether access is allowed.\n\n### Parameters\n\n```typescript\ninterface MCPCanParams {\n  /** Tool name (without server prefix) */\n  toolName: string;\n  /** Server/resource identifier */\n  serverName: string;\n  /** The action being authorized: \"discovery\" or \"execution\" */\n  action: \"discovery\" | \"execution\";\n  /** Tool arguments (only available for \"execution\" action) */\n  arguments?: Record<string, unknown>;\n  /** User identifier */\n  userId?: string;\n  /** User-defined context Map */\n  context?: Map<string | symbol, unknown>;\n}\n```\n\n### Return Value\n\nReturn a boolean or an object with `allowed` and optional `reason`:\n\n```typescript\n// Simple boolean\nreturn true;\nreturn false;\n\n// Object with reason (shown in error message when denied)\nreturn { allowed: true };\nreturn { allowed: false, reason: \"Insufficient permissions\" };\n```\n\n## When Actions Are Called\n\nThe `action` parameter tells you whether the check is for listing tools or executing a tool.\n\n### Discovery Action\n\nWhen you call `getTools()` with `filterOnDiscovery: true`, the `can` function is called for each tool with `action: \"discovery\"`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    filterOnDiscovery: true,\n  },\n});\n\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n```\n\n**What `can` receives (for each tool on the server):**\n\n```json\n{\n  \"toolName\": \"list_expenses\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"delete_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\nNote: `arguments` is `undefined` during discovery since no tool is being executed yet.\n\n### Execution Action\n\nWhen a tool is executed (via agent interaction or direct call) with `checkOnExecution: true`, the `can` function is called with `action: \"execution\"` and the tool's `arguments`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n\nconst tools = await mcp.getTools();\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o\",\n  tools,\n});\n\n// When user says \"add an expense for $50 coffee\"\n// and the agent decides to call the add_expense tool:\nawait agent.generateText(\"Add an expense for $50 coffee\", {\n  userId: \"user-123\",\n  context: new Map([[\"roles\", [\"manager\"]]]),\n});\n```\n\n**What `can` receives when the agent calls `add_expense`:**\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"execution\",\n  \"arguments\": {\n    \"amount\": 50,\n    \"description\": \"coffee\"\n  },\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n### Different Logic per Action\n\nYou can use different authorization logic depending on the action:\n\n```typescript\ncan: async ({ toolName, action, arguments: args, userId, context }) => {\n  const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n  if (action === \"discovery\") {\n    // During getTools(): hide admin tools from non-admins\n    if (toolName.startsWith(\"admin_\") && !roles.includes(\"admin\")) {\n      return false; // Tool won't appear in the list\n    }\n  }\n\n  if (action === \"execution\") {\n    // During tool execution: check specific permissions\n    if (toolName === \"delete_expense\") {\n      if (!roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete expenses\" };\n      }\n    }\n\n    // Check argument-based permissions (only available during execution)\n    if (toolName === \"transfer_funds\" && args?.amount > 10000) {\n      if (!roles.includes(\"senior_manager\")) {\n        return { allowed: false, reason: \"Transfers over $10,000 require senior manager approval\" };\n      }\n    }\n  }\n\n  return true;\n};\n```\n\n## Authorization Context\n\nWhen calling `getTools()` or during tool execution, provide authorization context:\n\n```typescript\ninterface MCPAuthorizationContext {\n  userId?: string;\n  context?: Map<string | symbol, unknown> | Record<string, unknown>;\n}\n\n// Example: pass context to getTools()\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: {\n    roles: [\"admin\"],\n    tenantId: \"acme-corp\",\n    permissions: [\"read\", \"write\"],\n  },\n});\n```\n\nThe context is converted to a `Map` and passed to your `can` function.\n\n## Examples\n\n### Role-Based Access\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      const toolPermissions: Record<string, string[]> = {\n        list_items: [\"user\", \"manager\", \"admin\"],\n        create_item: [\"user\", \"manager\", \"admin\"],\n        delete_item: [\"admin\"],\n        admin_panel: [\"admin\"],\n      };\n\n      const allowedRoles = toolPermissions[toolName] ?? [];\n      const hasPermission = roles.some((role) => allowedRoles.includes(role));\n\n      if (!hasPermission) {\n        return { allowed: false, reason: `Requires one of: ${allowedRoles.join(\", \")}` };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Attribute-Based Access Control\n\nUse tool arguments for attribute-based access control (only available during execution):\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, action, arguments: args, userId }) => {\n      // arguments is only available during execution\n      if (action === \"execution\" && toolName === \"get_user_data\") {\n        const requestedUserId = args?.userId as string;\n        if (requestedUserId !== userId) {\n          return { allowed: false, reason: \"Can only access your own data\" };\n        }\n      }\n\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n```\n\n## Policy-Based Authorization with Cerbos\n\nFor production applications, use a dedicated authorization service like [Cerbos](https://cerbos.dev/). Cerbos is an open-source authorization layer that lets you define access policies in YAML files.\n\nSee the full working example: [examples/with-cerbos](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos)\n\n### Project Structure\n\n```\nwith-cerbos/\n├── docker-compose.yml    # Cerbos PDP container\n├── policies/\n│   └── mcp_tools.yaml    # Authorization policies\n├── src/\n│   ├── index.ts          # Agent with MCP authorization\n│   └── mcp-server.ts     # MCP server with tools\n└── package.json\n```\n\n### Step 1: Start Cerbos PDP\n\nCreate `docker-compose.yml`:\n\n```yaml\nservices:\n  cerbos:\n    image: ghcr.io/cerbos/cerbos:latest\n    ports:\n      - \"3593:3593\" # gRPC\n      - \"3592:3592\" # HTTP\n    volumes:\n      - ./policies:/policies\n    command: [\"server\", \"--config=/conf.yaml\"]\n    configs:\n      - source: cerbos-config\n        target: /conf.yaml\n\nconfigs:\n  cerbos-config:\n    content: |\n      server:\n        httpListenAddr: \":3592\"\n        grpcListenAddr: \":3593\"\n      storage:\n        driver: disk\n        disk:\n          directory: /policies\n```\n\nStart with `docker compose up -d`.\n\n### Step 2: Define Policies\n\nCreate `policies/mcp_tools.yaml`:\n\n```yaml\napiVersion: api.cerbos.dev/v1\nresourcePolicy:\n  version: \"default\"\n  resource: \"mcp::expenses\"\n  rules:\n    # All users can list expenses\n    - actions: [\"list_expenses\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\", \"user\"]\n\n    # Only users can add expenses\n    - actions: [\"add_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"user\"]\n\n    # Managers and admins can approve/reject\n    - actions: [\"approve_expense\", \"reject_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\"]\n\n    # Only admins can delete\n    - actions: [\"delete_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\"]\n```\n\nThe policy structure:\n\n- `resource`: `mcp::expenses` matches the `serverName` in your MCP configuration\n- `actions`: Tool names (e.g., `list_expenses`, `add_expense`)\n- `roles`: Which roles can perform the action\n- `effect`: `EFFECT_ALLOW` or `EFFECT_DENY`\n\n### Step 3: Connect to Cerbos\n\n```typescript\nimport { GRPC } from \"@cerbos/grpc\";\nimport { Agent, MCPConfiguration, VoltAgent } from \"@voltagent/core\";\nimport type { MCPCanParams } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst cerbos = new GRPC(\"localhost:3593\", { tls: false });\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    expenses: {\n      type: \"http\",\n      url: \"http://localhost:3142/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, serverName, action, userId, context }: MCPCanParams) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [\"user\"];\n\n      const result = await cerbos.checkResource({\n        principal: {\n          id: userId ?? \"anonymous\",\n          roles,\n        },\n        resource: {\n          kind: `mcp::${serverName}`,\n          id: serverName,\n        },\n        actions: [toolName],\n      });\n\n      const allowed = result.isAllowed(toolName) ?? false;\n\n      return {\n        allowed,\n        reason: allowed ? undefined : `Access denied for ${toolName}`,\n      };\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Step 4: Create the Agent\n\n```typescript\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n\nconst agent = new Agent({\n  name: \"Finance Assistant\",\n  instructions: `You are a finance assistant that helps users manage expenses.\nAvailable actions depend on the user's role.`,\n  model: \"openai/gpt-4o-mini\",\n  tools,\n});\n\nnew VoltAgent({\n  agents: { agent },\n  server: honoServer(),\n});\n```\n\n### How It Works\n\n1. **Tool Discovery**: When `getTools()` is called, each tool is checked against Cerbos. Tools the user can't access are filtered out.\n\n2. **Tool Execution**: When the agent calls a tool, Cerbos verifies the user has permission before execution.\n\n3. **Policy Evaluation**: Cerbos matches the request against policies:\n   - `principal.roles` → matches policy `roles`\n   - `resource.kind` → matches policy `resource`\n   - `actions[0]` → matches policy `actions`\n\n### Example Flow\n\nUser with role `[\"manager\"]` calls `getTools()`:\n\n| Tool              | Cerbos Check                      | Result      |\n| ----------------- | --------------------------------- | ----------- |\n| `list_expenses`   | manager in [admin, manager, user] | ✅ Allowed  |\n| `add_expense`     | manager in [user]                 | ❌ Filtered |\n| `approve_expense` | manager in [admin, manager]       | ✅ Allowed  |\n| `delete_expense`  | manager in [admin]                | ❌ Filtered |\n\nThe manager sees only `list_expenses` and `approve_expense` in their tool list.\n\n## Error Handling\n\nWhen authorization fails at execution time, an `MCPAuthorizationError` is thrown:\n\n```typescript\nimport { MCPAuthorizationError } from \"@voltagent/core\";\n\ntry {\n  const result = await agent.generateText(\"Delete all expenses\", {\n    userId: \"user-123\",\n    context: new Map([[\"roles\", [\"user\"]]]),\n  });\n} catch (error) {\n  if (error instanceof MCPAuthorizationError) {\n    console.log(`Access denied for tool: ${error.toolName}`);\n    console.log(`Server: ${error.serverName}`);\n    console.log(`Reason: ${error.reason}`);\n  }\n}\n```\n\n## Configuration Reference\n\n### MCPAuthorizationConfig\n\n```typescript\ninterface MCPAuthorizationConfig {\n  /**\n   * Authorization function to check tool access.\n   */\n  can: MCPCanFunction;\n\n  /**\n   * Filter tools on discovery (getTools/getToolsets).\n   * When true, unauthorized tools are hidden from the tool list.\n   * @default false\n   */\n  filterOnDiscovery?: boolean;\n\n  /**\n   * Check authorization on execution (callTool).\n   * When true, each tool call is verified before execution.\n   * @default true\n   */\n  checkOnExecution?: boolean;\n}\n```\n\n## See Also\n\n- [MCP Configuration](./mcp.md) - Basic MCP setup\n- [MCP Server](./mcp-server.md) - Building MCP servers\n- [Cerbos Example](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos) - Full Cerbos integration example\n","website/docs/agents/mcp/mcp-server.md":"---\ntitle: MCP Server\ndescription: Quick start guide for exposing VoltAgent over the Model Context Protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent MCP Server\n\nFollow these steps to expose your VoltAgent project over MCP so you can browse tools, prompts, and resources from VoltOps Console (`https://console.voltagent.dev/mcp`) or an MCP-compatible IDE.\n\n## 1. Install the MCP server package\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/mcp-server\n    ```\n  </TabItem>\n</Tabs>\n\n## 2. Create an MCP server instance\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP example\",\n});\n```\n\n> 📘 **Tip:** When you expose agents through MCP, their `purpose` field becomes the tool description shown in clients. Keep it short and user-facing (fallback is the agent instructions if `purpose` is empty).\n\nThis minimal configuration:\n\n- Names the server `voltagent-example` (used in URLs and IDE listings).\n- Registers server metadata so clients can discover it even before you add tools/agents.\n- Enables all transports (`stdio`, `http`, `sse`) by default. Override `protocols` to disable transports you do not need.\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  protocols: {\n    stdio: true,\n    http: false,\n    sse: false,\n  },\n});\n```\n\nThe snippet above shows how to run in stdio-only mode (ideal for IDE integrations or command-line tooling).\n\n## 3. Register the server with VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { mcpServer } from \"./mcp/server\";\nimport { assistant } from \"./agents/assistant\";\nimport { expenseApprovalWorkflow } from \"./workflows/expense\";\nimport { logger } from \"./logger\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  workflows: {\n    expenseApprovalWorkflow,\n  },\n  mcpServers: {\n    mcpServer,\n  },\n  server: honoServer({ port: 3141 }),\n  logger,\n});\n```\n\n- `mcpServers` registers your MCP server alongside the agent/workflow registries.\n- Passing `server: honoServer(...)` makes VoltAgent expose MCP HTTP and SSE routes automatically. With the server running, visit [`https://console.voltagent.dev/mcp`](https://console.voltagent.dev/mcp) to browse, invoke, and debug tools.\n\n## 4. Find the MCP endpoint (HTTP + SSE)\n\nWhen you start the VoltAgent server, MCP transport endpoints are exposed per server ID:\n\n- **Streamable HTTP**: `POST /mcp/{serverId}/mcp`\n- **SSE** (if enabled): `GET /mcp/{serverId}/sse` and `POST /mcp/{serverId}/messages`\n\nThe `{serverId}` is a normalized version of the `name` (lowercased, spaces replaced) and may get a numeric suffix if there are duplicates. You can always discover it via the registry endpoint:\n\n```bash\ncurl http://localhost:3141/mcp/servers\n```\n\nExample response (trimmed):\n\n```json\n{\n  \"servers\": [\n    {\n      \"id\": \"voltagent-example\",\n      \"name\": \"voltagent-example\",\n      \"version\": \"0.1.0\"\n    }\n  ]\n}\n```\n\nOnce you have the `id`, the HTTP MCP URL becomes:\n\n```\nhttp://localhost:3141/mcp/voltagent-example/mcp\n```\n\n## 5. Connect via stdio (local process)\n\nSTDIO does not use an HTTP URL. The MCP client spawns your server process and communicates over stdin/stdout. In the startup log you should see a line like:\n\n```\nSTDIO  uses stdin/stdout. Example client: { type: \"stdio\", command: \"node\", args: [\"dist/index.js\"] }\n```\n\nClient example (MCPClient):\n\n```ts\nimport { MCPClient } from \"@voltagent/core\";\n\nconst client = new MCPClient({\n  clientInfo: { name: \"local-client\", version: \"0.1.0\" },\n  server: {\n    type: \"stdio\",\n    command: \"node\",\n    args: [\"dist/index.js\"],\n    cwd: process.cwd(),\n    env: { ...process.env },\n  },\n});\n\nawait client.connect();\nconst tools = await client.listTools();\n```\n\nReplace `command`/`args` with the entry that starts your VoltAgent + MCP server (for example `pnpm start -- --stdio` or `node dist/index.js` after build).\n\n## 6. Connect via HTTP\n\nUse the MCP URL in `MCPConfiguration`:\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    local: {\n      type: \"http\",\n      url: \"http://localhost:3141/mcp/voltagent-example/mcp\",\n    },\n  },\n});\n\nconst tools = await mcpConfig.getTools();\n```\n\n## 7. Optional: add MCP-only entries\n\nSometimes you want MCP clients to see helpers that are not (yet) registered with VoltAgent. Provide them as keyed objects (just like the main `VoltAgent` config) via the `agents`, `workflows`, or `tools` fields to append entries that live only on the MCP side:\n\n```ts title=\"src/mcp/server.ts\"\nimport { Agent, createTool, createWorkflowChain } from \"@voltagent/core\";\nimport { MCPServer } from \"@voltagent/mcp-server\";\nimport { z } from \"zod\";\n\nconst statusTool = createTool({\n  name: \"status\",\n  description: \"Return the current time\",\n  parameters: z.object({}),\n  async execute() {\n    return { status: \"ok\", time: new Date().toISOString() };\n  },\n});\n\nconst supportAgent = new Agent({\n  name: \"Support Agent\",\n  purpose: \"Route customer tickets to the correct queue.\",\n  instructions:\n    \"Use internal knowledge to triage customer tickets and respond with routing guidance.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [statusTool],\n});\n\nconst incidentWorkflow = createWorkflowChain({\n  id: \"incident-triage\",\n  name: \"Incident Triage\",\n  purpose: \"Placeholder entry for an external workflow.\",\n  input: z.object({ ticketId: z.string() }),\n}).andThen({\n  id: \"acknowledge\",\n  execute: async ({ data }) => ({ ...data, acknowledged: true }),\n});\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  tools: {\n    statusTool,\n  },\n  agents: {\n    supportAgent,\n  },\n  workflows: {\n    incidentWorkflow,\n  },\n});\n```\n\nThese configured entries behave like regular VoltAgent components for MCP clients, they appear in listings and can be invoked-yet they are not registered with the main `VoltAgent` instance.\n\n## 8. Optional: filter exposed agents/workflows/tools\n\nBy default, every agent, workflow, and tool registered with `VoltAgent` is visible to MCP clients. Provide filter functions when you need to hide or reorder the registry output for a specific transport:\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n  filterWorkflows: ({ items }) => items,\n  filterTools: ({ items }) => items,\n});\n```\n\nFilters receive the list of components sourced from the VoltAgent registries (plus any configured additions) and must return the array you want to expose. They are intended for pruning or sorting, use the `agents`/`workflows`/`tools` fields when you need to introduce brand-new entries.\n\n## 9. Optional: stream prompts and resources\n\nMCP clients can ask a server for structured prompt templates (`prompts/list`, `prompts/get`) and arbitrary resources (`resources/list`, `resources/read`). VoltAgent lets you seed static content and/or forward to a dynamic source via the new `adapters` field:\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\n// Replace these placeholders with your own services or data sources\nconst voltOps = {\n  prompts: {\n    list: async () => [\n      {\n        name: \"triage\",\n        description: \"Short ticket triage message\",\n        arguments: [],\n      },\n    ],\n    get: async (name: string, version?: string) => ({\n      description: `Prompt ${name}`,\n      messages: [{ role: \"user\", content: { type: \"text\", text: \"Summarise ticket {{id}}\" } }],\n      version,\n    }),\n  },\n};\n\nconst knowledgeBase = {\n  list: async () => [\n    {\n      uri: \"volt://docs/runbook\",\n      name: \"On-call runbook\",\n      description: \"Operational checklist\",\n      mimeType: \"text/markdown\",\n    },\n  ],\n  read: async (uri: string) => ({\n    uri,\n    mimeType: \"text/markdown\",\n    text: \"# On-call runbook\\n...\",\n  }),\n};\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP with prompts/resources\",\n  adapters: {\n    prompts: {\n      listPrompts: async () => voltOps.prompts.list(),\n      getPrompt: async (params) => voltOps.prompts.get(params.name, params.version),\n    },\n    resources: {\n      listResources: async () => knowledgeBase.list(),\n      readResource: async (uri) => knowledgeBase.read(uri),\n    },\n  },\n});\n```\n\n- The `adapters` block forwards MCP requests to any backend (VoltOps Prompt Manager, a documentation service, your own REST API).\n- When you change external data, call `await mcpServer.notifyPromptListChanged()` or `await mcpServer.notifyResourceListChanged()` so connected IDEs receive the standard `list_changed` notifications. If you update a specific resource at runtime, invoke `await mcpServer.notifyResourceUpdated(\"volt://docs/runbook\")` to push an incremental update only to subscribers.\n- If an adapter provides a `sendRequest` method, MCP clients can make `elicitation/create` calls. VoltAgent forwards these requests to the adapter so you can collect data from the user and return an `ElicitResult`. Tools can consume the bridge via `operationContext.elicitation`:\n\n```ts\nconst confirmAction = createTool({\n  name: \"confirm-action\",\n  description: \"Ask the operator to approve a risky step\",\n  parameters: z.object({ description: z.string() }),\n  async execute(args, operationContext) {\n    const handler = operationContext?.elicitation;\n\n    if (!handler) {\n      throw new Error(\"Elicitation bridge unavailable\");\n    }\n\n    return handler({\n      schema: {\n        type: \"object\",\n        properties: {\n          confirmed: { type: \"boolean\" },\n        },\n        required: [\"confirmed\"],\n      },\n      message: `Approve the following action: ${args.description}`,\n    });\n  },\n});\n```\n\nThat’s it! Your VoltAgent stack now speaks MCP. Start the agent, open VoltOps Console (or your preferred MCP client), and you’ll see the server listed with its tools, prompts, resources, and workflows ready to debug.\n","website/docs/agents/mcp/mcp.md":"---\ntitle: MCP Client\nslug: /agents/mcp\n---\n\n# Model Context Protocol (MCP)\n\nThe [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is a standardized protocol for AI agents to interact with external tools and services. VoltAgent implements MCP client capabilities, enabling agents to access functionalities like filesystem operations, browser automation, database interactions, and external AI models.\n\n## Transport Types\n\nVoltAgent supports four transport types for MCP connections:\n\n- **`http`**: Attempts streamable HTTP first, automatically falls back to SSE if not supported\n- **`streamable-http`**: Direct streamable HTTP transport (no fallback)\n- **`sse`**: Server-Sent Events transport\n- **`stdio`**: Standard input/output for local processes\n\n## Getting Started with MCPConfiguration\n\n`MCPConfiguration` manages connections to MCP servers and provides their tools to your agents.\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\nimport path from \"node:path\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    // HTTP with automatic fallback\n    github: {\n      type: \"http\",\n      url: \"https://api.githubcopilot.com/mcp\",\n      timeout: 15000, // Optional, default: 30000ms\n    },\n\n    // Streamable HTTP (no fallback)\n    reddit: {\n      type: \"streamable-http\",\n      url: \"https://mcp.composio.dev/reddit/your-api-key-here\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      sessionId: \"optional-session-id\", // Optional\n      timeout: 20000,\n    },\n\n    // SSE transport\n    linear: {\n      type: \"sse\",\n      url: \"https://mcp.linear.app/sse\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      eventSourceInit: { withCredentials: true },\n      timeout: 25000,\n    },\n\n    // stdio for local processes\n    filesystem: {\n      type: \"stdio\",\n      command: \"npx\",\n      args: [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        path.join(process.env.HOME || \"\", \"Desktop\"),\n      ],\n      cwd: process.env.HOME,\n      env: { NODE_ENV: \"production\" },\n      timeout: 10000,\n    },\n  },\n});\n```\n\n## Working with MCP Tools\n\nRetrieve tools from configured MCP servers as standard `Tool` objects compatible with VoltAgent agents.\n\n### Get All Tools as Flat Array\n\n`getTools()` returns all tools from all servers in a single array:\n\n```ts\nconst allTools = await mcpConfig.getTools();\n\n// allTools is Tool<any>[]\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n### Get Tools Organized by Server\n\n`getToolsets()` returns tools grouped by server name:\n\n```ts\nconst toolsets = await mcpConfig.getToolsets();\n\n// toolsets.filesystem is a ToolsetWithTools object\nconst filesystemTools = toolsets.filesystem.getTools();\n\nconst response = await agent.generateText(\"List all files in my Desktop folder\", {\n  userId: \"user123\",\n  tools: filesystemTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Event Handling\n\nAccess individual clients to listen for events:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\nif (clients.reddit) {\n  clients.reddit.on(\"connect\", () => {\n    console.log(\"Connected to Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"disconnect\", () => {\n    console.log(\"Disconnected from Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"error\", (error) => {\n    console.error(\"Reddit MCP error:\", error.message);\n  });\n\n  clients.reddit.on(\"toolCall\", (name, args, result) => {\n    console.log(`Tool ${name} called with:`, args);\n  });\n}\n```\n\nAvailable events: `connect`, `disconnect`, `error`, `toolCall`\n\n## Cleanup\n\nDisconnect clients when done to terminate processes and free resources:\n\n```ts\nawait mcpConfig.disconnect();\n```\n\nThis is especially important for `stdio` servers, which spawn child processes.\n\n## Adding MCP Tools to an Agent\n\nAdd MCP tools at initialization or per request:\n\n### At Agent Initialization\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst allTools = await mcpConfig.getTools();\n\nconst agent = new Agent({\n  name: \"MCP Agent\",\n  instructions: \"You can use MCP tools to access external systems\",\n  model: \"openai/gpt-4o\",\n  tools: allTools,\n});\n\nawait agent.generateText(\"List files in Desktop\", { userId: \"user123\" });\nawait mcpConfig.disconnect();\n```\n\n### At Request Time\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: \"You are a helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst allTools = await mcpConfig.getTools();\n\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Error Handling\n\nHandle failures during connection and tool execution:\n\n```ts\n// Connection errors\ntry {\n  const tools = await mcpConfig.getTools();\n} catch (error) {\n  console.error(\"Failed to fetch MCP tools:\", error);\n}\n\n// Client-specific errors via events\nconst clients = await mcpConfig.getClients();\nclients.filesystem?.on(\"error\", (error) => {\n  console.error(\"Filesystem MCP error:\", error);\n});\n\n// Tool execution errors\ntry {\n  const response = await agent.generateText(\"List files\", {\n    userId: \"user123\",\n    tools: await mcpConfig.getTools(),\n  });\n} catch (error) {\n  console.error(\"Agent execution error:\", error);\n}\n```\n\n### HTTP Fallback Behavior\n\nWhen using `type: \"http\"`, the client attempts streamable HTTP first. If it fails, it automatically creates a new SSE transport and retries the connection.\n\n## User Input Handling (Elicitation)\n\nSome MCP servers may request user input during tool execution. For example, a server might ask for confirmation before performing a destructive action. VoltAgent provides a dynamic API to handle these elicitation requests.\n\n### Setting Up an Elicitation Handler\n\nUse the `elicitation` bridge on individual clients to register handlers:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\n// Set a persistent handler\nclients.myServer.elicitation.setHandler(async (request) => {\n  console.log(\"Server asks:\", request.message);\n  console.log(\"Expected schema:\", request.requestedSchema);\n\n  // Collect user input (e.g., via CLI prompt or UI form)\n  const userConfirmed = await askUserForConfirmation(request.message);\n\n  return {\n    action: userConfirmed ? \"accept\" : \"decline\",\n    content: userConfirmed ? { confirmed: true } : undefined,\n  };\n});\n```\n\nThe handler receives a request object with:\n\n- `message`: Human-readable message from the server\n- `requestedSchema`: JSON schema describing the expected response format\n\nThe handler must return an object with:\n\n- `action`: One of `\"accept\"`, `\"decline\"`, or `\"cancel\"`\n- `content`: Optional data matching the requested schema (when accepting)\n\n### One-Time Handler\n\nHandle only the next elicitation request, then auto-remove:\n\n```ts\nclients.myServer.elicitation.once(async (request) => {\n  return { action: \"accept\", content: { approved: true } };\n});\n```\n\n### Removing the Handler\n\n```ts\nclients.myServer.elicitation.removeHandler();\n```\n\nWhen no handler is registered, elicitation requests are automatically cancelled.\n\n### Checking Handler Status\n\n```ts\nif (clients.myServer.elicitation.hasHandler) {\n  console.log(\"Handler is registered\");\n} else {\n  console.log(\"No handler - requests will be cancelled\");\n}\n```\n\n### Method Chaining\n\nAll elicitation methods support chaining:\n\n```ts\nclients.myServer.elicitation.setHandler(myHandler).removeHandler().setHandler(anotherHandler);\n```\n\n### Agent-Level Elicitation\n\nInstead of setting handlers on individual clients, you can pass an elicitation handler directly to `generateText` or `streamText`. This handler applies to all MCP tools during the request:\n\n```ts\nconst response = await agent.generateText(\"Perform action with MCP tool\", {\n  userId: \"user123\",\n  elicitation: async (request) => {\n    console.log(\"Server asks:\", request.message);\n\n    // Collect user input (CLI prompt, UI form, etc.)\n    const confirmed = await askUser(request.message);\n\n    return {\n      action: confirmed ? \"accept\" : \"decline\",\n      content: confirmed ? { confirmed: true } : undefined,\n    };\n  },\n});\n```\n\nThis approach is useful when:\n\n- You want request-scoped handlers that don't persist\n- You need access to request context (userId, conversationId) in your handler\n- You prefer not to manage client-level handlers manually\n\n## Lifecycle\n\n1. Create `MCPConfiguration` with server definitions\n2. Fetch tools with `getTools()` or `getToolsets()`\n3. Pass tools to agent (initialization or per request)\n4. Set up elicitation handlers if needed (optional)\n5. Use agent methods (`generateText`, `streamText`, etc.)\n6. Monitor with `getClients()` and event listeners (optional)\n7. Disconnect with `disconnect()`\n","website/docs/agents/memory.md":"---\ntitle: Memory\nslug: /agents/memory\n---\n\n# Agent Memory\n\nVoltAgent's `Memory` class stores conversation history and enables agents to maintain context across interactions. Supports persistent storage, semantic search, and working memory.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n- **Conversation Storage** - Messages stored per `userId` and `conversationId`\n- **Semantic Search** - Retrieve past messages by similarity (requires embedding + vector adapters)\n- **Working Memory** - Compact context storage (Markdown template, JSON schema, or free-form)\n- **Workflow State** - Suspendable workflow checkpoint storage\n\n## Quick Start\n\n```typescript\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"my-app-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// First message\nawait agent.generateText(\"My name is Sarah\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n\n// Agent remembers context\nawait agent.generateText(\"What's my name?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n```\n\n## Complete Documentation\n\nFor detailed configuration, provider setup, and advanced features:\n\n- **[Memory Overview](./memory/overview.md)** - Full memory system documentation\n- **[Memory API Endpoints](../api/endpoints/memory.md)** - HTTP endpoints for conversations and messages\n- **[Managed Memory](./memory/managed-memory.md)** - Production-ready hosted storage\n- **[Semantic Search](./memory/semantic-search.md)** - Vector-based message retrieval\n- **[Working Memory](./memory/working-memory.md)** - Compact context management\n- **[Storage Adapters](./memory/in-memory.md)** - Provider-specific guides\n","website/docs/agents/memory/cloudflare-d1.md":"---\ntitle: Cloudflare D1 Memory\nslug: /agents/memory/cloudflare-d1\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Cloudflare D1 Memory\n\n`D1MemoryAdapter` stores conversations in Cloudflare D1 for Workers deployments.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n</Tabs>\n\n## Cloudflare D1 binding\n\nAdd a D1 binding to your `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"voltagent\"\ndatabase_id = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n```\n\n## Configuration (Workers)\n\n```ts\nimport { Agent, Memory, VoltAgent } from \"@voltagent/core\";\nimport { D1MemoryAdapter } from \"@voltagent/cloudflare-d1\";\nimport { serverlessHono } from \"@voltagent/serverless-hono\";\nimport type { D1Database } from \"@cloudflare/workers-types\";\n\nimport { weatherTool } from \"./tools\";\n\ntype Env = {\n  DB: D1Database;\n  OPENAI_API_KEY: string;\n};\n\nconst createWorker = (env: Env) => {\n  const memory = new Memory({\n    storage: new D1MemoryAdapter({\n      binding: env.DB,\n      tablePrefix: \"voltagent_memory\",\n    }),\n  });\n\n  const agent = new Agent({\n    name: \"Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    tools: [weatherTool],\n    memory,\n  });\n\n  const voltAgent = new VoltAgent({\n    agents: { agent },\n    serverless: serverlessHono(),\n  });\n\n  return voltAgent.serverless().toCloudflareWorker();\n};\n\nlet cached: ReturnType<typeof createWorker> | undefined;\n\nexport default {\n  fetch: (request: Request, env: Env, ctx: ExecutionContext) => {\n    if (!cached) {\n      cached = createWorker(env);\n    }\n\n    return cached.fetch(request, env, ctx);\n  },\n};\n```\n\n### Configuration Options\n\n| Option         | Type         | Description                                              |\n| -------------- | ------------ | -------------------------------------------------------- |\n| `binding`      | `D1Database` | Worker D1 binding (required)                             |\n| `tablePrefix`  | `string`     | Table name prefix (default: `voltagent_memory`)          |\n| `maxRetries`   | `number`     | Retry count for busy/locked DB operations (default: `3`) |\n| `retryDelayMs` | `number`     | Initial backoff delay in ms (default: `100`)             |\n| `debug`        | `boolean`    | Enable debug logging (default: `false`)                  |\n| `logger`       | `Logger`     | Optional logger for structured logging                   |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n- `${tablePrefix}_steps`\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new D1MemoryAdapter({ binding: env.DB }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Optional)\n\nD1 provides storage only. To enable semantic search, pair it with an embedding model string (for example, `openai/text-embedding-3-small`) and a vector adapter such as `InMemoryVectorAdapter`.\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n## Learn More\n\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","website/docs/agents/memory/in-memory.md":"---\ntitle: In-Memory Storage\nslug: /agents/memory/in-memory\n---\n\n# In-Memory Storage\n\n`InMemoryStorageAdapter` stores conversation history in application memory. Data is lost when the application restarts.\n\n## Default Behavior\n\nAgents use in-memory storage by default when no `memory` option is provided:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Uses InMemoryStorageAdapter automatically\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users with questions.\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\nOmitting `memory` does **not** disable memory. To make an agent stateless, set `memory: false`.\n\n## Explicit Configuration\n\nConfigure storage limits explicitly:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n## Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved in memory\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Development)\n\nCombine with `InMemoryVectorAdapter` for semantic search during development:\n\n```ts\nimport { Memory, InMemoryVectorAdapter, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n});\n```\n\nBoth storage and vectors are lost on restart. For persistent vectors, use `LibSQLVectorAdapter`.\n\n## Use Cases\n\n### Development & Testing\n\nTest agent logic without database setup:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst testAgent = new Agent({\n  name: \"Test Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new InMemoryStorageAdapter(),\n  }),\n});\n\n// Test conversations without persistence\nawait testAgent.generateText(\"Test message\", {\n  memory: {\n    userId: \"test-user\",\n    conversationId: \"test-conversation\",\n  },\n});\n```\n\n### Stateless Deployments\n\nServerless functions or ephemeral containers where persistence isn't needed:\n\n```ts\n// Cloud function handler\nexport async function handler(event) {\n  const agent = new Agent({\n    name: \"Serverless Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    // Default in-memory storage\n  });\n\n  return await agent.generateText(event.message, {\n    memory: {\n      userId: event.userId,\n      conversationId: event.sessionId,\n    },\n  });\n}\n```\n\n### Demos & Examples\n\nQuick prototypes without infrastructure dependencies.\n\n## Limitations\n\n- **No persistence** - All data lost on restart\n- **Memory usage** - Large message counts consume application memory\n- **Not for production** - Use persistent adapters for production applications\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n","website/docs/agents/memory/libsql.md":"---\ntitle: LibSQL / SQLite Memory\nslug: /agents/memory/libsql\n---\n\n# LibSQL / SQLite Memory\n\n`LibSQLMemoryAdapter` stores conversations in LibSQL, supporting local SQLite files, remote instances, and self-hosted `sqld`.\n\n## Installation\n\n```bash\nnpm install @voltagent/libsql\n```\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\n// Local SQLite\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"file:./.voltagent/memory.db\", // or \":memory:\" for ephemeral\n  }),\n});\n\n// Remote instance\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"libsql://your-database.example.io\",\n    authToken: process.env.LIBSQL_AUTH_TOKEN,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option        | Type     | Description                                                |\n| ------------- | -------- | ---------------------------------------------------------- |\n| `url`         | `string` | Connection URL (`file:<path>`, `libsql://`, or `:memory:`) |\n| `authToken`   | `string` | Auth token for remote instances (optional for local files) |\n| `tablePrefix` | `string` | Table name prefix (default: `voltagent_memory`)            |\n| `logger`      | `Logger` | Optional logger for debugging                              |\n\n### URL Formats\n\n- **Local SQLite**: `file:./.voltagent/memory.db` or `file:memory.db`\n- **In-memory (testing)**: `:memory:` or `file::memory:`\n- **Remote instance** (e.g., Turso): `libsql://your-database.example.io`\n- **Self-hosted sqld**: `libsql://your-server.com` or `https://your-server.com`\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nSchema migrations run automatically when updating VoltAgent versions.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    template: `# Context\\n- Key facts:\\n`,\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Vector Storage (Optional)\n\nUse `LibSQLVectorAdapter` for persistent vector storage:\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n```\n\n**Vector adapter options:**\n\n- Local file: Same URL as storage adapter\n- In-memory: `:memory:` (vectors lost on restart)\n- Turso: Same URL + auth token as storage adapter\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n**Use cases:**\n\n- Local development with SQLite files\n- Edge deployments with remote instances\n- Serverless environments\n- Simple deployments without separate database servers\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Examples\n\n### Development with Local SQLite\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Dev Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: \"file:./.voltagent/dev-memory.db\",\n    }),\n  }),\n});\n```\n\n### Remote Instance\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: process.env.LIBSQL_DATABASE_URL!,\n      authToken: process.env.LIBSQL_AUTH_TOKEN!,\n    }),\n  }),\n});\n```\n\n### Testing with In-Memory Database\n\n```ts\nconst testMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \":memory:\", // ephemeral\n  }),\n});\n```\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search with LibSQLVectorAdapter\n","website/docs/agents/memory/managed-memory.md":"---\ntitle: Managed Memory\nslug: /agents/memory/managed-memory\n---\n\n# Managed Memory\n\nVoltOps Managed Memory is a production-ready hosted memory service for VoltAgent. Create a database through the VoltOps Console and connect using API credentials - no infrastructure provisioning or schema management required.\n\n## Availability\n\n- **US Region**: Virginia (us-east-1)\n- **EU Region**: Germany (eu-central-1)\n\n## Installation\n\n```bash\nnpm install @voltagent/voltagent-memory\n```\n\n## Configuration\n\n### Automatic Setup (Recommended)\n\nGet your credentials from [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory) and set environment variables:\n\n<video controls width=\"100%\">\n  <source src=\"https://cdn.voltagent.dev/docs/voltagent-managed-memory.mp4\" type=\"video/mp4\" />\n  Your browser does not support the video tag.\n</video>\n\n```bash\n# .env\nVOLTAGENT_PUBLIC_KEY=pk_...\nVOLTAGENT_SECRET_KEY=sk_...\n```\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\n// Adapter automatically uses VoltOps credentials from environment\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nThe adapter checks `AgentRegistry` for a global `VoltOpsClient` instance configured from environment variables. This is the simplest setup - no need to instantiate `VoltOpsClient` manually.\n\n### Manual Setup\n\nPass a `VoltOpsClient` instance explicitly:\n\n```ts\nimport { Agent, Memory, VoltOpsClient } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY!,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY!,\n  // baseUrl: \"https://api.voltagent.dev\", // optional\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient, // explicit client\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nUse manual setup when:\n\n- Running multiple adapters with different credentials\n- Dynamically changing credentials at runtime\n- Testing with mock clients\n\n### Configuration Options\n\n| Option          | Type            | Required | Description                                                      |\n| --------------- | --------------- | -------- | ---------------------------------------------------------------- |\n| `databaseName`  | `string`        | Yes\\*    | Database name from VoltOps Console                               |\n| `databaseId`    | `string`        | Yes\\*    | Database ID from VoltOps API (alternative to `databaseName`)     |\n| `voltOpsClient` | `VoltOpsClient` | No       | Explicit VoltOps client (optional if credentials in environment) |\n| `debug`         | `boolean`       | No       | Enable debug logging (default: `false`)                          |\n\n\\*Either `databaseName` or `databaseId` is required.\n\n## Creating a Database\n\n1. Navigate to [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory)\n2. Click **Create Database**\n3. Enter a name and select region (US or EU)\n4. Copy the database credentials\n\nTo get your VoltOps API keys, go to **Settings** in the console to find your public and secret keys.\n\n## Features\n\n### Conversation Storage\n\nAll `StorageAdapter` methods are supported:\n\n- Message persistence (`addMessage`, `addMessages`, `getMessages`, `clearMessages`)\n- Conversation management (`createConversation`, `getConversation`, `updateConversation`, `deleteConversation`)\n- Working memory (`getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`)\n- Workflow state (`getWorkflowState`, `setWorkflowState`, `updateWorkflowState`)\n\n### Vector Storage (Optional)\n\nEnable semantic search with `ManagedMemoryVectorAdapter`:\n\n```ts\nimport { ManagedMemoryAdapter, ManagedMemoryVectorAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new ManagedMemoryVectorAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n```\n\nBoth adapters resolve credentials the same way (environment or explicit client). See [Semantic Search](./semantic-search.md) for usage.\n\n## Migration from Self-Hosted\n\n### Export from LibSQL\n\n```ts\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst localAdapter = new LibSQLMemoryAdapter({\n  url: \"file:./.voltagent/memory.db\",\n});\n\n// Export conversations for a user\nconst conversations = await localAdapter.getConversationsByUserId(\"user-123\");\n\nfor (const conv of conversations) {\n  const messages = await localAdapter.getMessages(\"user-123\", conv.id);\n  console.log(`Conversation ${conv.id}: ${messages.length} messages`);\n}\n```\n\n### Import to Managed Memory\n\n```ts\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst managedMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\n// Import conversation\nawait managedMemory.createConversation({\n  id: conv.id,\n  userId: conv.userId,\n  resourceId: conv.resourceId,\n  title: conv.title,\n  metadata: conv.metadata,\n});\n\n// Import messages\nawait managedMemory.addMessages(messages, conv.userId, conv.id);\n```\n\nBulk import/export APIs are planned for future releases.\n\n## Use Cases\n\n### Development & Prototyping\n\n```ts\n// No database setup required for pilots\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"dev-memory\",\n  }),\n});\n```\n\n### Multi-Region Deployment\n\n```ts\n// US database for North American users\nconst usMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-us\",\n  }),\n});\n\n// EU database for European users\nconst euMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-eu\",\n  }),\n});\n```\n\n### Team Collaboration\n\nMultiple developers connect to the same managed database using shared VoltOps credentials. Create separate databases for staging and production.\n\n## Limitations\n\n- **Regional latency**: Choose the region closest to your application servers\n- **Storage quotas**: Check VoltOps Console for plan-specific limits\n- **Credential rotation**: Update environment variables when rotating credentials\n\n## Comparison with Self-Hosted\n\n| Feature             | Managed Memory    | Self-Hosted               |\n| ------------------- | ----------------- | ------------------------- |\n| Setup time          | < 3 minutes       | Hours                     |\n| Schema management   | Automatic         | Manual migrations         |\n| Regional hosting    | US & EU           | DIY                       |\n| Credential rotation | API-based         | Manual                    |\n| Monitoring          | Console dashboard | DIY                       |\n| Cost                | Usage-based       | Infrastructure + ops time |\n\nProduction-ready for teams without database infrastructure or during rapid deployment.\n\n## Troubleshooting\n\n### Adapter initialization failed\n\n```\nError: Unable to locate managed memory database\n```\n\n**Solution**: Verify `databaseName` matches the name in VoltOps Console, or use `databaseId` instead.\n\n### VoltOps client not available\n\n```\nError: VoltOps client is not available for managed memory initialization\n```\n\n**Solution**: Ensure `VOLTAGENT_PUBLIC_KEY` and `VOLTAGENT_SECRET_KEY` environment variables are set, or pass `voltOpsClient` explicitly.\n\n### Enable debug logging\n\n```ts\nconst adapter = new ManagedMemoryAdapter({\n  databaseName: \"production-memory\",\n  debug: true, // Logs all API calls and metadata\n});\n```\n\n## Learn More\n\n- **[Semantic Search](./semantic-search.md)** - Enable vector search with managed memory\n- **[Working Memory](./working-memory.md)** - Configure working memory with managed storage\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres alternative\n","website/docs/agents/memory/overview.md":"---\ntitle: Overview\nslug: /agents/memory/overview\n---\n\n# Memory Overview\n\nVoltAgent's `Memory` class stores conversation history and optional semantic search vectors. Agents retrieve past messages before generating responses and persist new interactions after completion.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Cloudflare D1**  | `@voltagent/cloudflare-d1`    | Cloudflare D1 (SQLite) | Cloudflare Workers deployments   |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- Auto-creates conversations on first message\n- Configurable message limits (oldest pruned first)\n\n### Conversation Titles (Optional)\n\nWhen enabled, VoltAgent generates a concise title from the first user message. Title generation runs only when the conversation is created and does not overwrite existing titles.\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: true,\n});\n```\n\nCustom configuration:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: {\n    enabled: true,\n    model: \"gpt-4o-mini\", // default agent model\n    systemPrompt: \"Generate a short title (max 6 words).\",\n    temperature: null, // optional; default is 0, use null to omit\n    maxLength: 60,\n    maxOutputTokens: 128, // optional; default is 32\n  },\n});\n```\n\nNotes:\n\n- The agent's main model is used unless `generateTitle.model` is provided.\n- `generateTitle.model` accepts either a provider/model string or an AI SDK model instance.\n- Title generation sends `temperature: 0` by default. Set `generateTitle.temperature` to `null` to\n  omit the parameter for reasoning models such as OpenAI `o`-series and `gpt-5-mini`.\n- Reasoning models may need a higher `maxOutputTokens` value because reasoning tokens count toward\n  the output budget. Configure `generateTitle.maxOutputTokens` if the default `32` is too low.\n- If title generation fails, VoltAgent keeps the default conversation title and logs the cause at\n  `warn` level, including a hint to set `generateTitle.temperature` to `null` when appropriate.\n- Only the first user message is summarized.\n- If you create conversations manually via the Memory API, set `title` explicitly.\n\n### Conversation Steps\n\n- Every LLM/text/tool step can be recorded with metadata (operationId, agent/sub-agent IDs, usage, tool arguments/results).\n- VoltOps Observability consumes these records to render the Memory Explorer “Steps” tab and to correlate traces/logs with memory.\n- Adapters that implement the step APIs persist sub-agent activity alongside primary agent steps, so hierarchies stay visible.\n- If an adapter does not support steps, the console warns with “Conversation steps are not supported by this memory adapter.”\n- By default, agents checkpoint conversation progress during multi-step runs (`conversationPersistence.mode: \"step\"`).\n- Tool completion events (`tool-result`, `tool-error`) trigger immediate flushes by default (`flushOnToolResult: true`), reducing history loss risk on crashes or process interruption.\n- Use `conversationPersistence.mode: \"finish\"` if you want end-of-turn persistence only.\n\n### Semantic Search (Optional)\n\n- Requires `embedding` + `vector` adapters\n- Auto-embeds messages on save\n- Retrieves similar past messages by content, not recency\n- Merges semantic results with recent messages\n\n### Working Memory (Optional)\n\n- Stores compact context across conversation turns\n- Three formats: Markdown template, JSON schema (Zod), or free-form\n- Two scopes: `conversation` (default) or `user`\n- Agent exposes tools: `get_working_memory`, `update_working_memory`, `clear_working_memory`\n- If `memory.options.readOnly: true` is set per call, only `get_working_memory` is exposed and memory writes are skipped\n\n### Workflow State\n\n- Suspendable workflow checkpoint storage\n- Tracks execution state, context, suspension metadata\n\n## Agent Configuration\n\nAgents accept a `memory` option:\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\n\n// Default when omitted: built-in in-memory storage\nconst agent1 = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  // memory omitted (or undefined)\n});\n\n// Disable memory entirely\nconst agent2 = new Agent({\n  name: \"Stateless\",\n  model: \"openai/gpt-4o-mini\",\n  memory: false,\n});\n\n// Persistent storage\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent3 = new Agent({\n  name: \"Persistent\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  }),\n});\n```\n\n**`memory` option behavior**\n\n| Value                 | Behavior                                                                |\n| --------------------- | ----------------------------------------------------------------------- |\n| Omitted / `undefined` | Conversation memory is enabled with a built-in `InMemoryStorageAdapter` |\n| `false`               | Conversation memory is disabled                                         |\n| `new Memory({ ... })` | Conversation memory is enabled with your configured adapter             |\n\nFor stateless sub-agents, set `memory: false` on each sub-agent explicitly.\n\nWhen a supervisor delegates to sub-agents, delegated messages are tagged with sub-agent metadata so supervisor memory reads can filter sub-agent records from parent conversation context.\n\n### Global Defaults (VoltAgent)\n\nSet default memory instances once at the VoltAgent entrypoint. Defaults apply only when an agent or workflow does not specify `memory`. If nothing is configured, VoltAgent still falls back to built-in in-memory storage. An explicit `memory: false` on an agent disables memory and bypasses defaults.\n\n```ts\nimport { Memory, VoltAgent } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agentMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/agent.db\" }),\n});\n\nconst workflowMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/workflows.db\" }),\n});\n\nnew VoltAgent({\n  agentMemory,\n  workflowMemory,\n  // memory: sharedFallbackMemory,\n});\n```\n\n**Precedence**\n\n- Agents: agent `memory` > `agentMemory` > `memory` > built-in in-memory\n- Workflows: workflow `memory` > `workflowMemory` > `memory` > built-in in-memory\n\n## Usage with User and Conversation IDs\n\nProvide `memory.userId` and `memory.conversationId` in generation calls to scope memory:\n\n```ts\nconst response = await agent.generateText(\"What did we discuss yesterday?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n  },\n});\n```\n\n**Behavior:**\n\n- **Both provided**: Retrieves history for that specific thread\n- **Only userId**: New `conversationId` generated per call (fresh context each time)\n- **Neither provided**: Uses default user ID with new conversation ID\n\n## Examples\n\n### Managed Memory (Zero Setup)\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { VoltOpsClient } from \"@voltagent/core\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Semantic Search + Working Memory\n\n```ts\nimport { Agent, Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\",\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n      goals: z.array(z.string()).optional(),\n    }),\n  },\n});\n\nconst agent = new Agent({\n  name: \"Smart Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// Enable semantic search per call\nconst result = await agent.generateText(\"What preferences did I mention?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n    options: {\n      semanticMemory: {\n        enabled: true,\n        semanticLimit: 5,\n        semanticThreshold: 0.7,\n      },\n    },\n  },\n});\n```\n\n## Custom Adapters\n\nImplement the `StorageAdapter` interface to use custom databases:\n\n```ts\nimport type { StorageAdapter, UIMessage, Conversation, OperationContext } from \"@voltagent/core\";\n\nexport class MyStorageAdapter implements StorageAdapter {\n  async addMessage(\n    msg: UIMessage,\n    userId: string,\n    conversationId: string,\n    context?: OperationContext // Optional: access operation context\n  ): Promise<void> {\n    // Access user-provided context values\n    const tenantId = context?.context.get(\"tenantId\");\n\n    // Use logger and tracing\n    context?.logger.info(\"Storing message\", { userId, tenantId });\n\n    // Store message in your database\n  }\n\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: { limit?: number },\n    context?: OperationContext // Optional: access operation context\n  ): Promise<UIMessage[]> {\n    // Retrieve messages in chronological order (oldest first)\n    return [];\n  }\n\n  async createConversation(input: CreateConversationInput): Promise<Conversation> {\n    // Create conversation record\n  }\n\n  // ... implement remaining StorageAdapter methods\n}\n```\n\nRequired methods:\n\n- If you want Memory Explorer’s Steps tab (and Observability APIs) to work, make sure your adapter persists conversation steps by implementing the methods below. Otherwise VoltAgent will surface the “Conversation steps are not supported by this memory adapter.” warning.\n\n- Messages: `addMessage`, `addMessages`, `getMessages`, `clearMessages`\n- Conversations: `createConversation`, `getConversation`, `getConversations`, `getConversationsByUserId`, `queryConversations`, `updateConversation`, `deleteConversation`\n- Conversation steps: `saveConversationSteps`, `getConversationSteps`\n- Working memory: `getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`\n- Workflow state: `getWorkflowState`, `queryWorkflowRuns`, `setWorkflowState`, `updateWorkflowState`, `getSuspendedWorkflowStates`\n\nUse `queryWorkflowRuns({ workflowId?, status?, from?, to?, limit?, offset? })` for filtered/paginated listings (ordered by newest first).\n\n### Advanced: Context-Aware Adapters\n\nCustom adapters can access `OperationContext` for dynamic, per-request behavior:\n\n```ts\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log with tracing\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n    });\n\n    // Use scoped ID for data isolation\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Pass tenant per request\nawait agent.generateText(\"Query\", {\n  memory: {\n    userId: \"user-123\",\n  },\n  context: { tenantId: \"company-abc\" }, // Different tenant = different data\n});\n```\n\n**Use cases for context-aware adapters:**\n\n- **Multi-tenancy**: Isolate data by tenant using composite keys\n- **Audit logging**: Track all memory access with full context\n- **Access control**: Validate permissions before queries\n- **Dynamic behavior**: Adjust queries based on runtime values\n\nSee [Operation Context](../context.md#memory-adapters) for more examples and patterns.\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Semantic Search](./semantic-search.md)** - Retrieve messages by similarity\n- **[Working Memory](./working-memory.md)** - Maintain compact context across turns\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n- **[In-Memory Storage](./in-memory.md)** - Default ephemeral storage\n","website/docs/agents/memory/postgres.md":"---\ntitle: PostgreSQL Memory\nslug: /agents/memory/postgres\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# PostgreSQL Memory\n\n`PostgreSQLMemoryAdapter` stores conversations in PostgreSQL for production applications with existing Postgres infrastructure.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/postgres\n```\n\n  </TabItem>\n</Tabs>\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\n// Using connection string\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n    // or: \"postgresql://user:password@localhost:5432/mydb\"\n  }),\n});\n\n// Using connection object\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST || \"localhost\",\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: process.env.NODE_ENV === \"production\",\n    },\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option           | Type               | Description                                                                            |\n| ---------------- | ------------------ | -------------------------------------------------------------------------------------- |\n| `connection`     | `string \\| object` | Connection string or object with `host`, `port`, `database`, `user`, `password`, `ssl` |\n| `maxConnections` | `number`           | Connection pool size (default: `10`)                                                   |\n| `tablePrefix`    | `string`           | Table name prefix (default: `voltagent_memory`)                                        |\n| `debug`          | `boolean`          | Enable debug logging (default: `false`)                                                |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nPostgreSQL version 12 or higher recommended.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- Supports complex queries with filtering, pagination, and sorting\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // or \"conversation\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nStorage:\n\n- Conversation scope: `conversations.metadata.workingMemory`\n- User scope: `${tablePrefix}_users.metadata.workingMemory`\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### PostgreSQL Vector Storage\n\nStore vector embeddings directly in PostgreSQL for semantic search (no extensions required):\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter, PostgresVectorAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new PostgresVectorAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n});\n```\n\n**How It Works:**\n\n- **BYTEA Storage**: Vectors are stored as binary data using PostgreSQL's native `BYTEA` type\n- **In-Memory Similarity**: Cosine similarity is computed in-memory after loading vectors\n- **No Extensions**: Works with vanilla PostgreSQL 12+ without any extensions\n- **Automatic Setup**: Creates tables and indexes automatically on first use\n\n**Configuration Options:**\n\n```ts\nconst vectorAdapter = new PostgresVectorAdapter({\n  connection: process.env.DATABASE_URL!,\n\n  // Optional: customize table name (default: \"voltagent_vector\")\n  tablePrefix: \"custom_vector\",\n\n  // Optional: LRU cache size for frequently accessed vectors (default: 100)\n  cacheSize: 100,\n\n  // Optional: max vector dimensions (default: 1536)\n  maxVectorDimensions: 1536,\n});\n```\n\n**Benefits:**\n\n- **Unified Storage**: Keep messages and vectors in the same database\n- **No Extensions**: Works with any PostgreSQL instance (managed or self-hosted)\n- **Simple Setup**: No extension installation or special configuration needed\n- **Standard Tools**: Use familiar PostgreSQL monitoring and backup tools\n\n**Performance Considerations:**\n\nThis adapter loads all vectors into memory for similarity computation, which works well for:\n\n- Small to medium datasets (< 10,000 vectors)\n- Development and prototyping\n- Environments where extension installation is restricted\n\nFor large-scale production with millions of vectors, consider specialized vector databases or use `InMemoryVectorAdapter` with periodic persistence.\n\nSee [Semantic Search](./semantic-search.md) for detailed configuration and usage.\n\n## Production Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: true, // Enable SSL for production\n    },\n    maxConnections: 20, // Adjust based on load\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Production Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Advanced SSL Configuration\n\nFor databases requiring custom CA certificates or client certificates:\n\n```ts\nimport fs from \"fs\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: {\n        rejectUnauthorized: true,\n        ca: fs.readFileSync(\"/path/to/ca-certificate.crt\").toString(),\n        // Optional: client certificate authentication\n        key: fs.readFileSync(\"/path/to/client-key.key\").toString(),\n        cert: fs.readFileSync(\"/path/to/client-cert.crt\").toString(),\n      },\n    },\n    maxConnections: 20,\n  }),\n});\n```\n\nThe `ssl` option accepts either:\n\n- `boolean` - Simple SSL enable/disable\n- `ConnectionOptions` - Full TLS configuration object with support for:\n  - `ca` - Custom Certificate Authority\n  - `key` - Client private key\n  - `cert` - Client certificate\n  - `rejectUnauthorized` - Certificate validation control\n  - All other Node.js [TLS options](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)\n\n### Security\n\n- Use SSL connections in production (`ssl: true` or advanced config)\n- Store credentials and certificates in environment variables or secure secret management\n- Implement regular database backups\n- Adjust `maxConnections` based on concurrent usage\n\n**Use cases:**\n\n- Applications with existing Postgres infrastructure\n- High-availability requirements (replication, failover)\n- Complex SQL queries or analytics on conversation data\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","website/docs/agents/memory/semantic-search.md":"---\ntitle: Semantic Search\nslug: /agents/memory/semantic-search\n---\n\n# Semantic Search\n\nSemantic search retrieves past messages by similarity rather than recency. It requires an embedding adapter (text to vectors) and a vector adapter (storage and search).\n\n## Configuration\n\n```ts\nimport { Agent, Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }), // or InMemoryVectorAdapter() for dev\n  enableCache: true, // optional embedding cache\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nYou can pass an embedding config object to set adapter options:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: {\n    model: \"openai/text-embedding-3-small\",\n    normalize: true,\n  },\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n```\n\n### Available Adapters\n\n**Embedding:**\n\nMemory accepts an embedding adapter or a provider-qualified model string such as\n`\"openai/text-embedding-3-small\"`.\n\n- `AiSdkEmbeddingAdapter` - Wraps any AI SDK embedding model\n\n**Vector Storage:**\n\n- `InMemoryVectorAdapter` (`@voltagent/core`) - Development only\n- `LibSQLVectorAdapter` (`@voltagent/libsql`) - Persistent vectors in SQLite/LibSQL/Turso\n- `PostgresVectorAdapter` (`@voltagent/postgres`) - Persistent vectors in Postgres with pgvector\n- `ManagedMemoryVectorAdapter` (`@voltagent/voltagent-memory`) - VoltOps-hosted vectors\n\n## Usage\n\nEnable semantic search per generation call:\n\n```ts\nconst result = await agent.generateText(\"What pricing model did we discuss?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n    options: {\n      semanticMemory: {\n        enabled: true, // default: auto-enabled when vector support is present\n        semanticLimit: 5, // number of similar messages to retrieve\n        semanticThreshold: 0.7, // minimum similarity score (0-1)\n        mergeStrategy: \"append\", // \"prepend\" | \"append\" | \"interleave\"\n      },\n    },\n  },\n});\n```\n\n### Default Behavior\n\nWhen `embedding` and `vector` adapters are configured:\n\n- Semantic search auto-enables for calls with `memory.userId` and `memory.conversationId`\n- Default `semanticLimit`: 5 messages\n- Default `semanticThreshold`: 0.7\n- Default `mergeStrategy`: `\"append\"` (recent messages first, then similar messages)\n\n### Merge Strategies\n\n- **`append`** (default): `[recent messages] + [similar messages]` - preserves chronological order\n- **`prepend`**: `[similar messages] + [recent messages]` - emphasizes relevance\n- **`interleave`**: Alternates between similar and recent messages\n\n## How It Works\n\n### On Message Save\n\nWhen saving messages, the `Memory` class:\n\n1. Extracts text content from `UIMessage.parts`\n2. Generates embeddings via the embedding adapter\n3. Stores vectors with metadata:\n   - ID: `msg_${conversationId}_${message.id}`\n   - Metadata: `{ messageId, conversationId, userId, role, createdAt }`\n\n### On Message Retrieval\n\nWhen semantic search is enabled:\n\n1. Embed the current query\n2. Search for similar vectors using the vector adapter\n3. Retrieve matching messages by ID\n4. Merge with recent messages using the configured strategy\n5. Remove duplicates (messages in both sets)\n\n## Programmatic API\n\nDirect search without agent generation:\n\n```ts\n// Check if vectors are configured\nconst hasVectors = memory.hasVectorSupport(); // boolean\n\n// Search similar messages\nconst results = await memory.searchSimilar(\"pricing discussion\", {\n  limit: 10,\n  threshold: 0.8,\n  filter: { userId: \"user-123\", conversationId: \"thread-abc\" },\n});\n\nfor (const result of results) {\n  console.log(result.id, result.score, result.metadata);\n}\n```\n\n## Embedding Cache\n\nEnable caching to avoid re-embedding identical text:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  enableCache: true, // enable cache\n  cacheSize: 1000, // max entries (default: 1000)\n  cacheTTL: 3600000, // TTL in ms (default: 1 hour)\n});\n```\n\nThe cache stores `text → vector` mappings in memory with LRU eviction.\n\n## Vector Adapters\n\n### InMemoryVectorAdapter\n\n```ts\nimport { InMemoryVectorAdapter } from \"@voltagent/core\";\n\nconst vector = new InMemoryVectorAdapter();\n```\n\n- Uses cosine similarity\n- Supports metadata filtering\n- Lost on restart (use for development only)\n\n### LibSQLVectorAdapter\n\n```ts\nimport { LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\n// Local SQLite\nconst vector = new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" });\n\n// In-memory (testing)\nconst vector = new LibSQLVectorAdapter({ url: \":memory:\" });\n\n// Turso\nconst vector = new LibSQLVectorAdapter({\n  url: \"libsql://your-db.turso.io\",\n  authToken: process.env.TURSO_AUTH_TOKEN,\n});\n```\n\n- Stores vectors as BLOBs\n- Supports metadata filtering and thresholds\n- Persistent across restarts\n\n### ManagedMemoryVectorAdapter\n\n```ts\nimport { ManagedMemoryVectorAdapter } from \"@voltagent/voltagent-memory\";\n\nconst vector = new ManagedMemoryVectorAdapter({\n  databaseName: \"production-memory\",\n  // voltOpsClient optional (auto-resolves from environment)\n});\n```\n\n- VoltOps-hosted vectors with zero setup\n- See [Managed Memory](./managed-memory.md) for configuration\n\n## Example: Full Semantic Search Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  enableCache: true,\n});\n\nconst agent = new Agent({\n  name: \"Research Assistant\",\n  instructions: \"Help users recall past discussions and find relevant information.\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// Semantic search automatically enabled\nconst result = await agent.generateText(\n  \"What did we decide about the API authentication approach?\",\n  {\n    memory: {\n      userId: \"user-123\",\n      conversationId: \"project-alpha\",\n      options: {\n        semanticMemory: {\n          semanticLimit: 10,\n          semanticThreshold: 0.75,\n        },\n      },\n    },\n  }\n);\n```\n\n## Learn More\n\n- **[Working Memory](./working-memory.md)** - Maintain compact context across turns\n- **[Managed Memory](./managed-memory.md)** - Zero-setup vector storage\n- **[LibSQL / Turso](./libsql.md)** - Self-hosted vector storage\n","website/docs/agents/memory/supabase.md":"---\ntitle: Supabase Memory\nslug: /agents/memory/supabase\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Supabase Memory\n\n`SupabaseMemoryAdapter` stores conversations in Supabase Postgres for applications already using Supabase.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n</Tabs>\n\n## Database Setup\n\nRun this SQL in your Supabase SQL Editor (adjusts table prefix if needed):\n\n<details>\n<summary>Schema SQL (click to expand)</summary>\n\n```sql\n-- Users table (for user-scoped working memory)\nCREATE TABLE IF NOT EXISTS voltagent_memory_users (\n  id TEXT PRIMARY KEY,\n  metadata JSONB,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Conversations table\nCREATE TABLE IF NOT EXISTS voltagent_memory_conversations (\n  id TEXT PRIMARY KEY,\n  resource_id TEXT NOT NULL,\n  user_id TEXT NOT NULL,\n  title TEXT NOT NULL,\n  metadata JSONB NOT NULL,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Messages table (UIMessage format)\nCREATE TABLE IF NOT EXISTS voltagent_memory_messages (\n  conversation_id TEXT NOT NULL REFERENCES voltagent_memory_conversations(id) ON DELETE CASCADE,\n  message_id TEXT NOT NULL,\n  user_id TEXT NOT NULL,\n  role TEXT NOT NULL,\n  parts JSONB,\n  metadata JSONB,\n  format_version INTEGER DEFAULT 2,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  PRIMARY KEY (conversation_id, message_id)\n);\n\n-- Workflow states (for suspension/resume)\nCREATE TABLE IF NOT EXISTS voltagent_memory_workflow_states (\n  id TEXT PRIMARY KEY,\n  workflow_id TEXT NOT NULL,\n  workflow_name TEXT NOT NULL,\n  status TEXT NOT NULL,\n  suspension JSONB,\n  events JSONB,\n  output JSONB,\n  cancellation JSONB,\n  user_id TEXT,\n  conversation_id TEXT,\n  metadata JSONB,\n  created_at TIMESTAMPTZ NOT NULL,\n  updated_at TIMESTAMPTZ NOT NULL\n);\n\n-- Conversation steps (structured LLM/tool events)\nCREATE TABLE IF NOT EXISTS voltagent_memory_steps (\n  id TEXT PRIMARY KEY,\n  conversation_id TEXT NOT NULL REFERENCES voltagent_memory_conversations(id) ON DELETE CASCADE,\n  user_id TEXT NOT NULL,\n  agent_id TEXT NOT NULL,\n  agent_name TEXT,\n  operation_id TEXT,\n  step_index INTEGER NOT NULL,\n  type TEXT NOT NULL,\n  role TEXT NOT NULL,\n  content TEXT,\n  arguments JSONB,\n  result JSONB,\n  usage JSONB,\n  sub_agent_id TEXT,\n  sub_agent_name TEXT,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Indexes\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_conversations_user_id\n  ON voltagent_memory_conversations(user_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_conversations_resource_id\n  ON voltagent_memory_conversations(resource_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_messages_conversation_id\n  ON voltagent_memory_messages(conversation_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_messages_created_at\n  ON voltagent_memory_messages(created_at);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_workflow_states_workflow_id\n  ON voltagent_memory_workflow_states(workflow_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_workflow_states_status\n  ON voltagent_memory_workflow_states(status);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_steps_conversation\n  ON voltagent_memory_steps(conversation_id, step_index);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_steps_operation\n  ON voltagent_memory_steps(conversation_id, operation_id);\n```\n\n</details>\n\n## Credentials\n\nGet your Supabase credentials:\n\n1. Go to [Supabase Dashboard](https://app.supabase.com)\n2. Open your project\n3. Go to **Project Settings** → **API**\n4. Copy **Project URL** and **anon key**\n\nStore as environment variables: `SUPABASE_URL` and `SUPABASE_KEY`\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\n\n// Using URL and key\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n});\n\n// Using existing Supabase client\nimport { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseClient = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_KEY!);\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    client: supabaseClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option        | Type             | Description                                           |\n| ------------- | ---------------- | ----------------------------------------------------- |\n| `supabaseUrl` | `string`         | Supabase project URL (required if not using `client`) |\n| `supabaseKey` | `string`         | Supabase anon key (required if not using `client`)    |\n| `client`      | `SupabaseClient` | Existing Supabase client (alternative to URL/key)     |\n| `tableName`   | `string`         | Table name prefix (default: `voltagent_memory`)       |\n| `debug`       | `boolean`        | Enable debug logging (default: `false`)               |\n| `logger`      | `Logger`         | Optional logger for structured logging                |\n\n**Note**: Table prefix must match the SQL schema. If you use a custom `tableName`, update the SQL accordingly.\n\n## Features\n\n- Messages stored per `userId` and `conversationId`\n- Conversation steps persisted for Observability (Memory Explorer “Steps” tab, sub-agent visibility, usage metadata)\n- Supports complex queries with filtering, pagination, and sorting\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\n```ts\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md).\n\n### Semantic Search\n\n```ts\nimport { Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(), // or pgvector adapter\n});\n```\n\nSee [Semantic Search](./semantic-search.md).\n\n## Production Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\nimport { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseClient = createClient(\n  process.env.SUPABASE_URL!,\n  process.env.SUPABASE_KEY! // or service_role key for backend\n);\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    client: supabaseClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n**Security:**\n\n- Use `anon` key with Row Level Security (RLS) policies\n- Use `service_role` key only in secure backend environments\n- Store credentials in environment variables\n\n**Use cases:**\n\n- Applications already using Supabase\n- Projects leveraging Supabase Auth, Realtime, or Storage\n- Environments requiring RLS policies\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","website/docs/agents/memory/working-memory.md":"---\ntitle: Working Memory\nslug: /agents/memory/working-memory\n---\n\n# Working Memory\n\nWorking memory stores compact context across conversation turns. Unlike full message history, it tracks key facts, preferences, or goals that persist throughout interactions.\n\n## Configuration\n\nThree formats supported:\n\n1. **Markdown template** - Structured text with sections\n2. **JSON schema** - Validated structured data using Zod\n3. **Free-form** - Unstructured text\n\nScope options:\n\n- **`conversation`** (default) - Context per conversation thread\n- **`user`** - Context shared across all user conversations\n\n### Markdown Template\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // default\n    template: `\n# User Profile\n- Name:\n- Role:\n- Timezone:\n\n# Current Goals\n-\n\n# Preferences\n-\n`,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### JSON Schema\n\n```ts\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // persist across conversations\n    schema: z.object({\n      profile: z\n        .object({\n          name: z.string().optional(),\n          role: z.string().optional(),\n          timezone: z.string().optional(),\n        })\n        .optional(),\n      preferences: z.array(z.string()).optional(),\n      goals: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\n### Free-Form\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true, // no template or schema\n  },\n});\n```\n\n## Agent Integration\n\nWhen working memory is enabled, the agent:\n\n1. **Adds instructions to system prompt** - Includes current working memory content and usage guidelines\n2. **Exposes tools automatically**:\n   - `get_working_memory()` - Retrieve current content\n   - `update_working_memory(content)` - Update content (validated against schema if configured)\n   - `clear_working_memory()` - Clear content\n\nIf you run an operation with `options.memory.options.readOnly: true`, the agent only exposes\n`get_working_memory()` and skips write operations.\n\nThe agent manages working memory proactively based on conversation flow.\n\n## Update Modes\n\nTwo modes available:\n\n### Append Mode (Default)\n\nSafely merges new information with existing content:\n\n```ts\n// Existing working memory:\n// { profile: { name: \"Alice\" }, goals: [\"Learn TypeScript\"] }\n\n// Agent calls update_working_memory with:\n// { goals: [\"Build an API\"] }\n\n// Result (merged):\n// { profile: { name: \"Alice\" }, goals: [\"Learn TypeScript\", \"Build an API\"] }\n```\n\n**For JSON schemas:**\n\n- Objects: Deep merge\n- Arrays: Deduplicated concatenation\n- Primitives: Overwrite\n\n**For Markdown:**\n\n- New content appended with separator\n\n### Replace Mode\n\nOverwrites all existing content:\n\n```ts\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: { profile: { name: \"Bob\" } }, // existing data lost\n  options: { mode: \"replace\" },\n});\n```\n\nUse replace mode only when intentionally resetting all context.\n\n## Programmatic API\n\nDirect memory access without agent tools:\n\n```ts\n// Get current working memory\nconst content = await memory.getWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n});\nconsole.log(content); // string (JSON or Markdown)\n\n// Update (default: append mode)\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: { goals: [\"Complete onboarding\"] }, // object or string\n});\n\n// Update (replace mode)\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: \"Fresh context\",\n  options: { mode: \"replace\" },\n});\n\n// Clear\nawait memory.clearWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n});\n\n// Introspect configuration\nconst format = memory.getWorkingMemoryFormat(); // \"markdown\" | \"json\" | null\nconst template = memory.getWorkingMemoryTemplate(); // string | null\nconst schema = memory.getWorkingMemorySchema(); // ZodObject | null\n```\n\n## Storage Implementation\n\nWorking memory is stored differently per scope:\n\n**Conversation scope:**\n\n- Stored in `conversations.metadata.workingMemory` field\n- Isolated per conversation thread\n\n**User scope:**\n\n- Stored in `${tablePrefix}_users.metadata.workingMemory` field (adapter-specific)\n- Shared across all user conversations\n\nAll official adapters (LibSQL, Postgres, Supabase, Managed Memory) support both scopes.\n\n## Example: User-Scoped Preferences\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\nimport { z } from \"zod\";\n\nconst userPreferencesSchema = z.object({\n  name: z.string().optional(),\n  timezone: z.string().optional(),\n  communicationStyle: z.enum([\"formal\", \"casual\"]).optional(),\n  interests: z.array(z.string()).optional(),\n});\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // persist across all conversations\n    schema: userPreferencesSchema,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Personal Assistant\",\n  instructions: \"Adapt responses based on user preferences stored in working memory.\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// First conversation\nawait agent.generateText(\"I prefer casual communication and I'm into AI and music.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"conv-1\",\n  },\n});\n\n// Different conversation - agent remembers user preferences\nawait agent.generateText(\"What should I learn next?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"conv-2\", // different thread\n  },\n});\n```\n\n## Example: Conversation-Scoped Goals\n\n```ts\nconst projectMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // isolated per project\n    template: `\n# Project Context\n- Name:\n- Deadline:\n- Tech Stack:\n\n# Current Sprint\n- Goals:\n- Blockers:\n`,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Project Manager\",\n  instructions: \"Track project context and help with sprint planning.\",\n  model: \"openai/gpt-4o-mini\",\n  memory: projectMemory,\n});\n\n// Each project gets its own working memory\nawait agent.generateText(\"Let's plan the e-commerce project using Next.js.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"project-ecommerce\",\n  },\n});\n\nawait agent.generateText(\"For the analytics dashboard, we'll use React and D3.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"project-analytics\",\n  },\n});\n```\n\n## Best Practices\n\n1. **Use JSON schemas for structured data** - Ensures type safety and validation\n2. **Use Markdown templates for narrative context** - Better for summaries, notes, observations\n3. **Default to append mode** - Safer than replace; preserves existing data\n4. **User scope for preferences** - Name, timezone, communication style\n5. **Conversation scope for session data** - Goals, tasks, project details\n6. **Keep it compact** - Working memory supplements message history, not replaces it\n\n## Learn More\n\n- **[Semantic Search](./semantic-search.md)** - Retrieve relevant past messages\n- **[Managed Memory](./managed-memory.md)** - Zero-setup working memory storage\n- **[LibSQL / Turso](./libsql.md)** - Self-hosted working memory storage\n","website/docs/agents/message-types.md":"---\ntitle: Understanding Message Types\n---\n\n# Understanding Message Types\n\nVoltAgent works with three different message type formats, each serving a specific purpose in the agent lifecycle. Understanding when and how to use each type is essential for building effective AI applications.\n\n## Overview\n\nThe three message types are:\n\n1. **MessageContent** - Raw content format for LLM communication (from AI SDK)\n2. **UIMessage** - Frontend-friendly format with metadata (from AI SDK)\n3. **VoltAgentTextStreamPart** - Streaming format with SubAgent support (VoltAgent extension)\n\nThese types come from the underlying AI SDK architecture and VoltAgent's multi-agent features. Rather than being redundant, they serve different layers of your application.\n\n## The Three Message Types\n\n### 1. MessageContent (AI SDK Content Type)\n\n**MessageContent** represents the raw content that gets sent to language models. It's the `content` property of a `ModelMessage`.\n\n#### Type Definition\n\n```typescript\ntype MessageContent = UserContent | AssistantContent | ToolContent | string;\n\n// Examples:\ntype UserContent = string | Array<TextPart | ImagePart | FilePart>;\ntype AssistantContent = string | Array<TextPart | ToolCallPart>;\ntype ToolContent = Array<ToolResultPart>;\n```\n\n#### Format\n\n```typescript\n// Simple string\nconst content: MessageContent = \"Hello, how can I help you?\";\n\n// Structured array with multiple parts\nconst content: MessageContent = [\n  { type: \"text\", text: \"Check this image:\" },\n  { type: \"image\", image: \"data:image/png;base64,...\" },\n  { type: \"text\", text: \"What do you see?\" },\n];\n```\n\n#### When to Use\n\n- ✅ Creating `ModelMessage` objects for AI SDK\n- ✅ Direct LLM provider calls\n- ✅ Low-level message manipulation\n- ✅ Working with `message.content` property\n\n#### Common Patterns\n\n```typescript\nimport { generateText } from \"ai\";\n\n// Using MessageContent in direct AI SDK calls\nconst result = await generateText({\n  model: \"openai/gpt-4\",\n  messages: [\n    {\n      role: \"user\",\n      content: \"Hello!\", // MessageContent (string)\n    },\n    {\n      role: \"user\",\n      content: [\n        // MessageContent (array)\n        { type: \"text\", text: \"Describe this:\" },\n        { type: \"image\", image: imageData },\n      ],\n    },\n  ],\n});\n```\n\n### 2. UIMessage (AI SDK UI Format)\n\n**UIMessage** is the modern format designed for frontend applications, chat UIs, and memory systems. It includes metadata like message IDs and uses a `parts` array instead of `content`.\n\n#### Type Definition\n\n```typescript\ninterface UIMessage {\n  id: string;\n  role: \"user\" | \"assistant\" | \"system\";\n  parts: Array<TextUIPart | FileUIPart | ToolUIPart>;\n  createdAt?: Date;\n  metadata?: Record<string, unknown>;\n}\n```\n\n#### Format\n\n```typescript\nconst message: UIMessage = {\n  id: \"msg-123\",\n  role: \"user\",\n  parts: [\n    { type: \"text\", text: \"Hello, \" },\n    { type: \"file\", url: \"image.png\", mediaType: \"image/png\" },\n    { type: \"text\", text: \"what is this?\" },\n  ],\n  createdAt: new Date(),\n  metadata: { source: \"mobile\" },\n};\n```\n\n#### When to Use\n\n- ✅ Memory operations (`memory.getMessages()`, `memory.addMessage()`)\n- ✅ Agent hooks (`onPrepareMessages`, `onAfterGeneration`)\n- ✅ Chat UI rendering and state management\n- ✅ Message history and conversation management\n- ✅ Frontend/backend communication\n\n#### Common Patterns\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  memory: myMemory,\n\n  hooks: {\n    // UIMessages in hooks\n    onPrepareMessages: async ({ messages }) => {\n      // messages is UIMessage[]\n      const enhanced = messages.map((msg) => ({\n        ...msg,\n        parts: [...msg.parts, { type: \"text\", text: \" [verified]\" }],\n      }));\n\n      return { messages: enhanced };\n    },\n  },\n});\n\n// Getting messages from memory\nconst conversationHistory = await memory.getMessages(userId, conversationId);\n// Returns UIMessage[]\n\nconversationHistory.forEach((msg) => {\n  console.log(`[${msg.id}] ${msg.role}:`, msg.parts);\n});\n```\n\n#### Feedback metadata\n\nWhen feedback is enabled, VoltAgent attaches feedback metadata to assistant UI messages under `message.metadata.feedback`. This is how UIs can show thumbs up/down and submit feedback later.\n\n```ts\nconst feedback = message.metadata?.feedback as\n  | {\n      traceId?: string;\n      key?: string;\n      url?: string;\n      expiresAt?: string;\n      provided?: boolean;\n      providedAt?: string;\n      feedbackId?: string;\n    }\n  | undefined;\n\nconst alreadyProvided = Boolean(feedback?.provided || feedback?.providedAt || feedback?.feedbackId);\n\nif (feedback?.url && !alreadyProvided) {\n  console.log(\"Submit feedback to:\", feedback.url);\n}\n```\n\nAfter submitting feedback successfully, persist the provided-state in memory with `agent.markFeedbackProvided(...)` so the UI still hides feedback controls after conversation reload.\n\nSee [Feedback](/observability-docs/feedback) for the full flow and API examples.\n\n### 3. VoltAgentTextStreamPart (Streaming Extension)\n\n**VoltAgentTextStreamPart** extends AI SDK's `TextStreamPart` with SubAgent metadata, enabling multi-agent coordination during streaming.\n\n#### Type Definition\n\n```typescript\ntype VoltAgentTextStreamPart<TOOLS extends Record<string, any> = Record<string, any>> =\n  TextStreamPart<TOOLS> & {\n    /**\n     * Optional response message identifier (carried on start/step chunks).\n     */\n    messageId?: string;\n\n    /**\n     * Optional identifier for the subagent that generated this event\n     */\n    subAgentId?: string;\n\n    /**\n     * Optional name of the subagent that generated this event\n     */\n    subAgentName?: string;\n  };\n\n// TextStreamPart types include:\n// - text-start, text-delta, text-end\n// - reasoning-start, reasoning-delta, reasoning-end\n// - tool-input-start, tool-input-delta, tool-input-end\n// - tool-call, tool-result, tool-error\n// - start, start-step, finish-step, finish, abort, error, source, file, raw\n```\n\n#### Format\n\n```typescript\n// Regular streaming part\nconst streamPart: VoltAgentTextStreamPart = {\n  type: \"text-delta\",\n  textDelta: \"Hello\",\n};\n\n// With SubAgent metadata\nconst subAgentStreamPart: VoltAgentTextStreamPart = {\n  type: \"text-delta\",\n  textDelta: \"The answer is 42\",\n  subAgentId: \"agent-calculator\",\n  subAgentName: \"Calculator Agent\",\n};\n```\n\n#### When to Use\n\n- ✅ Streaming responses (`agent.streamText()`)\n- ✅ Real-time UI updates\n- ✅ Multi-agent coordination and event forwarding\n- ✅ Identifying which SubAgent is responding\n- ✅ Building collaborative agent UIs\n\n#### Common Patterns\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  subAgents: [writerAgent, editorAgent],\n});\n\n// Streaming with SubAgent tracking\nconst stream = await agent.streamText(\"Write and edit a blog post\");\n\nfor await (const part of stream.fullStream) {\n  if (part.type === \"text-delta\") {\n    // Check which SubAgent is responding\n    if (part.subAgentId) {\n      console.log(`[${part.subAgentName}]: ${part.textDelta}`);\n    } else {\n      console.log(`[Main Agent]: ${part.textDelta}`);\n    }\n  }\n}\n```\n\n## Message Flow Diagram\n\nHere's how messages flow through a typical VoltAgent application:\n\n```mermaid\ngraph TB\n    User[User Input] -->|string / UIMessage array / ModelMessage array| Agent[Agent]\n\n    Agent -->|normalizes to| UIMsg[UIMessage]\n    UIMsg -->|stored in| Memory[(Memory Storage)]\n    UIMsg -->|passed to| Hooks[Agent Hooks]\n\n    Hooks -->|onPrepareMessages| Transform[Message Transform]\n    Transform -->|UIMessage array| Convert[Convert to ModelMessage]\n\n    Convert -->|ModelMessage with MessageContent| LLM[Language Model]\n\n    LLM -->|streaming response| Stream[Stream Parts]\n    Stream -->|VoltAgentTextStreamPart| UI[Real-time UI]\n\n    Stream -->|on finish| Response[Final Response]\n    Response -->|UIMessage| Memory\n\n    SubAgent[SubAgents] -.->|add metadata| Stream\n\n    style UIMsg fill:#3b82f6,stroke:#1e40af,stroke-width:2px,color:#fff\n    style Stream fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#fff\n    style Memory fill:#8b5cf6,stroke:#6d28d9,stroke-width:2px,color:#fff\n    style LLM fill:#10b981,stroke:#059669,stroke-width:2px,color:#fff\n```\n\n## Converting Between Types\n\nVoltAgent and AI SDK provide utilities for converting between message formats:\n\n### MessageContent ↔ UIMessage\n\n```typescript\nimport { convertToModelMessages } from \"ai\";\nimport type { UIMessage } from \"ai\";\n\n// UIMessage[] → ModelMessage[] (which contains MessageContent)\nconst uiMessages: UIMessage[] = await memory.getMessages(userId, conversationId);\nconst modelMessages = await convertToModelMessages(uiMessages);\n\n// Access MessageContent from ModelMessage\nconst content = modelMessages[0].content; // MessageContent\n\n// Extract text from either format using helpers\nimport { extractText } from \"@voltagent/core\";\n\nconst text1 = extractText(content); // From MessageContent\nconst text2 = extractText(uiMessages[0]); // From UIMessage (overloaded!)\n```\n\n### Working with Both Formats\n\nVoltAgent's `messageHelpers` utilities support both formats:\n\n```typescript\nimport { extractText, hasImagePart, extractImageParts } from \"@voltagent/core\";\n\n// Works with MessageContent\nconst content: MessageContent = [\n  { type: \"text\", text: \"Hello\" },\n  { type: \"image\", image: \"data:...\" },\n];\n\nconst text = extractText(content); // \"Hello\"\nconst hasImage = hasImagePart(content); // true\n\n// Also works with UIMessage!\nconst message: UIMessage = {\n  id: \"msg-1\",\n  role: \"user\",\n  parts: [\n    { type: \"text\", text: \"Hello\" },\n    { type: \"file\", url: \"image.png\", mediaType: \"image/png\" },\n  ],\n} as UIMessage;\n\nconst text2 = extractText(message); // \"Hello\"\nconst hasImage2 = hasImagePart(message); // true\nconst images = extractImageParts(message); // FileUIPart[]\n```\n\nSee [Message Helpers](/docs/utils/message-helpers) for the complete API.\n\n## Common Patterns and Use Cases\n\n### Pattern 1: Memory + UIMessage\n\n**Use Case**: Store and retrieve conversation history\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  memory: myMemory,\n});\n\n// Generate response (automatically stores UIMessages in memory)\nawait agent.generateText({\n  prompt: \"Hello!\",\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n});\n\n// Retrieve conversation history\nconst history = await myMemory.getMessages(\"user-123\", \"conv-456\");\n// Returns UIMessage[]\n\n// Display in UI\nhistory.forEach((msg) => {\n  const text = extractText(msg);\n  console.log(`${msg.role}: ${text}`);\n});\n```\n\n### Pattern 2: Streaming + VoltAgentTextStreamPart\n\n**Use Case**: Real-time UI updates with SubAgent identification\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst coordinator = new Agent({\n  subAgents: [researchAgent, writerAgent],\n});\n\nconst stream = await coordinator.streamText(\"Research and write about AI\");\n\n// Track which agent is responding\nlet currentAgent = null;\n\nfor await (const part of stream.fullStream) {\n  if (part.type === \"text-delta\") {\n    // Detect agent switches\n    if (part.subAgentId && part.subAgentId !== currentAgent) {\n      currentAgent = part.subAgentId;\n      console.log(`\\n[Switching to: ${part.subAgentName}]`);\n    }\n\n    process.stdout.write(part.textDelta);\n  }\n}\n```\n\n### Pattern 3: Hooks + UIMessage Transformation\n\n**Use Case**: Add context or filter sensitive data\n\n```typescript\nimport { Agent, messageHelpers } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  hooks: {\n    onPrepareMessages: async ({ messages }) => {\n      // Add timestamps to user messages\n      const enhanced = messages.map((msg) => {\n        if (msg.role === \"user\") {\n          return messageHelpers.addTimestampToMessage(msg);\n        }\n        return msg;\n      });\n\n      return { messages: enhanced };\n    },\n\n    onAfterGeneration: async ({ response }) => {\n      // Filter sensitive data from assistant responses\n      const filtered = messageHelpers.mapMessageContent(response, (text) => {\n        // Redact SSN patterns\n        return text.replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, \"[REDACTED]\");\n      });\n\n      return { response: filtered };\n    },\n  },\n});\n```\n\n## Decision Tree: Which Type Should I Use?\n\n```\nAre you working with...\n\n┌─ Memory operations (store/retrieve)?\n│  └─> Use UIMessage\n│\n├─ Agent hooks (transform messages)?\n│  └─> Use UIMessage\n│\n├─ Streaming responses?\n│  └─> Use VoltAgentTextStreamPart\n│     └─ Need SubAgent tracking?\n│        └─> Yes: Check subAgentId/subAgentName properties\n│\n├─ Direct AI SDK calls (generateText/streamText)?\n│  └─> Use MessageContent (in ModelMessage.content)\n│\n├─ Chat UI rendering?\n│  └─> Use UIMessage\n│\n└─ Message content manipulation?\n   └─> Use messageHelpers (supports both MessageContent and UIMessage!)\n```\n\n## FAQ\n\n### Q: Why not just one unified message type?\n\n**A**: These types come from different layers of the AI SDK and serve different purposes:\n\n- **MessageContent** is optimized for LLM communication (minimal structure)\n- **UIMessage** is optimized for UIs and storage (includes metadata, IDs)\n- **VoltAgentTextStreamPart** is optimized for streaming (real-time, event-based)\n\nUnifying them would lose these optimizations and break compatibility with AI SDK.\n\n### Q: Do I need to convert between types manually?\n\n**A**: Usually not! VoltAgent handles conversions automatically:\n\n- Agent automatically converts UIMessages to ModelMessages when calling LLMs\n- Memory automatically stores responses as UIMessages\n- `messageHelpers` utilities work with both MessageContent and UIMessage\n\nYou only need manual conversion for advanced use cases.\n\n### Q: Which type does `memory.getMessages()` return?\n\n**A**: Always **UIMessage[]**. Memory operations use UIMessage format for:\n\n- Message IDs (tracking individual messages)\n- Metadata (timestamps, sources, etc.)\n- Frontend compatibility\n\n### Q: Can I use MessageContent with message helpers?\n\n**A**: Yes! All extractor and checker functions in `messageHelpers` support both:\n\n```typescript\nimport { extractText } from \"@voltagent/core\";\n\n// Both work!\nextractText(messageContent); // MessageContent\nextractText(uiMessage); // UIMessage\n```\n\n### Q: How do I know which SubAgent is responding in a stream?\n\n**A**: Check the `subAgentId` and `subAgentName` properties:\n\n```typescript\nfor await (const part of stream.fullStream) {\n  if (part.subAgentId) {\n    console.log(`SubAgent: ${part.subAgentName}`);\n  } else {\n    console.log(\"Main Agent\");\n  }\n}\n```\n\n### Q: Are these types specific to VoltAgent?\n\n**A**: No! **MessageContent** and **UIMessage** come from Vercel's AI SDK. Only **VoltAgentTextStreamPart** is a VoltAgent extension (adding `subAgentId` and `subAgentName` to AI SDK's `TextStreamPart`).\n\n## Related Documentation\n\n- [Message Helpers](/docs/utils/message-helpers) - Utilities for working with both MessageContent and UIMessage\n- [Memory Guide](/docs/agents/memory) - How memory uses UIMessage format\n- [Agent Hooks](/docs/agents/hooks) - UIMessage transformation in hooks\n- [API Streaming](/docs/api/streaming) - Working with VoltAgentTextStreamPart\n\n## Summary\n\n| Type                        | Source    | Primary Use Case  | Key Features                       |\n| --------------------------- | --------- | ----------------- | ---------------------------------- |\n| **MessageContent**          | AI SDK    | LLM communication | String or array, minimal structure |\n| **UIMessage**               | AI SDK    | UI & Memory       | Has `id`, `parts`, metadata        |\n| **VoltAgentTextStreamPart** | VoltAgent | Streaming         | Has `subAgentId`, `subAgentName`   |\n\n**Golden Rule**: Let VoltAgent handle conversions automatically. Use `messageHelpers` for content manipulation, and check the documentation for each API to know which type it expects.\n","website/docs/agents/middleware.md":"---\ntitle: Middleware\nslug: /agents/middleware\n---\n\n# Agent Middleware\n\nMiddleware runs before guardrails and hooks on each agent call. Use it to read or modify input and output, or to stop a call with a typed error.\n\n## Execution Order\n\n`generateText` and `generateObject`:\n\n1. Input middlewares\n2. Input guardrails\n3. Hooks (`onStart`)\n4. Model call (with retries and fallback)\n5. Output middlewares\n6. Output guardrails\n7. Hooks (`onEnd`)\n\n`streamText` and `streamObject`:\n\n1. Input middlewares\n2. Input guardrails\n3. Stream starts\n\nOutput middlewares do not run in streaming calls.\n\n## Configure Middleware\n\nAgent-level middlewares run before per-call middlewares. Per-call middlewares are appended to the list for that call.\n\n```ts\nimport { Agent, createInputMiddleware, createOutputMiddleware } from \"@voltagent/core\";\n\nconst normalizeInput = createInputMiddleware({\n  name: \"NormalizeInput\",\n  handler: ({ input }) => {\n    if (typeof input !== \"string\") return input;\n    return input.trim();\n  },\n});\n\nconst requireSignature = createOutputMiddleware<string>({\n  name: \"RequireSignature\",\n  handler: ({ output, abort }) => {\n    if (!output.includes(\"-- Support\")) {\n      abort(\"Missing signature\", { retry: true, metadata: { reason: \"signature\" } });\n    }\n    return output;\n  },\n});\n\nconst agent = new Agent({\n  name: \"Support\",\n  instructions: \"Answer support questions with short, direct replies.\",\n  model: \"openai/gpt-4o-mini\",\n  maxMiddlewareRetries: 1,\n  inputMiddlewares: [normalizeInput],\n  outputMiddlewares: [requireSignature],\n});\n```\n\nPer-call middleware:\n\n```ts\nawait agent.generateText(\"hi\", {\n  inputMiddlewares: [({ input }) => (typeof input === \"string\" ? `Customer: ${input}` : input)],\n});\n```\n\n## Middleware API\n\nInput middleware receives:\n\n- `input`: current input value\n- `originalInput`: input from the caller\n- `agent`: agent instance\n- `context`: operation context\n- `operation`: operation name (for example `generateText`)\n- `retryCount`: current middleware retry count\n- `abort(reason?, options?)`: stop the call or request a retry\n\nOutput middleware receives:\n\n- `output`: current output value\n- `originalOutput`: output from the model\n- `usage`, `finishReason`, `warnings`: model metadata when available\n- `agent`, `context`, `operation`, `retryCount`, `abort(...)`\n\nReturn a new value to replace the input or output; return `void` to keep the current value.\n\n## Retry Behavior\n\nMiddleware retries are controlled by `maxMiddlewareRetries`. The default is `0`.\n\n- Call `abort(\"reason\", { retry: true })` to request a retry.\n- Each retry restarts the full attempt: input middleware, guardrails, hooks, model selection, model retries/fallback, output middleware.\n- The retry reason is added as a system message for the next attempt.\n- `retryCount` starts at `0` and increments after each middleware-triggered retry.\n- If `maxMiddlewareRetries` is exceeded, the middleware error is returned to the caller.\n- In streaming calls, retry can only happen before the stream starts, so only input middlewares can trigger it.\n\nMiddleware retries are separate from model retries. Model retries use `maxRetries` and are evaluated per model call. For model fallback configuration, see [Retries and Fallbacks](/docs/agents/retries-fallback).\n","website/docs/agents/multi-modal.md":"# Multi-Modal Inputs\n\nAgents can process text, images, and files in a single request. This document covers how to send multi-modal inputs to agent methods.\n\n## Basic Usage\n\nAgent methods (`generateText`, `streamText`, `generateObject`, `streamObject`) accept three input formats:\n\n- `string` - Plain text input\n- `UIMessage[]` - AI SDK's UI message format (used in chat interfaces and streaming)\n- `BaseMessage[]` - AI SDK's model message format (alias for `ModelMessage`)\n\nBoth message formats are from the AI SDK and re-exported by VoltAgent. To send images or files, use either message array format with structured content.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport type { BaseMessage } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"vision-assistant\",\n  instructions: \"Analyze images and answer questions about them.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"What's in this image?\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/photo.jpg\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\nconsole.log(result.text);\n```\n\n## Image Formats\n\nImages can be provided as URLs, data URIs, or base64 strings.\n\n### Using URLs\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Describe this chart\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/chart.png\",\n      },\n    ],\n  },\n];\n```\n\n### Using Base64\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst imageBuffer = readFileSync(\"./photo.jpg\");\nconst base64Image = imageBuffer.toString(\"base64\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"What's in this photo?\" },\n      {\n        type: \"image\",\n        image: `data:image/jpeg;base64,${base64Image}`,\n        mediaType: \"image/jpeg\",\n      },\n    ],\n  },\n];\n```\n\n### Using Binary Data\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst imageBuffer = readFileSync(\"./photo.jpg\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Analyze this image\" },\n      {\n        type: \"image\",\n        image: new Uint8Array(imageBuffer),\n        mediaType: \"image/jpeg\",\n      },\n    ],\n  },\n];\n```\n\n## File Attachments\n\nFor non-image files (PDFs, documents), use the file content type.\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst pdfBuffer = readFileSync(\"./report.pdf\");\nconst base64Pdf = pdfBuffer.toString(\"base64\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Summarize this document\" },\n      {\n        type: \"file\",\n        url: `data:application/pdf;base64,${base64Pdf}`,\n        mediaType: \"application/pdf\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\n```\n\n## Multiple Images\n\nYou can include multiple images in a single message.\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Compare these two images\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/before.jpg\",\n      },\n      {\n        type: \"image\",\n        image: \"https://example.com/after.jpg\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\n```\n\n## Streaming with Images\n\nStreaming works the same way with multi-modal inputs.\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Describe this image in detail\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/landscape.jpg\",\n      },\n    ],\n  },\n];\n\nconst stream = await agent.streamText(messages);\n\nfor await (const chunk of stream.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n## Structured Output with Images\n\nUse `generateObject` to extract structured data from images.\n\n```ts\nimport { z } from \"zod\";\n\nconst receiptSchema = z.object({\n  total: z.number(),\n  items: z.array(\n    z.object({\n      name: z.string(),\n      price: z.number(),\n    })\n  ),\n  date: z.string(),\n});\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Extract the receipt details\" },\n      {\n        type: \"image\",\n        image: \"data:image/jpeg;base64,/9j/4AAQSkZJRg...\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateObject(messages, receiptSchema);\nconsole.log(result.object);\n// { total: 42.50, items: [...], date: \"2024-01-15\" }\n```\n\n## Content Part Types Reference\n\nThese types are from the AI SDK and re-exported by VoltAgent for convenience.\n\nWhen using message arrays, the `content` field can be:\n\n- A `string` for text-only messages\n- An array of content parts for multi-modal messages\n\n### Text Part\n\n```ts\n{\n  type: \"text\",\n  text: \"Your text here\"\n}\n```\n\n### Image Part\n\n```ts\n{\n  type: \"image\",\n  image: string | URL | Uint8Array,  // URL, data URI, base64, or binary\n  mediaType?: string,                 // \"image/jpeg\", \"image/png\", etc.\n  alt?: string                        // Alternative text description\n}\n```\n\n### File Part\n\n```ts\n{\n  type: \"file\",\n  url: string,           // Absolute URL or data URI\n  mediaType: string      // \"application/pdf\", \"text/csv\", etc.\n}\n```\n\nFor complete type definitions, see the [AI SDK documentation](https://sdk.vercel.ai/docs/reference/ai-sdk-core/model-message).\n\n## Model Support\n\nNot all models support vision or file inputs. Check your provider's documentation.\n\nVision-capable models include:\n\n- **OpenAI**: `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`\n- **Anthropic**: `claude-3-5-sonnet`, `claude-3-opus`, `claude-3-haiku`\n- **Google**: `gemini-1.5-pro`, `gemini-1.5-flash`\n\nImage format support, size limits, and costs vary by provider. Refer to your model provider's documentation for details.\n\n## Error Handling\n\n```ts\ntry {\n  const messages: BaseMessage[] = [\n    {\n      role: \"user\",\n      content: [\n        { type: \"text\", text: \"What's in this image?\" },\n        {\n          type: \"image\",\n          image: \"https://example.com/image.jpg\",\n        },\n      ],\n    },\n  ];\n\n  const result = await agent.generateText(messages);\n  console.log(result.text);\n} catch (error) {\n  if (error.message.includes(\"vision\")) {\n    console.error(\"This model does not support image inputs\");\n  } else {\n    console.error(\"Error:\", error);\n  }\n}\n```\n\n## VoltOps Console\n\nThe [VoltOps Console](https://console.voltagent.dev/) includes a chat interface with file upload support.\n\nClick the attachment button (📎) to upload images or files. The console converts uploaded files to the appropriate message format (base64 data URIs) and sends them to your agent.\n\nThis allows you to test multi-modal agents without writing code for file handling.\n","website/docs/agents/overview.md":"---\ntitle: Overview\nslug: /agents/overview\n---\n\n# Agent Overview\n\nAn agent in VoltAgent wraps a language model with instructions, tools, memory, and other capabilities. You create an agent instance, then call its methods to generate responses or stream output.\n\nThere are two ways to use agents in VoltAgent:\n\n1. **Direct method calls** - Call agent methods (`generateText`, `streamText`) from your application code\n2. **REST API** - Use VoltAgent's HTTP server to expose agents as REST endpoints\n\nThis document covers both approaches, starting with the basics.\n\n## Creating an Agent\n\nAn agent requires three properties: a name, instructions, and a model.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n```\n\nYou can also use a model string instead of importing a provider:\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n```\n\nThe `instructions` property defines the agent's behavior. The `model` can be an ai-sdk `LanguageModel` or a `provider/model` string resolved by VoltAgent. See [Model Router & Registry](/docs/getting-started/model-router) for details.\n\nTo configure fallback lists and per-model retries, see [Retries and Fallbacks](/docs/agents/retries-fallback).\nTo run pre-guardrail input/output handlers, see [Middleware](/docs/agents/middleware).\n\n## Using Agents: Direct Method Calls\n\nAgents have two core methods for generating responses:\n\n### Text Generation\n\nUse these methods when you need text responses.\n\n**generateText** - Returns the complete text response at once.\n\n```ts\nconst result = await agent.generateText(\"What is TypeScript?\");\nconsole.log(result.text);\n```\n\n**streamText** - Streams text chunks as they're generated (for real-time UIs).\n\n```ts\nconst stream = await agent.streamText(\"Explain async/await\");\n\nfor await (const chunk of stream.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n### Streaming Features\n\nWhen using `streamText`, you can access detailed events and final values.\n\n#### fullStream for Detailed Events\n\nUse `fullStream` to receive detailed streaming events including tool calls, reasoning steps, and completion status.\n\n```ts\nconst response = await agent.streamText(\"Write a story\");\n\nfor await (const chunk of response.fullStream) {\n  switch (chunk.type) {\n    case \"reasoning-start\":\n      console.log(\"\\nReasoning started\");\n      break;\n    case \"reasoning-delta\":\n      process.stdout.write(chunk.delta ?? chunk.text ?? \"\");\n      break;\n    case \"reasoning-end\":\n      console.log(\"\\nReasoning completed\");\n      break;\n    case \"text-delta\":\n      process.stdout.write(chunk.delta ?? chunk.text ?? \"\");\n      break;\n    case \"tool-call\":\n      console.log(`\\nUsing tool: ${chunk.toolName}`);\n      break;\n    case \"tool-result\":\n      console.log(`Tool completed: ${chunk.toolName}`);\n      break;\n    case \"finish\":\n      console.log(`\\nDone! Tokens: ${chunk.usage?.totalTokens}`);\n      break;\n  }\n}\n```\n\n#### Promise-based Properties\n\nAccess final values as Promises that resolve when streaming completes.\n\n```ts\nconst response = await agent.streamText(\"Explain async/await\");\n\n// Process stream\n(async () => {\n  for await (const chunk of response.textStream) {\n    process.stdout.write(chunk);\n  }\n})();\n\n// Access final values (resolve when stream completes)\nconst [fullText, finalStepUsage, totalUsage, finishReason] = await Promise.all([\n  response.text, // Promise<string>\n  response.usage, // Promise<LanguageModelUsage> for the final step\n  response.totalUsage, // Promise<LanguageModelUsage> across all steps\n  response.finishReason, // Promise<string>\n]);\n\nconsole.log(`\\nTotal: ${fullText.length} chars, ${totalUsage?.totalTokens} tokens`);\nconsole.log(`Final step tokens: ${finalStepUsage?.totalTokens}`);\n```\n\n### Feedback (optional)\n\nIf you have VoltOps API keys configured, you can enable feedback per agent or per call. VoltAgent creates a short-lived feedback token and attaches it to the assistant message metadata and the result object.\n\n```ts\nconst result = await agent.generateText(\"Summarize this trace\", {\n  feedback: {\n    key: \"satisfaction\",\n    feedbackConfig: {\n      type: \"categorical\",\n      categories: [\n        { value: 1, label: \"Satisfied\" },\n        { value: 0, label: \"Unsatisfied\" },\n      ],\n    },\n  },\n});\n\nconsole.log(result.feedback?.url);\n```\n\n`result.feedback` may also include `provided`, `providedAt`, and `feedbackId` so UI clients can hide feedback controls once submitted.\n\nIf the feedback key is already registered, you can pass only `key` and let the stored config populate the token.\n\n```ts\nconst result = await agent.generateText(\"Quick rating?\", {\n  feedback: { key: \"satisfaction\" },\n});\n```\n\nTo persist \"already submitted\" state across memory reloads, you can use the result helper:\n\n```ts\nconst feedbackId = \"feedback-id-from-ingestion-response\"; // returned by your feedback ingestion API response\n\nif (result.feedback && !result.feedback.isProvided()) {\n  await result.feedback.markFeedbackProvided({\n    feedbackId, // optional\n  });\n}\n```\n\nYou can still call `agent.markFeedbackProvided(...)` directly if you prefer explicit control.\n\nThese helpers require memory-backed conversations (`userId` and `conversationId`) and are typically called from your backend after feedback ingestion succeeds.\n\nFor end-to-end examples (SDK, API, and useChat), see [Feedback](/observability-docs/feedback).\n\n### Structured Data Generation\n\nUse `output` with `generateText`/`streamText` to get structured data while still using tools and all agent capabilities.\n`generateObject` and `streamObject` are deprecated in VoltAgent 2.x.\n\n```ts\nimport { Output } from \"ai\";\nimport { z } from \"zod\";\n\nconst recipeSchema = z.object({\n  name: z.string(),\n  ingredients: z.array(z.string()),\n  steps: z.array(z.string()),\n  prepTime: z.number(),\n});\n\n// With generateText - supports tool calling\nconst result = await agent.generateText(\"Create a pasta recipe\", {\n  output: Output.object({ schema: recipeSchema }),\n});\nconsole.log(result.output); // { name: \"...\", ingredients: [...], ... }\n\n// With streamText - stream partial objects while using tools\nconst stream = await agent.streamText(\"Create a detailed recipe\", {\n  output: Output.object({ schema: recipeSchema }),\n});\n\nfor await (const partial of stream.partialOutputStream ?? []) {\n  console.log(partial); // Incrementally built object\n}\n\n// Constrained text generation\nconst haiku = await agent.generateText(\"Write a haiku about coding\", {\n  output: Output.text(),\n});\nconsole.log(haiku.output);\n```\n\n### Summarization\n\nSummarization inserts a system summary and keeps the last N non-system messages before each model call. Configure it with the `summarization` option on the agent. See [Summarization](./summarization.md) for configuration and storage details.\n\n### Conversation Persistence\n\nVoltAgent persists conversation messages and step records while a run is executing. By default, it uses step-level checkpoints, which is safer for long multi-step tool chains.\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users reliably.\",\n  model: \"openai/gpt-4o-mini\",\n  conversationPersistence: {\n    mode: \"step\", // \"step\" (default) or \"finish\"\n    debounceMs: 200, // default\n    flushOnToolResult: true, // default\n  },\n});\n```\n\nYou can also override this per call:\n\n```ts\nawait agent.generateText(\"Run the workflow\", {\n  memory: {\n    options: {\n      conversationPersistence: {\n        mode: \"finish\",\n      },\n    },\n  },\n});\n```\n\n`mode: \"step\"` schedules persistence during execution and flushes immediately on tool completion by default. `mode: \"finish\"` keeps the legacy behavior of persisting only at operation completion.\n\n### Input Types\n\nAll methods accept either a string or an array of messages:\n\n```ts\n// String input\nawait agent.generateText(\"Hello\");\n\n// Message array (for multimodal or conversation history)\nawait agent.generateText([{ role: \"user\", content: \"What's in this image?\" }]);\n```\n\n## Using Agents: REST API\n\nVoltAgent can expose agents through HTTP endpoints. This lets you call agents from any client (web, mobile, etc.).\n\n### Server Setup\n\nCreate a `VoltAgent` instance with a server provider:\n\n```ts\nimport { VoltAgent, Agent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst agent = new Agent({\n  name: \"assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n});\n\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer(), // Starts on port 3141 by default\n});\n```\n\n### Available Endpoints\n\nThe server exposes the following REST endpoints:\n\n#### Text Generation\n\n- `POST /agents/:id/text` - Generate complete text response (synchronous)\n- `POST /agents/:id/stream` - Stream raw fullStream events (SSE)\n- `POST /agents/:id/chat` - Stream UI messages for useChat hook (SSE)\n\nIf you need clients to reconnect after refresh and continue the same response, enable resumable streaming. See [Resumable Streaming](/docs/agents/resumable-streaming/).\n\n#### Structured Data\n\n- `POST /agents/:id/object` - Generate structured object (synchronous)\n- `POST /agents/:id/stream-object` - Stream object generation (SSE)\n\n**Endpoint comparison:**\n\n| Endpoint         | Method | Response Type | Use Case                                                |\n| ---------------- | ------ | ------------- | ------------------------------------------------------- |\n| `/text`          | POST   | JSON          | Complete text response at once                          |\n| `/stream`        | POST   | SSE           | Raw stream events (text/reasoning/tool/lifecycle parts) |\n| `/chat`          | POST   | SSE           | UI message stream for ai-sdk's useChat hook             |\n| `/object`        | POST   | JSON          | Complete structured object at once                      |\n| `/stream-object` | POST   | SSE           | Streaming partial objects                               |\n\n### Structured Output via HTTP\n\nYou can use `output` with the text endpoints (`/text`, `/stream`, `/chat`) to get structured data while maintaining tool calling capabilities. Add the `output` field to the `options` object in your request:\n\n```typescript\nconst response = await fetch(\"http://localhost:3141/agents/assistant/text\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    input: \"Create a recipe\",\n    options: {\n      output: {\n        type: \"object\",\n        schema: {\n          type: \"object\",\n          properties: {\n            name: { type: \"string\" },\n            ingredients: { type: \"array\", items: { type: \"string\" } },\n          },\n          required: [\"name\"],\n        },\n      },\n    },\n  }),\n});\n\nconst data = await response.json();\nconsole.log(data.data.output); // Structured object\n```\n\nFor detailed API reference and examples, see [Agent Endpoints - output](../api/endpoints/agents.md#using-output-for-structured-generation).\n\n### Calling from Next.js API Route\n\nExample Next.js API route using the SDK (not direct REST calls):\n\n```ts\n// app/api/chat/route.ts\nimport { agent } from \"@/voltagent\";\n\nexport async function POST(req: Request) {\n  const { messages, conversationId, userId } = await req.json();\n\n  const result = await agent.streamText(messages, {\n    memory: {\n      conversationId,\n      userId,\n    },\n  });\n\n  return result.toUIMessageStreamResponse();\n}\n```\n\nTo call the REST API directly from a client:\n\n```ts\n// Client-side code\nconst response = await fetch(\"http://localhost:3141/agents/assistant/chat\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    messages: [{ role: \"user\", content: \"Hello\" }],\n  }),\n});\n\n// Stream response (SSE)\nconst reader = response.body.getReader();\n// ... process stream chunks\n```\n\n## Constructor Options\n\nYou can configure agents with additional options:\n\n```typescript\nconst agent = new Agent({\n  // Required\n  name: \"MyAgent\", // Agent identifier\n  instructions: \"You are a helpful assistant\", // Behavior guidelines\n  model: \"openai/gpt-4o\", // Model string or ai-sdk LanguageModel\n\n  // Optional\n  id: \"custom-id\", // Unique ID (auto-generated if not provided)\n  purpose: \"Customer support agent\", // Agent purpose for supervisor context\n  tools: [weatherTool, searchTool], // Available tools\n  memory: memoryStorage, // Memory instance (omit -> built-in in-memory, false -> disable)\n  context: new Map([\n    // Default context for all operations\n    [\"environment\", \"production\"],\n  ]),\n  maxSteps: 10, // Maximum tool-use iterations\n  temperature: 0.7, // Default creativity (overridable per call)\n  maxOutputTokens: 512, // Default token limit (overridable per call)\n  subAgents: [researchAgent], // Sub-agents for delegation\n  supervisorConfig: {\n    // Supervisor behavior config\n    systemMessage: \"Custom supervisor instructions\",\n    includeAgentsMemory: true,\n  },\n\n  // Additional constructor parameters\n  hooks: createHooks({ onStart, onEnd }), // Lifecycle event handlers\n  retriever: new PineconeRetriever(), // RAG retriever\n  voice: new ElevenLabsVoice(), // Voice configuration\n  markdown: true, // Enable markdown formatting\n  voltOpsClient: new VoltOpsClient({\n    // Observability & prompt management\n    publicKey: \"...\",\n    secretKey: \"...\",\n  }),\n  maxHistoryEntries: 1000, // Max history entries to store\n});\n```\n\n## Advanced Features\n\nAgents support additional capabilities through configuration options. Each feature is briefly explained below with a link to detailed documentation.\n\n### Memory\n\nMemory stores conversation history so agents can reference past messages. If `memory` is omitted, agents use built-in in-memory storage. Use `memory: false` for stateless behavior, or configure a persistent adapter for long-term storage.\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n\nconst agent = new Agent({\n  name: \"Agent with Memory\",\n  model: \"openai/gpt-4o\",\n  memory,\n});\n```\n\n[Memory documentation](./memory/overview.md)\n\n### Tools\n\nTools let agents call functions to fetch data, perform actions, or interact with external systems. The LLM decides when to use tools based on the user's input.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather for a location\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => {\n    // Call weather API\n    return { temperature: 72, conditions: \"sunny\" };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Weather Assistant\",\n  instructions: \"Answer weather questions using the get_weather tool.\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n});\n```\n\n[Tools documentation](./tools.md)\n\n#### Using Agents as Tools\n\nAgents can be converted to tools and used by other agents:\n\n```ts\nconst writerAgent = new Agent({\n  id: \"writer\",\n  purpose: \"Writes blog posts\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst editorAgent = new Agent({\n  id: \"editor\",\n  purpose: \"Edits content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Coordinator uses them as tools\nconst coordinator = new Agent({\n  tools: [writerAgent.toTool(), editorAgent.toTool()],\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\nThis is useful when the LLM should decide which agents to call based on the request.\n\n### Guardrails\n\nGuardrails run before and after the model call to validate inputs or adjust outputs. Input guardrails receive the request text, while output guardrails can rewrite or block the response before it reaches the caller.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Guarded Assistant\",\n  instructions: \"Answer briefly.\",\n  model: \"openai/gpt-4o-mini\",\n  inputGuardrails: [\n    {\n      id: \"reject-empty\",\n      name: \"Reject Empty Prompts\",\n      handler: async ({ inputText }) => {\n        if (inputText.trim().length === 0) {\n          return {\n            pass: false,\n            action: \"block\",\n            message: \"Prompt cannot be empty.\",\n          };\n        }\n        return { pass: true };\n      },\n    },\n  ],\n  outputGuardrails: [\n    {\n      id: \"trim-output\",\n      name: \"Trim Whitespace\",\n      handler: async ({ output }) => ({\n        pass: true,\n        action: \"modify\",\n        modifiedOutput: typeof output === \"string\" ? output.trim() : output,\n      }),\n    },\n  ],\n});\n\nconst response = await agent.generateText(\"  Give me one fact about Mars.  \");\nconsole.log(response.text); // \"Give me one fact about Mars.\" (trimmed)\n```\n\n[Learn more about Guardrails](../guardrails/built-in.md)\n\n### Sub-Agents\n\nSub-agents let you delegate tasks to specialized agents. The parent agent can call sub-agents through an automatically generated `delegate_task` tool.\n\n```ts\nconst researchAgent = new Agent({\n  name: \"Researcher\",\n  instructions: \"Research topics thoroughly.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst writerAgent = new Agent({\n  name: \"Writer\",\n  instructions: \"Write clear, concise content.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst coordinator = new Agent({\n  name: \"Coordinator\",\n  instructions: \"Delegate research to Researcher and writing to Writer.\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researchAgent, writerAgent],\n});\n```\n\n#### Event Filtering\n\nWhen streaming with sub-agents, by default only `tool-call` and `tool-result` events are forwarded from sub-agents to reduce noise.\n\n**Enable all event types:**\n\n```ts\nconst coordinator = new Agent({\n  name: \"Coordinator\",\n  instructions: \"Coordinate between agents.\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researchAgent, writerAgent],\n  supervisorConfig: {\n    fullStreamEventForwarding: {\n      types: [\n        \"tool-call\",\n        \"tool-result\",\n        \"text-start\",\n        \"text-delta\",\n        \"text-end\",\n        \"reasoning-start\",\n        \"reasoning-delta\",\n        \"reasoning-end\",\n        \"source\",\n        \"error\",\n        \"finish\",\n      ],\n    },\n  },\n});\n\n// Now receive all sub-agent events\nconst response = await coordinator.streamText(\"Research and write about AI\");\nfor await (const chunk of response.fullStream) {\n  if (chunk.subAgentId && chunk.subAgentName) {\n    console.log(`[${chunk.agentPath?.join(\" > \") ?? chunk.subAgentName}] ${chunk.type}`);\n  }\n}\n```\n\n[Sub-Agents documentation](./subagents.md)\n\n### Hooks\n\nHooks let you run code at specific points in the agent lifecycle: before/after operations, before/after tool calls, etc. Use hooks for logging, monitoring, or custom logic.\n\n```ts\nimport { createHooks } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onStart: async ({ agent, context }) => {\n    console.log(`${agent.name} started`);\n  },\n  onEnd: async ({ agent, output, error }) => {\n    if (error) {\n      console.error(\"Error:\", error);\n    } else {\n      console.log(\"Completed:\", output);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: \"Answer questions.\",\n  model: \"openai/gpt-4o\",\n  hooks,\n});\n```\n\n[Hooks documentation](./hooks.md)\n\n### Prompt Management\n\nInstructions can be static strings, dynamic functions, or managed remotely via VoltOps. Dynamic instructions let you change agent behavior based on runtime context.\n\n```ts\n// Static instructions\nconst agent1 = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions.\",\n  model: \"openai/gpt-4o\",\n});\n\n// Dynamic instructions\nconst agent2 = new Agent({\n  name: \"Assistant\",\n  instructions: ({ context }) => {\n    const tier = context.get(\"tier\") || \"free\";\n    return tier === \"premium\" ? \"Provide detailed answers.\" : \"Provide concise answers.\";\n  },\n  model: \"openai/gpt-4o\",\n});\n```\n\n[Prompt Management documentation](./prompts.md)\n\n### Dynamic Agents\n\nAgent properties (instructions, model, tools) can be functions that evaluate at runtime based on context. This enables multi-tenant apps, role-based access, or per-user model selection.\n\n```ts\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: ({ context }) => {\n    const role = context.get(\"role\");\n    return role === \"admin\" ? \"Admin instructions.\" : \"User instructions.\";\n  },\n  model: ({ context }) => {\n    const tier = context.get(\"tier\");\n    return tier === \"premium\" ? \"openai/gpt-4o\" : \"openai/gpt-4o-mini\";\n  },\n});\n\nawait agent.generateText(\"Hello\", {\n  context: new Map([\n    [\"role\", \"admin\"],\n    [\"tier\", \"premium\"],\n  ]),\n});\n```\n\n[Dynamic Agents documentation](./dynamic-agents.md)\n\n### Operation Context\n\nPass request-specific data (like request IDs, user info) between hooks and tools using the operation context `Map`. Context is scoped to a single operation.\n\n```ts\nconst hooks = createHooks({\n  onStart: async ({ context }) => {\n    context.context.set(\"requestId\", `req-${Date.now()}`);\n  },\n});\n\nconst tool = createTool({\n  name: \"logger\",\n  parameters: z.object({ message: z.string() }),\n  execute: async (params, oc) => {\n    const requestId = oc?.context?.get(\"requestId\");\n    console.log(`[${requestId}] ${params.message}`);\n  },\n});\n```\n\n[Context documentation](./context.md)\n\n### Retriever (RAG)\n\nRetrievers fetch relevant context from external knowledge bases before calling the LLM. Implement the `BaseRetriever` interface to connect vector databases or document stores.\n\n```ts\nclass SimpleRetriever extends BaseRetriever {\n  async retrieve(input: string | BaseMessage[]): Promise<string> {\n    // Fetch relevant documents from vector DB or knowledge base\n    return \"Retrieved context goes here\";\n  }\n}\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer using retrieved context.\",\n  model: \"openai/gpt-4o\",\n  retriever: new SimpleRetriever(),\n});\n```\n\n[Retriever documentation](../rag/overview.md)\n\n### Models and Providers\n\nVoltAgent can use [ai-sdk](https://sdk.vercel.ai) models directly, but model strings are the default. Switch providers by changing the model string (or import another provider).\n\n```ts\nconst agent1 = new Agent({\n  name: \"OpenAI Agent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"Answer questions.\",\n});\n\nconst agent2 = new Agent({\n  name: \"Anthropic Agent\",\n  model: \"anthropic/claude-3-5-sonnet\",\n  instructions: \"Answer questions.\",\n});\n```\n\n[Providers and Models documentation](../getting-started/providers-models.md)\n\n### Call Settings\n\nConfigure generation behavior per-call with parameters like `temperature`, `maxOutputTokens`, `topP`, etc.\n\n```ts\nawait agent.generateText(\"Write a story\", {\n  temperature: 0.8,\n  maxOutputTokens: 500,\n  topP: 0.9,\n  seed: 12345,\n  // Provider-specific options\n  providerOptions: {\n    someProviderSpecificOption: {\n      foo: \"bar\",\n    },\n  },\n});\n```\n\n### Markdown Formatting\n\nEnable automatic markdown formatting in text responses by setting `markdown: true`. The agent will use markdown syntax (headings, lists, bold text, etc.) in its responses.\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n  markdown: true,\n});\n\nconst result = await agent.generateText(\"Explain how to make tea.\");\n// Response will be formatted with markdown\n```\n\n### Step Control (maxSteps)\n\n`maxSteps` limits how many LLM calls (steps) an agent can make in a single operation. Each tool call consumes a step. Default is 5.\n\n```ts\n// Set maxSteps at agent level\nconst agent = new Agent({\n  name: \"Agent\",\n  model: \"openai/gpt-4o\",\n  maxSteps: 5, // Default for all operations\n});\n\n// Or override per-call\nawait agent.generateText(\"Question\", { maxSteps: 3 });\n```\n\n### Cancellation (AbortController)\n\nCancel long-running operations using `AbortController`. Pass the signal to agent methods.\n\n```ts\nconst abortController = new AbortController();\n\nsetTimeout(() => abortController.abort(), 5000); // Cancel after 5s\n\ntry {\n  const response = await agent.generateText(\"Long task...\", {\n    abortSignal: abortController.signal,\n  });\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Cancelled\");\n  }\n}\n```\n\n[Cancellation documentation](./cancellation.md)\n\n### MCP (Model Context Protocol)\n\nConnect to external MCP servers to use their tools as if they were local tools.\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    myServer: {\n      type: \"http\",\n      url: \"https://mcp-server.example.com\",\n    },\n  },\n});\n\nconst mcpTools = await mcpConfig.getTools();\n\nconst agent = new Agent({\n  name: \"Agent\",\n  model: \"openai/gpt-4o\",\n  tools: mcpTools,\n});\n```\n\n[MCP documentation](./mcp/mcp.md)\n\n### Voice\n\nAdd speech-to-text and text-to-speech capabilities using voice providers.\n\n```ts\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\nconst voice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n  ttsModel: \"tts-1\",\n  voice: \"alloy\",\n});\n\nconst agent = new Agent({\n  name: \"Voice Assistant\",\n  model: \"openai/gpt-4o\",\n  voice,\n});\n\nconst result = await agent.generateText(\"Tell a story\");\nconst audioStream = await agent.voice.speak(result.text);\n```\n\n[Voice documentation](./voice.md)\n\n## Error Handling\n\nWrap agent calls in try-catch blocks. Errors during streaming are recorded in history and can be observed via the `onError` callback.\n\n## Next Steps\n\nSee the links above for detailed documentation on each feature. For working examples, visit the [examples repository](https://github.com/voltagent/voltagent/tree/main/examples).\n","website/docs/agents/plan-agent.md":"---\ntitle: PlanAgent\nslug: /agents/plan-agent\n---\n\n# PlanAgent\n\nPlanAgent is a higher-level agent that adds built-in planning, task delegation, filesystem tools, and summarization on top of the standard `Agent`. Use it when you want multi-step execution with explicit plans and tighter control over context.\n\n## Default Agent (baseline)\n\nStart with a normal `Agent` if you only need straightforward responses.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst result = await agent.generateText(\"What is VoltAgent?\");\nconsole.log(result.text);\n```\n\n## PlanAgent quickstart\n\nPlanAgent works like `Agent`, but adds planning, filesystem, and subagent tooling by default.\n\n```ts\nimport { PlanAgent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst internetSearch = createTool({\n  name: \"internet_search\",\n  description: \"Search the web for information\",\n  parameters: z.object({\n    query: z.string().describe(\"Search query\"),\n    maxResults: z.number().optional().default(5),\n  }),\n  execute: async ({ query, maxResults }) => {\n    const response = await fetch(\"https://api.tavily.com/search\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        Authorization: `Bearer ${process.env.TAVILY_API_KEY}`,\n      },\n      body: JSON.stringify({ query, maxResults }),\n    });\n    return response.json();\n  },\n});\n\nconst agent = new PlanAgent({\n  name: \"Researcher\",\n  systemPrompt: \"You are an expert researcher. Produce clear, sourced answers.\",\n  model: \"openai/gpt-4o\",\n  tools: [internetSearch],\n});\n\nconst result = await agent.generateText(\"What is VoltAgent?\");\nconsole.log(result.text);\n```\n\n## Dynamic systemPrompt\n\n`systemPrompt` can be a function that resolves per request. It receives `{ context, prompts }` and can return a string or a VoltOps prompt payload.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"DynamicPlanner\",\n  model: openai(\"gpt-4o\"),\n  systemPrompt: async ({ context }) => {\n    const tenant = context.get(\"tenant\") ?? \"default\";\n    return `Tenant: ${tenant}. Keep answers concise.`;\n  },\n});\n\nconst context = new Map<string | symbol, unknown>();\ncontext.set(\"tenant\", \"acme\");\n\nconst result = await agent.generateText(\"Summarize the roadmap\", { context });\nconsole.log(result.text);\n```\n\nIf you return a chat prompt:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"DynamicChatPlanner\",\n  model: openai(\"gpt-4o\"),\n  systemPrompt: async () => ({\n    type: \"chat\",\n    messages: [{ role: \"system\", content: \"You are a precise planner.\" }],\n  }),\n});\n```\n\nPlanAgent still appends its base prompt and any extension prompts.\n\n## Built-in capabilities\n\n### Planning with write_todos\n\nPlanAgent injects a planning prompt and a `write_todos` tool. The agent is expected to create a plan before using tools or producing long answers.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Planner\",\n  systemPrompt: \"Plan carefully before acting.\",\n  model: \"openai/gpt-4o\",\n  planning: {\n    systemPrompt: \"Always plan any multi-step task. Keep 4-8 concise steps and update as you go.\",\n  },\n});\n```\n\nDisable planning if you want a lighter agent:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"NoPlan\",\n  systemPrompt: \"Answer quickly.\",\n  model: \"openai/gpt-4o\",\n  planning: false,\n});\n```\n\n### State and persistence\n\nPlanAgent stores its todo list, filesystem state, and summaries per conversation. When a conversation continues, it injects the latest state back into the system prompt so the model can keep working from the same plan and files.\n\n### Filesystem tools\n\nPlanAgent provides `ls`, `read_file`, `write_file`, `edit_file`, `glob`, and `grep`. The default backend is an in-memory filesystem, but you can use a real filesystem backend.\n\n```ts\nimport { NodeFilesystemBackend } from \"@voltagent/core\";\n\nconst agent = new PlanAgent({\n  name: \"FileAgent\",\n  systemPrompt: \"Use the filesystem to store and retrieve context.\",\n  model: \"openai/gpt-4o\",\n  filesystem: {\n    backend: new NodeFilesystemBackend({\n      rootDir: process.cwd(),\n      virtualMode: true,\n    }),\n  },\n});\n```\n\n### Task tool and subagents\n\nPlanAgent adds a `task` tool that can delegate work to subagents for isolated, multi-step tasks.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate deep research tasks to subagents.\",\n  model: \"openai/gpt-4o\",\n  subagents: [\n    {\n      name: \"research-analyst\",\n      description: \"Deep research and synthesis\",\n      systemPrompt: \"Research thoroughly and return a concise report.\",\n      tools: [internetSearch],\n    },\n  ],\n});\n```\n\nYou can also tune the task tool behavior:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  task: {\n    taskDescription: \"Use subagents for complex, independent work.\",\n    maxSteps: 6,\n  },\n});\n```\n\nTo forward subagent stream events when you call `planAgent.streamText(...)`, configure the task's supervisor config:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  task: {\n    supervisorConfig: {\n      fullStreamEventForwarding: {\n        types: [\"tool-call\", \"tool-result\", \"text-delta\"],\n      },\n    },\n  },\n});\n```\n\nPlanAgent also adds a default `general-purpose` subagent unless you disable it:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  generalPurposeAgent: false,\n});\n```\n\n### Summarization\n\nPlanAgent can automatically summarize older conversation history and keep recent messages in context.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Summarizer\",\n  systemPrompt: \"Keep context tight.\",\n  model: \"openai/gpt-4o\",\n  summarization: {\n    triggerTokens: 120_000,\n    keepMessages: 6,\n    maxOutputTokens: 600,\n  },\n});\n```\n\nDisable it when you want full context preserved:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"NoSummary\",\n  systemPrompt: \"Keep full context.\",\n  model: \"openai/gpt-4o\",\n  summarization: false,\n});\n```\n\n### Tool result eviction\n\nLarge tool outputs can be offloaded to the filesystem to keep the model context small.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Evictor\",\n  systemPrompt: \"Store large tool outputs on the filesystem.\",\n  model: \"openai/gpt-4o\",\n  toolResultEviction: {\n    enabled: true,\n    tokenLimit: 20000,\n  },\n});\n```\n\n### Extensions\n\nUse `extensions` to add new toolkits, prompts, or hooks without forking PlanAgent.\n\n```ts\nimport type { PlanAgentExtension } from \"@voltagent/core\";\n\nconst debugExtension: PlanAgentExtension = {\n  name: \"debug\",\n  apply: () => ({\n    systemPrompt: \"Log key decisions to the user.\",\n  }),\n};\n\nconst agent = new PlanAgent({\n  name: \"Extended\",\n  systemPrompt: \"Be helpful.\",\n  model: \"openai/gpt-4o\",\n  extensions: [debugExtension],\n});\n```\n\n## Configuration reference (high-level)\n\n- `systemPrompt`: static string or dynamic function for per-request prompts\n- `planning`: configure or disable planning (`write_todos`)\n- `filesystem`: configure backend and tool prompts\n- `task`: configure task tool prompts and limits\n- `subagents`: custom subagent definitions\n- `generalPurposeAgent`: add/remove the default general-purpose subagent\n- `summarization`: configure summarization triggers and model\n- `toolResultEviction`: offload large tool results to filesystem\n- `extensions`: attach custom tooling or hooks\n\nUse PlanAgent when your workloads require explicit plans, context offloading, or structured delegation to subagents.\n","website/docs/agents/prompts.md":"# Agent Instructions\n\n## Overview\n\nVoltAgent supports three approaches for defining agent instructions. Each approach addresses different requirements around flexibility, team collaboration, and deployment workflows.\n\n### The Three Approaches\n\n| Approach                 | Definition                    | Context Access | Team Workflow    |\n| ------------------------ | ----------------------------- | -------------- | ---------------- |\n| **Static Instructions**  | Hardcoded string              | No             | Code-based       |\n| **Dynamic Instructions** | Function with runtime context | Yes            | Code-based       |\n| **VoltOps Management**   | Externally managed prompts    | Yes            | Platform-managed |\n\n## Static Instructions\n\nStatic instructions are literal strings assigned to the `instructions` property.\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: \"You are a customer support agent. Help users with their questions.\",\n});\n```\n\n**Type signature:**\n\n```typescript\ninstructions: string;\n```\n\n### When to Use\n\nUse static instructions when:\n\n- Agent behavior is consistent across all interactions\n- No runtime context is needed\n- Instructions rarely change\n- Team members edit prompts through code reviews\n\nAvoid when:\n\n- Different users need different behavior\n- Instructions depend on runtime data (user tier, time, location)\n- Non-technical team members need to edit prompts\n- You need prompt versioning outside of code commits\n\n## Dynamic Instructions\n\nDynamic instructions are functions that receive runtime context and return instructions.\n\n### Returning Strings\n\nFunctions can return a plain string based on context:\n\n```typescript\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ context }) => {\n    const userTier = context.get(\"userTier\") || \"basic\";\n\n    if (userTier === \"premium\") {\n      return \"You are a premium customer support agent. Provide detailed explanations and prioritize this customer's requests.\";\n    }\n    return \"You are a customer support agent. Provide helpful but concise answers.\";\n  },\n});\n```\n\n**Using with context:**\n\n```typescript\nconst premiumContext = new Map();\npremiumContext.set(\"userTier\", \"premium\");\n\nconst response = await agent.generateText(\"I need help\", {\n  context: premiumContext,\n});\n```\n\n### Returning PromptContent Objects\n\nFunctions can also return `PromptContent` objects for text or chat-based instructions.\n\n**Text type:**\n\n```typescript\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ context }) => {\n    return {\n      type: \"text\",\n      text: \"You are a customer support agent.\",\n    };\n  },\n});\n```\n\n**Chat type with multiple messages:**\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"ChatAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async () => {\n    return {\n      type: \"chat\",\n      messages: [\n        {\n          role: \"system\",\n          content: \"You are a helpful assistant.\",\n        },\n        {\n          role: \"user\",\n          content: \"Hello!\",\n        },\n        {\n          role: \"assistant\",\n          content: \"Hi! How can I help you today?\",\n        },\n      ],\n    };\n  },\n});\n```\n\n**Chat type with provider-specific options:**\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"CachedAgent\",\n  model: \"anthropic/claude-3-7-sonnet-20250219\",\n  instructions: async () => {\n    return {\n      type: \"chat\",\n      messages: [\n        {\n          role: \"system\",\n          content: \"Long system prompt that should be cached...\",\n          providerOptions: {\n            anthropic: {\n              cacheControl: { type: \"ephemeral\", ttl: \"5m\" },\n            },\n          },\n        },\n      ],\n    };\n  },\n});\n```\n\n**Type signature:**\n\n```typescript\ninstructions: (options: DynamicValueOptions) => Promise<string | PromptContent>;\n\ninterface DynamicValueOptions {\n  context: Map<string | symbol, unknown>;\n  headers?: Record<string, string>;\n  prompts: PromptHelper;\n}\n\ninterface PromptContent {\n  type: \"text\" | \"chat\";\n  text?: string;\n  messages?: ChatMessage[];\n}\n```\n\n### When to Use\n\nUse dynamic instructions when:\n\n- Agent behavior depends on user properties (tier, role, preferences)\n- Instructions need runtime data (time, location, session state)\n- Different tenants require different behavior\n- Conditional logic determines instruction content\n\nAvoid when:\n\n- Multiple non-technical stakeholders need to edit prompts\n- You need prompt version history outside of code\n- Collaborative prompt editing is required\n- Prompts should update without deploying code\n\n## VoltOps Prompt Management\n\nVoltOps separates prompt content from application code. Prompts are created and versioned in the VoltOps platform, then fetched at runtime.\n\nFor complete documentation on VoltOps Prompt Management, see:\n\n- [Prompt Management Overview](/prompt-engineering-docs/) - Creating, versioning, labels, and managing prompts\n- [Usage Guide](/prompt-engineering-docs/usage/) - Integration with agents, caching, variables, and error handling\n\n### Quick Example\n\n```typescript\nimport { Agent, VoltAgent, VoltOpsClient } from \"@voltagent/core\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ prompts }) => {\n    return await prompts.getPrompt({\n      promptName: \"customer-support-prompt\",\n      label: \"production\",\n      variables: { companyName: \"VoltAgent Corp\" },\n    });\n  },\n});\n\nnew VoltAgent({\n  agents: { agent },\n  voltOpsClient: voltOpsClient,\n});\n```\n\n### When to Use\n\nUse VoltOps when:\n\n- Non-technical team members edit prompts\n- Audit trails and approval workflows are required\n- Multiple environments need different prompt versions\n- Prompt analytics and monitoring are needed\n\nAvoid when:\n\n- External dependencies are not acceptable\n- Offline operation is required\n\n## Comparison\n\n| Feature                   | Static      | Dynamic     | VoltOps            |\n| ------------------------- | ----------- | ----------- | ------------------ |\n| **Type**                  | String      | Function    | External platform  |\n| **Context Access**        | No          | Yes         | Yes (via function) |\n| **Runtime Flexibility**   | None        | Full        | Full               |\n| **Team Collaboration**    | Code review | Code review | Platform UI        |\n| **Version Control**       | Git         | Git         | VoltOps + Git      |\n| **Non-technical Editing** | No          | No          | Yes                |\n| **Analytics**             | No          | No          | Yes                |\n| **Offline Support**       | Yes         | Yes         | No                 |\n| **External Dependency**   | No          | No          | Yes                |\n\n## Examples\n\n**Static (solo developer, consistent behavior):**\n\n```typescript\nconst agent = new Agent({\n  instructions: \"You are a code reviewer. Focus on security and performance.\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n**Dynamic (user-specific behavior):**\n\n```typescript\nconst agent = new Agent({\n  instructions: async ({ context }) => {\n    const tier = context.get(\"tier\");\n    return tier === \"premium\"\n      ? \"You are a premium support agent with deep technical expertise.\"\n      : \"You are a support agent providing efficient solutions.\";\n  },\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n**VoltOps (team collaboration, versioning):**\n\n```typescript\nconst agent = new Agent({\n  instructions: async ({ prompts }) => {\n    return await prompts.getPrompt({\n      promptName: \"customer-support-agent\",\n      label: process.env.NODE_ENV === \"production\" ? \"production\" : \"development\",\n    });\n  },\n  model: \"openai/gpt-4o-mini\",\n  voltOpsClient: voltOpsClient,\n});\n```\n","website/docs/agents/providers.md":"---\ntitle: Using Models\nslug: /agents/providers\n---\n\n# Using Models with Agents\n\nVoltAgent 1.x has no `llm` provider prop. Set `model` to either:\n\n- An ai-sdk `LanguageModel` (imported from a provider package), or\n- A model string like `openai/gpt-4o-mini` resolved by the built-in model router.\n\n## Quick Example\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"my-agent\",\n  instructions: \"Helpful and concise\",\n  model: \"anthropic/claude-3-5-sonnet\",\n});\n\nconst res = await agent.generateText(\"Say hi\", { temperature: 0.2 });\nconsole.log(res.text);\n```\n\nYou can also pass a `LanguageModel` directly:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport { anthropic } from \"@ai-sdk/anthropic\";\n\nconst agent = new Agent({\n  name: \"my-agent\",\n  instructions: \"Helpful and concise\",\n  model: anthropic(\"claude-3-5-sonnet\"),\n});\n```\n\n## Provider Options (IntelliSense)\n\nUse `providerOptions` for provider-specific settings in `generateText`, `streamText`, `generateObject`, and `streamObject` calls.\n\n```ts\nawait agent.generateText(\"Draft a summary\", {\n  temperature: 0.3,\n  maxOutputTokens: 300,\n  providerOptions: {\n    openai: {\n      reasoningEffort: \"medium\",\n      textVerbosity: \"low\",\n    },\n    anthropic: {\n      sendReasoning: true,\n      cacheControl: { type: \"ephemeral\" },\n    },\n    google: {\n      thinkingConfig: {\n        thinkingBudget: 1024,\n      },\n    },\n    xai: {\n      reasoningEffort: \"medium\",\n    },\n  },\n});\n```\n\nTypeScript IntelliSense is available for `openai`, `anthropic`, `google`, and `xai` option objects.\n\n## Provider Selection\n\nUse any ai-sdk provider package you have configured (OpenAI, Anthropic, Google, Groq, Mistral, Vertex, Bedrock, etc.), or use model strings.\nFor installation and an up-to-date model matrix, see:\n\n**[Providers & Models](/docs/getting-started/providers-models)**\n\nFor how model strings are resolved and how environment variables map, see:\n\n**[Model Router & Registry](/docs/getting-started/model-router)**\n\n## Retries and Fallbacks\n\nFor retry counts and fallback lists, see:\n\n**[Retries and Fallbacks](/docs/agents/retries-fallback)**\n","website/docs/agents/resumable-streaming.md":"---\ntitle: Resumable Streaming\nslug: /agents/resumable-streaming\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Resumable Streaming\n\nResumable streaming lets a client reconnect to an in-flight stream (for example after a refresh) and continue receiving the same response. VoltAgent provides this via `@voltagent/resumable-streams`.\n\n<video controls loop muted playsInline style={{width: '100%', height: 'auto'}}>\n\n  <source src=\"https://cdn.voltagent.dev/docs/resumable-stream.mp4\" type=\"video/mp4\" />\n  Your browser does not support the video tag.\n</video>\n\n## Install\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n    ```bash\n    npm install @voltagent/resumable-streams\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/resumable-streams\n    ```\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n    ```bash\n    pnpm add @voltagent/resumable-streams\n    ```\n  </TabItem>\n</Tabs>\n\n## How it works\n\nResumable streaming is split into two storages:\n\n1. **Stream store**: stores stream chunks and pub/sub messages.\n2. **Active stream store**: maps `userId + \"-\" + conversationId` -> `streamId`.\n\nWhen a stream starts, a new `streamId` is created, the stream store persists the output, and the active stream store is updated. When the client reconnects, the active stream store is used to find the `streamId`, and the stream store resumes from the last position. When the stream finishes, the active stream store entry is cleared.\n\n## Basic usage (Hono server)\n\nCreate a Redis-backed store, build the adapter, and pass it to the Hono server.\n\n```ts\nimport { Agent, VoltAgent } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst resumableStream = await createResumableStreamAdapter({ streamStore });\n\nconst agent = new Agent({\n  id: \"assistant\",\n  name: \"Resumable Stream Agent\",\n  instructions: \"You are a helpful assistant.\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer({\n    resumableStream: { adapter: resumableStream },\n  }),\n});\n```\n\nBy default, the adapter uses `streamStore` as the `activeStreamStore`.  \nFor production, use a shared stream store (Redis) so multiple instances can resume.\n\n### Enabling resumable streams\n\nResumable streams are **opt-in**. You must set:\n\n- `options.resumableStream: true`\n- `options.conversationId`\n- `options.userId`\n\n```ts\nawait fetch(`${baseUrl}/agents/${agentId}/chat`, {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: `{\"input\":\"Hello!\",\"options\":{\"conversationId\":\"conv-1\",\"userId\":\"user-1\",\"resumableStream\":true}}`,\n});\n```\n\n### Server-level default (optional)\n\nIf you want resumable streaming enabled by default, set it at the server level and omit the per-request flag. You can still opt out by sending `options.resumableStream: false`.\n\n```ts\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer({\n    resumableStream: {\n      adapter: resumableStream,\n      defaultEnabled: true,\n    },\n  }),\n});\n```\n\n### Resume endpoint\n\nThe Hono server exposes:\n\n- `GET /agents/:id/chat/:conversationId/stream?userId=...`\n\nIf there is no active stream, it returns `204`. `userId` is required.\n\n## Store options\n\n### VoltOps managed store\n\n`createResumableStreamVoltOpsStore` stores streams in VoltOps. It uses:\n\n- the global `VoltOpsClient` if one is already configured by `VoltAgent`, or\n- your explicit `voltOpsClient`, or\n- `VOLTAGENT_PUBLIC_KEY` + `VOLTAGENT_SECRET_KEY` from env.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamVoltOpsStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamVoltOpsStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\nIf you need a custom base URL, pass `baseUrl` or set `VOLTAGENT_API_BASE_URL`.\n\nCustom VoltOps client:\n\n```ts\nimport { VoltOpsClient } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamVoltOpsStore,\n} from \"@voltagent/resumable-streams\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst streamStore = await createResumableStreamVoltOpsStore({ voltOpsClient });\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### Redis store (recommended)\n\n`createResumableStreamRedisStore` reads `REDIS_URL` or `KV_URL` by default.  \nIf you need a custom connection string, pass your own Redis clients as `publisher` and `subscriber`.\n\n```ts\nimport { createClient } from \"redis\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\n// Option A: use REDIS_URL or KV_URL from env\nconst streamStore = await createResumableStreamRedisStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n\n// Option B: pass your own clients (custom connection string)\nconst publisher = createClient({ url: \"redis://localhost:6379\" });\nconst subscriber = createClient({ url: \"redis://localhost:6379\" });\nconst customStreamStore = await createResumableStreamRedisStore({ publisher, subscriber });\nconst adapter = await createResumableStreamAdapter({ streamStore: customStreamStore });\n```\n\nIf you pass custom clients, you must connect them yourself.\n\n### Memory store (dev/test)\n\n`createResumableStreamMemoryStore` keeps everything in-process.  \nData is lost on restart and not shared across instances.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamMemoryStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamMemoryStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### Generic store (custom backend)\n\n`createResumableStreamGenericStore` accepts your own `publisher`/`subscriber`.  \nUse this for non-Redis backends that still provide pub/sub + KV semantics.\n\n```ts\nimport type {\n  ResumableStreamPublisher,\n  ResumableStreamSubscriber,\n} from \"@voltagent/resumable-streams\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamGenericStore,\n} from \"@voltagent/resumable-streams\";\n\nconst publisher: ResumableStreamPublisher = {\n  async connect() {},\n  async publish(channel, message) {\n    return 1;\n  },\n  async set(key, value, options) {\n    return \"OK\";\n  },\n  async get(key) {\n    return null;\n  },\n  async incr(key) {\n    return 1;\n  },\n};\n\nconst subscriber: ResumableStreamSubscriber = {\n  async connect() {},\n  async subscribe(channel, callback) {\n    return 1;\n  },\n  async unsubscribe(channel) {},\n};\n\nconst streamStore = await createResumableStreamGenericStore({\n  publisher,\n  subscriber,\n  keyPrefix: \"voltagent\",\n  waitUntil: null,\n});\n\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n## Next.js + AI SDK (useChat)\n\nThis section mirrors the `with-nextjs-resumable-stream` example. Full project:  \nhttps://github.com/VoltAgent/voltagent/tree/main/examples/with-nextjs-resumable-stream\n\n### 1) Adapter helper\n\nCreate the adapter once and reuse it across routes.\n\n```ts\n// lib/resumable-stream.ts\nimport type { ResumableStreamAdapter } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\nimport { after } from \"next/server\";\n\nlet adapterPromise: Promise<ResumableStreamAdapter> | undefined;\n\nexport function getResumableStreamAdapter() {\n  if (!adapterPromise) {\n    adapterPromise = (async () => {\n      const streamStore = await createResumableStreamRedisStore({ waitUntil: after });\n      return createResumableStreamAdapter({ streamStore });\n    })();\n  }\n\n  return adapterPromise;\n}\n```\n\n### 2) POST route (create stream)\n\n```ts\n// app/api/chat/route.ts\nimport { getResumableStreamAdapter } from \"@/lib/resumable-stream\";\nimport { agent } from \"@/voltagent\";\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\nimport { setWaitUntil } from \"@voltagent/core\";\nimport { after } from \"next/server\";\n\nexport async function POST(req: Request) {\n  const body = await req.json();\n  const { message, messages, options } = body;\n  const conversationId = options.conversationId;\n  const userId = options.userId;\n  const input = message ?? messages;\n\n  setWaitUntil(after);\n\n  const adapter = await getResumableStreamAdapter();\n  const session = createResumableChatSession({\n    adapter,\n    conversationId,\n    userId,\n    agentId: agent.id,\n  });\n\n  await session.clearActiveStream();\n\n  const result = await agent.streamText(input, {\n    userId,\n    conversationId,\n  });\n\n  return result.toUIMessageStreamResponse({\n    consumeSseStream: session.consumeSseStream,\n    onFinish: session.onFinish,\n  });\n}\n```\n\n### 3) GET route (resume stream)\n\n```ts\n// app/api/chat/[id]/stream/route.ts\nimport { getResumableStreamAdapter } from \"@/lib/resumable-stream\";\nimport { agent } from \"@/voltagent\";\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\n\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params;\n  const userId = new URL(request.url).searchParams.get(\"userId\") as string;\n\n  const adapter = await getResumableStreamAdapter();\n  const session = createResumableChatSession({\n    adapter,\n    conversationId: id,\n    userId,\n    agentId: agent.id,\n  });\n\n  return session.resumeResponse();\n}\n```\n\n### 4) Client-side resume\n\n```tsx\n// components/chat-interface.tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\n\nconst { messages, sendMessage } = useChat({\n  id: chatId,\n  messages: initialMessages,\n  resume: true,\n  transport: new DefaultChatTransport({\n    api: \"/api/chat\",\n    prepareSendMessagesRequest: ({ id, messages }) => ({\n      body: {\n        message: messages[messages.length - 1],\n        options: {\n          conversationId: id,\n          userId,\n        },\n      },\n    }),\n    prepareReconnectToStreamRequest: ({ id }) => ({\n      api: `/api/chat/${id}/stream?userId=${encodeURIComponent(userId)}`,\n    }),\n  }),\n});\n```\n\n## Custom usage (no AI SDK)\n\nUse `createResumableChatSession` when you control the SSE pipeline.\n\n```ts\nimport {\n  createResumableChatSession,\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n\nconst sseHeaders = {\n  \"Content-Type\": \"text/event-stream\",\n  \"Cache-Control\": \"no-cache\",\n  Connection: \"keep-alive\",\n};\n\nexport async function POST(req: Request) {\n  const { options, agentId, text } = await req.json();\n  const conversationId = options.conversationId;\n  const userId = options.userId;\n\n  const session = createResumableChatSession({\n    adapter,\n    conversationId,\n    userId,\n    agentId,\n    resumeHeaders: sseHeaders,\n  });\n\n  const stream = new ReadableStream<string>({\n    start(controller) {\n      controller.enqueue(`data: ${text}\\n\\n`);\n      controller.close();\n    },\n  });\n\n  const [persistStream, responseStream] = stream.tee();\n  await session.createStream(persistStream);\n\n  return new Response(responseStream.pipeThrough(new TextEncoderStream()), {\n    headers: sseHeaders,\n  });\n}\n```\n\nIf you want JSON payloads in SSE, serialize them with your preferred helper.\n\n## Route helpers (optional)\n\n`createResumableChatHandlers` can generate `POST`/`GET` handlers.  \nDefaults:\n\n- `conversationId`: `body.options.conversationId` (GET uses `params.id`)\n- `userId`: `body.options.userId` (GET uses `?userId=...`)\n\n## Custom stores and adapters\n\nYou have two extension points:\n\n- Implement `ResumableStreamStore` and pass it to the adapter.\n- Implement `ResumableStreamAdapter` directly for full control.\n\n`ResumableStreamStore` handles stream chunk storage.  \nIf it also implements `ResumableStreamActiveStore`, it can be used as `activeStreamStore`.  \nOtherwise, pass a separate `activeStreamStore`.\n\n## Important notes\n\n- Keying rule: `userId + \"-\" + conversationId`. `userId` is required.  \n  If you do not have a user identity, generate a stable id (for example `crypto.randomUUID()`).\n- When resumable streaming is enabled, the abort signal is removed.  \n  If you need abortable streams, do not use `resumableStream`.\n- Resumable streaming does not persist messages. For message persistence, enable memory.  \n  See [Memory](../agents/memory.md).\n\n## VoltOps plan limits (managed store)\n\nWhen you use the VoltOps managed store, concurrent resumable streams are limited per project:\n\n- Free: 1 concurrent stream\n- Core: 100 concurrent streams\n- Pro: 1000 concurrent streams\n\n## API reference (@voltagent/resumable-streams)\n\n### createResumableStreamAdapter\n\nBuilds the adapter used by servers or custom routes.\n\nProps:\n\n- `streamStore` (required): a `ResumableStreamStore` for stream chunks.\n- `activeStreamStore` (optional): overrides the active stream index.  \n  If omitted, the adapter uses `streamStore` when it implements `ResumableStreamActiveStore`.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore({ keyPrefix: \"voltagent\" });\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### createResumableStreamRedisStore\n\nCreates a Redis-backed stream store.\n\nProps:\n\n- `keyPrefix` (optional): prefix for Redis keys.\n- `waitUntil` (optional): keep-alive hook for serverless environments.\n- `publisher` (optional): Redis publisher client.\n- `subscriber` (optional): Redis subscriber client.\n\n```ts\nimport { createClient } from \"redis\";\nimport { createResumableStreamRedisStore } from \"@voltagent/resumable-streams\";\n\n// Option A: use REDIS_URL or KV_URL from env\nconst streamStore = await createResumableStreamRedisStore();\n\n// Option B: custom clients (custom connection string)\nconst publisher = createClient({ url: \"redis://localhost:6379\" });\nconst subscriber = createClient({ url: \"redis://localhost:6379\" });\nconst customStreamStore = await createResumableStreamRedisStore({ publisher, subscriber });\n```\n\nIf you pass custom clients, you must connect them yourself.\n\n### createResumableStreamVoltOpsStore\n\nCreates a managed store backed by VoltOps.\n\nProps:\n\n- `voltOpsClient` (optional)\n- `publicKey` / `secretKey` (optional, used if `voltOpsClient` is not provided)\n- `baseUrl` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport { createResumableStreamVoltOpsStore } from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamVoltOpsStore({\n  baseUrl: \"https://api.voltagent.dev\",\n});\n```\n\n### createResumableStreamMemoryStore\n\nCreates an in-process stream store (dev/test).\n\nProps:\n\n- `keyPrefix` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport { createResumableStreamMemoryStore } from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamMemoryStore({ keyPrefix: \"local\" });\n```\n\n### createResumableStreamGenericStore\n\nCreates a store from a custom pub/sub + KV backend.\n\nProps:\n\n- `publisher` (required)\n- `subscriber` (required)\n- `keyPrefix` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport type {\n  ResumableStreamPublisher,\n  ResumableStreamSubscriber,\n} from \"@voltagent/resumable-streams\";\nimport { createResumableStreamGenericStore } from \"@voltagent/resumable-streams\";\n\nconst publisher: ResumableStreamPublisher = {\n  async connect() {},\n  async publish(channel, message) {\n    return 1;\n  },\n  async set(key, value, options) {\n    return \"OK\";\n  },\n  async get(key) {\n    return null;\n  },\n  async incr(key) {\n    return 1;\n  },\n};\n\nconst subscriber: ResumableStreamSubscriber = {\n  async connect() {},\n  async subscribe(channel, callback) {\n    return 1;\n  },\n  async unsubscribe(channel) {},\n};\n\nconst streamStore = await createResumableStreamGenericStore({\n  publisher,\n  subscriber,\n  keyPrefix: \"voltagent\",\n  waitUntil: null,\n});\n```\n\n### createMemoryResumableStreamActiveStore\n\nCreates an in-memory active stream index. Use this to override `activeStreamStore`.\n\n```ts\nimport {\n  createMemoryResumableStreamActiveStore,\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst activeStreamStore = createMemoryResumableStreamActiveStore();\nconst adapter = await createResumableStreamAdapter({ streamStore, activeStreamStore });\n```\n\n### createResumableChatSession\n\nBuilds a session helper for custom route handlers.\n\nProps:\n\n- `adapter` (required)\n- `conversationId` (required)\n- `userId` (required)\n- `agentId` (optional)\n- `logger` (optional)\n- `resumeHeaders` (optional): headers for SSE resume responses\n\n```ts\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\n\nconst session = createResumableChatSession({\n  adapter,\n  conversationId,\n  userId,\n  agentId,\n});\n\nconst result = await agent.streamText(input, { conversationId, userId });\nreturn result.toUIMessageStreamResponse({\n  consumeSseStream: session.consumeSseStream,\n  onFinish: session.onFinish,\n});\n```\n\n```ts\nreturn session.resumeResponse();\n```\n\n### createResumableChatHandlers\n\nGenerates `POST` and `GET` handlers.\n\nProps:\n\n- `agent` (required)\n- `adapter` (required)\n- `waitUntil` (optional)\n- `logger` (optional)\n- `agentId` (optional)\n- `sendReasoning` (optional)\n- `sendSources` (optional)\n- `resolveInput` (optional)\n- `resolveConversationId` (optional)\n- `resolveUserId` (optional)\n- `resolveOptions` (optional)\n\n```ts\nimport { createResumableChatHandlers } from \"@voltagent/resumable-streams\";\n\nconst { POST, GET } = createResumableChatHandlers({\n  agent,\n  adapter,\n  resolveConversationId: ({ body }) => body.options.conversationId,\n  resolveUserId: ({ body }) => body.options.userId,\n});\n\nexport { POST, GET };\n```\n\n## Type contracts\n\n### ResumableStreamStore\n\n- `createNewResumableStream(streamId, makeStream, skipCharacters?)`\n- `resumeExistingStream(streamId, skipCharacters?)`\n\n### ResumableStreamActiveStore\n\n- `getActiveStreamId(context)`\n- `setActiveStreamId(context, streamId)`\n- `clearActiveStream(context)`\n\n### ResumableStreamAdapter\n\n- `createStream({ conversationId, userId, stream, agentId? })`\n- `resumeStream(streamId)`\n- `getActiveStreamId(context)`\n- `clearActiveStream(context)`\n\n### ResumableStreamPublisher / ResumableStreamSubscriber\n\nPublisher methods:\n\n- `connect()`\n- `publish(channel, message)`\n- `set(key, value, { EX })` (TTL in seconds)\n- `get(key)`\n- `incr(key)`\n\nSubscriber methods:\n\n- `connect()`\n- `subscribe(channel, callback)`\n- `unsubscribe(channel)`\n","website/docs/agents/retries-fallback.md":"---\ntitle: Retries and Fallbacks\nslug: /agents/retries-fallback\n---\n\n# Retries and Fallbacks\n\nVoltAgent supports per-call retries and ordered model fallback lists. Retries are handled by VoltAgent with exponential backoff. After retries are exhausted for a model, VoltAgent attempts the next model in the list.\n\n## Configure Retries\n\nYou can set a default retry count at the agent level, then override it per model or per call.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Support\",\n  instructions: \"Resolve tickets quickly.\",\n  model: \"openai/gpt-4o-mini\",\n  maxRetries: 3,\n});\n\n// Per-call override\nawait agent.generateText(\"Summarize this ticket\", { maxRetries: 0 });\n```\n\n- `maxRetries` is the number of retry attempts for a single model call (total attempts = `maxRetries + 1`).\n- Priority order: per-call `maxRetries` > per-model `maxRetries` > agent `maxRetries` > default `3`.\n- `0` disables retries for that call.\n- Retry delay uses exponential backoff: 1s, 2s, 4s, 8s (max 10s).\n- Errors with `isRetryable: false` skip retries; fallback can still run.\n\n## Configure Fallbacks\n\nProvide a list of models. VoltAgent tries them in order until one succeeds.\nEach entry can have its own retry count and can be toggled on/off.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport { anthropic } from \"@ai-sdk/anthropic\";\n\nconst agent = new Agent({\n  name: \"FallbackAgent\",\n  instructions: \"Be concise.\",\n  model: [\n    { id: \"primary\", model: \"openai/gpt-4o-mini\", maxRetries: 2 },\n    { id: \"secondary\", model: anthropic(\"claude-3-5-sonnet\"), maxRetries: 1 },\n    { id: \"tertiary\", model: \"google/gemini-2.0-flash\", enabled: true },\n  ],\n});\n```\n\nNotes:\n\n- The first enabled model is the primary.\n- `maxRetries` falls back to the agent default if omitted.\n- `enabled: false` lets you keep a model configured but temporarily disabled.\n\n## How Fallback Works\n\nVoltAgent tries each enabled model in order:\n\n1. Resolve the model (dynamic or static).\n2. Execute the call with that model, retrying up to `maxRetries` on error.\n3. If retries are exhausted, move to the next model.\n\nFallback **does not** trigger for:\n\n- Abort or bail errors.\n- Guardrail blocks.\n- Tool execution errors.\n\nStreaming behavior:\n\n- Retries and fallback happen only if the stream fails before the first output chunk.\n- If a stream fails after output starts, the error is surfaced to the caller and the stream is not restarted.\n\n## Middleware Retries\n\nMiddleware retries are separate from model retries and fallback. A middleware can call `abort(\"reason\", { retry: true })` to restart the full attempt.\n\n- Controlled by `maxMiddlewareRetries` (agent-level or per-call).\n- Each retry reruns the full pipeline: middleware, guardrails, hooks, model selection, model retries, and fallback.\n- In streaming calls, only input middlewares can trigger a retry before the stream starts.\n\nSee [Middleware](/docs/agents/middleware) for configuration and API details.\n\n## Real-World Scenarios\n\n### 1. Provider Outage During a Traffic Spike\n\nThe primary provider returns 5xx errors during a traffic spike. Configure a fallback provider with its own retry count.\n\n```ts\nconst agent = new Agent({\n  name: \"LaunchAssistant\",\n  instructions: \"Handle onboarding questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n    { model: \"anthropic/claude-3-5-sonnet\", maxRetries: 2 },\n  ],\n});\n```\n\nExpected behavior:\n\n- Primary errors are retried up to `maxRetries`.\n- After retries, the secondary provider is attempted.\n\n### 2. Rate Limits During Peak Hours\n\nThe primary model returns 429 during peak hours. Set `maxRetries: 0` on the primary and allow retries on the fallback.\n\n```ts\nconst agent = new Agent({\n  name: \"PeakTrafficAgent\",\n  instructions: \"Answer pricing questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 0 },\n    { model: \"groq/llama-3.3-70b-versatile\", maxRetries: 2 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The primary fails without retry delay.\n- The fallback is attempted and can retry if needed.\n\n### 3. Timeouts in One Region\n\nRequests in a region start timing out. Configure a regional primary with a global fallback.\n\n```ts\nconst agent = new Agent({\n  name: \"RegionalAgent\",\n  instructions: \"Support users in APAC.\",\n  model: [\n    { model: \"google/gemini-2.0-flash\", maxRetries: 1 },\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n  ],\n});\n```\n\nExpected behavior:\n\n- Timeouts on the primary move the call to the fallback after retries.\n- The fallback handles calls that the primary fails.\n\n### 4. Compliance or Data Residency Constraints\n\nSome users require a specific provider for data residency. Select the primary model dynamically and keep a fallback entry.\n\n```ts\nconst agent = new Agent({\n  name: \"ComplianceAgent\",\n  instructions: \"Handle regulated requests.\",\n  model: [\n    {\n      model: async ({ context }) => {\n        const region = (context.get(\"region\") as string) || \"us\";\n        return region === \"eu\" ? \"mistral/mistral-large-latest\" : \"openai/gpt-4o-mini\";\n      },\n      maxRetries: 1,\n    },\n    { model: \"anthropic/claude-3-5-sonnet\", maxRetries: 1 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The model selection uses `context` values on each call.\n- If the selected model fails, the fallback is attempted.\n\n### 5. Cost Control with Fallback\n\nUse a lower-cost primary model and a second model for error cases.\n\n```ts\nconst agent = new Agent({\n  name: \"CostAwareAgent\",\n  instructions: \"Handle receipts and billing questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n    { model: \"openai/gpt-4o\", maxRetries: 0 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The primary handles successful calls.\n- The fallback is only used when the primary errors.\n\n## Troubleshooting Tips\n\n- If fallbacks never trigger, check for guardrail blocks or tool errors.\n- If retries are skipped, check whether the provider marks the error as `isRetryable: false`.\n- If all configured models are disabled, VoltAgent throws `MODEL_LIST_EMPTY`.\n- For long streaming sessions, consider application-level retry on stream errors.\n- Keep the fallback list short to avoid long end-to-end latency when providers are degraded.\n","website/docs/agents/subagents.md":"---\ntitle: Sub-agents\nslug: /agents/sub-agents\n---\n\n# Sub-agents\n\nSub-agents are agents that work under a supervisor agent to handle specific tasks. This architecture allows you to create agent workflows where each sub-agent focuses on a specific domain, coordinated by a supervisor.\n\n## Why Use Sub-agents?\n\n- **Task delegation**: Assign specific tasks to agents configured for particular domains (e.g., coding, translation, data analysis)\n- **Workflow orchestration**: Build multi-step workflows by delegating tasks to appropriate agents\n- **Code organization**: Break down complex problems into smaller components\n- **Modularity**: Add or swap agents without disrupting the entire system\n\n## Creating and Using Sub-agents\n\n### Creating Individual Agents\n\nCreate the agents that will serve as sub-agents:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Create an agent for content creation\nconst contentCreatorAgent = new Agent({\n  name: \"ContentCreator\",\n  instructions: \"Creates short text content on requested topics\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Create an agent for formatting\nconst formatterAgent = new Agent({\n  name: \"Formatter\",\n  instructions: \"Formats and styles text content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Give sub-agents a concise purpose to control what the supervisor sees\nconst summarizerAgent = new Agent({\n  name: \"Summarizer\",\n  purpose: \"Summarize long support tickets\",\n  instructions:\n    \"Read the conversation and produce a concise summary highlighting blockers and owners\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n### Creating a Supervisor Agent\n\nPass the agents in the `subAgents` array during supervisor initialization:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst supervisorAgent = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinates between content creation and formatting agents\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [contentCreatorAgent, formatterAgent, summarizerAgent],\n});\n```\n\n:::tip Advanced Sub-agent Configuration\nBy default, sub-agents use the `streamText` method. You can specify different methods like `generateText`, `generateObject`, or `streamObject` with custom schemas and options.\n\nSee: [Advanced Configuration](#advanced-configuration)\n:::\n\n## Customizing Supervisor Behavior\n\nSupervisor agents use an automatically generated system message that includes guidelines for managing sub-agents. Customize this behavior using the `supervisorConfig` option.\n\n### Purpose vs. Instructions (what the supervisor sees)\n\n- The supervisor lists sub-agents in a `<specialized_agents>` block using the sub-agent `purpose` when provided; if `purpose` is missing, it falls back to `instructions`, and if both are missing it uses `\"Dynamic instructions\"`.\n- Set a short `purpose` to avoid leaking long/verbose instructions into the supervisor’s prompt and to keep the prompt small. The full `instructions` still run for the sub-agent itself when delegated.\n- If you leave `purpose` empty, expect the supervisor prompt to include the entire `instructions` string for that sub-agent.\n\n:::info Default System Message\nSee the [generateSupervisorSystemMessage implementation](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/subagent/index.ts#L131) on GitHub.\n:::\n\n:::note Type Safety\nThe `supervisorConfig` option is only available when `subAgents` are provided. TypeScript will prevent you from using `supervisorConfig` on agents without sub-agents.\n:::\n\n### Basic Supervisor Configuration\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst supervisorAgent = new Agent({\n  name: \"Content Supervisor\",\n  instructions: \"Coordinate content creation workflow\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    // Add custom guidelines to the default ones\n    customGuidelines: [\n      \"Always thank the user at the end\",\n      \"Keep responses concise and actionable\",\n      \"Prioritize user experience\",\n    ],\n\n    // Control whether prior sub-agent interactions are injected into supervisor prompt\n    includeAgentsMemory: true, // default: true\n  },\n});\n```\n\n### Stream Event Forwarding Configuration\n\nControl which events from sub-agents are forwarded to the parent stream. By default, only `tool-call` and `tool-result` events are forwarded.\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Content Supervisor\",\n  instructions: \"Coordinate content creation workflow\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    // Configure which sub-agent events to forward\n    fullStreamEventForwarding: {\n      // Default: ['tool-call', 'tool-result']\n      types: [\n        \"tool-call\",\n        \"tool-result\",\n        \"text-delta\",\n        \"reasoning-start\",\n        \"reasoning-delta\",\n        \"reasoning-end\",\n        \"source\",\n        \"error\",\n        \"finish\",\n      ],\n    },\n  },\n});\n```\n\n**Common Configurations:**\n\n```ts\n// Minimal - Only tool events (default)\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result'],\n}\n\n// Text + Tools - Include text generation\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result', 'text-delta'],\n}\n\n// Full visibility - All events including reasoning\nfullStreamEventForwarding: {\n  types: [\n    'tool-call',\n    'tool-result',\n    'text-delta',\n    'reasoning-start',\n    'reasoning-delta',\n    'reasoning-end',\n    'source',\n    'error',\n    'finish'\n  ],\n}\n\n// Clean tool names - No agent prefix (add prefix manually when consuming events if desired)\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result'],\n}\n```\n\nThis configuration balances stream performance and information detail for sub-agent interactions.\n\n### Error Handling Configuration\n\nControl how the supervisor handles sub-agent failures.\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate between agents\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [dataProcessor, analyzer],\n\n  supervisorConfig: {\n    // Control whether stream errors throw exceptions\n    throwOnStreamError: false, // default: false\n\n    // Control whether error messages appear in empty responses\n    includeErrorInEmptyResponse: true, // default: true\n  },\n});\n```\n\n#### Configuration Options\n\n**`throwOnStreamError`** (boolean, default: `false`)\n\n- When `false`: Stream errors are caught and returned as error results with `status: \"error\"`\n- When `true`: Stream errors throw exceptions that must be caught with try/catch\n- Set to `true` to handle errors at a higher level or trigger retry logic\n\n**`includeErrorInEmptyResponse`** (boolean, default: `true`)\n\n- When `true`: Error messages are included in the response when no content was generated\n- When `false`: Returns empty string in result, but still marks status as \"error\"\n- Set to `false` to handle error messaging yourself\n\n#### Common Error Handling Patterns\n\n**Default - Graceful Error Handling:**\n\n```ts\n// Errors are returned as results with helpful messages\nsupervisorConfig: {\n  throwOnStreamError: false,\n  includeErrorInEmptyResponse: true,\n}\n\n// Usage:\nconst result = await supervisor.streamText(\"Process data\");\n// If sub-agent fails:\n// result contains error message like \"Error in DataProcessor: Stream failed\"\n```\n\n:::info Retry Behavior\nVoltAgent retries model calls based on `maxRetries`. `throwOnStreamError` controls how stream errors are surfaced; it does not change retry or fallback behavior.\n:::\n\n**Silent Errors - Custom Messaging:**\n\n```ts\n// Errors don't include automatic messages\nsupervisorConfig: {\n  includeErrorInEmptyResponse: false,\n}\n\n// Usage with custom error handling:\nconst result = await supervisor.streamText(\"Process data\");\nfor await (const event of result.fullStream) {\n  if (event.type === \"error\") {\n    // Provide custom user-friendly error message\n    console.log(\"We're having trouble processing your request. Please try again.\");\n  }\n}\n```\n\n**Production Setup - Error Tracking:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: false, // Don't crash the app\n  includeErrorInEmptyResponse: true, // Help with debugging\n\n  // Capture error events for monitoring\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result', 'error'],\n  },\n}\n```\n\n#### Error Handling in Practice\n\nThe supervisor's behavior when a sub-agent encounters an error depends on your configuration:\n\n```ts\n// Example: Sub-agent fails during stream\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [unreliableAgent],\n  supervisorConfig: {\n    throwOnStreamError: false,\n    includeErrorInEmptyResponse: true,\n  },\n});\n\n// The supervisor handles the failure\nconst response = await supervisor.streamText(\"Do something risky\");\n\n// Check the response\nif (response.status === \"error\") {\n  console.log(\"Sub-agent failed:\", response.error);\n  // response.result contains: \"Error in UnreliableAgent: [error details]\"\n} else {\n  // Process successful response\n}\n```\n\n#### Using with fullStream\n\nWhen using `fullStream`, the configuration controls what you receive from sub-agents. VoltAgent\nforwards metadata onto each forwarded chunk so you can attribute every event:\n\n- `subAgentId` / `subAgentName`: the sub-agent that produced the chunk\n- `executingAgentId` / `executingAgentName`: same as above (reserved for nested handoffs)\n- `parentAgentId` / `parentAgentName`: the supervisor that forwarded the chunk\n- `agentPath`: ordered names from supervisor → executing agent\n\nThis metadata is only on the streamed events (fullStream / UI streams); it is **not** sent back to\nthe model provider or injected into the LLM messages.\n\n```ts\n// Stream with full event details\nconst result = await supervisorAgent.streamText(\"Create and edit content\");\n\n// Process different event types\nfor await (const event of result.fullStream) {\n  if (event.type === \"tool-call\") {\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Tool called: ${event.toolName}`);\n    }\n  } else if (event.type === \"tool-result\") {\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Tool result:`, event.output);\n    }\n  } else if (event.type === \"text-delta\") {\n    // Only appears if included in types array\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Text: ${event.text ?? event.delta ?? \"\"}`);\n    }\n  }\n}\n```\n\n#### Filtering Sub-agent Events\n\nIdentify which events come from sub-agents by checking for `subAgentId` and `subAgentName` properties:\n\n```ts\nconst result = await supervisorAgent.streamText(\"Create and edit content\");\n\nfor await (const event of result.fullStream) {\n  if (event.subAgentId && event.subAgentName) {\n    console.log(\n      `Event from sub-agent ${event.subAgentName} (path: ${event.agentPath?.join(\" > \")})`\n    );\n    console.log(`  Type: ${event.type}`);\n    console.log(`  Payload:`, {\n      toolName: event.toolName,\n      output: event.output,\n      text: (event as { text?: string }).text,\n    });\n    continue;\n  }\n\n  // This is from the supervisor agent itself\n  console.log(`Supervisor event: ${event.type}`);\n}\n```\n\nThis allows you to:\n\n- Distinguish between supervisor and sub-agent events\n- Filter events by specific sub-agent\n- Apply different handling logic based on the event source\n\n#### Filtering Historical Conversations\n\nSub-agent metadata is also persisted in memory, so you can apply the same filtering logic when you replay a conversation later:\n\n```ts\nconst messages = await memory.getMessages(userId, conversationId);\n\nconst writerMessages = messages.filter(\n  (message) => message.metadata?.subAgentName === \"WriterAgent\"\n);\n\nfor (const message of writerMessages) {\n  console.log(`[${message.metadata?.subAgentName}]`, message.parts);\n}\n```\n\nEvery `UIMessage` in memory now includes the `metadata.subAgentId` and `metadata.subAgentName` fields, making it easy to render supervisor history the same way you render live streams.\n\n### Complete System Message Override\n\nProvide a custom `systemMessage` to replace the default template:\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Custom Supervisor\",\n  instructions: \"This will be ignored when systemMessage is provided\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    systemMessage: `\nYou are a content manager named \"ContentBot\".\n\nYour team:\n- Writer: Creates original content\n- Editor: Reviews and improves content\n\nYour workflow:\n1. Analyze user requests\n2. Use delegate_task to assign work to appropriate specialists\n3. Coordinate between specialists as needed\n4. Provide final responses\n5. Maintain a professional tone\n\nRemember: Use the delegate_task tool to assign tasks to your specialists.\n    `.trim(),\n\n    // Control memory inclusion even with custom system message\n    includeAgentsMemory: true,\n  },\n});\n```\n\n### Quick Usage\n\n**Add custom rules:**\n\n```ts\nsupervisorConfig: {\n  customGuidelines: [\"Verify sources\", \"Include confidence levels\"];\n}\n```\n\n**Override entire system message:**\n\n```ts\nsupervisorConfig: {\n  systemMessage: \"You are TaskBot. Use delegate_task(task, [agentNames]) to assign work.\";\n}\n```\n\n**Control memory:**\n\n```ts\nsupervisorConfig: {\n  includeAgentsMemory: false; // Exclude prior sub-agent interactions from supervisor prompt (default: true)\n}\n```\n\n:::note `includeAgentsMemory` vs `memory: false`\n`includeAgentsMemory` only controls prompt construction for the supervisor. It does not disable conversation memory inside each sub-agent. If you need a stateless sub-agent, set `memory: false` on that sub-agent.\n:::\n\n**Stateless sub-agent:**\n\n```ts\nconst writerAgent = new Agent({\n  name: \"Writer\",\n  instructions: \"Write concise drafts.\",\n  model: \"openai/gpt-4o-mini\",\n  memory: false,\n});\n```\n\n**Configure event forwarding:**\n\n```ts\nsupervisorConfig: {\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result', 'text-delta'], // Control which events to forward\n  }\n}\n```\n\n**Handle errors gracefully:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: false, // Return errors as results (default)\n  includeErrorInEmptyResponse: true // Include error details in response (default)\n}\n```\n\n**Throw exceptions for custom error handling:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: true; // Throw exceptions on sub-agent failures\n}\n```\n\n## How Sub-agents Work\n\nThe supervisor agent delegates tasks to its sub-agents using the automatically provided `delegate_task` tool.\n\n1. A user sends a request to the supervisor agent.\n2. The supervisor's LLM analyzes the request and its system prompt (which lists available sub-agents).\n3. Based on the task, the supervisor decides which sub-agent(s) to use.\n4. The supervisor uses the `delegate_task` tool to hand off the task(s).\n\n### The `delegate_task` Tool\n\nThis tool is automatically added to supervisor agents and handles delegation.\n\n- **Name**: `delegate_task`\n- **Description**: \"Delegate a task to one or more specialized agents\"\n- **Parameters**:\n  - `task` (string, required): The task description to be delegated\n  - `targetAgents` (array of strings, required): Sub-agent names to delegate the task to. The supervisor can delegate to multiple agents simultaneously\n  - `context` (object, optional): Additional context needed by the sub-agent(s)\n- **Execution**:\n  - Finds the sub-agent instances based on the provided names\n  - Calls the `handoffTask` (or `handoffToMultiple`) method internally\n  - Tags delegated sub-agent messages with metadata so supervisor memory reads can exclude sub-agent records\n  - Passes the supervisor's agent ID (`parentAgentId`) and history entry ID (`parentHistoryEntryId`) for observability\n- **Returns**:\n  - **Always returns an array** of result objects (even for single agent):\n    ```ts\n    [\n      {\n        agentName: string; // Name of the sub-agent that executed the task\n        response: string; // The text result returned by the sub-agent\n        usage?: UsageInfo; // Token usage information\n        bailed?: boolean; // True if onHandoffComplete called bail()\n      },\n      // ... more results if multiple agents were targeted\n    ]\n    ```\n\n  When `bailed: true`, the supervisor's execution is terminated immediately and the subagent's response is returned to the user. See [Early Termination (Bail)](#early-termination-bail) for details.\n\n5. Sub-agents process their delegated tasks independently. They can use their own tools or delegate further if they are also supervisors.\n6. Each sub-agent returns its result to the `delegate_task` tool execution context.\n7. The supervisor receives the results from the `delegate_task` tool.\n8. The supervisor synthesizes the final response based on its instructions and the received results.\n\n## Complete Example\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Create agents\nconst writer = new Agent({\n  name: \"Writer\",\n  instructions: \"Write creative stories\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst translator = new Agent({\n  name: \"Translator\",\n  instructions: \"Translate text accurately\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Create supervisor\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate story writing and translation\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writer, translator],\n});\n\n// Use it\nconst result = await supervisor.streamText(\"Write a story about AI and translate to Spanish\");\n\nfor await (const chunk of result.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n**What happens:**\n\n1. Supervisor analyzes request\n2. Calls `delegate_task` → Writer creates story\n3. Calls `delegate_task` → Translator translates\n4. Combines results and responds\n\n## Using Hooks\n\nVoltAgent provides hooks to monitor and control the supervisor/subagent workflow:\n\n### `onHandoff` Hook\n\nTriggered when delegation begins:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [writer, translator],\n  hooks: {\n    onHandoff: ({ agent, sourceAgent }) => {\n      console.log(`${sourceAgent.name} → ${agent.name}`);\n    },\n  },\n});\n```\n\n### `onHandoffComplete` Hook\n\nTriggered when a subagent completes execution. This hook enables **early termination (bail)** to optimize token usage:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [dataAnalyzer, reportGenerator],\n  hooks: {\n    onHandoffComplete: async ({ agent, sourceAgent, result, messages, usage, context, bail }) => {\n      // Bail if subagent produced final output\n      if (agent.name === \"Report Generator\") {\n        context.logger?.info(\"Final report ready, bailing\");\n        bail(); // Skip supervisor processing\n      }\n    },\n  },\n});\n```\n\nSee [Early Termination (Bail)](#early-termination-bail) below for detailed usage.\n\n## Context Sharing\n\nSub-agents automatically inherit the supervisor's context:\n\n```ts\n// Supervisor passes context\nconst response = await supervisor.streamText(\"Task\", {\n  context: new Map([[\"projectId\", \"123\"]]),\n});\n\n// Sub-agent receives it automatically\nconst subAgent = new Agent({\n  hooks: {\n    onStart: (context) => {\n      const projectId = context.context.get(\"projectId\"); // \"123\"\n    },\n  },\n});\n```\n\n## Early Termination (Bail)\n\n### The Problem\n\nIn supervisor/subagent workflows, subagents **always** return to the supervisor for processing, even when they generate final outputs (like JSON structures or reports) that need no additional handling. This wastes tokens:\n\n```\nCurrent flow:\nSupervisor → SubAgent (generates 2K token JSON) → Supervisor (processes JSON) → User\n                                                    ↑ Wastes ~2K tokens\n```\n\n**Example impact:**\n\n- Without bail: ~2,650 tokens per request\n- With bail: ~560 tokens per request\n- **Savings: 79%** (~$0.020 per request)\n\n### The Solution\n\nThe `onHandoffComplete` hook allows supervisors to intercept subagent results and **bail** (skip supervisor processing) when the subagent produces final output:\n\n```\nNew flow:\nSupervisor → SubAgent → bail() → User ✅\n```\n\n### Basic Usage\n\nCall `bail()` in the `onHandoffComplete` hook to terminate early:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Workout Supervisor\",\n  subAgents: [exerciseAgent, workoutBuilder],\n  hooks: {\n    onHandoffComplete: async ({ agent, result, bail, context }) => {\n      // Workout Builder produces final JSON - no processing needed\n      if (agent.name === \"Workout Builder\") {\n        context.logger?.info(\"Final output received, bailing\");\n        bail(); // Skip supervisor, return directly to user\n      }\n      // Default: continue to supervisor for processing\n    },\n  },\n});\n```\n\n### Conditional Bail Logic\n\nBail based on agent name, result size, or content:\n\n```ts\nhooks: {\n  onHandoffComplete: async ({ agent, result, bail, context }) => {\n    // By agent name\n    if (agent.name === \"Report Generator\") {\n      bail();\n      return;\n    }\n\n    // By result size (save tokens)\n    if (result.length > 2000) {\n      context.logger?.warn(\"Large result, bailing to save tokens\");\n      bail();\n      return;\n    }\n\n    // By result content\n    if (result.includes(\"FINAL_OUTPUT\")) {\n      bail();\n      return;\n    }\n\n    // Default: continue to supervisor\n  },\n}\n```\n\n### Transform Before Bail\n\nOptionally transform the result before bailing:\n\n```ts\nhooks: {\n  onHandoffComplete: async ({ agent, result, bail }) => {\n    if (agent.name === \"Report Generator\") {\n      // Add metadata before returning\n      const transformed = `# Final Report\\n\\n${result}\\n\\n---\\nGenerated at: ${new Date().toISOString()}`;\n      bail(transformed); // Bail with transformed result\n    }\n  },\n}\n```\n\n### Hook Parameters\n\n```ts\ninterface OnHandoffCompleteHookArgs {\n  agent: Agent; // Target agent (subagent)\n  sourceAgent: Agent; // Source agent (supervisor)\n  result: string; // Subagent's output\n  messages: UIMessage[]; // Full conversation messages\n  usage?: UsageInfo; // Token usage info\n  context: OperationContext; // Operation context\n  bail: (transformedResult?: string) => void; // Call to bail\n}\n```\n\n### Accessing Bailed Results\n\nWhen a subagent bails, the **subagent's result** is returned to the user (not the supervisor's):\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [\n    createSubagent({\n      agent: workoutBuilder,\n      method: \"generateObject\",\n      schema: WorkoutSchema,\n    }),\n  ],\n  hooks: {\n    onHandoffComplete: async ({ agent, bail }) => {\n      if (agent.name === \"Workout Builder\") {\n        bail(); // Return workout JSON directly\n      }\n    },\n  },\n});\n\nconst result = await supervisor.generateText(\"Create workout\");\nconsole.log(result.text); // Contains workout JSON, not supervisor's processing\n```\n\n### Supported Methods\n\nBail works with methods that support tools:\n\n- ✅ `generateText` - Aborts execution, returns bailed result\n- ✅ `streamText` - Aborts stream immediately, returns bailed result\n- ❌ `generateObject` - No tool support, bail not applicable\n- ❌ `streamObject` - No tool support, bail not applicable\n\n### Stream Event Visibility with Bail\n\nWhen using bail with `toUIMessageStream()` or consuming `fullStream` events, you need to configure which events are forwarded from subagents.\n\n**Default Behavior:**\n\nBy default, only `tool-call` and `tool-result` events are forwarded from subagents. This means **subagent text chunks are NOT visible** in the stream:\n\n```ts\n// Default configuration (implicit)\nsupervisorConfig: {\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result'], // ⚠️ text-delta NOT included\n  }\n}\n```\n\n**To See Subagent Text Output:**\n\nWhen a subagent bails and produces text output, you must explicitly include `text-delta` in the forwarded event types:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [workoutBuilder],\n  supervisorConfig: {\n    fullStreamEventForwarding: {\n      types: [\"tool-call\", \"tool-result\", \"text-delta\"], // ✅ Include text-delta\n    },\n  },\n  hooks: {\n    onHandoffComplete: ({ agent, bail }) => {\n      if (agent.name === \"Workout Builder\") {\n        bail(); // Subagent text will be visible in stream\n      }\n    },\n  },\n});\n```\n\n**Consuming Bailed Subagent Output:**\n\nWhen using `toUIMessageStream()`, subagent text appears as `data-subagent-stream` events that are automatically grouped and rendered in the UI:\n\n```ts\nconst result = await supervisor.streamText(\"Create workout\");\n\nfor await (const message of result.toUIMessageStream()) {\n  // Message parts include grouped subagent output; each subagent chunk carries:\n  // subAgentId, subAgentName, executingAgent*, parentAgent*, agentPath\n  // These parts are emitted as `data-subagent-stream` entries.\n}\n```\n\n**Event Types Reference:**\n\n| Event Type                                              | Description                              | Default         |\n| ------------------------------------------------------- | ---------------------------------------- | --------------- |\n| `tool-call`                                             | Tool invocations                         | ✅ Included     |\n| `tool-result`                                           | Tool results                             | ✅ Included     |\n| `text-delta`                                            | Text chunk generation                    | ❌ NOT included |\n| `reasoning-start` / `reasoning-delta` / `reasoning-end` | Model reasoning lifecycle (if available) | ❌ NOT included |\n| `source`                                                | Retrieved sources (if available)         | ❌ NOT included |\n| `error`                                                 | Error events                             | ❌ NOT included |\n| `finish`                                                | Stream completion                        | ❌ NOT included |\n\n### Observability\n\nBailed subagents are tracked in observability with visual indicators:\n\n**Logging:**\n\n```\n[INFO] Supervisor bailed after handoff\n  supervisor: Workout Supervisor\n  subagent: Workout Builder\n  transformed: false\n  resultLength: 450\n```\n\n**OpenTelemetry Attributes:**\n\nBoth supervisor and subagent spans get attributes:\n\n```ts\n// Supervisor span attributes\n{\n  \"bailed\": true,\n  \"bail.subagent\": \"Workout Builder\",\n  \"bail.transformed\": false\n}\n\n// Subagent span attributes\n{\n  \"bailed\": true,\n  \"bail.supervisor\": \"Workout Supervisor\",\n  \"bail.transformed\": false\n}\n```\n\n### Use Cases\n\nPerfect for scenarios where specialized subagents generate final outputs:\n\n1. **JSON/Structured data generators** - Workout builders, report generators, data exporters\n2. **Large content producers** - Document creators, extensive data analysis\n3. **Token optimization** - Skip processing for expensive results\n4. **Business logic** - Conditional routing based on result characteristics\n\n### Best Practices\n\n**✅ DO:**\n\n- Bail when subagent produces final, ready-to-use output\n- Use conditional logic to bail selectively\n- Log bail decisions for debugging\n- Transform results before bailing when needed\n\n**❌ DON'T:**\n\n- Bail on every subagent (defeats supervisor purpose)\n- Bail when supervisor needs to process/combine results\n- Forget to handle non-bailed flow (default case)\n\n````\n\n## Step Control\n\nControl workflow steps with `maxSteps`:\n\n```ts\nconst supervisor = new Agent({\n  subAgents: [writer, editor],\n  maxSteps: 20, // Inherited by all sub-agents\n});\n\n// Override per request\nconst result = await supervisor.generateText(\"Task\", { maxSteps: 10 });\n````\n\n**Default:** `10 × number_of_sub-agents` (prevents infinite loops)\n\n## Observability\n\nSub-agent operations are automatically linked to their supervisor for traceability in monitoring tools.\n\n## Advanced Configuration\n\nUse different execution methods for sub-agents:\n\n```ts\nimport { createSubagent } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst AnalysisSchema = z.object({\n  insights: z.array(z.string()),\n  confidence: z.number().min(0).max(1),\n});\n\nconst supervisor = new Agent({\n  subAgents: [\n    writer, // Default streamText\n    createSubagent({\n      agent: analyzer,\n      method: \"generateObject\",\n      schema: AnalysisSchema,\n      options: { temperature: 0.1 },\n    }),\n  ],\n});\n```\n\n**Available methods:**\n\n- `streamText` (default) - Real-time text streaming\n- `generateText` - Text generation\n- `generateObject` - Structured data with Zod schema\n- `streamObject` - Streaming structured data\n\n## Dynamic Sub-agents\n\nAdd sub-agents after initialization:\n\n```ts\nsupervisor.addSubAgent(newAgent);\n```\n\n## Remove Sub-agents\n\n```ts\nsupervisor.removeSubAgent(agentId);\n```\n\n## Troubleshooting\n\n**Sub-agent not being called?**\n\n- Check agent names match exactly\n- Make supervisor instructions explicit about when to delegate\n- Use `onHandoff` hook to debug delegation flow\n","website/docs/agents/summarization.md":"---\ntitle: Summarization\nslug: /agents/summarization\n---\n\n# Summarization\n\nSummarization shortens long conversations by inserting a system summary and keeping the last N non-system messages. It is configured per agent.\n\n## Configuration\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n  summarization: {\n    enabled: true,\n    triggerTokens: 120_000,\n    keepMessages: 6,\n    maxOutputTokens: 800,\n    systemPrompt: \"Summarize the conversation for the next step.\",\n    model: \"openai/gpt-4o-mini\",\n  },\n});\n```\n\nOptions:\n\n- `enabled`: Enable or disable summarization.\n- `triggerTokens`: Token estimate threshold. The estimate uses a simple character-to-token heuristic.\n- `keepMessages`: Number of most recent non-system messages kept after summarization.\n- `maxOutputTokens`: Token budget for the summary generation call.\n- `systemPrompt`: Prompt used for summary generation. If omitted, a default system prompt is used.\n- `model`: Optional model for summarization; defaults to the agent model.\n\n## How Summarization Runs\n\nOn each request, VoltAgent:\n\n1. Removes any previous summary system message (identified by `<agent_summary>` markers).\n2. Calculates a token estimate for non-system messages.\n3. If `nonSystemMessages.length > keepMessages` and the token estimate meets `triggerTokens`, it generates a summary.\n4. Stores the summary state and injects a system message that contains the summary.\n5. Returns `[systemMessages, summaryMessage, tailMessages]` to the model.\n\nIf summary generation fails, the original sanitized messages are used.\n\nThe injected summary system message looks like:\n\n```text\n<agent_summary>\n...summary text...\n</agent_summary>\n```\n\n## Storage Behavior\n\nSummary state is stored under the conversation metadata key `agent` when memory is enabled and `conversationId` is present. The state includes:\n\n- `summary`\n- `summaryMessageCount`\n- `summaryUpdatedAt`\n\nIf memory is disabled or `conversationId` is missing, summary state is kept in process memory and resets on restart.\n\nIf you manage conversation metadata, reserve the `metadata.agent` key for VoltAgent summarization state.\n\n## When To Use It\n\nUse summarization when conversation history grows past model limits or when token usage needs to be capped. Avoid it when you require full message fidelity for audits or deterministic replay.\n\n## Notes for Framework Integrations\n\n- Pass `conversationId` consistently to keep summaries across requests.\n- If you render messages in a UI, filter summary system messages by the `<agent_summary>` marker.\n- If you hook into `onPrepareMessages`, that hook runs after summarization and receives the summary-injected message list.\n","website/docs/agents/tools.md":"---\ntitle: Tools\nslug: /agents/tools\n---\n\n# Tools\n\nTools enable agents to interact with external systems, APIs, databases, and perform specific actions beyond generating text. An agent uses its model to decide when to call tools based on their descriptions and the current context.\n\n## Creating a Tool\n\nUse `createTool` to define a tool with type-safe parameters and execution logic.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather for a location\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  execute: async ({ location }) => {\n    // Call weather API\n    const response = await fetch(`https://api.weather.com/current?city=${location}`);\n    const data = await response.json();\n\n    return {\n      location,\n      temperature: data.temp,\n      conditions: data.conditions,\n    };\n  },\n});\n```\n\nEach tool has:\n\n- **name**: Unique identifier\n- **description**: Explains what the tool does (the model uses this to decide when to call it)\n- **parameters**: Input schema defined with Zod\n- **execute**: Function that runs when the tool is called\n- **providerOptions** (optional): Provider-specific options for advanced features\n- **tags** (optional): Optional user-defined tags for organizing or labeling tools.\n\nThe `execute` function's parameter types are automatically inferred from the Zod schema, providing full IntelliSense support.\n\n## Tool Hooks\n\nYou can attach tool-specific hooks to observe or post-process a tool result. Tool hooks run before agent-level hooks, and agent `onToolEnd` can still override the output afterward.\n\n**Hook parameters:**\n\n- `onStart`: `{ tool, args, options }`\n- `onEnd`: `{ tool, args, output, error, options }` (return `{ output }` to override the result)\n\n> Overrides are re-validated if the tool has an `outputSchema`. For streaming tools (AsyncIterable), overrides apply only to the final output.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst summarizeTool = createTool({\n  name: \"summarize_text\",\n  description: \"Summarize text with a hard cap\",\n  parameters: z.object({ text: z.string() }),\n  execute: async ({ text }) => text,\n  hooks: {\n    onStart: ({ tool }) => {\n      console.log(`[tool] ${tool.name} starting`);\n    },\n    onEnd: ({ output }) => {\n      if (typeof output === \"string\") {\n        return { output: output.slice(0, 500) };\n      }\n    },\n  },\n});\n```\n\n## Streaming Tool Results (Preliminary)\n\nIf your tool can provide progress or intermediate status, return an `AsyncIterable` from `execute`.\nEach `yield` is emitted as a preliminary tool result, and the **last yielded value** is treated as the final result.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherOutputSchema = z.discriminatedUnion(\"status\", [\n  z.object({\n    status: z.literal(\"loading\"),\n    text: z.string(),\n    weather: z.undefined().optional(),\n  }),\n  z.object({\n    status: z.literal(\"success\"),\n    text: z.string(),\n    weather: z.object({\n      location: z.string(),\n      temperature: z.number(),\n    }),\n  }),\n]);\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a location\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  outputSchema: weatherOutputSchema,\n  async *execute({ location }) {\n    yield {\n      status: \"loading\" as const,\n      text: `Getting weather for ${location}`,\n      weather: undefined,\n    };\n\n    await new Promise((resolve) => setTimeout(resolve, 3000));\n\n    const temperature = 72 + Math.floor(Math.random() * 21) - 10;\n\n    yield {\n      status: \"success\" as const,\n      text: `The weather in ${location} is ${temperature}F`,\n      weather: { location, temperature },\n    };\n  },\n});\n```\n\n### Tool Tags\n\nTags are optional string labels that help organize and categorize tools.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Example: Database tools with tags\nconst queryTool = createTool({\n  name: \"query_database\",\n  description: \"Execute a read-only SQL query\",\n  tags: [\"database\", \"read-only\", \"sql\"],\n  parameters: z.object({\n    query: z.string().describe(\"SQL query to execute\"),\n  }),\n  execute: async ({ query }) => {\n    // Execute query\n    return { results: [] };\n  },\n});\n\nconst updateTool = createTool({\n  name: \"update_record\",\n  description: \"Update database records\",\n  tags: [\"database\", \"write\", \"sql\", \"destructive\"],\n  parameters: z.object({\n    table: z.string().describe(\"Table name\"),\n    id: z.string().describe(\"Record ID\"),\n    data: z.record(z.unknown()).describe(\"Data to update\"),\n  }),\n  execute: async ({ table, id, data }) => {\n    // Update record\n    return { success: true };\n  },\n});\n\n// Example: External API tools\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather data\",\n  tags: [\"weather\", \"external-api\", \"read-only\"],\n  parameters: z.object({\n    location: z.string().describe(\"City name\"),\n  }),\n  execute: async ({ location }) => {\n    // Fetch weather\n    return { temperature: 72, conditions: \"sunny\" };\n  },\n});\n```\n\nTags are included in OpenTelemetry spans and visible in the VoltAgent observability dashboard, making it easy to analyze tool usage patterns and debug agent behavior.\n\nTool tags also flow through the REST tool endpoints (`GET /tools` and `POST /tools/:name/execute` responses), so you can build frontend tooling or access control based on tags.\n\n### Provider-Specific Options\n\nYou can pass provider-specific options to enable advanced features like caching. Currently supported providers include Anthropic, OpenAI, Google, and others.\n\n#### Anthropic Cache Control\n\nAnthropic's prompt caching can reduce costs and latency for repeated tool calls:\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst cityAttractionsTool = createTool({\n  name: \"get_city_attractions\",\n  description: \"Get tourist attractions for a city\",\n  parameters: z.object({\n    city: z.string().describe(\"The city name\"),\n  }),\n  providerOptions: {\n    anthropic: {\n      cacheControl: { type: \"ephemeral\" },\n    },\n  },\n  execute: async ({ city }) => {\n    return await fetchAttractions(city);\n  },\n});\n```\n\nThe `cacheControl` option tells Anthropic to cache the tool definition, improving performance for subsequent calls. Learn more about [Anthropic's cache control](https://ai-sdk.dev/providers/ai-sdk-providers/anthropic#cache-control).\n\n## Using Tools with Agents\n\nAdd tools when creating an agent. The model decides when to use them based on the user's input and tool descriptions.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Weather Assistant\",\n  instructions: \"An assistant that provides weather information\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n});\n\n// The model calls the tool when appropriate\nconst response = await agent.generateText(\"What's the weather in Paris?\");\nconsole.log(response.text);\n// \"The current weather in Paris is 22°C and sunny.\"\n```\n\n### Using Multiple Tools\n\nAgents can use multiple tools together to answer complex queries.\n\n```ts\nconst calculatorTool = createTool({\n  name: \"calculate\",\n  description: \"Perform mathematical calculations\",\n  parameters: z.object({\n    expression: z.string().describe(\"The mathematical expression to evaluate\"),\n  }),\n  execute: async ({ expression }) => {\n    // Use a safe math parser in production\n    const result = eval(expression);\n    return { result };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Multi-Tool Assistant\",\n  instructions: \"An assistant that can check weather and perform calculations\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool, calculatorTool],\n});\n\nconst response = await agent.generateText(\"What's the weather in Paris? Also, what is 24 * 7?\");\n// The model uses both tools and combines the results\n```\n\n### Dynamic Tool Registration\n\nAdd tools to an agent after creation or provide them per request.\n\n```ts\n// Add tools after agent creation\nagent.addTools([calculatorTool]);\n\n// Provide tools for a specific request only\nconst response = await agent.generateText(\"Calculate 123 * 456\", {\n  tools: [calculatorTool],\n});\n```\n\n## Accessing Operation Context\n\nThe `execute` function receives a `ToolExecuteOptions` object as its second parameter, which extends `Partial<OperationContext>` and provides access to all operation metadata and control mechanisms.\n\n```ts\nconst debugTool = createTool({\n  name: \"log_debug_info\",\n  description: \"Logs debugging information\",\n  parameters: z.object({\n    message: z.string().describe(\"Debug message to log\"),\n  }),\n  execute: async (args, options) => {\n    // Access tool context (tool-specific metadata)\n    const { name, callId, messages } = options?.toolContext || {};\n    console.log(\"Tool name:\", name);\n    console.log(\"Tool call ID:\", callId);\n    console.log(\"Message history length:\", messages?.length);\n\n    // Access operation metadata directly from options\n    console.log(\"Operation ID:\", options?.operationId);\n    console.log(\"User ID:\", options?.userId);\n    console.log(\"Conversation ID:\", options?.conversationId);\n\n    // Access the original input\n    console.log(\"Original input:\", options?.input);\n\n    // Access user-defined context Map\n    const customValue = options?.context?.get(\"customKey\");\n\n    // Use the operation-scoped logger with tool name\n    options?.logger?.info(`${name}: Logging ${args.message}`);\n\n    // Check if operation is still active\n    if (!options?.isActive) {\n      throw new Error(\"Operation has been cancelled\");\n    }\n\n    return `Logged: ${args.message}`;\n  },\n});\n```\n\nThe `options` parameter includes all `OperationContext` fields plus an **optional** `toolContext` object:\n\n**Tool execution context (`toolContext?` - optional):**\n\n> **Note:** `toolContext` is always populated when your tool is called from a VoltAgent agent. It may be `undefined` when called from external systems (e.g., MCP servers). Always use optional chaining: `options?.toolContext?.name`.\n\n- `toolContext.name`: Name of the tool being executed\n- `toolContext.callId`: Unique identifier for this specific tool call (from AI SDK)\n- `toolContext.messages`: Message history at the time of tool call (from AI SDK)\n- `toolContext.abortSignal`: Abort signal for detecting cancellation (from AI SDK)\n\n**From OperationContext:**\n\n- `operationId`: Unique identifier for this operation\n- `userId`: Optional user identifier\n- `conversationId`: Optional conversation identifier\n- `workspace`: Workspace instance configured on the agent (if available)\n- `context`: Map for user-provided context values\n- `systemContext`: Map for internal system values\n- `isActive`: Whether the operation is still active\n- `input`: The original input (string, UIMessage[], or BaseMessage[])\n- `abortController`: AbortController for cancelling the operation\n- `logger`: Execution-scoped logger with full context\n- `traceContext`: OpenTelemetry trace context\n- `elicitation`: Optional function for requesting user input\n\n> - Since `ToolExecuteOptions` extends `Partial<OperationContext>`, you can name the second parameter anything you like (`options`, `context`, `ctx`, etc.)\n\nExample: reading workspace content inside a custom tool call:\n\n```ts\nconst readWorkspaceData = createTool({\n  name: \"read_workspace_data\",\n  description: \"Read a workspace file and inspect sandbox output\",\n  parameters: z.object({ path: z.string() }),\n  execute: async ({ path }, options) => {\n    const workspace = options?.workspace;\n    if (!workspace) {\n      return \"No workspace configured\";\n    }\n\n    const content = await workspace.filesystem.read(path);\n    const sandboxResult = await workspace.sandbox?.execute({\n      command: \"cat\",\n      args: [path],\n      operationContext: options as any,\n    });\n\n    return `filesystem=${content}\\nsandbox=${sandboxResult?.stdout ?? \"\"}`;\n  },\n});\n```\n\n## Cancellation with AbortController\n\nTools can respond to cancellation signals and cancel the entire operation when needed.\n\n```ts\nconst searchTool = createTool({\n  name: \"search_web\",\n  description: \"Search the web for information\",\n  parameters: z.object({\n    query: z.string().describe(\"The search query\"),\n  }),\n  execute: async (args, options) => {\n    const abortController = options?.abortController;\n    const signal = abortController?.signal;\n\n    // Check if already aborted\n    if (signal?.aborted) {\n      throw new Error(\"Search was cancelled before it started\");\n    }\n\n    // Tool can trigger abort to cancel the entire operation\n    if (args.query.includes(\"forbidden\")) {\n      abortController?.abort(\"Forbidden query detected\");\n      throw new Error(\"Search query contains forbidden terms\");\n    }\n\n    try {\n      // Pass signal to fetch for cancellation support\n      const response = await fetch(`https://api.search.com?q=${args.query}`, { signal });\n\n      // Abort based on response\n      if (!response.ok && response.status === 429) {\n        abortController?.abort(\"Rate limit exceeded\");\n        throw new Error(\"API rate limit exceeded\");\n      }\n\n      return await response.json();\n    } catch (error) {\n      if (error.name === \"AbortError\") {\n        throw new Error(\"Search was cancelled during execution\");\n      }\n      throw error;\n    }\n  },\n});\n```\n\nWhen calling an agent with an abort signal:\n\n```ts\nimport { isAbortError } from \"@voltagent/core\";\n\nconst abortController = new AbortController();\n\n// Set timeout to abort after 30 seconds\nsetTimeout(() => abortController.abort(\"Operation timeout\"), 30000);\n\ntry {\n  const response = await agent.generateText(\"Search for the latest AI developments\", {\n    abortSignal: abortController.signal,\n  });\n  console.log(response.text);\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Operation was cancelled:\", error.message);\n  } else {\n    console.error(\"Error:\", error);\n  }\n}\n```\n\n## Tool Execution Approval (needsApproval)\n\nSome tools should not run automatically because they trigger sensitive actions (payments, file changes, command execution, etc). Set `needsApproval` to require explicit user approval before the tool executes.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst runCommandTool = createTool({\n  name: \"runCommand\",\n  description: \"Run a shell command\",\n  parameters: z.object({\n    command: z.string().describe(\"Command to execute\"),\n  }),\n  needsApproval: true,\n  execute: async ({ command }) => {\n    // Execute safely\n    return { ok: true, command };\n  },\n});\n```\n\n`needsApproval` can also be a function for dynamic policies:\n\n```ts\nconst processPaymentTool = createTool({\n  name: \"processPayment\",\n  description: \"Charge a customer\",\n  parameters: z.object({\n    amount: z.number(),\n    customerId: z.string(),\n  }),\n  needsApproval: async ({ amount }) => amount > 1000,\n  execute: async ({ amount, customerId }) => {\n    return { success: true, amount, customerId };\n  },\n});\n```\n\n### How the Approval Flow Works\n\n1. The model calls a tool with `needsApproval`.\n2. VoltAgent returns a tool part with `state: \"approval-requested\"` instead of executing.\n3. Your UI asks the user to approve or deny.\n4. Send a tool approval response back to VoltAgent.\n5. If approved, the tool executes in the next step. If denied, the model sees the denial and can respond accordingly.\n\n### UI Implementation (useChat)\n\nVoltAgent's `/agents/:id/chat` stream is compatible with AI SDK `useChat`, so you can render the approval request and send a response from the UI.\n\n```tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses } from \"ai\";\n\nfunction Chat() {\n  const { messages, addToolApprovalResponse } = useChat({\n    transport: new DefaultChatTransport({ api: \"/agents/weather/chat\" }),\n    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,\n  });\n\n  return (\n    <div>\n      {messages.map((message) => (\n        <div key={message.id}>\n          {message.parts.map((part) => {\n            if (part.type === \"tool-runCommand\") {\n              if (part.state === \"approval-requested\") {\n                return (\n                  <div key={part.toolCallId}>\n                    <p>Approve command: {part.input.command}?</p>\n                    <button\n                      onClick={() =>\n                        addToolApprovalResponse({\n                          id: part.approval.id,\n                          approved: true,\n                        })\n                      }\n                    >\n                      Approve\n                    </button>\n                    <button\n                      onClick={() =>\n                        addToolApprovalResponse({\n                          id: part.approval.id,\n                          approved: false,\n                          reason: \"User denied\",\n                        })\n                      }\n                    >\n                      Deny\n                    </button>\n                  </div>\n                );\n              }\n            }\n            return null;\n          })}\n        </div>\n      ))}\n    </div>\n  );\n}\n```\n\nIf you do not use `sendAutomaticallyWhen`, call `sendMessage` manually after approval to continue the flow.\n\n## Client-Side Tools\n\nClient-side tools execute in the browser or client application instead of on the server. They're useful for accessing browser APIs, user permissions, or client-specific features.\n\nFor a complete working setup, see the example: [with-client-side-tools](https://github.com/VoltAgent/voltagent/tree/main/examples/with-client-side-tools).\n\n### What Makes a Tool Client-Side?\n\nA tool without an `execute` function is automatically client-side.\n\n```ts\n// Client-side tool (no execute function)\nconst getLocationTool = createTool({\n  name: \"getLocation\",\n  description: \"Get the user's current location\",\n  parameters: z.object({}),\n  // No execute = client-side\n});\n\n// Another client-side tool\nconst readClipboardTool = createTool({\n  name: \"readClipboard\",\n  description: \"Read content from the user's clipboard\",\n  parameters: z.object({}),\n});\n\n// Server-side tool (has execute)\nconst getWeatherTool = createTool({\n  name: \"getWeather\",\n  description: \"Get current weather for a city\",\n  parameters: z.object({\n    city: z.string().describe(\"City name\"),\n  }),\n  execute: async ({ city }) => {\n    const response = await fetch(`https://api.weather.com/current?city=${city}`);\n    return await response.json();\n  },\n});\n```\n\n### Handling Client-Side Tools\n\nUse the `onToolCall` callback with `useChat` to handle client-side tool execution.\n\n```tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport type { ClientSideToolResult } from \"@voltagent/core\";\nimport { useState, useEffect, useCallback } from \"react\";\n\nfunction Chat() {\n  const [result, setResult] = useState<ClientSideToolResult | null>(null);\n\n  const handleToolCall = useCallback(async ({ toolCall }) => {\n    // Handle automatic execution for getLocation\n    if (toolCall.toolName === \"getLocation\") {\n      navigator.geolocation.getCurrentPosition(\n        (position) => {\n          setResult({\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            output: {\n              latitude: position.coords.latitude,\n              longitude: position.coords.longitude,\n              accuracy: position.coords.accuracy,\n            },\n          });\n        },\n        (error) => {\n          setResult({\n            state: \"output-error\",\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            errorText: error.message,\n          });\n        }\n      );\n    }\n  }, []);\n\n  const { messages, sendMessage, addToolResult, status } = useChat({\n    transport: new DefaultChatTransport({ api: \"/api/chat\" }),\n    onToolCall: handleToolCall,\n  });\n\n  // Send results back to the model\n  useEffect(() => {\n    if (!result) return;\n    addToolResult(result);\n  }, [result, addToolResult]);\n\n  return (\n    <div>\n      {messages.map((message) => (\n        <div key={message.id}>{/* Render message */}</div>\n      ))}\n    </div>\n  );\n}\n```\n\n### Interactive Client-Side Tools\n\nFor tools requiring user interaction, render UI components:\n\n```tsx\nfunction ReadClipboardTool({\n  callId,\n  state,\n  addToolResult,\n}: {\n  callId: string;\n  state?: string;\n  addToolResult: (res: ClientSideToolResult) => void;\n}) {\n  if (state === \"input-streaming\" || state === \"input-available\") {\n    return (\n      <div>\n        <p>Allow access to your clipboard?</p>\n        <button\n          onClick={async () => {\n            try {\n              const text = await navigator.clipboard.readText();\n              addToolResult({\n                tool: \"readClipboard\",\n                toolCallId: callId,\n                output: { content: text },\n              });\n            } catch {\n              addToolResult({\n                state: \"output-error\",\n                tool: \"readClipboard\",\n                toolCallId: callId,\n                errorText: \"Clipboard access denied\",\n              });\n            }\n          }}\n        >\n          Yes\n        </button>\n        <button\n          onClick={() =>\n            addToolResult({\n              state: \"output-error\",\n              tool: \"readClipboard\",\n              toolCallId: callId,\n              errorText: \"Access denied\",\n            })\n          }\n        >\n          No\n        </button>\n      </div>\n    );\n  }\n  return <div>readClipboard: {state}</div>;\n}\n```\n\n**Important**: You must call `addToolResult` to send the tool result back to the model. Without this, the model considers the tool call a failure.\n\n## Agent Tool Hooks\n\nHooks let you respond to tool execution events for logging, UI updates, or additional actions. Use `onToolError` when you need to customize the error payload before it is returned to the model. For tool-level hooks (per tool), see the **Tool Hooks** section above.\n\n```ts\nimport { Agent, createHooks, isAbortError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ agent, tool, context, args }) {\n    console.log(`Tool starting: ${tool.name}`);\n    console.log(`Agent: ${agent.name}`);\n    console.log(`Operation ID: ${context.operationId}`);\n    console.log(`Arguments:`, args);\n  },\n\n  onToolEnd({ agent, tool, output, error, context }) {\n    console.log(`Tool completed: ${tool.name}`);\n\n    if (error) {\n      if (isAbortError(error)) {\n        console.log(`Tool was aborted: ${error.message}`);\n      } else {\n        console.error(`Tool failed: ${error.message}`);\n      }\n    } else {\n      console.log(`Result:`, output);\n    }\n  },\n\n  onToolError({ tool, originalError }) {\n    const maybeAxios = (originalError as any).isAxiosError === true;\n    if (!maybeAxios) {\n      return;\n    }\n\n    return {\n      output: {\n        error: true,\n        message: originalError.message,\n        code: (originalError as any).code,\n        status: (originalError as any).response?.status,\n      },\n    };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Assistant with Tool Hooks\",\n  instructions: \"An assistant that logs tool execution\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n  hooks: hooks,\n});\n```\n\n### Using Tags with Hooks\n\nTool tags can be accessed in hooks for implementing custom logic like access control or conditional behavior:\n\n```ts\nimport { createHooks, ToolDeniedError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ tool, context }) {\n    // Check if tool has destructive tag and user has permission\n    if (tool.tags?.includes(\"destructive\")) {\n      const userRole = context.context.get(\"userRole\");\n      if (userRole !== \"admin\") {\n        throw new ToolDeniedError({\n          toolName: tool.name,\n          message: \"Admin permission required for destructive operations\",\n          code: \"TOOL_FORBIDDEN\",\n        });\n      }\n    }\n\n    // Log tools by category\n    if (tool.tags?.includes(\"external-api\")) {\n      console.log(`Calling external API: ${tool.name}`);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Controlled Assistant\",\n  instructions: \"An assistant with tag-based access control\",\n  model: \"openai/gpt-4o\",\n  tools: [queryTool, updateTool, weatherTool],\n  hooks: hooks,\n});\n\n// Usage with context\nconst response = await agent.generateText(\"Update the user record\", {\n  context: { userRole: \"admin\" }, // Admin can use destructive tools\n});\n```\n\n### Policy Enforcement with ToolDeniedError\n\nThrow `ToolDeniedError` from a hook to block a tool and immediately stop the entire agent operation.\n\n```ts\nimport { createHooks, ToolDeniedError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ tool, context }) {\n    const plan = context.context.get(\"userPlan\");\n\n    // Block expensive tools for non-pro users\n    if (tool.name === \"search_web\" && plan !== \"pro\") {\n      throw new ToolDeniedError({\n        toolName: tool.name,\n        message: \"Pro plan required to use web search.\",\n        code: \"TOOL_PLAN_REQUIRED\",\n        httpStatus: 402,\n      });\n    }\n  },\n});\n```\n\nCatching the denial:\n\n```ts\nimport { isToolDeniedError } from \"@voltagent/core\";\n\ntry {\n  const res = await agent.generateText(\"Please search the web\", { hooks });\n} catch (err) {\n  if (isToolDeniedError(err)) {\n    console.log(\"Tool denied:\", {\n      tool: err.name,\n      status: err.httpStatus,\n      code: err.code,\n      message: err.message,\n    });\n    // Show upgrade UI, redirect, etc.\n  } else {\n    console.error(\"Operation failed:\", err);\n  }\n}\n```\n\nAllowed `code` values:\n\n- `TOOL_ERROR`\n- `TOOL_FORBIDDEN`\n- `TOOL_PLAN_REQUIRED`\n- `TOOL_QUOTA_EXCEEDED`\n- Custom codes (e.g., `\"TOOL_REGION_BLOCKED\"`)\n\n### Multi-modal Tool Results\n\nTools can return images and media content to the LLM using the `toModelOutput` function. This enables visual workflows where tools can provide screenshots, generated images, or other media for the LLM to analyze.\n\n**Supported Providers:** Anthropic, OpenAI\n\n#### Screenshot Tool Example\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\nimport fs from \"fs\";\n\nconst screenshotTool = createTool({\n  name: \"take_screenshot\",\n  description: \"Takes a screenshot of the screen\",\n  parameters: z.object({\n    region: z.string().optional().describe(\"Region to capture\"),\n  }),\n  execute: async ({ region }) => {\n    // Take screenshot and return base64\n    const imageData = fs.readFileSync(\"./screenshot.png\").toString(\"base64\");\n\n    return {\n      type: \"image\",\n      data: imageData,\n      timestamp: new Date().toISOString(),\n    };\n  },\n  // Convert output to multi-modal content for LLM\n  toModelOutput: ({ output }) => ({\n    type: \"content\",\n    value: [\n      {\n        type: \"text\",\n        text: `Screenshot captured at ${output.timestamp}`,\n      },\n      {\n        type: \"media\",\n        data: output.data,\n        mediaType: \"image/png\",\n      },\n    ],\n  }),\n});\n```\n\n#### Return Formats\n\nThe `toModelOutput` function can return different content types:\n\n**Text only:**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"text\",\n  value: \"Operation completed successfully\",\n});\n```\n\n**JSON data:**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"json\",\n  value: { status: \"success\", data: output },\n});\n```\n\n**Multi-modal (text + image):**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"content\",\n  value: [\n    { type: \"text\", text: \"Here is the image:\" },\n    { type: \"media\", data: output.base64, mediaType: \"image/png\" },\n  ],\n});\n```\n\n**Error handling:**\n\n```ts\ntoModelOutput: ({ output }) => {\n  if (output.error) {\n    return {\n      type: \"error-text\",\n      value: `Failed: ${output.error}`,\n    };\n  }\n  return {\n    type: \"text\",\n    value: output.result,\n  };\n};\n```\n\nLearn more: [AI SDK Multi-modal Tool Results](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#multi-modal-tool-results)\n\n## Best Practices\n\n### Clear Descriptions\n\nProvide clear descriptions for tools and parameters. The model relies on these to understand when and how to use tools.\n\n**Bad:**\n\n```ts\nconst badTool = createTool({\n  name: \"search\",\n  description: \"Searches things\", // Too vague\n  parameters: z.object({\n    q: z.string(), // No description\n  }),\n  execute: async (args) => {\n    /* ... */\n  },\n});\n```\n\n**Good:**\n\n```ts\nconst goodTool = createTool({\n  name: \"search_web\",\n  description:\n    \"Searches the web for current information. Use when you need recent or factual information not in your training data.\",\n  parameters: z.object({\n    query: z.string().describe(\"The search query. Be specific about what information is needed.\"),\n    results_count: z\n      .number()\n      .min(1)\n      .max(10)\n      .optional()\n      .describe(\"Number of results to return. Defaults to 3.\"),\n  }),\n  execute: async (args) => {\n    /* ... */\n  },\n});\n```\n\n### Error Handling\n\nImplement error handling that provides useful feedback to the model.\n\n```ts\nexecute: async (args) => {\n  try {\n    const result = await performOperation(args);\n    return result;\n  } catch (error) {\n    throw new Error(`Failed to process request: ${error.message}`);\n  }\n};\n```\n\n### Timeout Handling\n\nFor long-running operations, implement timeouts using `AbortController`.\n\n```ts\nexecute: async (args, options) => {\n  const timeoutController = new AbortController();\n  const timeoutId = setTimeout(() => {\n    timeoutController.abort(\"Operation timed out\");\n  }, 5000);\n\n  try {\n    // Listen to parent abort if provided\n    const parentController = options?.abortController;\n    if (parentController?.signal) {\n      parentController.signal.addEventListener(\"abort\", () => {\n        timeoutController.abort(\"Parent operation aborted\");\n        clearTimeout(timeoutId);\n      });\n    }\n\n    const result = await fetch(url, {\n      signal: timeoutController.signal,\n    });\n    clearTimeout(timeoutId);\n    return result;\n  } catch (error) {\n    clearTimeout(timeoutId);\n    throw error;\n  }\n};\n```\n\n## MCP (Model Context Protocol) Support\n\nVoltAgent supports the [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol/mcp), allowing agents to connect with external MCP-compatible servers and tools.\n\n### Using MCP Tools\n\nConnect to MCP servers and use their tools with your agents:\n\n```ts\nimport { Agent, MCPConfiguration } from \"@voltagent/core\";\n\n// Configure MCP servers\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    // HTTP server\n    browserTools: {\n      type: \"http\",\n      url: \"https://your-mcp-server.example.com/browser\",\n    },\n    // Local stdio server\n    localAI: {\n      type: \"stdio\",\n      command: \"python\",\n      args: [\"local_ai_server.py\"],\n    },\n  },\n});\n\n// Get tools grouped by server\nconst toolsets = await mcpConfig.getToolsets();\nconst browserToolsOnly = toolsets.browserTools.getTools();\n\n// Or get all tools combined\nconst allMcpTools = await mcpConfig.getTools();\n\n// Create agent with MCP tools\nconst agent = new Agent({\n  name: \"MCP-Enhanced Assistant\",\n  description: \"Assistant with MCP tools\",\n  model: \"openai/gpt-4o\",\n  tools: allMcpTools,\n});\n```\n\nFor detailed MCP setup and usage, see the [MCP documentation](./mcp/mcp.md).\n","website/docs/agents/voice.md":"---\ntitle: Voice\nslug: /agents/voice\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Voice\n\nThe `@voltagent/voice` package provides text-to-speech (TTS) and speech-to-text (STT) capabilities through various providers. It allows your applications to speak text and transcribe audio with minimal setup.\n\n## Installation\n\nInstall the package using your preferred package manager:\n\n<Tabs>\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/voice\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/voice\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/voice\n```\n\n  </TabItem>\n</Tabs>\n\n## Supported Providers\n\n- **OpenAI**: High-quality voices and transcription.\n- **ElevenLabs**: Realistic, customizable voices.\n- **xsAI**: Lightweight OpenAI-compatible voice API.\n\n## Basic Usage\n\nFirst, initialize a voice provider instance.\n\n### Initialize a Voice Provider\n\n```typescript\n// Initialize with OpenAI\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\nconst openAIVoice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY, // Ensure API key is set in environment variables\n  ttsModel: \"tts-1\",\n  voice: \"alloy\", // Available voices: alloy, echo, fable, onyx, nova, shimmer\n});\n\n// Or initialize with ElevenLabs\nimport { ElevenLabsVoiceProvider } from \"@voltagent/voice\";\n\nconst elevenLabsVoice = new ElevenLabsVoiceProvider({\n  apiKey: process.env.ELEVENLABS_API_KEY, // Ensure API key is set\n  ttsModel: \"eleven_multilingual_v2\",\n  voice: \"Rachel\", // Example voice ID\n});\n\n// Or initialize with xsAI\nimport { XsAIVoiceProvider } from \"@voltagent/voice\";\n\nconst xsAIVoice = new XsAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY!,\n  ttsModel: \"tts-1\",\n  voice: \"alloy\",\n  // If you are not using OpenAI, simply specify the `baseURL`\n});\n```\n\n**Note:** It's recommended to manage API keys securely, for example, using environment variables.\n\n### Text-to-Speech (TTS)\n\nConvert text into an audio stream. You can then process this stream, for example, by saving it to a file.\n\n```typescript\nimport { createWriteStream } from \"node:fs\";\nimport { PassThrough } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\"; // Use pipeline for better error handling\n\n// --- Example 1: Basic Speak and Save to File ---\n\nconsole.log(\"Generating audio...\");\n// Get the audio stream for the text\nconst audioStream = await openAIVoice.speak(\"Hello from VoltAgent!\");\n\nconsole.log(\"Saving audio to output.mp3...\");\n// Create a file stream to write the audio\nconst fileStream = createWriteStream(\"output.mp3\");\n\ntry {\n  // Pipe the audio stream to the file stream and wait for completion\n  await pipeline(audioStream, fileStream);\n  console.log(\"Audio successfully saved to output.mp3\");\n} catch (error) {\n  console.error(\"Failed to save audio:\", error);\n}\n\n// --- Example 2: Speak with Options and Save ---\n\nconsole.log(\"Generating custom audio...\");\nconst customAudioStream = await elevenLabsVoice.speak(\"Speaking faster now.\", {\n  // Provider-specific options can be passed here.\n  // For OpenAI, you might use:\n  // voice: \"nova\", // Override the default voice\n  // speed: 1.5,    // Adjust speaking speed (1.0 is default)\n});\n\nconsole.log(\"Saving custom audio to custom_output.mp3...\");\nconst customFileStream = createWriteStream(\"custom_output.mp3\");\n\ntry {\n  // Pipe the custom audio stream to the file stream\n  await pipeline(customAudioStream, customFileStream);\n  console.log(\"Custom audio successfully saved to custom_output.mp3\");\n} catch (error) {\n  console.error(\"Failed to save custom audio:\", error);\n}\n\n// Note: The audioStream is a standard Node.js Readable stream.\n// You can pipe it to other destinations or process it directly.\n```\n\n### Speech-to-Text (STT)\n\nTranscribe audio from a stream into text.\n\n```typescript\nimport { createReadStream } from \"node:fs\";\n\n// Ensure you have an audio file (e.g., input.mp3)\nconst audioFileStream = createReadStream(\"input.mp3\");\n\n// Basic transcription with OpenAI\nconst transcribedText = await openAIVoice.listen(audioFileStream);\nconsole.log(\"Transcription:\", transcribedText);\n\n// Transcription with options (e.g., language hint)\nconst transcribedSpanish = await openAIVoice.listen(audioFileStream, {\n  language: \"es\", // Specify the language code\n});\nconsole.log(\"Spanish Transcription:\", transcribedSpanish);\n\n// Streaming transcription (Provider-dependent, e.g., OpenAI)\ntry {\n  const streamingTranscription = await openAIVoice.listen(audioFileStream, {\n    stream: true, // Enable streaming\n  });\n\n  for await (const chunk of streamingTranscription) {\n    // Process transcription chunks as they arrive\n    // The structure of 'chunk' depends on the provider\n    console.log(\"Stream Chunk:\", chunk);\n  }\n} catch (error) {\n  console.error(\"Streaming transcription failed:\", error);\n}\n```\n\n### List Available Voices\n\nFetch the list of voices supported by the provider.\n\n```typescript\n// Using the initialized openAIVoice instance\nconst availableVoices = await openAIVoice.getVoices();\nconsole.log(\"Available OpenAI Voices:\", availableVoices);\n/* Example Output:\n[\n  { id: 'alloy', name: 'Alloy', language: 'en', gender: 'neutral' },\n  { id: 'echo', name: 'Echo', language: 'en', gender: 'male' },\n  // ... other voices\n]\n*/\n\nconst elevenLabsVoices = await elevenLabsVoice.getVoices();\nconsole.log(\"Available ElevenLabs Voices:\", elevenLabsVoices); // Structure might differ\n```\n\n## Integrating with Agent\n\nYou can integrate the voice provider directly into an `Agent` instance.\n\n```typescript\nimport { Agent, OpenAIAgentProvider } from \"@voltagent/core\";\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\n// Initialize the voice provider\nconst voice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n  ttsModel: \"tts-1\",\n  voice: \"nova\",\n});\n\n// Initialize the agent provider\nconst provider = new OpenAIAgentProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n});\n\n// Create the agent, passing the voice instance\nconst voiceEnabledAgent = new Agent({\n  name: \"VoiceBot\",\n  instructions: \"An agent that can speak.\",\n  provider,\n  model: \"openai/gpt-4o\", // Choose an appropriate model\n  voice: voice, // Assign the voice provider here\n});\n\n// Now the agent instance potentially uses the voice capabilities\n// (Specific usage within the agent depends on future VoltAgent features or custom implementations)\n\n// Example: Manually trigger speech from agent's response\n// const response = await voiceEnabledAgent.generateText(\"Tell me a short story.\");\n// if (voiceEnabledAgent.voice) {\n//   const audio = await voiceEnabledAgent.voice.speak(response.text);\n//   // Pipe audio to output\n//   audio.pipe(createWriteStream(\"story.mp3\"));\n// }\n```\n\n**Note:** The direct integration of `voice` within the `Agent`'s core methods (`generateText`, `streamText`, etc.) for automatic speaking/listening is under development. Currently, you can access the `agent.voice` property to manually trigger TTS or STT.\n\n## Event Handling\n\nListen for events emitted by the voice provider instance.\n\n```typescript\n// Using the initialized openAIVoice instance\n\n// Listen for the start of speech synthesis\nopenAIVoice.on(\"speaking\", (data) => {\n  console.log(`[Speaking Started] Text: \"${data.text}\"`);\n});\n\n// Listen for the start of transcription\nopenAIVoice.on(\"listening\", (data) => {\n  // data might contain information about the audio stream\n  console.log(\"[Listening Started]\");\n});\n\n// Handle errors\nopenAIVoice.on(\"error\", (errorData) => {\n  console.error(\n    `[Voice Error] Code: ${errorData.code}, Message: ${errorData.message}`,\n    errorData.details\n  );\n});\n\n// Trigger an action that emits events\ntry {\n  await openAIVoice.speak(\"Testing event listeners.\");\n  // const audioInput = createReadStream(\"input.mp3\");\n  // await openAIVoice.listen(audioInput);\n} catch (error) {\n  // Errors caught here might also be emitted via the 'error' event\n  console.error(\"Direct action failed:\", error);\n}\n```\n\n## Creating a Custom Voice Provider\n\nExtend the `BaseVoiceProvider` to integrate other TTS/STT services.\n\n```typescript\nimport {\n  BaseVoiceProvider,\n  VoiceProviderEvents,\n  VoiceMetadata,\n  VoiceSpeakOptions,\n  VoiceListenOptions,\n} from \"@voltagent/voice\";\nimport { PassThrough, Readable } from \"node:stream\"; // Import Readable\n\n// Define your custom provider options\ntype MyCustomProviderOptions = {\n  apiKey: string;\n  region?: string;\n  // Add other necessary options\n};\n\n// Define potential errors specific to your provider\nenum MyCustomErrorCode {\n  AuthError = \"AUTH_ERROR\",\n  ApiError = \"API_ERROR\",\n  InvalidInput = \"INVALID_INPUT\",\n}\n\nexport class MyCustomVoiceProvider extends BaseVoiceProvider<\n  MyCustomProviderOptions,\n  VoiceProviderEvents\n> {\n  private readonly apiKey: string;\n  private readonly region: string;\n  // Add client instances or other state needed\n\n  constructor(options: MyCustomProviderOptions) {\n    super(options); // Pass options to the base class\n    this.apiKey = options.apiKey;\n    this.region = options.region || \"default-region\";\n    // Initialize your API client here\n  }\n\n  /**\n   * Connect to the service if needed (e.g., WebSockets).\n   */\n  async connect(): Promise<void> {\n    console.log(\"Connecting to Custom Voice Service...\");\n    // Add connection logic if required\n    // Simulating async operation\n    await new Promise((resolve) => setTimeout(resolve, 100));\n    console.log(\"Connected.\");\n  }\n\n  /**\n   * Disconnect from the service.\n   */\n  disconnect(): void {\n    console.log(\"Disconnecting from Custom Voice Service...\");\n    // Add disconnection logic\n  }\n\n  /**\n   * Send audio data (e.g., for streaming STT).\n   */\n  async send(audioData: Readable): Promise<void> {\n    console.log(\"Sending audio data chunk...\");\n    // Implementation for sending streaming audio\n    // Read from audioData stream and send to your API\n    // Example: audioData.pipe(yourApiStream);\n    await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate async send\n  }\n\n  /**\n   * Convert text to speech.\n   */\n  async speak(\n    text: string, // Assuming text is always string for simplicity here\n    options?: VoiceSpeakOptions // Use standardized options type\n  ): Promise<Readable> {\n    // Return standard Readable stream\n    this.emit(\"speaking\", { text }); // Emit standard event\n\n    try {\n      console.log(`Speaking text: \"${text}\" with voice: ${options?.voice}`);\n      // --- Your TTS API Call Logic ---\n      // const apiResponse = await yourTTSClient.synthesize({\n      //   text: text,\n      //   voice: options?.voice || 'default-voice',\n      //   apiKey: this.apiKey,\n      //   // map other options like speed if applicable\n      // });\n      // -----------------------------\n\n      // Simulate receiving audio data from API\n      await new Promise((resolve) => setTimeout(resolve, 500));\n      const audioBuffer = Buffer.from(`Simulated audio for: ${text}`);\n\n      // Return audio data as a Readable stream\n      const passThrough = new PassThrough();\n      passThrough.end(audioBuffer);\n      return passThrough;\n    } catch (error: any) {\n      const errorMessage = error.message || \"Unknown TTS error\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError, // Use your specific error codes\n        details: error,\n      });\n      throw new Error(`Custom TTS failed: ${errorMessage}`); // Re-throw a standard error\n    }\n  }\n\n  /**\n   * Convert speech to text.\n   */\n  async listen(\n    audio: Readable, // Expect a Readable stream\n    options?: VoiceListenOptions // Use standardized options type\n  ): Promise<string | AsyncIterable<any>> {\n    // Return string or stream based on options\n    this.emit(\"listening\", { audio }); // Emit standard event\n\n    try {\n      console.log(`Listening with language: ${options?.language}, stream: ${options?.stream}`);\n\n      // --- Your STT API Call Logic ---\n      // Handle both standard and streaming modes\n      if (options?.stream) {\n        // Implement streaming transcription logic\n        // Example: const transcriptStream = yourSTTClient.startStream(audio, { language: options.language });\n        // return transcriptStream; // Return the async iterable from your client\n\n        // --- Placeholder for streaming ---\n        async function* generateChunks() {\n          yield { delta: \"Simulated \" };\n          await new Promise((resolve) => setTimeout(resolve, 200));\n          yield { delta: \"streamed \" };\n          await new Promise((resolve) => setTimeout(resolve, 200));\n          yield { delta: \"transcript.\" };\n        }\n        return generateChunks();\n        // --- End Placeholder ---\n      } else {\n        // Implement non-streaming transcription logic\n        // Example: const result = await yourSTTClient.transcribe(audio, { language: options?.language });\n        // return result.text;\n\n        // --- Placeholder for non-streaming ---\n        // Simulate reading the stream and getting a result\n        let fullAudio = Buffer.alloc(0);\n        for await (const chunk of audio) {\n          fullAudio = Buffer.concat([fullAudio, chunk]);\n        }\n        await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate API call\n        return \"Simulated full transcript of audio.\";\n        // --- End Placeholder ---\n      }\n      // -----------------------------\n    } catch (error: any) {\n      const errorMessage = error.message || \"Unknown STT error\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError, // Use your specific error codes\n        details: error,\n      });\n      throw new Error(`Custom STT failed: ${errorMessage}`); // Re-throw a standard error\n    }\n  }\n\n  /**\n   * Fetch available voices from your service.\n   */\n  async getVoices(): Promise<VoiceMetadata[]> {\n    try {\n      // --- Your API Call Logic to Get Voices ---\n      // const apiVoices = await yourClient.listVoices({ apiKey: this.apiKey });\n      // return apiVoices.map(v => ({ id: v.id, name: v.name, language: v.lang, gender: v.gender }));\n      // ---------------------------------------\n\n      // --- Placeholder ---\n      await new Promise((resolve) => setTimeout(resolve, 100)); // Simulate API call\n      return [\n        { id: \"custom-voice-1\", name: \"Custom Voice One\", language: \"en\", gender: \"female\" },\n        { id: \"custom-voice-2\", name: \"Custom Voice Two\", language: \"es\", gender: \"male\" },\n      ];\n      // --- End Placeholder ---\n    } catch (error: any) {\n      const errorMessage = error.message || \"Failed to fetch custom voices\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError,\n        details: error,\n      });\n      // Return empty array or throw, depending on desired behavior\n      return [];\n    }\n  }\n}\n\n// Example usage of the custom provider\n// const myVoice = new MyCustomVoiceProvider({ apiKey: \"YOUR_CUSTOM_API_KEY\" });\n// const audio = await myVoice.speak(\"Hello from my custom provider!\");\n// audio.pipe(createWriteStream(\"custom_output.mp3\"));\n```\n\nThis updated custom provider example uses standardized types like `VoiceSpeakOptions`, `VoiceListenOptions`, `VoiceMetadata`, and `Readable` stream types for better consistency and type safety. It also includes more detailed placeholders for API logic and error handling.\n","website/docs/agents/voltagent-instance.md":"---\ntitle: VoltAgent Instance\nsidebar_label: VoltAgent Instance\n---\n\n# VoltAgent Instance\n\n`VoltAgent` is the application entrypoint. It wires agents and workflows together, applies global defaults, and optionally starts an HTTP or serverless provider.\n\n## Basic Setup\n\n```ts\nimport { Agent, VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst assistant = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users get answers fast.\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nnew VoltAgent({\n  agents: { assistant },\n  server: honoServer({ port: 3141 }),\n});\n```\n\n## What VoltAgent Manages\n\n- Registers agents and workflows for API access and observability.\n- Applies global defaults (logger, observability, memory, conversation persistence).\n- Boots HTTP or serverless providers.\n- Registers triggers, MCP servers, and A2A servers.\n- Coordinates graceful shutdown for servers and telemetry.\n\n## Registering Agents and Workflows\n\nYou can register instances on startup or later at runtime.\n\n```ts\nimport { VoltAgent } from \"@voltagent/core\";\n\nconst app = new VoltAgent({\n  agents: { assistant },\n  workflows: { onboardingWorkflow },\n});\n\napp.registerAgent(anotherAgent);\napp.registerWorkflow(anotherWorkflow);\n```\n\nTo register triggers at runtime:\n\n```ts\napp.registerTriggers(triggers);\n```\n\n## Memory Defaults\n\nSet default memory once at the VoltAgent entrypoint. Defaults apply only when an agent or workflow does not specify `memory`. Omitting `memory` does not disable it; it still resolves to configured defaults (or built-in in-memory). An explicit `memory: false` on an agent disables memory entirely.\n\n```ts\nimport { Memory, VoltAgent } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agentMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/agent.db\" }),\n});\n\nconst workflowMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/workflows.db\" }),\n});\n\nnew VoltAgent({\n  agentMemory,\n  workflowMemory,\n  // memory: sharedFallbackMemory,\n});\n```\n\n**Precedence**\n\n- Agents: agent `memory` > `agentMemory` > `memory` > built-in in-memory\n- Workflows: workflow `memory` > `workflowMemory` > `memory` > built-in in-memory\n\nSee [Memory Overview](/docs/agents/memory/overview) for adapter options.\n\n## Conversation Persistence Defaults\n\nSet a default persistence strategy for agents that do not explicitly define `conversationPersistence`.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  agentConversationPersistence: {\n    mode: \"step\", // default\n    debounceMs: 200, // default\n    flushOnToolResult: true, // default\n  },\n});\n```\n\n**Precedence**\n\n- Per-call `options.memory.options.conversationPersistence` (preferred)\n- Per-call `options.conversationPersistence` (deprecated)\n- Agent `conversationPersistence`\n- VoltAgent `agentConversationPersistence`\n- Built-in defaults (`mode: \"step\"`, `debounceMs: 200`, `flushOnToolResult: true`)\n\n## Observability and Logging\n\nVoltAgent creates an OpenTelemetry-based observability instance by default. You can provide your own instance or logger if you want full control.\n\n```ts\nimport { VoltAgent, VoltAgentObservability } from \"@voltagent/core\";\n\nconst observability = new VoltAgentObservability({\n  serviceName: \"my-voltagent-app\",\n});\n\nnew VoltAgent({\n  agents: { assistant },\n  observability,\n});\n```\n\nSee [Observability Overview](/docs/observability/overview) and [Logging](/docs/observability/logging) for details.\n\nIf you already have a `VoltOpsClient` or a custom logger, pass them once and VoltAgent shares them across agents and workflows.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  voltOpsClient,\n  logger,\n});\n```\n\n## Server and Serverless Providers\n\nUse `server` to expose HTTP endpoints, or `serverless` for fetch-based runtimes.\n\n```ts\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst app = new VoltAgent({\n  agents: { assistant },\n  server: honoServer({ port: 3141, enableSwaggerUI: true }),\n});\n```\n\nSee [API Overview](/docs/api/overview) for the full endpoint list.\n\nFor HTTP servers, use `startServer()` and `stopServer()` if you need manual control:\n\n```ts\nawait app.startServer();\nawait app.stopServer();\n```\n\nYou can read the server instance directly:\n\n```ts\nconst serverInstance = app.getServerInstance();\n```\n\nIf you are using a serverless provider, you can access it with `serverless()`:\n\n```ts\nimport { serverlessHono } from \"@voltagent/serverless-hono\";\n\nconst serverlessApp = new VoltAgent({\n  agents: { assistant },\n  serverless: serverlessHono(),\n});\n\nconst serverlessProvider = serverlessApp.serverless();\n```\n\n## Triggers and Protocol Servers\n\nVoltAgent can register triggers and protocol servers alongside your agents.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  triggers,\n  mcpServers,\n  a2aServers,\n});\n```\n\nSee [Triggers](/docs/triggers) and [MCP](/docs/agents/mcp) for setup details.\nFor A2A servers, see [A2A Server](/docs/agents/a2a/a2a-server).\n\n## Runtime Accessors and Shutdown\n\nVoltAgent exposes accessors for registered components and a graceful shutdown helper.\n\n```ts\nconst agents = app.getAgents();\nconst workflows = app.getWorkflows();\nconst observability = app.getObservability();\n\nawait app.shutdown();\n```\n\n## Dependency Update Checks\n\n`checkDependencies` controls a background dependency update check intended for Node runtimes. It is skipped in serverless or edge environments, and the current core implementation is a no-op placeholder.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  checkDependencies: false,\n});\n```\n","website/docs/tools/overview.md":"---\ntitle: Overview\n---\n\n# Tools & Toolkits\n\nVoltAgent allows you to extend the capabilities of your AI agents by providing them with **Tools**. Tools enable agents to interact with external APIs, perform calculations, access databases, or execute virtually any custom code. This guide covers how to define and use individual tools and the new `Toolkit` concept for managing related tools.\n\n## Defining a Single Tool\n\nThe most basic way to define a tool is using the `createTool` helper function (or instantiating the `Tool` class directly).\n\nA tool requires:\n\n- `name`: A unique name for the tool (used by the LLM to call it).\n- `description`: A clear description of what the tool does (used by the LLM to decide when to use it).\n- `parameters`: A Zod schema defining the input arguments the tool expects.\n- `execute`: An asynchronous function that contains the tool's logic, taking the validated arguments as input.\n- `outputSchema` (optional): A Zod schema defining the expected output format. When provided, the tool's output will be validated against this schema.\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define a simple weather tool\nconst getWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches the current weather for a given location.\",\n  parameters: z.object({\n    location: z.string().describe(\"The city and state, e.g., San Francisco, CA\"),\n  }),\n  execute: async ({ location }) => {\n    // In a real scenario, you would call a weather API here\n    console.log(`Fetching weather for ${location}...`);\n    if (location.toLowerCase().includes(\"tokyo\")) {\n      return { temperature: \"15°C\", condition: \"Cloudy\" };\n    }\n    return { temperature: \"22°C\", condition: \"Sunny\" };\n  },\n});\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that can fetch weather information.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeatherTool], // Add the tool to the agent\n});\n\n// Now the agent can use the 'get_weather' tool when asked about weather.\n```\n\n## Accessing Operation Context in Tools\n\nTools can access operation metadata, user context, and control mechanisms through the second parameter:\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Tool that uses operation context\nconst contextAwareWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches weather with user preferences\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  execute: async ({ location }, options) => {\n    // Access user-defined context\n    const units = options?.context?.get(\"preferredUnits\") || \"celsius\";\n    const userId = options?.userId;\n\n    // Use operation-scoped logger\n    options?.logger?.info(`Fetching weather for ${location} in ${units}`);\n\n    // Check abort signal\n    if (options?.abortSignal?.aborted) {\n      throw new Error(\"Request was cancelled\");\n    }\n\n    // Call weather API with user preferences\n    const response = await fetch(\n      `https://api.weather.com/current?city=${location}&units=${units}`,\n      { signal: options?.abortSignal }\n    );\n\n    return await response.json();\n  },\n});\n\n// Use the tool with context\nconst context = new Map();\ncontext.set(\"preferredUnits\", \"fahrenheit\");\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that respects user preferences\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [contextAwareWeatherTool],\n});\n\nconst response = await agent.generateText(\"What's the weather in Paris?\", {\n  userId: \"user123\",\n  context,\n});\n```\n\nThe `options` parameter includes:\n\n- **Operation metadata**: `operationId`, `userId`, `conversationId`\n- **User context**: `context` (Map for custom data)\n- **Control mechanisms**: `abortController`, `abortSignal`\n- **Logging**: `logger` (operation-scoped logger)\n- **AI SDK data**: `toolCallId`, `messages`\n\nFor more details, see the [Tools documentation](/docs/agents/tools#accessing-operation-context).\n\n## Tool Output Schema Validation\n\nVoltAgent supports optional output schema validation for tools. This feature ensures that tool outputs conform to a predefined structure, providing several benefits:\n\n- **Type Safety**: Tool outputs are typed based on the schema\n- **Runtime Validation**: Invalid outputs are caught immediately\n- **Error Recovery**: When validation fails, the LLM receives an error message and can retry with corrected output\n- **Consistency**: All tool responses follow the same structure\n- **Documentation**: Output schemas serve as API contracts\n\n### Example with Output Schema\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define the output schema\nconst weatherOutputSchema = z.object({\n  location: z.string(),\n  temperature: z.number(),\n  condition: z.enum([\"sunny\", \"cloudy\", \"rainy\", \"snowy\"]),\n  humidity: z.number().min(0).max(100),\n  forecast: z.object({\n    high: z.number(),\n    low: z.number(),\n    description: z.string(),\n  }),\n});\n\n// Create a tool with output validation\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather with forecast\",\n  parameters: z.object({\n    location: z.string().describe(\"City name\"),\n  }),\n  outputSchema: weatherOutputSchema, // Optional output schema\n  execute: async ({ location }) => {\n    // This output will be validated against weatherOutputSchema\n    return {\n      location,\n      temperature: 22,\n      condition: \"sunny\",\n      humidity: 65,\n      forecast: {\n        high: 25,\n        low: 18,\n        description: \"Clear skies throughout the day\",\n      },\n    };\n  },\n});\n```\n\n### How Output Validation Works\n\n1. When a tool is executed, its output is validated against the `outputSchema` if provided\n2. If validation succeeds, the validated output is returned\n3. If validation fails, an error object is returned to the LLM:\n   ```json\n   {\n     \"error\": true,\n     \"message\": \"Output validation failed: Expected number, received string\",\n     \"validationErrors\": [...],\n     \"actualOutput\": {...}\n   }\n   ```\n4. The LLM can see the validation error and potentially fix the issue by calling the tool again\n\n### Best Practices\n\n- Use output schemas for tools that need consistent response formats\n- Keep schemas focused and avoid overly complex nested structures\n- Use descriptive error messages in your schemas with `.describe()`\n- Consider making some fields optional with `.optional()` for flexibility\n- Output schemas are completely optional - tools without them work as before\n\n## Grouping Tools with Toolkits\n\nOften, several tools work together logically. For instance, tools for step-by-step reasoning (`think`, `analyze`) or a set of tools interacting with the same API. For these scenarios, VoltAgent provides **Toolkits**.\n\nA `Toolkit` allows you to:\n\n1.  **Group related tools:** Keep your tool management organized.\n2.  **Define shared instructions:** Provide common guidance to the LLM on how to use all tools within the toolkit.\n3.  **Control instruction injection:** Decide if the toolkit's shared instructions should be automatically added to the agent's system prompt.\n\n### Defining a Toolkit\n\nA `Toolkit` is an object with the following structure:\n\n```typescript\nimport { createTool, createToolkit, type Tool, type Toolkit } from \"@voltagent/core\";\n\nconst myCalculatorToolkit = createToolkit({\n  name: \"calculator_toolkit\",\n  description: \"Tools for performing basic arithmetic operations.\",\n  // Optional instructions for the LLM\n  instructions: `Use these tools for calculations. Always use 'add' for addition, 'subtract' for subtraction.`,\n  // Set to true to add the above instructions to the system prompt\n  addInstructions: true,\n  tools: [\n    createTool({\n      /* ... definition for 'add' tool ... */\n    }),\n    createTool({\n      /* ... definition for 'subtract' tool ... */\n    }),\n    // ... other calculator tools\n  ],\n});\n```\n\n**Important:** With the introduction of Toolkits, individual `Tool` instances no longer have their own `instructions` or `addInstructions` properties. Instructions are managed at the Toolkit level.\n\n### Adding Tools and Toolkits to an Agent\n\nThe `tools` option in the `Agent` constructor now accepts an array containing both individual `Tool` objects and `Toolkit` objects. The `ToolManager` handles both seamlessly.\n\n```typescript\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool, createToolkit, type Toolkit } from \"@voltagent/core\";\n// ... import other tools and toolkits ...\n\nconst agent = new Agent({\n  name: \"MultiToolAgent\",\n  instructions: \"An agent with various tools and toolkits.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [\n    getWeatherTool, // Add an individual tool\n    myCalculatorToolkit, // Add a toolkit\n    openai.tools.webSearch(), // Add a provider-defined tool\n    // ... other tools or toolkits\n  ],\n});\n```\n\n### Automatic Instructions\n\nWhen an agent is initialized, its `getSystemMessage` method checks all the `Toolkit`s provided in the `tools` array. If a `Toolkit` has `addInstructions: true` and defines an `instructions` string, those instructions will be automatically appended to the agent's base description, forming part of the final system prompt sent to the LLM.\n\n## Provider-defined Tools\n\nSome providers expose their own tools (via the Vercel AI SDK).\n\n- They can be standalone tools or live inside Toolkits and are subject to name-conflict checks when adding toolkits.\n- They are not executable on your server via the usual `Tool.execute` handler — the provider manages them.\n\n## Next Steps\n\n- See the [Reasoning Tools](/docs/tools/reasoning-tool/) documentation for a practical example of a pre-built toolkit.\n- Explore creating your own tools to connect agents to your specific data sources and APIs.\n","website/docs/tools/tool-routing.md":"---\ntitle: Tool Routing\n---\n\n# Tool Routing\n\nTool routing keeps the full tool pool hidden and exposes two system tools to the model:\n\n- `searchTools` returns tool metadata and schemas for a query.\n- `callTool` executes a tool by name with validated arguments.\n\nOnly `searchTools`, `callTool`, and any tools listed in `toolRouting.expose` are visible to the model.\n\n## How Tool Routing Works\n\n- Tool routing builds a hidden pool from `toolRouting.pool` or all registered tools (by default).\n- The model calls `searchTools` with the user request and receives tool details.\n- The model calls `callTool` with the exact tool name and schema-compliant arguments.\n- Pool tools stay hidden unless explicitly exposed.\n\n## Quick Setup With Embedding Search\n\nThis configuration enables embedding-based tool search and exposes only `searchTools` and `callTool` to the model.\n\n```ts\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    temperatureC: 22,\n    condition: \"sunny\",\n  }),\n});\n\nconst getTimeZone = createTool({\n  name: \"get_time_zone\",\n  description: \"Get the time zone offset for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    timeZone: \"UTC+1\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Tool Routing Agent\",\n  instructions:\n    \"When you need a tool, call searchTools with the user request, then call callTool with the exact tool name and schema-compliant arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeather, getTimeZone],\n  toolRouting: {\n    embedding: \"openai/text-embedding-3-small\",\n    topK: 2,\n  },\n});\n```\n\nNotes:\n\n- If `toolRouting.pool` is not set, the pool defaults to all tools registered in the agent.\n- If `toolRouting.expose` is not set, only `searchTools` and `callTool` are visible to the model.\n\n## Tool Pool and Exposed Tools\n\nUse `pool` to define the hidden tool set and `expose` to keep a subset visible.\n\n```ts\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({ location: z.string() }),\n  execute: async ({ location }) => ({ location, temperatureC: 22 }),\n});\n\nconst getStatus = createTool({\n  name: \"get_status\",\n  description: \"Return the service status\",\n  parameters: z.object({}),\n  execute: async () => ({ status: \"ok\" }),\n});\n\nconst agent = new Agent({\n  name: \"Support Agent\",\n  instructions:\n    \"Use searchTools to find tool schemas, then call callTool with validated arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    pool: [getWeather],\n    expose: [getStatus],\n  },\n});\n```\n\n## Enforcing Search Before Call\n\nBy default, `callTool` requires the tool to appear in a prior `searchTools` result.\nYou can disable this behavior:\n\n```ts\nconst agent = new Agent({\n  name: \"Relaxed Agent\",\n  instructions: \"Use searchTools before callTool when possible.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    pool: [\n      /* tools and toolkits */\n    ],\n    enforceSearchBeforeCall: false,\n  },\n});\n```\n\n## Embedding Configuration\n\n`toolRouting.embedding` accepts either a model string or an object with extra options.\n\n- `\"openai/text-embedding-3-small\"`\n- `\"text-embedding-3-small\"`\n\nProvider-qualified strings use the same registry and type list as agent model strings.\n\n```ts\nconst agent = new Agent({\n  name: \"Custom Embedding Agent\",\n  instructions:\n    \"Use searchTools to discover tool schemas, then call callTool with the right arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: {\n      model: \"openai/text-embedding-3-small\",\n      topK: 3,\n      toolText: (tool) => {\n        const tags = tool.tags?.join(\", \") ?? \"\";\n        return [tool.name, tool.description, tags].filter(Boolean).join(\"\\n\");\n      },\n    },\n  },\n});\n```\n\nThe embedding search caches vectors in memory. The cache is reset when the process restarts.\nA tool text change for a given tool name triggers a new embedding for that tool.\n\n## Per-Call Overrides\n\nYou can disable tool routing for a single call or provide a temporary routing config.\n\n```ts\nconst result = await agent.generateText(\"List tools\", {\n  toolRouting: false,\n});\n\nconst routed = await agent.generateText(\"What time is it in Berlin?\", {\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    topK: 1,\n  },\n});\n```\n\n## Hooks, Approval, and Error Handling\n\n- `needsApproval`, input/output guardrails, and tool hooks still run when tools are executed via `callTool`.\n- If a tool is not in the pool, `callTool` returns a tool error.\n- `callTool` validates arguments against the target schema and surfaces validation errors for retries.\n- Provider tools and MCP tools can be pooled; execution still runs through approvals and hooks.\n\n## Observability\n\nTool routing adds spans for selection and embedding steps:\n\n- `tool.search.selection:*` spans include `tool.search.candidates`, `tool.search.selection.count`, and selected names.\n- `tool.search.embedding:*` spans include `embedding.model`, `embedding.dimensions`, and cache hit/miss counts.\n\nThese spans appear under the `searchTools` tool span in the execution trace.\n\n## PlanAgent\n\n`PlanAgent` accepts `toolRouting` in its options. It passes routing config to its internal agent.\n\n```ts\nimport { PlanAgent } from \"@voltagent/core\";\n\nconst agent = new PlanAgent({\n  name: \"Planning Agent\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n  },\n});\n```\n"},"files":{".cursor/rules/agents.mdc":"---\ndescription: \nglobs: \nalwaysApply: true\n---\nALWAYS read the `AGENTS.md` in the root of the repository and apply the instructions to the current task.\n","AGENTS.md":"# VoltAgent\n\nVoltAgent is an open-source TypeScript framework for building and orchestrating AI agents.\n\n## What is VoltAgent?\n\nVoltAgent is a comprehensive framework that enables developers to build sophisticated AI agents with memory, tools, observability, and sub-agent capabilities. It provides direct AI SDK integration with comprehensive OpenTelemetry tracing built-in.\n\n## Overview\n\n- View [`docs/structure.md`](./docs/structure.md) for the repository structure and organization\n- View [`docs/tooling.md`](./docs/tooling.md) for development tools and utilities\n- View [`docs/testing.md`](./docs/testing.md) for testing guidelines and commands\n- View [`docs/linting.md`](./docs/linting.md) for code formatting and linting\n\n## Validating Changes\n\nTo validate your changes you can run the following commands:\n\n```bash\npnpm test:all        # Run all tests\npnpm build:all       # Build all packages\npnpm lint            # Run linting checks\npnpm lint:fix        # Auto-fix linting issues\n```\n\n## Important Notes for AI Agents\n\n1. **Always check existing patterns** before implementing new features\n2. **Use the established registry patterns** for agent and tool management\n3. **Maintain type safety** - this is a TypeScript-first codebase\n4. **Follow the monorepo structure** - changes may impact multiple packages\n5. **Test your changes** - ensure all tests pass before committing\n\n## Gotchas\n\n- **JSON.stringify** SHOULD NEVER BE USED, used the `safeStringify` function instead, imported from `@voltagent/internal`\n","CLAUDE.md":"AGENTS.md","examples/with-client-side-tools/README.md":"# VoltAgent Client-Side Tools Example\n\nThis Next.js example demonstrates how to use client-side tools with VoltAgent, providing a smooth developer experience similar to Vercel AI SDK.\n\n## Features\n\n- 🎨 **UI Tools**: Change theme, show notifications\n- 📊 **Data Tools**: Get location, read clipboard\n- 🤝 **Interactive Tools**: Ask for user confirmation\n- ⚡ **Smooth DX**: Uses Vercel AI SDK's `useChat` hook\n- 🔄 **Automatic Handling**: Tools without `execute` are automatically client-side\n\n## Running the Example\n\n1. Set your OpenAI API key:\n\n```bash\nexport OPENAI_API_KEY=\"your-api-key\"\n```\n\n2. Install dependencies:\n\n```bash\npnpm install\n```\n\n3. Run the development server:\n\n```bash\npnpm dev\n```\n\n4. Start the VoltAgent built-in server in a separate terminal:\n\n```bash\npnpm voltagent:run\n```\n\n5. Open [http://localhost:3000](http://localhost:3000)\n\nTo inspect runs, open `https://console.voltagent.dev` and connect it to `http://localhost:3141`.\n\n### How It Works\n\n### 1. Define Tools (No Execute = Client-Side)\n\n```typescript\n// Client-side tool (no execute function)\nconst getLocationTool = createTool({\n  name: \"getLocation\",\n  description: \"Get user's current location\",\n  parameters: z.object({}),\n  // No execute = automatically client-side\n});\n```\n\n### 2. Handle Client-Side Tools with useChat\n\n```typescript\nconst [result, setResult] = useState<ClientSideToolResult | null>(null);\n\nconst { messages, input, handleSubmit, addToolResult } = useChat({\n  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,\n  // Automatic client-side tool execution\n  async onToolCall({ toolCall }) {\n    if (toolCall.toolName === \"getLocation\") {\n      navigator.geolocation.getCurrentPosition(\n        (position) => {\n          const payload: ClientSideToolResult = {\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            output: {\n              latitude: position.coords.latitude,\n              longitude: position.coords.longitude,\n              accuracy: position.coords.accuracy,\n            },\n          };\n          setResult(payload);\n        },\n        (error) => {\n          const payload: ClientSideToolResult = {\n            state: \"output-error\",\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            errorText: error.message,\n          };\n          setResult(payload);\n        }\n      );\n    }\n  },\n});\n```\n\n### 2. Sending back tool results to the model\n\nWhen a client-side tool is executed, you **MUST** call `addToolResult` to provide the tool result to the model. The model will then use the result to update the conversation state.\nOtherwise, the model will consider the tool call as a failure.\n\n```ts\nuseEffect(() => {\n  if (!result) return;\n  addToolResult(result);\n}, [result, addToolResult]);\n```\n\n## Tool Types\n\n1. **Client-side Tools**: Execute client-side, don't return data\n   - Change theme\n   - Show notifications\n   - Get location\n   - Read clipboard\n   - Ask for confirmation\n\n2. **Server Tools**: Traditional server-side execution\n   - Get weather\n   - Database queries\n\n## Developer Experience\n\nThe integration is designed to be as simple as possible:\n\n1. Tools without `execute` are automatically client-side\n2. `useChat` hook handles all the streaming complexity\n3. `onToolCall` callback invoked when a tool call is received\n4. You **must** call `addToolResult` to send back the tool result.\n5. Full TypeScript support\n\n## Try These Examples\n\n- \"What's my current location?\" - Data tool\n- \"What's in my clipboard?\" - Data tool\n- \"What's the weather in San Francisco?\" - Server tool\n\n## Security Considerations\n\nClient-side tools have access to browser APIs, so:\n\n- Always validate tool arguments\n- Request user permission for sensitive operations\n- Use the interactive pattern for destructive actions\n- Consider rate limiting tool executions\n","examples/with-tools/README.md":"<div align=\"center\">\n<a href=\"https://voltagent.dev/\">\n<img width=\"1800\" alt=\"435380213-b6253409-8741-462b-a346-834cd18565a9\" src=\"https://github.com/user-attachments/assets/452a03e7-eeda-4394-9ee7-0ffbcf37245c\" />\n</a>\n\n<br/>\n<br/>\n\n<div align=\"center\">\n    <a href=\"https://voltagent.dev\">Home Page</a> |\n    <a href=\"https://voltagent.dev/docs/\">Documentation</a> |\n    <a href=\"https://github.com/voltagent/voltagent/tree/main/examples\">Examples</a> |\n    <a href=\"https://s.voltagent.dev/discord\">Discord</a> |\n    <a href=\"https://voltagent.dev/blog/\">Blog</a>\n</div>\n</div>\n\n<br/>\n\n<div align=\"center\">\n    <strong>VoltAgent is an open source TypeScript framework for building and orchestrating AI agents.</strong><br>\nEscape the limitations of no-code builders and the complexity of starting from scratch.\n    <br />\n    <br />\n</div>\n\n<div align=\"center\">\n    \n[![npm version](https://img.shields.io/npm/v/@voltagent/core.svg)](https://www.npmjs.com/package/@voltagent/core)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CODE_OF_CONDUCT.md)\n[![Discord](https://img.shields.io/discord/1361559153780195478.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://s.voltagent.dev/discord)\n[![Twitter Follow](https://img.shields.io/twitter/follow/voltagent_dev?style=social)](https://twitter.com/voltagent_dev)\n    \n</div>\n\n<br/>\n\n<div align=\"center\">\n<a href=\"https://voltagent.dev/\">\n<img width=\"896\" alt=\"VoltAgent Schema\" src=\"https://github.com/user-attachments/assets/f0627868-6153-4f63-ba7f-bdfcc5dd603d\" />\n</a>\n\n</div>\n\n## VoltAgent: Build AI Agents Fast and Flexibly\n\nVoltAgent is an open-source TypeScript framework for creating and managing AI agents. It provides modular components to build, customize, and scale agents with ease. From connecting to APIs and memory management to supporting multiple LLMs, VoltAgent simplifies the process of creating sophisticated AI systems. It enables fast development, maintains clean code, and offers flexibility to switch between models and tools without vendor lock-in.\n\n## Try Example\n\n```bash\nnpm create voltagent-app@latest -- --example with-tools\n```\n","examples/with-workspace/workspace/skills/data-analysis/SKILL.md":"---\nname: Workspace Data Analyst\ndescription: Analyze CSV files in the workspace and summarize insights.\nversion: \"1.0.0\"\ntags:\n  - data\n  - csv\n  - analysis\nreferences:\n  - references/schema.md\nassets:\n  - assets/sample.csv\n---\n\nWhen analyzing CSV data:\n\n1. Load the file and inspect headers.\n2. Summarize totals, averages, and outliers.\n3. Provide a short insight summary and recommended next steps.\n","examples/with-workspace/workspace/skills/data-analysis/references/schema.md":"# CSV Schema\n\nExpected columns:\n\n- customer_id: unique identifier\n- segment: smb | mid-market | enterprise\n- mrr: monthly recurring revenue (number)\n- region: NA | EU | APAC\n- plan: Starter | Pro | Enterprise\n","examples/with-workspace/workspace/skills/playwright-cli/SKILL.md":"---\nname: playwright-cli\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(playwright-cli:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\nplaywright-cli fill e5 \"user@example.com\"\nplaywright-cli drag e2 e8\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\nplaywright-cli snapshot --filename=after-click.yaml\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli network\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start\nplaywright-cli video-stop video.webm\n```\n\n### Install\n\n```bash\nplaywright-cli install --skills\nplaywright-cli install-browser\n```\n\n### Configuration\n\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n# Connect to browser via extension\nplaywright-cli open --extension\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n### Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli network\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Specific tasks\n\n- **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n- **Running Playwright code** [references/running-code.md](references/running-code.md)\n- **Browser session management** [references/session-management.md](references/session-management.md)\n- **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n- **Test generation** [references/test-generation.md](references/test-generation.md)\n- **Tracing** [references/tracing.md](references/tracing.md)\n- **Video recording** [references/video-recording.md](references/video-recording.md)\n","examples/with-workspace/workspace/skills/playwright-cli/references/request-mocking.md":"# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/running-code.md":"# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.loading', { state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.result', { timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const [download] = await Promise.all([\n    page.waitForEvent('download'),\n    page.click('a.download-link')\n  ]);\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.click('.maybe-missing', { timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.fill('input[name=email]', 'user@example.com');\n  await page.fill('input[name=password]', 'secret');\n  await page.click('button[type=submit]');\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/session-management.md":"# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/storage-state.md":"# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1735689600,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1735689600\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n","examples/with-workspace/workspace/skills/playwright-cli/references/test-generation.md":"# Test Generation\n\nGenerate Playwright test code automatically as you interact with the browser.\n\n## How It Works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into your test files.\n\n## Example Workflow\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n## Building a Test File\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"login flow\", async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto(\"https://example.com/login\");\n  await page.getByRole(\"textbox\", { name: \"Email\" }).fill(\"user@example.com\");\n  await page.getByRole(\"textbox\", { name: \"Password\" }).fill(\"password123\");\n  await page.getByRole(\"button\", { name: \"Sign In\" }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n## Best Practices\n\n### 1. Use Semantic Locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator(\"#submit-btn\").click();\n```\n\n### 2. Explore Before Recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### 3. Add Assertions Manually\n\nGenerated code captures actions but not assertions. Add expectations in your test:\n\n```typescript\n// Generated action\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Manual assertion\nawait expect(page.getByText(\"Success\")).toBeVisible();\n```\n","examples/with-workspace/workspace/skills/playwright-cli/references/tracing.md":"# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category        | Details                                            |\n| --------------- | -------------------------------------------------- |\n| **Actions**     | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM**         | Full DOM snapshot before/after each action         |\n| **Screenshots** | Visual state at each step                          |\n| **Network**     | All requests, responses, headers, bodies, timing   |\n| **Console**     | All console.log, warn, error messages              |\n| **Timing**      | Precise timing for each operation                  |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature                 | Trace       | Video       | Screenshot       |\n| ----------------------- | ----------- | ----------- | ---------------- |\n| **Format**              | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection**      | Yes         | No          | No               |\n| **Network details**     | Yes         | No          | No               |\n| **Step-by-step replay** | Yes         | Continuous  | Single frame     |\n| **File size**           | Medium      | Large       | Small            |\n| **Best for**            | Debugging   | Demos       | Quick capture    |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n","examples/with-workspace/workspace/skills/playwright-cli/references/video-recording.md":"# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Start recording\nplaywright-cli video-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop demo.webm\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-stop recordings/login-flow-2024-01-15.webm\nplaywright-cli video-stop recordings/checkout-test-run-42.webm\n```\n\n## Tracing vs Video\n\n| Feature  | Video                | Tracing                                  |\n| -------- | -------------------- | ---------------------------------------- |\n| Output   | WebM file            | Trace file (viewable in Trace Viewer)    |\n| Shows    | Visual recording     | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis                      |\n| Size     | Larger               | Smaller                                  |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n","examples/with-xquik-tools/README.md":"# VoltAgent with Xquik Tools\n\nThis example shows how to add Xquik REST API tools to a VoltAgent research agent for public X/Twitter data.\n\n## Features\n\n- Search public posts with X query operators\n- Look up posts by ID with author, metrics, and media\n- Look up public user profiles by username or ID\n- Fetch recent public posts from a user\n- Retrieve public X/Twitter trends by WOEID region\n\n## Prerequisites\n\n1. **Xquik API Key**: Create an API key from the [Xquik dashboard](https://dashboard.xquik.com)\n2. **OpenAI API Key**: Used by the example agent model\n\n## Setup\n\n1. Install dependencies:\n\n   ```bash\n   pnpm install\n   ```\n\n2. Configure environment variables:\n\n   ```bash\n   cp .env.example .env\n   ```\n\n   Then edit `.env`:\n\n   ```env\n   OPENAI_API_KEY=your_openai_api_key_here\n   XQUIK_API_KEY=your_xquik_api_key_here\n   XQUIK_BASE_URL=https://xquik.com/api/v1\n   ```\n\n3. Run the example:\n\n   ```bash\n   pnpm dev\n   ```\n\nThe agent runs on the default VoltAgent server port and exposes one agent named `xquikResearchAgent`.\n\n## Tools Available\n\n### 1. Search X Posts\n\n- **Purpose**: Search public X/Twitter posts with X query operators\n- **Endpoint**: `GET /x/tweets/search`\n- **Parameters**:\n  - `query`: Search query string\n  - `queryType`: `Latest` or `Top`\n  - `limit`: Maximum posts to return\n  - `cursor`: Optional pagination cursor\n  - `sinceTime`, `untilTime`: Optional ISO 8601 time bounds\n\n### 2. Get X Post\n\n- **Purpose**: Look up a public post by ID\n- **Endpoint**: `GET /x/tweets/{id}`\n\n### 3. Get X User\n\n- **Purpose**: Look up a public user profile by username or ID\n- **Endpoint**: `GET /x/users/{id}`\n\n### 4. Get X User Posts\n\n- **Purpose**: Fetch recent public posts from a user\n- **Endpoint**: `GET /x/users/{id}/tweets`\n- **Parameters**:\n  - `user`: Username without `@`, or numeric user ID\n  - `cursor`: Optional pagination cursor\n  - `includeReplies`: Include replies\n  - `includeParentTweet`: Include parent posts for replies\n\n### 5. Get X Trends\n\n- **Purpose**: Fetch public trends by WOEID region\n- **Endpoint**: `GET /x/trends`\n- **Parameters**:\n  - `woeid`: Region WOEID, with `1` for worldwide\n  - `count`: Number of trends to return\n\n## Example Queries\n\n- \"Search recent posts about AI agent frameworks and summarize the top themes.\"\n- \"Look up @voltagent_dev and summarize recent public posts.\"\n- \"Find worldwide X trends and explain which ones relate to developer tools.\"\n- \"Get this post by ID and extract the author, timestamp, and engagement metrics.\"\n\n## API Integration\n\nThis example uses the Xquik public REST API with the `x-api-key` header and the `xquik-api-contract` header set to `2026-04-29`.\n\nSee the [Xquik API reference](https://docs.xquik.com/api-reference/overview) for endpoint details.\n\n## Troubleshooting\n\n- **Missing API key**: Set `XQUIK_API_KEY` in `.env`.\n- **Authentication errors**: Create a fresh API key from the Xquik dashboard.\n- **Empty results**: Try a broader query, switch between `Latest` and `Top`, or remove time bounds.\n- **Regional trends**: Use WOEID `1` for worldwide, `23424977` for the US, or another supported region.\n","packages/a2a-server/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fa2a-server%2FREADME.md -->","packages/ag-ui/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fag-ui%2FREADME.md -->","packages/cli/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcli%2FREADME.md -->","packages/cloudflare-d1/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcloudflare-d1%2FREADME.md -->","packages/core/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcore%2FREADME.md -->","packages/create-voltagent-app/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcreate-voltagent-app%2FREADME.md -->","packages/docs-mcp/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fdocs-mcp%2FREADME.md -->","packages/evals/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fevals%2FREADME.md -->","packages/internal/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Finternal%2FREADME.md -->","packages/langfuse-exporter/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flangfuse-exporter%2FREADME.md -->","packages/libsql/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flibsql%2FREADME.md -->","packages/logger/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flogger%2FREADME.md -->","packages/mcp-server/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fmcp-server%2FREADME.md -->","packages/postgres/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fpostgres%2FREADME.md -->","packages/sandbox-blaxel/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-blaxel%2FREADME.md -->","packages/sandbox-daytona/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-daytona%2FREADME.md -->","packages/sandbox-e2b/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-e2b%2FREADME.md -->","packages/scorers/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fscorers%2FREADME.md -->","packages/sdk/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsdk%2FREADME.md -->","packages/server-core/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserver-core%2FREADME.md -->","packages/server-elysia/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserver-elysia%2FREADME.md -->","packages/serverless-hono/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserverless-hono%2FREADME.md -->","packages/supabase/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsupabase%2FREADME.md -->","packages/vercel-ai-exporter/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvercel-ai-exporter%2FREADME.md -->","packages/voice/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvoice%2FREADME.md -->","packages/voltagent-memory/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvoltagent-memory%2FREADME.md -->","tools/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=tools%2FREADME.md -->","tools/core/README.md":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=tools%2Fcore%2FREADME.md -->","website/docs/agents/a2a/a2a-server.md":"---\ntitle: A2A Server\ndescription: Step-by-step guide for exposing VoltAgent over the Agent-to-Agent protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent A2A Server Quick Start\n\nFollow the steps below to let other agents talk to your VoltAgent application over the Agent-to-Agent (A2A) protocol.\n\n## Install Packages\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n</Tabs>\n\n`@voltagent/a2a-server` exposes VoltAgent agents via JSON-RPC. `@voltagent/server-hono` wires the routes into the default Hono server (feel free to swap it out for another framework later).\n\n## Create An A2A Server Instance\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  description: \"VoltAgent A2A example\",\n  provider: {\n    organization: \"Acme\",\n    url: \"https://acme.example\",\n  },\n});\n```\n\nThe server metadata feeds the discovery card served from `/.well-known/{serverId}/agent-card.json`. The card's `url` field points to `/a2a/{serverId}`, and when the card is fetched over HTTP VoltAgent returns that URL as an absolute address.\n\n## Register The Server With VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { a2aServer } from \"./a2a/server\";\nimport { assistant } from \"./agents/assistant\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  a2aServers: {\n    a2aServer,\n  },\n  server: honoServer({ port: 3141 }),\n});\n```\n\nWith this in place, VoltAgent automatically exposes:\n\n- `GET /.well-known/{serverId}/agent-card.json`\n- `POST /a2a/{serverId}`\n\nThe JSON-RPC handler accepts `message/send`, `message/stream`, `tasks/get`, and `tasks/cancel` requests.\n\n## Available Endpoints\n\n| Method | Path                                      | Description                                                                                                      |\n| ------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |\n| `GET`  | `/.well-known/{serverId}/agent-card.json` | Returns the discovery card for the specified A2A server. The card's `url` points to `/a2a/{serverId}`.           |\n| `POST` | `/a2a/{serverId}`                         | Accepts JSON-RPC 2.0 requests. Supported methods: `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`. |\n\nExample JSON-RPC payload for `message/send`:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    \"message\": {\n      \"kind\": \"message\",\n      \"role\": \"user\",\n      \"messageId\": \"msg-1\",\n      \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }]\n    }\n  }\n}\n```\n\nYou can include an optional `context` object alongside the JSON-RPC payload to forward metadata such as `userId`, `sessionId`, or arbitrary `metadata` fields:\n\n```json\n{\n  \"context\": {\n    \"userId\": \"user-42\",\n    \"sessionId\": \"chat-1\",\n    \"metadata\": { \"tenant\": \"acme\" }\n  },\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    /* ... */\n  }\n}\n```\n\nThe context values surface inside the agent call so you can track callers or reuse an existing conversation identifier. `context.userId` is forwarded directly as the VoltAgent `userId` option, while `sessionId` and any nested `metadata` keys are merged into the agent's `options.context` map together with the request-level `params.metadata` and per-message `message.metadata` fields.\n\nIf you cannot modify the JSON body, send the same object as a JSON-encoded query parameter. VoltAgent reads `context` (or the legacy `runtimeContext`) from the query string and merges it with the body-provided values, letting the body override duplicated keys while metadata objects are combined.\n\n```bash\ncurl \\\n  -X POST \"http://localhost:3141/a2a/support-agent?context=%7B%22userId%22%3A%22user-42%22%7D\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": { \"message\": { \"kind\": \"message\", \"role\": \"user\", \"messageId\": \"msg-1\", \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }] } }\n}'\n```\n\n## Configure Which Agents Are Exposed (Optional)\n\nYou can publish agents that are not part of the primary VoltAgent registry or filter the combined list before answering A2A requests:\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\nimport { assistant } from \"./agents/assistant\";\nimport { shadowAgent } from \"./agents/shadow\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  agents: {\n    \"shadow-agent\": shadowAgent,\n  },\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n});\n```\n\nConfigured agents appear alongside registry entries. The filter receives the merged list and can drop or reorder entries before discovery and JSON-RPC calls run.\n\nVoltAgent already formats Server-Sent Events when you use `@voltagent/server-hono`, so no extra work is needed for streaming clients. If you forward chunks manually, serialize each payload with `safeStringify` and prepend `\\x1E` before the JSON string.\n\n## Provide A Different Task Store (Optional)\n\nBy default, `A2AServer` uses an in-memory store. Provide your own implementation (Redis, Postgres, KV…) by implementing the `TaskStore` interface:\n\n```ts title=\"src/a2a/redis-store.ts\"\nimport { createClient } from \"redis\";\nimport type { TaskRecord, TaskStore } from \"@voltagent/a2a-server\";\n\nexport class RedisTaskStore implements TaskStore {\n  private client = createClient({ url: process.env.REDIS_URL });\n\n  async load({ agentId, taskId }: { agentId: string; taskId: string }): Promise<TaskRecord | null> {\n    const raw = await this.client.get(`${agentId}::${taskId}`);\n    return raw ? (JSON.parse(raw) as TaskRecord) : null;\n  }\n\n  async save({ agentId, data }: { agentId: string; data: TaskRecord }): Promise<void> {\n    await this.client.set(`${agentId}::${data.id}`, JSON.stringify(data));\n  }\n}\n```\n\nPass the store during initialisation:\n\n```ts title=\"src/a2a/server.ts\"\nimport { RedisTaskStore } from \"./redis-store\";\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { assistant } from \"../agents/assistant\";\n\nconst redisTaskStore = new RedisTaskStore();\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n});\n\nexport const voltAgent = new VoltAgent({\n  agents: { assistant },\n  a2aServers: { a2aServer },\n  server: honoServer({ port: 3141 }),\n});\n\na2aServer.initialize({\n  agentRegistry: voltAgent.agentRegistry,\n  taskStore: redisTaskStore,\n});\n```\n\nRemember to implement TTL or pruning inside your custom store if you need to cap history growth.\n\n## Run The Example Smoke Test\n\nCreate the example project and run its smoke test to ensure everything is wired correctly:\n\n```bash\nnpm create voltagent-app@latest -- --example with-a2a-server\ncd with-a2a-server\npnpm test:smoke\n```\n\nThe test sends a `message/send`, streams a `message/stream`, and exercises `tasks/cancel` to verify streaming cancellation propagation.\n\n## Troubleshooting checklist\n\n- **404 for discovery card**: ensure the `serverId` in `/.well-known/{serverId}/agent-card.json` matches `A2AServer({ id })`, or the normalized `name` when `id` is omitted. It does not come from `VoltAgent({ agents: { ... } })` or the `a2aServers` map key.\n- **Unexpected JSON in SSE**: confirm you are stripping the `\\x1E` prefix before parsing the JSON payload.\n- **Cancellation not propagating**: verify you call `tasks/cancel` with the task ID from the stream and that your TaskStore preserves the `activeCancellations` set.\n","website/docs/agents/cancellation.md":"---\ntitle: Cancellation\nslug: /agents/cancellation\n---\n\n# Cancellation\n\nVoltAgent implements cancellation through the standard `AbortController` API. Pass an `AbortController` instance to agent methods to stop LLM generation, tool execution, and multi-agent workflows.\n\n## Basic Cancellation\n\nCreate an `AbortController` and pass it to agent methods:\n\n```typescript\nimport { Agent, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst abortController = new AbortController();\n\n// Cancel after 5 seconds\nsetTimeout(() => {\n  abortController.abort(\"Timeout: Operation took too long\");\n}, 5000);\n\ntry {\n  const response = await agent.generateText(\"Write a detailed analysis...\", {\n    abortController,\n  });\n  console.log(response.text);\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Operation cancelled:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## How Cancellation Works\n\nWhen you provide an `AbortController` to an agent method:\n\n1. The signal is passed to the LLM provider\n2. Tools receive the `OperationContext` containing the `abortController`\n3. Subagents inherit the parent's `abortController`\n4. All operations check the signal state before proceeding\n\nCancellation propagates through the entire operation chain for clean shutdown at every level.\n\n:::note Parameter Deprecation\nThe `signal` parameter is deprecated. Use `abortController` instead. The `signal` parameter will be removed in a future version.\n:::\n\n## Streaming Cancellation\n\nCancellation works with streaming responses:\n\n```typescript\nconst abortController = new AbortController();\n\n// User can cancel anytime\ndocument.getElementById(\"stop-btn\")?.addEventListener(\"click\", () => {\n  abortController.abort(\"User requested stop\");\n});\n\nconst response = await agent.streamText(\"Generate a long report...\", {\n  abortController,\n});\n\ntry {\n  for await (const chunk of response.textStream) {\n    process.stdout.write(chunk);\n  }\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"\\nStream cancelled\");\n  }\n}\n```\n\nWith `fullStream`, you receive detailed cancellation feedback:\n\n```typescript\nconst response = await agent.streamText(\"Complex task...\", {\n  abortController,\n});\n\nfor await (const event of response.fullStream) {\n  if (event.type === \"error\" && isAbortError(event.error)) {\n    console.log(\"Cancelled during:\", event.context);\n    break;\n  }\n  // Process other events\n}\n```\n\n## Tool Cancellation\n\nTools receive the `OperationContext` as the second parameter. Access `abortController` through the context to respond to or trigger cancellation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst dataProcessingTool = createTool({\n  name: \"process_data\",\n  description: \"Process large datasets\",\n  parameters: z.object({\n    dataset: z.string(),\n    operation: z.string(),\n  }),\n  execute: async (args, context) => {\n    const abortController = context?.abortController;\n    const signal = abortController?.signal;\n\n    // Check if already aborted\n    if (signal?.aborted) {\n      return { error: \"Operation was already cancelled\" };\n    }\n\n    // Tool can trigger abort based on conditions\n    if (args.dataset === \"restricted\") {\n      abortController?.abort(\"Access to restricted dataset denied\");\n      return { error: \"Dataset access denied\" };\n    }\n\n    // Pass signal to async operations\n    try {\n      const response = await fetch(`/api/process/${args.dataset}`, {\n        method: \"POST\",\n        body: JSON.stringify({ operation: args.operation }),\n        signal,\n      });\n\n      return await response.json();\n    } catch (error) {\n      if (error.name === \"AbortError\") {\n        return { error: \"Processing cancelled\" };\n      }\n      throw error;\n    }\n  },\n});\n```\n\n## Multi-Agent Cancellation\n\nIn supervisor-subagent architectures, the abort signal propagates to all subagents:\n\n```typescript\nconst researcher = new Agent({\n  name: \"Researcher\",\n  instructions: \"Research topics thoroughly\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst writer = new Agent({\n  name: \"Writer\",\n  instructions: \"Write detailed content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate research and writing\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researcher, writer],\n});\n\nconst abortController = new AbortController();\n\n// Cancel supervisor and any active subagent operations\nsetTimeout(() => {\n  abortController.abort(\"Deadline reached\");\n}, 10000);\n\nconst response = await supervisor.streamText(\"Research and write about quantum computing\", {\n  abortController,\n});\n```\n\nWhen the supervisor is cancelled:\n\n- Any active `delegate_task` operations stop\n- Subagents inherit the parent's `abortController`\n- All tool executions within subagents are cancelled\n- The entire workflow stops\n\n## Timeout Implementation\n\nImplement timeouts for operations:\n\n```typescript\nconst abortController = new AbortController();\n\n// Set a timeout to abort after 30 seconds\nconst timeoutId = setTimeout(() => {\n  abortController.abort(\"Operation timeout - exceeded 30 seconds\");\n}, 30000);\n\ntry {\n  const response = await agent.generateText(\"Complex analysis...\", {\n    abortController,\n  });\n\n  // Clear timeout if operation completes\n  clearTimeout(timeoutId);\n  console.log(response.text);\n} catch (error) {\n  clearTimeout(timeoutId); // Always clear the timeout\n\n  if (isAbortError(error)) {\n    console.log(\"Operation timed out:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## Error Detection with Hooks\n\nAgent hooks can detect and handle abort errors:\n\n```typescript\nimport { createHooks, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ error, context }) => {\n      if (isAbortError(error)) {\n        // Handle abort - error.name === \"AbortError\"\n        console.log(\"Operation aborted:\", error.message);\n\n        // Cleanup resources\n        await cleanup(context);\n\n        // Log metrics\n        await logAbortMetrics({\n          operationId: context.operationId,\n          reason: error.message,\n          duration: Date.now() - context.startTime,\n        });\n      } else if (error) {\n        // Handle other errors\n        console.error(\"Operation failed:\", error);\n      }\n    },\n  }),\n});\n```\n\n## REST API Cancellation\n\nClients can cancel REST API requests by closing the connection:\n\n```typescript\n// Client-side cancellation\nconst controller = new AbortController();\n\nconst response = await fetch(\"http://localhost:3141/agents/my-agent/stream\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    input: \"Generate a comprehensive report\",\n  }),\n  signal: controller.signal,\n});\n\n// Cancel button\ndocument.getElementById(\"cancel\")?.addEventListener(\"click\", () => {\n  controller.abort();\n});\n\n// Process stream\nconst reader = response.body?.getReader();\nif (reader) {\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      // Process chunk\n    }\n  } catch (error) {\n    if (error.name === \"AbortError\") {\n      console.log(\"Stream cancelled\");\n    }\n  }\n}\n```\n\nThe server detects client disconnection and aborts the agent operation.\n\n## Cancellation States\n\nOperations can be in different states regarding cancellation:\n\n```typescript\nconst abortController = new AbortController();\n\n// Check if aborted\nif (abortController.signal.aborted) {\n  console.log(\"Already aborted\");\n}\n\n// Listen for abort\nabortController.signal.addEventListener(\"abort\", () => {\n  console.log(\"Just aborted:\", abortController.signal.reason);\n});\n\n// Trigger abort with reason\nabortController.abort(\"User cancelled\");\n```\n\n## Implementation Patterns\n\n### Graceful Shutdown\n\nFor individual agent operations, manually handle shutdown signals:\n\n```typescript\nasync function processWithGracefulShutdown(agent: Agent, input: string) {\n  const abortController = new AbortController();\n\n  // Handle shutdown signals\n  const shutdownHandler = () => {\n    console.log(\"Shutting down...\");\n    abortController.abort(\"System shutdown\");\n  };\n\n  process.on(\"SIGINT\", shutdownHandler);\n  process.on(\"SIGTERM\", shutdownHandler);\n\n  try {\n    const response = await agent.generateText(input, {\n      abortController,\n    });\n    return response.text;\n  } finally {\n    process.off(\"SIGINT\", shutdownHandler);\n    process.off(\"SIGTERM\", shutdownHandler);\n  }\n}\n```\n\n### VoltAgent-Level Shutdown\n\nWhen using the `VoltAgent` class with a server, shutdown is handled automatically:\n\n```typescript\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst voltAgent = new VoltAgent({\n  agents: { myAgent },\n  server: honoServer({ port: 3141 }),\n});\n\n// VoltAgent automatically registers SIGINT/SIGTERM handlers\n// For programmatic shutdown:\nawait voltAgent.shutdown(); // Stops server, workflows, and telemetry\n```\n\nVoltAgent automatically registers SIGINT/SIGTERM handlers that clean up the server, suspend workflows, and shutdown telemetry. See [Server Architecture - Graceful Shutdown](../api/server-architecture.md#graceful-shutdown) for details.\n\n### Concurrent Operations with Cancellation\n\n```typescript\nasync function processMultiple(agent: Agent, inputs: string[]) {\n  const abortController = new AbortController();\n\n  // Cancel all if any fails\n  const promises = inputs.map(async (input) => {\n    try {\n      return await agent.generateText(input, { abortController });\n    } catch (error) {\n      abortController.abort(\"One operation failed\");\n      throw error;\n    }\n  });\n\n  return Promise.all(promises);\n}\n```\n\n## Next Steps\n\n- Learn about [Tools](./tools.md) and how they handle cancellation\n- Explore [Sub-Agents](./subagents.md) for multi-agent cancellation patterns\n- See [Hooks](./hooks.md) for cancellation detection and handling\n","website/docs/agents/context.md":"---\ntitle: Operation Context (context)\nslug: /agents/context\ndescription: Pass custom data through agent operations using context.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Operation Context (`context`)\n\nThe `context` field in `OperationContext` is a Map that passes custom data throughout a single agent operation. All components (hooks, tools, retrievers, sub-agents) can read and write to this shared data structure during execution.\n\n## Basic Concept\n\nHere's how `context` flows through an agent operation:\n\n```\nYou → Agent → Hooks → Tools → Retrievers → Memory → Sub-Agents\n     ↑                                                        ↓\n     ← ← ← ← ← ← context flows everywhere ← ← ← ← ← ← ← ← ←\n```\n\n## Initialize context\n\nYou can provide initial data in two ways:\n\n### Method 1: Set Default Context in Constructor\n\nSet default `context` when creating the agent. This context is used for all operations unless overridden at execution time:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\n// Set default context at agent creation\nconst defaultContext = new Map();\ndefaultContext.set(\"environment\", \"production\");\ndefaultContext.set(\"projectId\", \"my-project\");\n\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  context: defaultContext, // Default context for all operations\n});\n\n// Uses default context automatically\nconst response1 = await agent.generateText(\"Hello!\");\nconsole.log(\"Environment:\", response1.context?.get(\"environment\")); // \"production\"\n\n// Override with execution context (replaces default completely)\nconst response2 = await agent.generateText(\"Debug this\", {\n  context: new Map([\n    [\"environment\", \"development\"],\n    [\"projectId\", \"my-project\"],\n  ]),\n});\nconsole.log(\"Environment:\", response2.context?.get(\"environment\")); // \"development\"\nconsole.log(\"Project ID:\", response2.context?.get(\"projectId\")); // \"my-project\"\n```\n\n### Method 2: Pass Context During Execution\n\nPass `context` when calling the agent to provide operation-specific data:\n\n```typescript\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Pass context when calling the agent\nconst executionContext = new Map();\nexecutionContext.set(\"language\", \"English\");\n\nconst response = await agent.generateText(\"Hello!\", {\n  context: executionContext,\n});\n// Now you can access the data from the response\nconsole.log(\"Language:\", response.context?.get(\"language\"));\n```\n\n## Sending User Information\n\nYou can pass user information to the agent using the `context`. This information is automatically picked up by the tracing system and associated with the trace.\n\nSupported fields:\n\n- `user.name`: User's full name\n- `user.email`: User's email address\n- `user.avatar`: User's avatar URL (optional, falls back to Gravatar using email if not provided)\n- `user.metadata`: Additional metadata object\n\n<Tabs>\n  <TabItem value=\"sdk\" label=\"SDK\" default>\n\nWhen using the SDK, pass user information in the `context` Map:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({ ... });\n\nconst context = new Map();\ncontext.set(\"user.name\", \"John Doe\");\ncontext.set(\"user.email\", \"john@example.com\");\ncontext.set(\"user.avatar\", \"https://example.com/avatar.jpg\");\ncontext.set(\"user.metadata\", { plan: \"pro\" });\n\nawait agent.generateText(\"Hello\", { userId: \"user_123\", context });\n```\n\n  </TabItem>\n  <TabItem value=\"api\" label=\"REST API\">\n\nWhen using the REST API, you can pass user information in the `context` object. You can use either a nested `user` object or flat keys.\n\n**Option 1: Nested User Object**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user\": {\n      \"name\": \"John Doe\",\n      \"email\": \"john@example.com\",\n      \"avatar\": \"https://example.com/avatar.jpg\",\n      \"metadata\": { \"plan\": \"pro\" }\n    }\n  }\n}\n```\n\n**Option 2: Flat Keys**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user.name\": \"John Doe\",\n    \"user.email\": \"john@example.com\",\n    \"user.avatar\": \"https://example.com/avatar.jpg\",\n    \"user.metadata\": { \"plan\": \"pro\" }\n  }\n}\n```\n\n  </TabItem>\n</Tabs>\n\n## Hooks Access context\n\nHooks can read and write to `context`:\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"HookAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Read data that was passed in\n      const language = context.context.get(\"language\");\n      console.log(`Starting operation for language: ${language}`);\n\n      // Add new data\n      context.context.set(\"requestId\", `req-${Date.now()}`);\n      context.context.set(\"startTime\", new Date().toISOString());\n    },\n    onEnd: (context) => {\n      // Read data from context\n      const requestId = context.context.get(\"requestId\");\n      const startTime = context.context.get(\"startTime\");\n      console.log(`Request ${requestId} completed (started at ${startTime})`);\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\n\nawait agent.generateText(\"Hello!\", { context });\n```\n\n## Tools Access Context\n\nTools receive the full operation context through the second parameter (`options`), which includes all `OperationContext` fields directly accessible:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst loggerTool = createTool({\n  name: \"log_message\",\n  description: \"Logs a message with user context\",\n  parameters: z.object({\n    message: z.string(),\n  }),\n  execute: async ({ message }, options) => {\n    // Access user-defined context directly\n    const language = options?.context?.get(\"language\");\n    const requestId = options?.context?.get(\"requestId\");\n\n    // Access operation metadata\n    const userId = options?.userId;\n    const operationId = options?.operationId;\n\n    // Use the operation-scoped logger\n    options?.logger?.info(`[${requestId}] User ${userId}: ${message}`);\n\n    console.log(`[${requestId}] Language ${language}: ${message}`);\n\n    // Write to context\n    const ctx = options?.context;\n    if (ctx) {\n      const logs = ctx.get(\"logs\") || [];\n      logs.push({\n        message,\n        timestamp: new Date().toISOString(),\n        userId,\n        operationId,\n      });\n      ctx.set(\"logs\", logs);\n    }\n\n    return `Message logged for language ${language}`;\n  },\n});\n\nconst agentWithTool = new Agent({\n  name: \"ToolAgent\",\n  model: \"openai/gpt-4o\",\n  tools: [loggerTool],\n  instructions: \"Use the log_message tool to log what the user says.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\ncontext.set(\"requestId\", \"req-456\");\n\nconst response = await agentWithTool.generateText(\"Log this: Hello world!\", {\n  context,\n  userId: \"user123\",\n});\n\n// Check what was logged\nconst logs = response.context?.get(\"logs\");\nconsole.log(\"All logs:\", logs);\n```\n\n### What's Available in Tool Options?\n\nThe `options` parameter in tool `execute` functions combines `ToolExecuteOptions` and `Partial<OperationContext>`:\n\n**From OperationContext:**\n\n- `operationId` - Unique operation identifier\n- `userId` - User identifier (if provided)\n- `conversationId` - Conversation identifier (if provided)\n- `workspace` - Workspace instance configured on the agent (if available)\n- `context` - User-defined context Map (read/write)\n- `systemContext` - Internal system context Map\n- `logger` - Operation-scoped logger with full context\n- `abortController` - For operation cancellation\n- `traceContext` - OpenTelemetry trace context\n- `input` - Original input to the agent\n- `isActive` - Whether operation is still active\n\n**Tool-specific context (from `toolContext?` - optional):**\n\n> **Note:** `toolContext` is always populated when your tool is called from a VoltAgent agent. It may be `undefined` when called externally.\n\n- `toolContext?.name` - Name of the tool being executed\n- `toolContext?.callId` - Unique identifier for this tool call (from AI SDK)\n- `toolContext?.messages` - Message history at tool call time (from AI SDK)\n- `toolContext?.abortSignal` - Abort signal for cancellation (from AI SDK)\n\n```typescript\n// Example: Authorization check using context\nconst sensitiveDataTool = createTool({\n  name: \"get_sensitive_data\",\n  description: \"Retrieves sensitive user data\",\n  parameters: z.object({\n    dataType: z.string(),\n  }),\n  execute: async ({ dataType }, options) => {\n    // Check user role from context\n    const userRole = options?.context?.get(\"userRole\");\n\n    if (userRole !== \"admin\") {\n      throw new Error(\"Unauthorized: Admin role required\");\n    }\n\n    // Log access with full context including tool name\n    const toolName = options?.toolContext?.name;\n    options?.logger?.warn(`${toolName}: Sensitive data accessed: ${dataType}`, {\n      userId: options?.userId,\n      operationId: options?.operationId,\n    });\n\n    return await fetchSensitiveData(dataType);\n  },\n});\n```\n\n## Access Input and Output in Context\n\nThe `OperationContext` provides access to both the input and output of the current operation, making debugging and observability much easier.\n\n### Accessing Input\n\nThe `input` field contains the original input provided to the agent operation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst logTool = createTool({\n  name: \"log_input\",\n  description: \"Logs the original user input\",\n  parameters: z.object({}),\n  execute: async (args, options) => {\n    const input = options?.operationContext?.input;\n    console.log(\"Original input:\", input);\n    // input can be: string, UIMessage[], or BaseMessage[]\n\n    return \"Input logged successfully\";\n  },\n});\n```\n\n### Accessing Output\n\nThe `output` field contains the generated response (only available after generation completes, mainly useful in hooks):\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onEnd: async ({ context }) => {\n    // Access both input and output\n    console.log(\"User asked:\", context.input);\n    console.log(\"Agent responded:\", context.output);\n\n    // Output type depends on the method used:\n    // - generateText/streamText: string\n    // - generateObject/streamObject: object\n\n    // Log complete interaction\n    const interaction = {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    };\n\n    console.log(\"Complete interaction:\", interaction);\n  },\n});\n```\n\n### Use Cases\n\nThe `input` and `output` fields are particularly useful for:\n\n- **Audit Logging**: Track what users asked and what the agent responded\n- **Analytics**: Analyze input/output patterns across conversations\n- **Debugging**: Trace issues by comparing input to output\n- **Quality Assurance**: Monitor agent responses for quality control\n- **Custom Metrics**: Calculate response time, token efficiency, etc.\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst auditHooks = createHooks({\n  onEnd: async ({ context, output }) => {\n    // Log complete audit trail\n    await auditLog.save({\n      timestamp: context.startTime,\n      operationId: context.operationId,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      input: context.input,\n      output: context.output,\n      finalStepUsage: output?.usage,\n      totalUsage: output?.totalUsage,\n    });\n  },\n});\n\nconst auditedAgent = new Agent({\n  name: \"Audited Assistant\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  hooks: auditHooks,\n});\n```\n\n## OperationContext Properties\n\nThe `OperationContext` type (defined in [types.ts](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/types.ts)) contains multiple properties beyond the user-managed `context` Map. Understanding these properties helps with debugging, observability, and advanced use cases.\n\n### User-Managed Fields\n\nThese fields are set by you and used to identify and track operations:\n\n```typescript\ninterface OperationContext {\n  // User data storage - read and write custom values\n  readonly context: Map<string | symbol, unknown>;\n\n  // Optional user identifier\n  userId?: string;\n\n  // Optional conversation identifier\n  conversationId?: string;\n\n  // Workspace configured on the agent (if any)\n  workspace?: Workspace;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst response = await agent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n  context: new Map([[\"language\", \"en\"]]),\n});\n\n// Access in hooks or tools\nconsole.log(operationContext.userId); // \"user-123\"\nconsole.log(operationContext.conversationId); // \"conv-456\"\nconsole.log(operationContext.context.get(\"language\")); // \"en\"\nconsole.log(operationContext.workspace?.id); // e.g. \"demo-workspace\"\n```\n\n### Operation Metadata (Read-Only)\n\nThese fields are automatically set by the framework and provide metadata about the current operation:\n\n```typescript\ninterface OperationContext {\n  // Unique identifier for this operation\n  readonly operationId: string;\n\n  // When the operation started\n  startTime: Date;\n\n  // Whether the operation is still active\n  isActive: boolean;\n\n  // Parent agent ID if this is a sub-agent execution\n  parentAgentId?: string;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst agent = new Agent({\n  name: \"TrackedAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ context }) => {\n      const duration = Date.now() - context.startTime.getTime();\n      console.log(`Operation ${context.operationId} took ${duration}ms`);\n\n      if (context.parentAgentId) {\n        console.log(`Executed as sub-agent of ${context.parentAgentId}`);\n      }\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n```\n\n### System Fields (Advanced)\n\nThese fields are used internally by the framework. Most users don't need to interact with them directly:\n\n```typescript\ninterface OperationContext {\n  // Internal state management (used by framework)\n  readonly systemContext: Map<string | symbol, unknown>;\n\n  // Scoped logger with operation context\n  logger: Logger;\n\n  // OpenTelemetry span management\n  traceContext: AgentTraceContext;\n\n  // Full conversation history including tool calls\n  conversationSteps?: StepWithContent[];\n\n  // Cancellation control\n  abortController: AbortController;\n\n  // Error to throw when operation is aborted\n  cancellationError?: CancellationError;\n\n  // Bridge for requesting user input (human-in-the-loop)\n  elicitation?: (request: unknown) => Promise<unknown>;\n}\n```\n\n**System Context vs User Context:**\n\n- **`context`**: Your data - use this for sharing information between components\n- **`systemContext`**: Framework internals - stores stream writers, internal state, etc.\n\nThe framework uses `systemContext` to pass internal state between components. For example, when a sub-agent streams data, the parent agent's stream writer is stored in `systemContext` (see [subagent/index.ts:145-148](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/subagent/index.ts#L145-L148)).\n\n### Input/Output (Automatically Set)\n\nThese fields capture the operation's input and output:\n\n```typescript\ninterface OperationContext {\n  // Original input provided to the agent\n  input?: string | UIMessage[] | BaseMessage[];\n\n  // Generated response (text or object)\n  output?: string | object;\n}\n```\n\nThese fields are documented in detail in the \"Access Input and Output in Context\" section below.\n\n### Operation Lifecycle Example\n\nHere's how to use metadata fields for observability:\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst observableAgent = new Agent({\n  name: \"ObservableAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: ({ context }) => {\n      // Use the scoped logger\n      context.logger.info(\"Operation started\", {\n        operationId: context.operationId,\n        userId: context.userId,\n        conversationId: context.conversationId,\n      });\n\n      // Store start metrics\n      context.context.set(\"metrics\", {\n        startTime: context.startTime,\n        operationId: context.operationId,\n      });\n    },\n    onEnd: ({ context, output }) => {\n      const metrics = context.context.get(\"metrics\") as any;\n      const duration = Date.now() - context.startTime.getTime();\n\n      context.logger.info(\"Operation completed\", {\n        operationId: context.operationId,\n        duration,\n        inputType: typeof context.input,\n        outputType: typeof context.output,\n        isActive: context.isActive,\n      });\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// The logger automatically includes userId, conversationId, and operationId\nconst response = await observableAgent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n});\n```\n\n### Cancellation with AbortController\n\nThe `abortController` field allows operation cancellation:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"CancellableAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Create an abort controller\nconst abortController = new AbortController();\n\n// Pass the signal to the agent\nconst responsePromise = agent.generateText(\"Write a long story\", {\n  abortSignal: abortController.signal,\n});\n\n// Cancel after 1 second\nsetTimeout(() => {\n  abortController.abort(\"User cancelled\");\n}, 1000);\n\ntry {\n  const response = await responsePromise;\n} catch (error) {\n  if (error.name === \"AbortError\") {\n    console.log(\"Operation was cancelled\");\n  }\n}\n```\n\nThe agent creates an internal `AbortController` and cascades the external signal to it (see [agent.ts:3385-3395](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3385-L3395)). Sub-agents automatically inherit the parent's abort controller.\n\n## Retrievers Store References\n\nRetrievers can store source information in `context`:\n\n```typescript\nimport { BaseRetriever } from \"@voltagent/core\";\n\nclass SimpleRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    // Simulate finding documents\n    const foundDocs = [\n      { title: \"VoltAgent Guide\", url: \"https://docs.example.com\" },\n      { title: \"Agent Tutorial\", url: \"https://tutorial.example.com\" },\n    ];\n\n    // Store references in context\n    if (options?.context) {\n      options.context.set(\"references\", foundDocs);\n      options.context.set(\"searchQuery\", input);\n    }\n\n    // Return content for LLM\n    return foundDocs.map((doc) => `${doc.title}: Some helpful content...`).join(\"\\n\");\n  }\n}\n\nconst agentWithRetriever = new Agent({\n  name: \"RetrievalAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new SimpleRetriever(),\n  instructions: \"Answer using retrieved information.\",\n});\n\n// Usage\nconst response = await agentWithRetriever.generateText(\"How do I use VoltAgent?\");\n\nconsole.log(\"Answer:\", response.text);\nconsole.log(\"Search query:\", response.context?.get(\"searchQuery\"));\nconsole.log(\"References:\", response.context?.get(\"references\"));\n```\n\n## Sub-Agents Automatically Inherit Context\n\nWhen a supervisor delegates to sub-agents, the parent's `OperationContext` is passed down. The sub-agent receives the same `context` Map reference (not a copy), so modifications are visible to both parent and sub-agent.\n\n```typescript\n// Worker agent - automatically receives supervisor's context\nconst workerAgent = new Agent({\n  name: \"WorkerAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Automatically gets context from supervisor\n      const projectId = context.context.get(\"projectId\");\n      const language = context.context.get(\"language\");\n      console.log(`Worker starting for project ${projectId}, language ${language}`);\n\n      // Can add its own data too\n      context.context.set(\"workerStartTime\", new Date().toISOString());\n    },\n  }),\n  instructions: \"You are a worker that processes tasks.\",\n});\n\n// Supervisor agent\nconst supervisorAgent = new Agent({\n  name: \"SupervisorAgent\",\n  model: \"openai/gpt-4o\",\n  subAgents: [workerAgent],\n  hooks: createHooks({\n    onStart: (context) => {\n      // Set up project context\n      context.context.set(\"projectId\", `project-${Date.now()}`);\n      context.context.set(\"supervisorId\", \"supervisor-001\");\n    },\n  }),\n  instructions: \"You supervise tasks. Delegate work to WorkerAgent when needed.\",\n});\n\n// Usage\nconst initialContext = new Map();\ninitialContext.set(\"language\", \"English\");\ninitialContext.set(\"priority\", \"high\");\n\nconst response = await supervisorAgent.generateText(\"Please delegate this task to the worker\", {\n  context: initialContext,\n});\n\n// Final context includes data from both supervisor and worker\nconsole.log(\"Project ID:\", response.context?.get(\"projectId\"));\nconsole.log(\"Worker start time:\", response.context?.get(\"workerStartTime\"));\n```\n\n### Context Inheritance Priority\n\nWhen creating an `OperationContext`, the framework follows this priority (see [agent.ts:3343-3371](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3343-L3371)):\n\n1. **Parent context** (if sub-agent): Use parent's context Map reference\n2. **Runtime context** (if provided at execution): Use the provided Map\n3. **Agent-level context** (if set in constructor): Use the agent's default Map\n4. **New Map**: Create an empty Map if none exists\n\nThe framework then fills in missing keys from lower-priority sources without overriding existing values. This means:\n\n- Sub-agents see all parent context data\n- Parent's values take precedence over runtime and agent-level defaults\n- Runtime values take precedence over agent-level defaults\n\n**Important**: The context Map reference is preserved, not cloned. This enables bidirectional updates - changes made by sub-agents are visible to the parent.\n\nFor more details on sub-agent architecture, see the [Sub-Agents guide](./subagents.md).\n\n## Complete Flow Example\n\nHere's how all pieces work together:\n\n```typescript\nimport { Agent, createHooks, createTool, BaseRetriever } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Simple retriever\nclass BasicRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    if (options.context) {\n      options.context.set(\"references\", [{ title: \"Document 1\", source: \"knowledge-base\" }]);\n    }\n    return \"Retrieved content about the topic\";\n  }\n}\n\n// Simple tool\nconst counterTool = createTool({\n  name: \"increment_counter\",\n  description: \"Increments a counter\",\n  parameters: z.object({}),\n  execute: async (_, options) => {\n    const ctx = options?.context;\n    if (ctx) {\n      const count = (ctx.get(\"counter\") || 0) + 1;\n      ctx.set(\"counter\", count);\n      return `Counter is now: ${count}`;\n    }\n    return \"Counter incremented\";\n  },\n});\n\n// Agent with everything\nconst fullAgent = new Agent({\n  name: \"FullAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new BasicRetriever(),\n  tools: [counterTool],\n  hooks: createHooks({\n    onStart: (context) => {\n      console.log(\"🚀 Operation started\");\n      context.context.set(\"operationId\", `op-${Date.now()}`);\n    },\n    onEnd: (context) => {\n      const opId = context.context.get(\"operationId\");\n      const counter = context.context.get(\"counter\");\n      const references = context.context.get(\"references\");\n\n      console.log(\"✅ Operation completed\");\n      console.log(`Operation ID: ${opId}`);\n      console.log(`Counter final value: ${counter}`);\n      console.log(`References found: ${references?.length || 0}`);\n    },\n  }),\n  instructions: \"Use tools and retrieval to help users. Always increment the counter.\",\n});\n\n// Usage showing the complete flow\nasync function demonstrateFlow() {\n  const initialContext = new Map();\n  initialContext.set(\"language\", \"English\");\n\n  const response = await fullAgent.generateText(\n    \"Use the increment tool and search for information\",\n    { context: initialContext }\n  );\n\n  console.log(\"Text:\", response.text);\n  const finalContext = response.context;\n  for (const [key, value] of finalContext.entries()) {\n    console.log(`${String(key)}: ${JSON.stringify(value)}`);\n  }\n}\n```\n\n## Key Points\n\n### Context Access by Component\n\n1. **Hooks**: Access via `context.context` in `onStart`/`onEnd` callbacks\n2. **Tools**: Access via `options.operationContext.context` in `execute` function\n3. **Retrievers**: Access via `options.context` in `retrieve` method\n4. **Sub-Agents**: Automatically receive parent's `context` Map reference\n5. **Response**: Access final state via `response.context`\n6. **Dynamic Values**: Constructor context is available in dynamic instructions, model, and tools functions\n\n### OperationContext Fields\n\nThe `OperationContext` contains:\n\n- **User-managed**: `context` (Map), `userId`, `conversationId`, `workspace`\n- **Metadata**: `operationId`, `startTime`, `isActive`, `parentAgentId`\n- **System**: `systemContext`, `logger`, `traceContext`, `conversationSteps`, `abortController`, `cancellationError`, `elicitation`\n- **Input/Output**: `input`, `output` (automatically set)\n\n### Context Initialization Priority\n\n1. Parent context (for sub-agents) - highest priority\n2. Runtime context (execution parameter)\n3. Agent-level context (constructor)\n4. New empty Map - lowest priority\n\nLower-priority sources only fill in missing keys; they don't override existing values. The Map reference is preserved (not cloned) to enable bidirectional updates between parent and sub-agents.\n\n### Tips\n\n**Extend rather than replace default context:**\n\n```typescript\nconst agent = new Agent({\n  context: new Map([[\"env\", \"production\"]]),\n  // ...\n});\n\n// Extend the default context\nconst extendedContext = new Map(agent.context);\nextendedContext.set(\"requestId\", \"req-123\");\n\nawait agent.generateText(\"Hello\", { context: extendedContext });\n```\n\n**Use metadata fields for observability:**\n\n```typescript\nhooks: createHooks({\n  onEnd: ({ context }) => {\n    console.log(\n      `Operation ${context.operationId} completed in ${Date.now() - context.startTime.getTime()}ms`\n    );\n  },\n});\n```\n\n## Memory Adapters\n\nCustom memory adapters can access `OperationContext` to implement cross-cutting concerns like multi-tenancy, logging, and access control without modifying core adapter logic.\n\n### Example: Multi-Tenant Data Isolation\n\nCustom memory adapters can read user-provided context to implement tenant-scoped data access:\n\n```typescript\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext, GetMessagesOptions, UIMessage } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant ID from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID is required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log access for audit trail\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n      traceId: context.traceContext.getTraceId(),\n    });\n\n    // Use scoped user ID to isolate tenant data\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Tenant A: user-123's data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-abc\"]]), // Stores as \"company-abc:user-123\"\n});\n\n// Tenant B: same user ID, but different tenant = different data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-xyz\"]]), // Stores as \"company-xyz:user-123\"\n});\n```\n\n**What this enables:**\n\n- **Data isolation** between tenants using composite keys\n- **Per-request tenant context** without adapter reconfiguration\n- **Audit logging** with full operation context\n- **Flexible access control** based on runtime values\n\nAll memory adapters (InMemory, Postgres, LibSQL, Supabase) support `OperationContext` as an optional parameter.\n","website/docs/agents/dynamic-agents.md":"---\ntitle: Dynamic Agents\nslug: /agents/dynamic-agents\n---\n\n# Dynamic Agents\n\nDynamic agents use functions instead of static values for `instructions`, `model`, and `tools`. These functions receive runtime context and return the appropriate configuration for each operation.\n\nWhen you call `generateText()` or `streamText()`, the agent resolves dynamic values using the `context` map you provide. This lets you configure agent behavior per-request based on user roles, subscription tiers, language preferences, or any custom logic.\n\n## Use Cases\n\nDynamic agents work well for:\n\n- Multi-tenant applications where different users need different capabilities\n- Role-based access control where admins get different tools than regular users\n- Subscription tiers where premium users get access to different models\n- Internationalization where responses adapt to user's language\n- A/B testing where different users get different configurations\n\n## How Dynamic Resolution Works\n\nWhen you provide a function instead of a static value, the agent calls it during each operation:\n\n1. You call `agent.generateText(input, { context })`\n2. Agent invokes your function with `DynamicValueOptions`\n3. Function receives `{ context, prompts }` and returns the value\n4. Agent uses the resolved value for that operation\n\nThe resolution happens in [agent.ts:2583-2606](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L2583-L2606) via the `resolveValue()` method. Dynamic functions are called on every operation, so keep them synchronous or fast.\n\n### Type System\n\nDynamic values use the `DynamicValue<T>` type:\n\n```typescript\ntype DynamicValue<T> = (options: DynamicValueOptions) => Promise<T> | T;\n\ninterface DynamicValueOptions {\n  context: Map<string | symbol, unknown>; // Runtime context you provide\n  headers?: Record<string, string>; // HTTP request headers, when called through a server adapter\n  prompts: PromptHelper; // VoltOps prompt management\n}\n```\n\nThe agent defines three specific dynamic types:\n\n- `InstructionsDynamicValue`: `string | DynamicValue<string | PromptContent>`\n- `ModelDynamicValue<T>`: `T | DynamicValue<T>`\n- `ToolsDynamicValue`: `(Tool | Toolkit)[] | DynamicValue<(Tool | Toolkit)[]>`\n\n## Basic Example\n\nHere's a dynamic agent that changes instructions based on user role:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst dynamicAgent = new Agent({\n  name: \"Adaptive Assistant\",\n\n  // Dynamic instructions based on user context\n  instructions: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n    const language = (context.get(\"language\") as string) || \"English\";\n\n    if (role === \"admin\") {\n      return `You are an admin assistant with special privileges. Respond in ${language}.`;\n    } else {\n      return `You are a helpful assistant. Respond in ${language}.`;\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Dynamic Properties\n\nYou can make `instructions`, `model`, and `tools` dynamic. Each is resolved independently during operation execution.\n\n### Dynamic Instructions\n\nInstructions can be a string or a function that returns a string (or `PromptContent` when using VoltOps).\n\n```ts\nconst agent = new Agent({\n  name: \"Multilingual Support Agent\",\n\n  instructions: ({ context }) => {\n    const language = (context.get(\"language\") as string) || \"English\";\n    const supportTier = (context.get(\"supportTier\") as string) || \"basic\";\n\n    let baseInstructions = `You are a customer support agent. Respond in ${language}.`;\n\n    if (supportTier === \"premium\") {\n      baseInstructions += \" You can offer advanced troubleshooting and priority support.\";\n    } else {\n      baseInstructions += \" Provide standard support within basic guidelines.\";\n    }\n\n    return baseInstructions;\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n### Request Headers\n\nWhen an agent is called through the built-in HTTP endpoints, VoltAgent exposes the request headers to dynamic `instructions`, `model`, and `tools` functions as `headers`. Header names are normalized to lowercase.\n\n```ts\nconst agent = new Agent({\n  name: \"Tenant Agent\",\n  instructions: \"You are a tenant-aware assistant.\",\n  model: ({ headers }) => {\n    const tenantId = headers?.[\"x-tenant-id\"];\n\n    return tenantId === \"enterprise\" ? \"openai/gpt-4o\" : \"openai/gpt-4o-mini\";\n  },\n  tools: ({ headers }) => {\n    const token = headers?.authorization;\n\n    return token ? [createTenantTool(token)] : [];\n  },\n});\n```\n\nFor direct in-process calls, pass `requestHeaders`:\n\n```ts\nawait agent.generateText(\"Hello\", {\n  requestHeaders: {\n    authorization: \"Bearer token\",\n    \"x-tenant-id\": \"tenant-1\",\n  },\n});\n```\n\n### Dynamic Models\n\nThe model can be a static `LanguageModel`, a `provider/model` string, or a function returning either. The agent resolves the model at [agent.ts:1672](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1672) before each generation call.\n\n```ts\nconst agent = new Agent({\n  name: \"Tier-Based Assistant\",\n  instructions: \"You are a helpful assistant.\",\n\n  // Different models for different subscription tiers\n  model: ({ context }) => {\n    const tier = (context.get(\"tier\") as string) || \"free\";\n\n    switch (tier) {\n      case \"premium\":\n        return \"openai/gpt-4o\";\n      case \"pro\":\n        return \"openai/gpt-4o-mini\";\n      default:\n        return \"openai/gpt-3.5-turbo\";\n    }\n  },\n});\n```\n\n### Dynamic Tools\n\nTools can be a static array or a function returning an array. The agent stores dynamic tool functions separately in the `dynamicTools` property ([agent.ts:337](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L337)) and resolves them at [agent.ts:1673](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1673).\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Regular user tool\nconst basicTool = createTool({\n  name: \"get_help\",\n  description: \"Get basic help information\",\n  parameters: z.object({\n    topic: z.string().describe(\"Help topic\"),\n  }),\n  execute: async ({ topic }) => {\n    return `Here's basic help about ${topic}`;\n  },\n});\n\n// Admin-only tool\nconst adminTool = createTool({\n  name: \"admin_action\",\n  description: \"Perform administrative actions\",\n  parameters: z.object({\n    action: z.string().describe(\"Admin action to perform\"),\n  }),\n  execute: async ({ action }) => {\n    return `Admin action performed: ${action}`;\n  },\n});\n\nconst agent = new Agent({\n  name: \"Role-Based Agent\",\n  instructions: \"You are an assistant with role-based capabilities.\",\n\n  // Different tools based on user role\n  tools: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n\n    if (role === \"admin\") {\n      return [basicTool, adminTool]; // Admins get both tools\n    } else {\n      return [basicTool]; // Regular users get basic tools only\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Execution\n\nPass `context` as an option to any generation method. The agent uses this context to resolve dynamic values:\n\n```ts\n// Create context with relevant information\nconst context = new Map<string, unknown>();\ncontext.set(\"role\", \"admin\");\ncontext.set(\"language\", \"Spanish\");\ncontext.set(\"tier\", \"premium\");\ncontext.set(\"company\", \"TechCorp\");\n\n// Generate response with context\nconst response = await dynamicAgent.generateText(\"Help me manage the system settings\", {\n  context,\n});\n\nconsole.log(response.text);\n// The agent will respond in Spanish, with admin capabilities,\n// using the premium model, and have access to admin tools\n```\n\nYou can also use it with streaming:\n\n```ts\nconst streamResponse = await dynamicAgent.streamText(\"What products are available?\", {\n  context: new Map([\n    [\"role\", \"customer\"],\n    [\"language\", \"French\"],\n    [\"tier\", \"basic\"],\n  ]),\n});\n\nfor await (const chunk of streamResponse.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n## REST API Usage\n\nYou can pass `context` via the REST API to trigger dynamic resolution over HTTP.\n\n### API Request Format\n\nPass the `context` object within the `options` field of your API request:\n\n```json\n{\n  \"input\": \"Your message to the agent\",\n  \"options\": {\n    \"context\": {\n      \"role\": \"admin\",\n      \"language\": \"Spanish\",\n      \"tier\": \"premium\"\n    },\n    \"temperature\": 0.7,\n    \"maxTokens\": 500\n  }\n}\n```\n\n### REST API Examples\n\n**Basic Text Generation:**\n\n```bash\n# Admin user requesting help in Spanish\ncurl -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/text \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"I need to update system settings\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"admin\",\n           \"language\": \"Spanish\",\n           \"tier\": \"enterprise\"\n         }\n       }\n     }'\n```\n\n**Streaming with Real-time Context:**\n\n```bash\n# Stream response for different user roles\ncurl -N -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/stream \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"Write a summary of our quarterly results\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"manager\",\n           \"department\": \"finance\",\n           \"accessLevel\": \"confidential\",\n           \"language\": \"English\"\n         }\n       }\n     }'\n```\n\n## VoltOps Integration\n\nThe VoltOps dashboard lets you test dynamic agents by setting context values through a visual interface.\n\n<!-- GIF PLACEHOLDER: VoltOps Dynamic Agents Demo -->\n\n![VoltOps Dynamic Agents Demo](https://cdn.voltagent.dev/docs/user-context-demo.gif)\n\nYou can:\n\n- Set context values through a form\n- Test different user roles and configurations\n- Monitor how dynamic properties change based on context\n- Debug dynamic agent logic\n\n## Best Practices\n\n### Context Validation\n\nValidate context values and provide defaults:\n\n```ts\ninstructions: ({ context }) => {\n  // Always provide defaults and validate types\n  const role = (context.get(\"role\") as string) || \"user\";\n  const language = (context.get(\"language\") as string) || \"English\";\n\n  // Validate known values\n  const validRoles = [\"admin\", \"support\", \"customer\"];\n  const actualRole = validRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${actualRole} assistant. Respond in ${language}.`;\n};\n```\n\n### Performance\n\nDynamic functions run on every operation. Avoid async operations or heavy computation:\n\n```ts\n// ✅ Good - Fast synchronous logic\nmodel: ({ context }) => {\n  const tier = context.get('tier') as string;\n  return tier === 'premium' ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n},\n\n// ❌ Avoid - API calls or database queries\nmodel: async ({ context }) => {\n  const userProfile = await fetchUserFromDatabase(context.get('userId'));\n  return determineModelFromProfile(userProfile);\n}\n```\n\n### Security\n\nUse allowlists for security-sensitive values:\n\n```ts\ninstructions: ({ context }) => {\n  const role = context.get(\"role\") as string;\n\n  // ✅ Use allowlists for security-sensitive operations\n  const allowedRoles = [\"admin\", \"support\", \"customer\"];\n  const safeRole = allowedRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${safeRole} assistant.`;\n};\n```\n\n### Context Structure\n\nUse consistent context key naming across your application:\n\n```ts\n// ✅ Consistent naming convention\nconst createUserContext = (user: User) => {\n  const context = new Map<string, unknown>();\n  context.set(\"user.id\", user.id);\n  context.set(\"user.role\", user.role);\n  context.set(\"user.tier\", user.subscriptionTier);\n  context.set(\"user.language\", user.preferredLanguage);\n  context.set(\"request.timestamp\", Date.now());\n  return context;\n};\n```\n\n## Advanced Patterns\n\n### Conditional Resolution\n\nMake properties dynamic only when context requires it:\n\n```ts\nconst agent = new Agent({\n  name: \"Conditionally Dynamic Agent\",\n\n  // Static instructions most of the time\n  instructions: \"You are a helpful assistant.\",\n\n  // But dynamic model when user context is provided\n  model: ({ context }) => {\n    // If no tier specified, use default static model\n    if (!context.has(\"tier\")) {\n      return \"openai/gpt-4o-mini\"; // Default model\n    }\n\n    // Otherwise, choose based on tier\n    const tier = context.get(\"tier\") as string;\n    return tier === \"premium\" ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n  },\n\n  // model is selected dynamically above\n});\n```\n\n### Context Between Operations\n\nPass the same context to related operations for consistency:\n\n```ts\n// Main agent with context\nconst parentResponse = await dynamicAgent.generateText(\"Create a summary\", {\n  context: myContext,\n});\n\n// Sub-agent inherits the same context\nconst detailResponse = await detailAgent.generateText(\n  \"Provide more details\",\n  { context: myContext } // Same context for consistency\n);\n```\n\n## Error Handling\n\nWrap dynamic logic in try-catch and provide fallbacks:\n\n```ts\nmodel: ({ context }) => {\n  try {\n    const tier = context.get(\"tier\") as string;\n\n    if (tier === \"premium\") {\n      return \"openai/gpt-4o\";\n    }\n    return \"openai/gpt-3.5-turbo\";\n  } catch (error) {\n    console.warn(\"Error in dynamic model selection:\", error);\n    // Fallback to safe default\n    return \"openai/gpt-3.5-turbo\";\n  }\n};\n```\n","website/docs/agents/hooks.md":"---\ntitle: Hooks\nslug: /agents/hooks\n---\n\n# Agent Hooks\n\nHooks intercept specific points in the agent execution pipeline. Use them to observe or modify behavior during agent operations: before a request starts, when preparing messages for the LLM, before and after tool execution, or after completion.\n\n## Defining Hooks\n\nDefine hooks using the `createHooks` helper function. Pass the resulting object to the Agent constructor, or to individual method calls like `generateText` and `streamText`.\n\n```ts\nimport {\n  Agent,\n  createHooks,\n  messageHelpers,\n  type AgentTool,\n  type AgentOperationOutput,\n  type VoltAgentError,\n  type OnStartHookArgs,\n  type OnEndHookArgs,\n  type OnPrepareMessagesHookArgs,\n  type OnPrepareModelMessagesHookArgs,\n  type OnToolStartHookArgs,\n  type OnToolEndHookArgs,\n  type OnToolErrorHookArgs,\n  type OnHandoffHookArgs,\n} from \"@voltagent/core\";\n\n// Define a collection of hooks using the helper\nconst myAgentHooks = createHooks({\n  /**\n   * Called before the agent starts processing a request.\n   */\n  onStart: async (args: OnStartHookArgs) => {\n    const { agent, context } = args;\n    console.log(`[Hook] Agent ${agent.name} starting interaction at ${new Date().toISOString()}`);\n    console.log(`[Hook] Operation ID: ${context.operationId}`);\n  },\n\n  /**\n   * Called after VoltAgent sanitizes UI messages but before the LLM receives them.\n   * `rawMessages` contains the unsanitized list for inspection or metadata recovery.\n   */\n  onPrepareMessages: async (args: OnPrepareMessagesHookArgs) => {\n    const { messages, rawMessages, context } = args;\n    console.log(`Preparing ${messages.length} sanitized messages for LLM`);\n\n    // Add timestamp to each message\n    const timestamp = new Date().toLocaleTimeString();\n    const enhanced = messages.map((msg) => messageHelpers.addTimestampToMessage(msg, timestamp));\n\n    if (rawMessages) {\n      // Access raw message structure for audit logging\n      console.debug(`First raw message parts:`, rawMessages[0]?.parts);\n    }\n\n    return { messages: enhanced };\n  },\n\n  /**\n   * Called after UI messages are converted into provider-specific ModelMessage objects.\n   */\n  onPrepareModelMessages: async (args: OnPrepareModelMessagesHookArgs) => {\n    const { modelMessages, uiMessages } = args;\n    console.log(`Model payload contains ${modelMessages.length} messages`);\n\n    // Inject a system message if none exists\n    if (!modelMessages.some((msg) => msg.role === \"system\")) {\n      return {\n        modelMessages: [\n          {\n            role: \"system\",\n            content: [{ type: \"text\", text: \"Operate within safety budget\" }],\n          },\n          ...modelMessages,\n        ],\n      };\n    }\n\n    return {};\n  },\n\n  /**\n   * Called after the agent completes a request (success or failure).\n   */\n  onEnd: async (args: OnEndHookArgs) => {\n    const { agent, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Agent ${agent.name} finished with error:`, error.message);\n      console.error(`[Hook] Error Details:`, JSON.stringify(error, null, 2));\n    } else if (output) {\n      console.log(`[Hook] Agent ${agent.name} finished successfully.`);\n      // Log aggregate usage or inspect output type\n      if (\"totalUsage\" in output && output.totalUsage) {\n        console.log(`[Hook] Total Token Usage: ${output.totalUsage.totalTokens}`);\n      }\n      if (\"text\" in output && output.text) {\n        console.log(`[Hook] Final text length: ${output.text.length}`);\n      }\n      if (\"object\" in output && output.object) {\n        console.log(`[Hook] Final object keys: ${Object.keys(output.object).join(\", \")}`);\n      }\n    }\n  },\n\n  /**\n   * Called before a tool executes.\n   */\n  onToolStart: async (args: OnToolStartHookArgs) => {\n    const { agent, tool, context, args: toolArgs } = args;\n    console.log(`[Hook] Agent ${agent.name} starting tool: ${tool.name}`);\n    console.log(`[Hook] Tool arguments:`, toolArgs);\n  },\n\n  /**\n   * Called after a tool completes or throws an error.\n   */\n  onToolEnd: async (args: OnToolEndHookArgs) => {\n    const { agent, tool, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Tool ${tool.name} failed:`, error.message);\n      console.error(`[Hook] Tool Error Details:`, JSON.stringify(error, null, 2));\n    } else {\n      console.log(`[Hook] Tool ${tool.name} completed with result:`, output);\n    }\n  },\n\n  /**\n   * Called only when a tool throws, before VoltAgent serializes the error payload.\n   */\n  onToolError: async (args: OnToolErrorHookArgs) => {\n    const { tool, originalError } = args;\n    console.error(`[Hook] Raw tool error from ${tool.name}:`, originalError.message);\n\n    // Return { output } to customize what gets sent back to the model.\n    return {\n      output: {\n        error: true,\n        message: \"External API request failed\",\n      },\n    };\n  },\n\n  /**\n   * Called when a task is handed off from a source agent to this agent.\n   */\n  onHandoff: async (args: OnHandoffHookArgs) => {\n    const { agent, sourceAgent } = args;\n    console.log(`[Hook] Task handed off from ${sourceAgent.name} to ${agent.name}`);\n  },\n});\n\n// Pass hooks to the Agent constructor\nconst agentWithHooks = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\n// Or define hooks inline\nconst agentWithInlineHooks = new Agent({\n  name: \"Inline Hooks Agent\",\n  instructions: \"Another assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: {\n    onStart: async ({ agent, context }) => {\n      /* ... */\n    },\n    onEnd: async ({ agent, output, error, context }) => {\n      /* ... */\n    },\n  },\n});\n```\n\n## Passing Hooks to Methods\n\nPass hooks to `generateText`, `streamText`, `generateObject`, or `streamObject` to run hooks for that specific invocation only.\n\n:::warning\nMethod-level hooks do not override agent-level hooks. Both will execute. For most hooks, the method-level hook runs first, then the agent-level hook. For `onPrepareMessages` and `onPrepareModelMessages`, the method-level hook replaces the agent-level hook entirely.\n:::\n\n```ts\nconst agent = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\nawait agent.generateText(\"Hello, how are you?\", {\n  hooks: {\n    onEnd: async ({ context }) => {\n      console.log(\"End of generation for this invocation!\");\n    },\n  },\n});\n```\n\nFor example, store conversation history only for specific endpoints:\n\n```ts\nconst agent = new Agent({\n  name: \"Translation Agent\",\n  instructions: \"A translation agent that translates text from English to French\",\n  model: \"openai/gpt-4o\",\n});\n\n// for the translate endpoint, we don't want to store the conversation history\napp.post(\"/api/translate\", async (req, res) => {\n  const result = await agent.generateText(req.body.text);\n  return result;\n});\n\n// for the chat endpoint, we want to store the conversation history\napp.post(\"/api/translate/chat\", async (req, res) => {\n  const result = await agent.streamText(req.body.text, {\n    hooks: {\n      onEnd: async ({ context }) => {\n        await chatStore.save({\n          conversationId: context.conversationId,\n          messages: context.steps,\n        });\n      },\n    },\n  });\n\n  return result.textStream;\n});\n```\n\n## Available Hooks\n\nAll hooks receive a single argument object containing relevant information.\n\n### Choosing the right message hook\n\n| Hook                     | Stage                                                              | When to use                                                                                           |\n| ------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `onPrepareMessages`      | Operates on sanitized `UIMessage[]` built from memory + user input | Transform messages using helper functions, redact content, or access `rawMessages` for logging.       |\n| `onPrepareModelMessages` | Runs after `convertToModelMessages` on `ModelMessage[]`            | Apply provider-specific adjustments, inject system directives, or modify the final payload structure. |\n\nUse `onPrepareMessages` for general transformations and `onPrepareModelMessages` for provider-specific adjustments. Both hooks can be used together; VoltAgent applies them in that order.\n\n### `onStart`\n\n- **Triggered:** Before the agent begins processing a request (`generateText`, `streamText`, etc.).\n- **Argument Object (`OnStartHookArgs`):** `{ agent: Agent, context: OperationContext }`\n- **Use Cases:** Initialization logic, request logging, setting up request-scoped resources.\n\n```ts\n// Example: Log the start of an operation\nonStart: async ({ agent, context }) => {\n  console.log(`Agent ${agent.name} starting operation ${context.operationId}`);\n};\n```\n\n### `onPrepareMessages`\n\n- **Triggered:** After VoltAgent assembles conversation history and sanitizes UI messages, before conversion to provider-specific payloads.\n- **Argument Object (`OnPrepareMessagesHookArgs`):** `{ messages: UIMessage[], rawMessages?: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Transform sanitized messages (add timestamps, redact content), or access `rawMessages` for logging or metadata recovery.\n- **Return:** `{ messages: UIMessage[] }` to replace the sanitized list, or an empty object to keep the original.\n- **Notes:**\n  - `messages` contains sanitized UI messages safe for the LLM.\n  - `rawMessages` contains the full structure before sanitization, useful for logging or metadata reconstruction.\n\n```ts\nonPrepareMessages: async ({ messages, rawMessages }) => {\n  const tagged = messages.map((msg) =>\n    messageHelpers.addTimestampToMessage(msg, new Date().toISOString())\n  );\n\n  if (rawMessages) {\n    auditTrail.write(rawMessages); // your own analytics sink\n  }\n\n  return { messages: tagged };\n};\n```\n\n### `onPrepareModelMessages`\n\n- **Triggered:** After UI messages are converted via `convertToModelMessages`, immediately before the provider receives them.\n- **Argument Object (`OnPrepareModelMessagesHookArgs`):** `{ modelMessages: ModelMessage[], uiMessages: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Apply provider-specific transformations, inject final system messages, compress conversation length, or add structured metadata.\n- **Return:** `{ modelMessages: ModelMessage[] }` with a replacement list, or an empty object to keep the original.\n- **Tip:** Use `onPrepareMessages` for general transformations, and this hook for provider-specific adjustments.\n\n```ts\nonPrepareModelMessages: async ({ modelMessages }) => {\n  // Force the last message to include a \"speak clearly\" reminder for voice models\n  const last = modelMessages.at(-1);\n  if (last && last.role === \"user\" && Array.isArray(last.content)) {\n    last.content.push({ type: \"text\", text: \"Please answer succinctly.\" });\n  }\n\n  return { modelMessages };\n};\n```\n\n### `onEnd`\n\n- **Triggered:** After the agent completes processing a request (success or failure).\n- **Argument Object (`OnEndHookArgs`):** `{ agent: Agent, output: AgentOperationOutput | undefined, error: VoltAgentError | undefined, conversationId: string, context: OperationContext }`\n- **Use Cases:** Cleanup, logging, analyzing output or errors, recording usage statistics, storing conversation history.\n- **Note:** The `output` structure depends on the method called. Check for `text` or `object` fields. `error` contains the structured `VoltAgentError` on failure.\n\n```ts\n// Example: Log the outcome of an operation and store conversation history\nonEnd: async ({ agent, output, error, conversationId, context }) => {\n  if (error) {\n    console.error(`Agent ${agent.name} operation ${context.operationId} failed: ${error.message}`);\n    console.log(`User input: \"${context.historyEntry.input}\"`);\n    // Only user input available on error (no assistant response)\n  } else {\n    // Check output type if needed\n    if (output && \"text\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with text output.`\n      );\n    } else if (output && \"object\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with object output.`\n      );\n    } else {\n      console.log(`Agent ${agent.name} operation ${context.operationId} succeeded.`);\n    }\n\n    // Access input and output directly from context\n    console.log(\"Request input:\", context.input);\n    console.log(\"Generated output:\", context.output);\n\n    // Log the complete conversation flow\n    console.log(`Conversation flow:`, {\n      user: context.historyEntry.input,\n      assistant: context.steps, // the assistant steps\n      totalMessages: context.steps.length,\n      toolInteractions: context.steps.flatMap((s) => s.toolInvocations || []).length,\n      toolsUsed: context.steps.flatMap((s) => s.toolInvocations || []).map((t) => t.toolName),\n    });\n\n    // Log complete interaction using input/output\n    console.log(\"Full interaction:\", {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    });\n\n    // Log usage if available\n    if (output?.totalUsage) {\n      console.log(`  Total usage: ${output.totalUsage.totalTokens} tokens`);\n    }\n  }\n};\n```\n\n### `onRetry`\n\n- **Triggered:** When VoltAgent schedules a retry for an LLM attempt or a middleware abort.\n- **Argument Object (`OnRetryHookArgs`):** Includes `source`, `operation`, `context`, and retry details.\n- **Use Cases:** Retry metrics, alerts, tracking retry sources.\n\n```ts\nonRetry: async (args) => {\n  if (args.source === \"llm\") {\n    console.log(\n      `LLM retry ${args.nextAttempt}/${args.maxRetries + 1} for ${args.modelName} (${args.operation})`\n    );\n  } else {\n    console.log(\n      `Middleware retry ${args.retryCount + 1}/${args.maxRetries + 1} for ${args.middlewareId ?? \"unknown\"}`\n    );\n  }\n};\n```\n\n### `onFallback`\n\n- **Triggered:** When VoltAgent selects the next model candidate.\n- **Argument Object (`OnFallbackHookArgs`):** Includes `stage`, `fromModel`, `fromModelIndex`, `nextModel`, and `error`.\n- **Use Cases:** Tracking model failover, audit logs, fallback diagnostics.\n\n```ts\nonFallback: async ({ stage, fromModel, nextModel, operation }) => {\n  console.warn(\n    `Fallback (${stage}) from ${fromModel} to ${nextModel ?? \"next\"} during ${operation}`\n  );\n};\n```\n\n### `onToolStart`\n\n- **Triggered:** Before an agent executes a tool.\n- **Argument Object (`OnToolStartHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool being executed\n  - `args`: Tool input arguments\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Use Cases:** Logging tool usage, inspecting tool arguments, or validating inputs before execution.\n\n```ts\n// Example: Log tool invocation with arguments\nonToolStart: async ({ agent, tool, args, context }) => {\n  console.log(`Agent ${agent.name} invoking tool '${tool.name}'`);\n  console.log(`Tool arguments:`, args);\n};\n```\n\n### `onToolEnd`\n\n- **Triggered:** After a tool execution completes or throws an error.\n- **Argument Object (`OnToolEndHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that completed\n  - `output`: Tool output (undefined on error)\n  - `error`: VoltAgentError when tool throws (undefined on success)\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** `{ output }` to replace the tool result. The replacement is validated again if the tool has an `outputSchema`.\n- **Use Cases:** Logging tool results or errors, post-processing output, triggering actions based on success or failure.\n\n```ts\n// Example: Log the result or error of a tool execution\nonToolEnd: async ({ agent, tool, output, error, context }) => {\n  if (error) {\n    console.error(\n      `Tool '${tool.name}' failed in operation ${context.operationId}: ${error.message}`\n    );\n  } else {\n    console.log(\n      `Tool '${tool.name}' succeeded in operation ${context.operationId}. Result:`,\n      output\n    );\n  }\n};\n```\n\n### `onToolError`\n\n- **Triggered:** Only when a tool throws, before VoltAgent builds its default serialized error result.\n- **Argument Object (`OnToolErrorHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that failed\n  - `args`: Tool input arguments\n  - `error`: Structured `VoltAgentError` for the tool failure\n  - `originalError`: Original thrown error normalized to an `Error` instance\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** Optional `{ output }` replacement payload. If omitted, VoltAgent returns its default serialized error payload.\n- **Use Cases:** Centralized error normalization (for example Axios), redacting sensitive fields, shrinking error payload size before it is sent to the model.\n\n```ts\nonToolError: async ({ tool, error, originalError }) => {\n  const maybeAxios = (originalError as any).isAxiosError === true;\n  if (!maybeAxios) {\n    return;\n  }\n\n  return {\n    output: {\n      error: true,\n      name: error.name,\n      message: originalError.message,\n      code: (originalError as any).code,\n      status: (originalError as any).response?.status,\n    },\n  };\n};\n```\n\n### Tool-level hooks (per tool)\n\nTool hooks run for a specific tool instance and are called before/after execution. Tool-level `onEnd` runs before agent-level `onToolEnd`. If both return `{ output }`, the agent hook wins. Any override is re-validated when `outputSchema` is present.\n\n**Tool hook parameters:**\n\n- `onStart`: `{ tool, args, options }`\n- `onEnd`: `{ tool, args, output, error, options }` (return `{ output }` to override)\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst normalizeTool = createTool({\n  name: \"normalize_text\",\n  description: \"Normalize and trim text\",\n  parameters: z.object({ text: z.string() }),\n  execute: async ({ text }) => text,\n  hooks: {\n    onStart: ({ tool }) => {\n      console.log(`[tool] ${tool.name} starting`);\n    },\n    onEnd: ({ output }) => {\n      if (typeof output === \"string\") {\n        return { output: output.trim() };\n      }\n    },\n  },\n});\n```\n\n### `onHandoff`\n\n- **Triggered:** When one agent delegates a task to another agent via the `delegate_task` tool.\n- **Argument Object (`OnHandoffHookArgs`):** `{ agent: Agent, sourceAgent: Agent }`\n- **Use Cases:** Tracking workflow in multi-agent systems, logging agent collaboration.\n\n```ts\n// Example: Log agent handoffs\nonHandoff: async ({ agent, sourceAgent }) => {\n  console.log(`Task handed off from agent '${sourceAgent.name}' to agent '${agent.name}'`);\n};\n```\n\n## Hook Execution and Error Handling\n\n- **Async Execution:** Hooks can be `async` functions. VoltAgent awaits completion before proceeding. Long-running operations in hooks add latency to agent response time.\n- **Error Handling:** Errors thrown inside hooks may interrupt agent execution. Use `try...catch` within hooks or design them to be reliable.\n- **Hook Merging:** When hooks are passed to both the Agent constructor and a method call:\n  - Most hooks (`onStart`, `onEnd`, `onError`, `onHandoff`, `onToolStart`, `onToolEnd`, `onToolError`, `onStepFinish`, `onRetry`, `onFallback`) execute both: method-level first, then agent-level.\n  - Message hooks (`onPrepareMessages`, `onPrepareModelMessages`) do not merge: method-level replaces agent-level entirely.\n\n## Additional Hooks\n\n- **`onError`**: Called when an error occurs during agent execution. Receives `{ agent: Agent, error: Error, context: OperationContext }`.\n- **`onToolError`**: Called when a tool throws, before default tool error serialization. Receives `{ agent, tool, args, error, originalError, context }` and can return `{ output }`.\n- **`onStepFinish`**: Called after each step in multi-step agent execution. Receives `{ agent: Agent, step: any, context: OperationContext }`.\n- **`onRetry`**: Called when VoltAgent schedules a retry. Receives `{ source, operation, ... }` with retry metadata.\n- **`onFallback`**: Called when VoltAgent selects the next model candidate. Receives `{ stage, fromModel, nextModel, error, ... }`.\n\n## Common Use Cases\n\n1.  **Logging & Observability**: Track execution steps, timings, inputs, outputs, and errors.\n2.  **Analytics**: Collect usage data (token counts, tool usage frequency, success/error rates).\n3.  **Message Transformation**: Modify messages before they reach the LLM or provider.\n4.  **State Management**: Initialize or clean up request-specific resources.\n5.  **Workflow Orchestration**: Trigger external actions based on agent events.\n6.  **UI Integration**: Convert `OperationContext` to messages for the Vercel AI SDK using `@voltagent/vercel-ui`.\n\n## Examples\n\n### Message Transformation with onPrepareMessages\n\nTransform messages before they reach the LLM using `onPrepareMessages` and message helper functions:\n\n```ts\nimport { Agent, createHooks, messageHelpers } from \"@voltagent/core\";\n\nconst enhancedHooks = createHooks({\n  onPrepareMessages: async ({ messages, context }) => {\n    const enhanced = messages.map((msg) => {\n      // Add timestamps to user messages\n      if (msg.role === \"user\") {\n        const timestamp = new Date().toLocaleTimeString();\n        msg = messageHelpers.addTimestampToMessage(msg, timestamp);\n      }\n\n      // Redact sensitive data\n      msg = messageHelpers.mapMessageContent(msg, (text) => {\n        text = text.replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, \"[SSN-REDACTED]\");\n        text = text.replace(/\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g, \"[CC-REDACTED]\");\n        return text;\n      });\n\n      return msg;\n    });\n\n    // Add context based on user\n    if (context.context?.get && context.context.get(\"userId\")) {\n      const systemContext = {\n        role: \"system\" as const,\n        content: `User ID: ${context.context.get(\"userId\")}. Provide personalized responses.`,\n      };\n      enhanced.unshift(systemContext);\n    }\n\n    return { messages: enhanced };\n  },\n\n  onEnd: async ({ output, context }) => {\n    console.log(`Messages processed for operation ${context.operationId}`);\n    if (output?.totalUsage) {\n      console.log(`Tokens used: ${output.totalUsage.totalTokens}`);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Privacy-Aware Assistant\",\n  instructions: \"A helpful assistant that protects user privacy\",\n  model: \"openai/gpt-4o-mini\",\n  hooks: enhancedHooks,\n});\n\n// User message: \"My SSN is 123-45-6789\"\n// LLM receives: \"[10:30:45] My SSN is [SSN-REDACTED]\"\n```\n\n### Inspecting Output in onEnd\n\nThe `output` parameter structure depends on the agent method called. Check for `text` or `object` fields:\n\nFor token accounting, VoltAgent exposes the AI SDK usage contract directly:\n\n- `output.usage` is the final step usage, matching AI SDK `result.usage`.\n- `output.totalUsage` is the aggregate usage across all steps, matching AI SDK `result.totalUsage`.\n- `output.steps?.[index].usage` is the per-step usage, matching AI SDK `result.steps[].usage`.\n\nIn a single-step run, `usage` and `totalUsage` are usually the same. In a multi-step tool run, use `totalUsage` for total cost or billing, `usage` for the final model step, and `steps[].usage` for step-level analysis.\n\n```ts\nconst hooks = createHooks({\n  onEnd: async ({ output }) => {\n    if (!output) return; // operation failed or was aborted\n\n    const finalStepUsage = output.usage;\n    const aggregateUsage = output.totalUsage;\n    const perStepUsage = output.steps?.map((step) => step.usage);\n\n    console.log({\n      finalStepTokens: finalStepUsage?.totalTokens,\n      aggregateTokens: aggregateUsage?.totalTokens,\n      stepTokens: perStepUsage?.map((usage) => usage.totalTokens),\n    });\n\n    // Handle text results\n    if (\"text\" in output && output.text) {\n      console.log(\"Final text:\", output.text);\n      return;\n    }\n\n    // Handle object results\n    if (\"object\" in output && output.object) {\n      console.log(\"Final object keys:\", Object.keys(output.object));\n    }\n  },\n});\n```\n","website/docs/agents/mcp/authorization.md":"---\ntitle: MCP Authorization\ndescription: Add access control to MCP tools with VoltAgent's authorization layer\n---\n\n# MCP Authorization\n\nVoltAgent provides an authorization layer for MCP (Model Context Protocol) tools. This allows you to control which tools users can see and execute based on roles, permissions, or any custom logic.\n\n## Overview\n\nThe MCP authorization layer supports two modes:\n\n1. **Tool Discovery Filtering** (`filterOnDiscovery`) - Hide tools from users who don't have permission to use them\n2. **Execution-Time Checks** (`checkOnExecution`) - Verify permissions before each tool call\n\nYou can enable either or both modes depending on your security requirements.\n\n## Quick Start\n\nAdd authorization with the `can` function:\n\n```typescript\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    myServer: {\n      type: \"http\",\n      url: \"http://localhost:3000/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, action, userId, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      // Admin-only tools\n      if (toolName === \"delete_item\" && !roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete items\" };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n\n// Get tools with authorization context\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"], department: \"engineering\" },\n});\n```\n\n## The `can` Function\n\nThe `can` function receives authorization parameters and returns whether access is allowed.\n\n### Parameters\n\n```typescript\ninterface MCPCanParams {\n  /** Tool name (without server prefix) */\n  toolName: string;\n  /** Server/resource identifier */\n  serverName: string;\n  /** The action being authorized: \"discovery\" or \"execution\" */\n  action: \"discovery\" | \"execution\";\n  /** Tool arguments (only available for \"execution\" action) */\n  arguments?: Record<string, unknown>;\n  /** User identifier */\n  userId?: string;\n  /** User-defined context Map */\n  context?: Map<string | symbol, unknown>;\n}\n```\n\n### Return Value\n\nReturn a boolean or an object with `allowed` and optional `reason`:\n\n```typescript\n// Simple boolean\nreturn true;\nreturn false;\n\n// Object with reason (shown in error message when denied)\nreturn { allowed: true };\nreturn { allowed: false, reason: \"Insufficient permissions\" };\n```\n\n## When Actions Are Called\n\nThe `action` parameter tells you whether the check is for listing tools or executing a tool.\n\n### Discovery Action\n\nWhen you call `getTools()` with `filterOnDiscovery: true`, the `can` function is called for each tool with `action: \"discovery\"`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    filterOnDiscovery: true,\n  },\n});\n\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n```\n\n**What `can` receives (for each tool on the server):**\n\n```json\n{\n  \"toolName\": \"list_expenses\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"delete_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\nNote: `arguments` is `undefined` during discovery since no tool is being executed yet.\n\n### Execution Action\n\nWhen a tool is executed (via agent interaction or direct call) with `checkOnExecution: true`, the `can` function is called with `action: \"execution\"` and the tool's `arguments`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n\nconst tools = await mcp.getTools();\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o\",\n  tools,\n});\n\n// When user says \"add an expense for $50 coffee\"\n// and the agent decides to call the add_expense tool:\nawait agent.generateText(\"Add an expense for $50 coffee\", {\n  userId: \"user-123\",\n  context: new Map([[\"roles\", [\"manager\"]]]),\n});\n```\n\n**What `can` receives when the agent calls `add_expense`:**\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"execution\",\n  \"arguments\": {\n    \"amount\": 50,\n    \"description\": \"coffee\"\n  },\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n### Different Logic per Action\n\nYou can use different authorization logic depending on the action:\n\n```typescript\ncan: async ({ toolName, action, arguments: args, userId, context }) => {\n  const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n  if (action === \"discovery\") {\n    // During getTools(): hide admin tools from non-admins\n    if (toolName.startsWith(\"admin_\") && !roles.includes(\"admin\")) {\n      return false; // Tool won't appear in the list\n    }\n  }\n\n  if (action === \"execution\") {\n    // During tool execution: check specific permissions\n    if (toolName === \"delete_expense\") {\n      if (!roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete expenses\" };\n      }\n    }\n\n    // Check argument-based permissions (only available during execution)\n    if (toolName === \"transfer_funds\" && args?.amount > 10000) {\n      if (!roles.includes(\"senior_manager\")) {\n        return { allowed: false, reason: \"Transfers over $10,000 require senior manager approval\" };\n      }\n    }\n  }\n\n  return true;\n};\n```\n\n## Authorization Context\n\nWhen calling `getTools()` or during tool execution, provide authorization context:\n\n```typescript\ninterface MCPAuthorizationContext {\n  userId?: string;\n  context?: Map<string | symbol, unknown> | Record<string, unknown>;\n}\n\n// Example: pass context to getTools()\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: {\n    roles: [\"admin\"],\n    tenantId: \"acme-corp\",\n    permissions: [\"read\", \"write\"],\n  },\n});\n```\n\nThe context is converted to a `Map` and passed to your `can` function.\n\n## Examples\n\n### Role-Based Access\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      const toolPermissions: Record<string, string[]> = {\n        list_items: [\"user\", \"manager\", \"admin\"],\n        create_item: [\"user\", \"manager\", \"admin\"],\n        delete_item: [\"admin\"],\n        admin_panel: [\"admin\"],\n      };\n\n      const allowedRoles = toolPermissions[toolName] ?? [];\n      const hasPermission = roles.some((role) => allowedRoles.includes(role));\n\n      if (!hasPermission) {\n        return { allowed: false, reason: `Requires one of: ${allowedRoles.join(\", \")}` };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Attribute-Based Access Control\n\nUse tool arguments for attribute-based access control (only available during execution):\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, action, arguments: args, userId }) => {\n      // arguments is only available during execution\n      if (action === \"execution\" && toolName === \"get_user_data\") {\n        const requestedUserId = args?.userId as string;\n        if (requestedUserId !== userId) {\n          return { allowed: false, reason: \"Can only access your own data\" };\n        }\n      }\n\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n```\n\n## Policy-Based Authorization with Cerbos\n\nFor production applications, use a dedicated authorization service like [Cerbos](https://cerbos.dev/). Cerbos is an open-source authorization layer that lets you define access policies in YAML files.\n\nSee the full working example: [examples/with-cerbos](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos)\n\n### Project Structure\n\n```\nwith-cerbos/\n├── docker-compose.yml    # Cerbos PDP container\n├── policies/\n│   └── mcp_tools.yaml    # Authorization policies\n├── src/\n│   ├── index.ts          # Agent with MCP authorization\n│   └── mcp-server.ts     # MCP server with tools\n└── package.json\n```\n\n### Step 1: Start Cerbos PDP\n\nCreate `docker-compose.yml`:\n\n```yaml\nservices:\n  cerbos:\n    image: ghcr.io/cerbos/cerbos:latest\n    ports:\n      - \"3593:3593\" # gRPC\n      - \"3592:3592\" # HTTP\n    volumes:\n      - ./policies:/policies\n    command: [\"server\", \"--config=/conf.yaml\"]\n    configs:\n      - source: cerbos-config\n        target: /conf.yaml\n\nconfigs:\n  cerbos-config:\n    content: |\n      server:\n        httpListenAddr: \":3592\"\n        grpcListenAddr: \":3593\"\n      storage:\n        driver: disk\n        disk:\n          directory: /policies\n```\n\nStart with `docker compose up -d`.\n\n### Step 2: Define Policies\n\nCreate `policies/mcp_tools.yaml`:\n\n```yaml\napiVersion: api.cerbos.dev/v1\nresourcePolicy:\n  version: \"default\"\n  resource: \"mcp::expenses\"\n  rules:\n    # All users can list expenses\n    - actions: [\"list_expenses\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\", \"user\"]\n\n    # Only users can add expenses\n    - actions: [\"add_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"user\"]\n\n    # Managers and admins can approve/reject\n    - actions: [\"approve_expense\", \"reject_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\"]\n\n    # Only admins can delete\n    - actions: [\"delete_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\"]\n```\n\nThe policy structure:\n\n- `resource`: `mcp::expenses` matches the `serverName` in your MCP configuration\n- `actions`: Tool names (e.g., `list_expenses`, `add_expense`)\n- `roles`: Which roles can perform the action\n- `effect`: `EFFECT_ALLOW` or `EFFECT_DENY`\n\n### Step 3: Connect to Cerbos\n\n```typescript\nimport { GRPC } from \"@cerbos/grpc\";\nimport { Agent, MCPConfiguration, VoltAgent } from \"@voltagent/core\";\nimport type { MCPCanParams } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst cerbos = new GRPC(\"localhost:3593\", { tls: false });\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    expenses: {\n      type: \"http\",\n      url: \"http://localhost:3142/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, serverName, action, userId, context }: MCPCanParams) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [\"user\"];\n\n      const result = await cerbos.checkResource({\n        principal: {\n          id: userId ?? \"anonymous\",\n          roles,\n        },\n        resource: {\n          kind: `mcp::${serverName}`,\n          id: serverName,\n        },\n        actions: [toolName],\n      });\n\n      const allowed = result.isAllowed(toolName) ?? false;\n\n      return {\n        allowed,\n        reason: allowed ? undefined : `Access denied for ${toolName}`,\n      };\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Step 4: Create the Agent\n\n```typescript\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n\nconst agent = new Agent({\n  name: \"Finance Assistant\",\n  instructions: `You are a finance assistant that helps users manage expenses.\nAvailable actions depend on the user's role.`,\n  model: \"openai/gpt-4o-mini\",\n  tools,\n});\n\nnew VoltAgent({\n  agents: { agent },\n  server: honoServer(),\n});\n```\n\n### How It Works\n\n1. **Tool Discovery**: When `getTools()` is called, each tool is checked against Cerbos. Tools the user can't access are filtered out.\n\n2. **Tool Execution**: When the agent calls a tool, Cerbos verifies the user has permission before execution.\n\n3. **Policy Evaluation**: Cerbos matches the request against policies:\n   - `principal.roles` → matches policy `roles`\n   - `resource.kind` → matches policy `resource`\n   - `actions[0]` → matches policy `actions`\n\n### Example Flow\n\nUser with role `[\"manager\"]` calls `getTools()`:\n\n| Tool              | Cerbos Check                      | Result      |\n| ----------------- | --------------------------------- | ----------- |\n| `list_expenses`   | manager in [admin, manager, user] | ✅ Allowed  |\n| `add_expense`     | manager in [user]                 | ❌ Filtered |\n| `approve_expense` | manager in [admin, manager]       | ✅ Allowed  |\n| `delete_expense`  | manager in [admin]                | ❌ Filtered |\n\nThe manager sees only `list_expenses` and `approve_expense` in their tool list.\n\n## Error Handling\n\nWhen authorization fails at execution time, an `MCPAuthorizationError` is thrown:\n\n```typescript\nimport { MCPAuthorizationError } from \"@voltagent/core\";\n\ntry {\n  const result = await agent.generateText(\"Delete all expenses\", {\n    userId: \"user-123\",\n    context: new Map([[\"roles\", [\"user\"]]]),\n  });\n} catch (error) {\n  if (error instanceof MCPAuthorizationError) {\n    console.log(`Access denied for tool: ${error.toolName}`);\n    console.log(`Server: ${error.serverName}`);\n    console.log(`Reason: ${error.reason}`);\n  }\n}\n```\n\n## Configuration Reference\n\n### MCPAuthorizationConfig\n\n```typescript\ninterface MCPAuthorizationConfig {\n  /**\n   * Authorization function to check tool access.\n   */\n  can: MCPCanFunction;\n\n  /**\n   * Filter tools on discovery (getTools/getToolsets).\n   * When true, unauthorized tools are hidden from the tool list.\n   * @default false\n   */\n  filterOnDiscovery?: boolean;\n\n  /**\n   * Check authorization on execution (callTool).\n   * When true, each tool call is verified before execution.\n   * @default true\n   */\n  checkOnExecution?: boolean;\n}\n```\n\n## See Also\n\n- [MCP Configuration](./mcp.md) - Basic MCP setup\n- [MCP Server](./mcp-server.md) - Building MCP servers\n- [Cerbos Example](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos) - Full Cerbos integration example\n","website/docs/agents/mcp/mcp-server.md":"---\ntitle: MCP Server\ndescription: Quick start guide for exposing VoltAgent over the Model Context Protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent MCP Server\n\nFollow these steps to expose your VoltAgent project over MCP so you can browse tools, prompts, and resources from VoltOps Console (`https://console.voltagent.dev/mcp`) or an MCP-compatible IDE.\n\n## 1. Install the MCP server package\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/mcp-server\n    ```\n  </TabItem>\n</Tabs>\n\n## 2. Create an MCP server instance\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP example\",\n});\n```\n\n> 📘 **Tip:** When you expose agents through MCP, their `purpose` field becomes the tool description shown in clients. Keep it short and user-facing (fallback is the agent instructions if `purpose` is empty).\n\nThis minimal configuration:\n\n- Names the server `voltagent-example` (used in URLs and IDE listings).\n- Registers server metadata so clients can discover it even before you add tools/agents.\n- Enables all transports (`stdio`, `http`, `sse`) by default. Override `protocols` to disable transports you do not need.\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  protocols: {\n    stdio: true,\n    http: false,\n    sse: false,\n  },\n});\n```\n\nThe snippet above shows how to run in stdio-only mode (ideal for IDE integrations or command-line tooling).\n\n## 3. Register the server with VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { mcpServer } from \"./mcp/server\";\nimport { assistant } from \"./agents/assistant\";\nimport { expenseApprovalWorkflow } from \"./workflows/expense\";\nimport { logger } from \"./logger\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  workflows: {\n    expenseApprovalWorkflow,\n  },\n  mcpServers: {\n    mcpServer,\n  },\n  server: honoServer({ port: 3141 }),\n  logger,\n});\n```\n\n- `mcpServers` registers your MCP server alongside the agent/workflow registries.\n- Passing `server: honoServer(...)` makes VoltAgent expose MCP HTTP and SSE routes automatically. With the server running, visit [`https://console.voltagent.dev/mcp`](https://console.voltagent.dev/mcp) to browse, invoke, and debug tools.\n\n## 4. Find the MCP endpoint (HTTP + SSE)\n\nWhen you start the VoltAgent server, MCP transport endpoints are exposed per server ID:\n\n- **Streamable HTTP**: `POST /mcp/{serverId}/mcp`\n- **SSE** (if enabled): `GET /mcp/{serverId}/sse` and `POST /mcp/{serverId}/messages`\n\nThe `{serverId}` is a normalized version of the `name` (lowercased, spaces replaced) and may get a numeric suffix if there are duplicates. You can always discover it via the registry endpoint:\n\n```bash\ncurl http://localhost:3141/mcp/servers\n```\n\nExample response (trimmed):\n\n```json\n{\n  \"servers\": [\n    {\n      \"id\": \"voltagent-example\",\n      \"name\": \"voltagent-example\",\n      \"version\": \"0.1.0\"\n    }\n  ]\n}\n```\n\nOnce you have the `id`, the HTTP MCP URL becomes:\n\n```\nhttp://localhost:3141/mcp/voltagent-example/mcp\n```\n\n## 5. Connect via stdio (local process)\n\nSTDIO does not use an HTTP URL. The MCP client spawns your server process and communicates over stdin/stdout. In the startup log you should see a line like:\n\n```\nSTDIO  uses stdin/stdout. Example client: { type: \"stdio\", command: \"node\", args: [\"dist/index.js\"] }\n```\n\nClient example (MCPClient):\n\n```ts\nimport { MCPClient } from \"@voltagent/core\";\n\nconst client = new MCPClient({\n  clientInfo: { name: \"local-client\", version: \"0.1.0\" },\n  server: {\n    type: \"stdio\",\n    command: \"node\",\n    args: [\"dist/index.js\"],\n    cwd: process.cwd(),\n    env: { ...process.env },\n  },\n});\n\nawait client.connect();\nconst tools = await client.listTools();\n```\n\nReplace `command`/`args` with the entry that starts your VoltAgent + MCP server (for example `pnpm start -- --stdio` or `node dist/index.js` after build).\n\n## 6. Connect via HTTP\n\nUse the MCP URL in `MCPConfiguration`:\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    local: {\n      type: \"http\",\n      url: \"http://localhost:3141/mcp/voltagent-example/mcp\",\n    },\n  },\n});\n\nconst tools = await mcpConfig.getTools();\n```\n\n## 7. Optional: add MCP-only entries\n\nSometimes you want MCP clients to see helpers that are not (yet) registered with VoltAgent. Provide them as keyed objects (just like the main `VoltAgent` config) via the `agents`, `workflows`, or `tools` fields to append entries that live only on the MCP side:\n\n```ts title=\"src/mcp/server.ts\"\nimport { Agent, createTool, createWorkflowChain } from \"@voltagent/core\";\nimport { MCPServer } from \"@voltagent/mcp-server\";\nimport { z } from \"zod\";\n\nconst statusTool = createTool({\n  name: \"status\",\n  description: \"Return the current time\",\n  parameters: z.object({}),\n  async execute() {\n    return { status: \"ok\", time: new Date().toISOString() };\n  },\n});\n\nconst supportAgent = new Agent({\n  name: \"Support Agent\",\n  purpose: \"Route customer tickets to the correct queue.\",\n  instructions:\n    \"Use internal knowledge to triage customer tickets and respond with routing guidance.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [statusTool],\n});\n\nconst incidentWorkflow = createWorkflowChain({\n  id: \"incident-triage\",\n  name: \"Incident Triage\",\n  purpose: \"Placeholder entry for an external workflow.\",\n  input: z.object({ ticketId: z.string() }),\n}).andThen({\n  id: \"acknowledge\",\n  execute: async ({ data }) => ({ ...data, acknowledged: true }),\n});\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  tools: {\n    statusTool,\n  },\n  agents: {\n    supportAgent,\n  },\n  workflows: {\n    incidentWorkflow,\n  },\n});\n```\n\nThese configured entries behave like regular VoltAgent components for MCP clients, they appear in listings and can be invoked-yet they are not registered with the main `VoltAgent` instance.\n\n## 8. Optional: filter exposed agents/workflows/tools\n\nBy default, every agent, workflow, and tool registered with `VoltAgent` is visible to MCP clients. Provide filter functions when you need to hide or reorder the registry output for a specific transport:\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n  filterWorkflows: ({ items }) => items,\n  filterTools: ({ items }) => items,\n});\n```\n\nFilters receive the list of components sourced from the VoltAgent registries (plus any configured additions) and must return the array you want to expose. They are intended for pruning or sorting, use the `agents`/`workflows`/`tools` fields when you need to introduce brand-new entries.\n\n## 9. Optional: stream prompts and resources\n\nMCP clients can ask a server for structured prompt templates (`prompts/list`, `prompts/get`) and arbitrary resources (`resources/list`, `resources/read`). VoltAgent lets you seed static content and/or forward to a dynamic source via the new `adapters` field:\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\n// Replace these placeholders with your own services or data sources\nconst voltOps = {\n  prompts: {\n    list: async () => [\n      {\n        name: \"triage\",\n        description: \"Short ticket triage message\",\n        arguments: [],\n      },\n    ],\n    get: async (name: string, version?: string) => ({\n      description: `Prompt ${name}`,\n      messages: [{ role: \"user\", content: { type: \"text\", text: \"Summarise ticket {{id}}\" } }],\n      version,\n    }),\n  },\n};\n\nconst knowledgeBase = {\n  list: async () => [\n    {\n      uri: \"volt://docs/runbook\",\n      name: \"On-call runbook\",\n      description: \"Operational checklist\",\n      mimeType: \"text/markdown\",\n    },\n  ],\n  read: async (uri: string) => ({\n    uri,\n    mimeType: \"text/markdown\",\n    text: \"# On-call runbook\\n...\",\n  }),\n};\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP with prompts/resources\",\n  adapters: {\n    prompts: {\n      listPrompts: async () => voltOps.prompts.list(),\n      getPrompt: async (params) => voltOps.prompts.get(params.name, params.version),\n    },\n    resources: {\n      listResources: async () => knowledgeBase.list(),\n      readResource: async (uri) => knowledgeBase.read(uri),\n    },\n  },\n});\n```\n\n- The `adapters` block forwards MCP requests to any backend (VoltOps Prompt Manager, a documentation service, your own REST API).\n- When you change external data, call `await mcpServer.notifyPromptListChanged()` or `await mcpServer.notifyResourceListChanged()` so connected IDEs receive the standard `list_changed` notifications. If you update a specific resource at runtime, invoke `await mcpServer.notifyResourceUpdated(\"volt://docs/runbook\")` to push an incremental update only to subscribers.\n- If an adapter provides a `sendRequest` method, MCP clients can make `elicitation/create` calls. VoltAgent forwards these requests to the adapter so you can collect data from the user and return an `ElicitResult`. Tools can consume the bridge via `operationContext.elicitation`:\n\n```ts\nconst confirmAction = createTool({\n  name: \"confirm-action\",\n  description: \"Ask the operator to approve a risky step\",\n  parameters: z.object({ description: z.string() }),\n  async execute(args, operationContext) {\n    const handler = operationContext?.elicitation;\n\n    if (!handler) {\n      throw new Error(\"Elicitation bridge unavailable\");\n    }\n\n    return handler({\n      schema: {\n        type: \"object\",\n        properties: {\n          confirmed: { type: \"boolean\" },\n        },\n        required: [\"confirmed\"],\n      },\n      message: `Approve the following action: ${args.description}`,\n    });\n  },\n});\n```\n\nThat’s it! Your VoltAgent stack now speaks MCP. Start the agent, open VoltOps Console (or your preferred MCP client), and you’ll see the server listed with its tools, prompts, resources, and workflows ready to debug.\n","website/docs/agents/mcp/mcp.md":"---\ntitle: MCP Client\nslug: /agents/mcp\n---\n\n# Model Context Protocol (MCP)\n\nThe [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is a standardized protocol for AI agents to interact with external tools and services. VoltAgent implements MCP client capabilities, enabling agents to access functionalities like filesystem operations, browser automation, database interactions, and external AI models.\n\n## Transport Types\n\nVoltAgent supports four transport types for MCP connections:\n\n- **`http`**: Attempts streamable HTTP first, automatically falls back to SSE if not supported\n- **`streamable-http`**: Direct streamable HTTP transport (no fallback)\n- **`sse`**: Server-Sent Events transport\n- **`stdio`**: Standard input/output for local processes\n\n## Getting Started with MCPConfiguration\n\n`MCPConfiguration` manages connections to MCP servers and provides their tools to your agents.\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\nimport path from \"node:path\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    // HTTP with automatic fallback\n    github: {\n      type: \"http\",\n      url: \"https://api.githubcopilot.com/mcp\",\n      timeout: 15000, // Optional, default: 30000ms\n    },\n\n    // Streamable HTTP (no fallback)\n    reddit: {\n      type: \"streamable-http\",\n      url: \"https://mcp.composio.dev/reddit/your-api-key-here\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      sessionId: \"optional-session-id\", // Optional\n      timeout: 20000,\n    },\n\n    // SSE transport\n    linear: {\n      type: \"sse\",\n      url: \"https://mcp.linear.app/sse\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      eventSourceInit: { withCredentials: true },\n      timeout: 25000,\n    },\n\n    // stdio for local processes\n    filesystem: {\n      type: \"stdio\",\n      command: \"npx\",\n      args: [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        path.join(process.env.HOME || \"\", \"Desktop\"),\n      ],\n      cwd: process.env.HOME,\n      env: { NODE_ENV: \"production\" },\n      timeout: 10000,\n    },\n  },\n});\n```\n\n## Working with MCP Tools\n\nRetrieve tools from configured MCP servers as standard `Tool` objects compatible with VoltAgent agents.\n\n### Get All Tools as Flat Array\n\n`getTools()` returns all tools from all servers in a single array:\n\n```ts\nconst allTools = await mcpConfig.getTools();\n\n// allTools is Tool<any>[]\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n### Get Tools Organized by Server\n\n`getToolsets()` returns tools grouped by server name:\n\n```ts\nconst toolsets = await mcpConfig.getToolsets();\n\n// toolsets.filesystem is a ToolsetWithTools object\nconst filesystemTools = toolsets.filesystem.getTools();\n\nconst response = await agent.generateText(\"List all files in my Desktop folder\", {\n  userId: \"user123\",\n  tools: filesystemTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Event Handling\n\nAccess individual clients to listen for events:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\nif (clients.reddit) {\n  clients.reddit.on(\"connect\", () => {\n    console.log(\"Connected to Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"disconnect\", () => {\n    console.log(\"Disconnected from Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"error\", (error) => {\n    console.error(\"Reddit MCP error:\", error.message);\n  });\n\n  clients.reddit.on(\"toolCall\", (name, args, result) => {\n    console.log(`Tool ${name} called with:`, args);\n  });\n}\n```\n\nAvailable events: `connect`, `disconnect`, `error`, `toolCall`\n\n## Cleanup\n\nDisconnect clients when done to terminate processes and free resources:\n\n```ts\nawait mcpConfig.disconnect();\n```\n\nThis is especially important for `stdio` servers, which spawn child processes.\n\n## Adding MCP Tools to an Agent\n\nAdd MCP tools at initialization or per request:\n\n### At Agent Initialization\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst allTools = await mcpConfig.getTools();\n\nconst agent = new Agent({\n  name: \"MCP Agent\",\n  instructions: \"You can use MCP tools to access external systems\",\n  model: \"openai/gpt-4o\",\n  tools: allTools,\n});\n\nawait agent.generateText(\"List files in Desktop\", { userId: \"user123\" });\nawait mcpConfig.disconnect();\n```\n\n### At Request Time\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: \"You are a helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst allTools = await mcpConfig.getTools();\n\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Error Handling\n\nHandle failures during connection and tool execution:\n\n```ts\n// Connection errors\ntry {\n  const tools = await mcpConfig.getTools();\n} catch (error) {\n  console.error(\"Failed to fetch MCP tools:\", error);\n}\n\n// Client-specific errors via events\nconst clients = await mcpConfig.getClients();\nclients.filesystem?.on(\"error\", (error) => {\n  console.error(\"Filesystem MCP error:\", error);\n});\n\n// Tool execution errors\ntry {\n  const response = await agent.generateText(\"List files\", {\n    userId: \"user123\",\n    tools: await mcpConfig.getTools(),\n  });\n} catch (error) {\n  console.error(\"Agent execution error:\", error);\n}\n```\n\n### HTTP Fallback Behavior\n\nWhen using `type: \"http\"`, the client attempts streamable HTTP first. If it fails, it automatically creates a new SSE transport and retries the connection.\n\n## User Input Handling (Elicitation)\n\nSome MCP servers may request user input during tool execution. For example, a server might ask for confirmation before performing a destructive action. VoltAgent provides a dynamic API to handle these elicitation requests.\n\n### Setting Up an Elicitation Handler\n\nUse the `elicitation` bridge on individual clients to register handlers:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\n// Set a persistent handler\nclients.myServer.elicitation.setHandler(async (request) => {\n  console.log(\"Server asks:\", request.message);\n  console.log(\"Expected schema:\", request.requestedSchema);\n\n  // Collect user input (e.g., via CLI prompt or UI form)\n  const userConfirmed = await askUserForConfirmation(request.message);\n\n  return {\n    action: userConfirmed ? \"accept\" : \"decline\",\n    content: userConfirmed ? { confirmed: true } : undefined,\n  };\n});\n```\n\nThe handler receives a request object with:\n\n- `message`: Human-readable message from the server\n- `requestedSchema`: JSON schema describing the expected response format\n\nThe handler must return an object with:\n\n- `action`: One of `\"accept\"`, `\"decline\"`, or `\"cancel\"`\n- `content`: Optional data matching the requested schema (when accepting)\n\n### One-Time Handler\n\nHandle only the next elicitation request, then auto-remove:\n\n```ts\nclients.myServer.elicitation.once(async (request) => {\n  return { action: \"accept\", content: { approved: true } };\n});\n```\n\n### Removing the Handler\n\n```ts\nclients.myServer.elicitation.removeHandler();\n```\n\nWhen no handler is registered, elicitation requests are automatically cancelled.\n\n### Checking Handler Status\n\n```ts\nif (clients.myServer.elicitation.hasHandler) {\n  console.log(\"Handler is registered\");\n} else {\n  console.log(\"No handler - requests will be cancelled\");\n}\n```\n\n### Method Chaining\n\nAll elicitation methods support chaining:\n\n```ts\nclients.myServer.elicitation.setHandler(myHandler).removeHandler().setHandler(anotherHandler);\n```\n\n### Agent-Level Elicitation\n\nInstead of setting handlers on individual clients, you can pass an elicitation handler directly to `generateText` or `streamText`. This handler applies to all MCP tools during the request:\n\n```ts\nconst response = await agent.generateText(\"Perform action with MCP tool\", {\n  userId: \"user123\",\n  elicitation: async (request) => {\n    console.log(\"Server asks:\", request.message);\n\n    // Collect user input (CLI prompt, UI form, etc.)\n    const confirmed = await askUser(request.message);\n\n    return {\n      action: confirmed ? \"accept\" : \"decline\",\n      content: confirmed ? { confirmed: true } : undefined,\n    };\n  },\n});\n```\n\nThis approach is useful when:\n\n- You want request-scoped handlers that don't persist\n- You need access to request context (userId, conversationId) in your handler\n- You prefer not to manage client-level handlers manually\n\n## Lifecycle\n\n1. Create `MCPConfiguration` with server definitions\n2. Fetch tools with `getTools()` or `getToolsets()`\n3. Pass tools to agent (initialization or per request)\n4. Set up elicitation handlers if needed (optional)\n5. Use agent methods (`generateText`, `streamText`, etc.)\n6. Monitor with `getClients()` and event listeners (optional)\n7. Disconnect with `disconnect()`\n","website/docs/agents/memory.md":"---\ntitle: Memory\nslug: /agents/memory\n---\n\n# Agent Memory\n\nVoltAgent's `Memory` class stores conversation history and enables agents to maintain context across interactions. Supports persistent storage, semantic search, and working memory.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n- **Conversation Storage** - Messages stored per `userId` and `conversationId`\n- **Semantic Search** - Retrieve past messages by similarity (requires embedding + vector adapters)\n- **Working Memory** - Compact context storage (Markdown template, JSON schema, or free-form)\n- **Workflow State** - Suspendable workflow checkpoint storage\n\n## Quick Start\n\n```typescript\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"my-app-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// First message\nawait agent.generateText(\"My name is Sarah\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n\n// Agent remembers context\nawait agent.generateText(\"What's my name?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n```\n\n## Complete Documentation\n\nFor detailed configuration, provider setup, and advanced features:\n\n- **[Memory Overview](./memory/overview.md)** - Full memory system documentation\n- **[Memory API Endpoints](../api/endpoints/memory.md)** - HTTP endpoints for conversations and messages\n- **[Managed Memory](./memory/managed-memory.md)** - Production-ready hosted storage\n- **[Semantic Search](./memory/semantic-search.md)** - Vector-based message retrieval\n- **[Working Memory](./memory/working-memory.md)** - Compact context management\n- **[Storage Adapters](./memory/in-memory.md)** - Provider-specific guides\n","website/docs/agents/memory/cloudflare-d1.md":"---\ntitle: Cloudflare D1 Memory\nslug: /agents/memory/cloudflare-d1\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Cloudflare D1 Memory\n\n`D1MemoryAdapter` stores conversations in Cloudflare D1 for Workers deployments.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n</Tabs>\n\n## Cloudflare D1 binding\n\nAdd a D1 binding to your `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"voltagent\"\ndatabase_id = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n```\n\n## Configuration (Workers)\n\n```ts\nimport { Agent, Memory, VoltAgent } from \"@voltagent/core\";\nimport { D1MemoryAdapter } from \"@voltagent/cloudflare-d1\";\nimport { serverlessHono } from \"@voltagent/serverless-hono\";\nimport type { D1Database } from \"@cloudflare/workers-types\";\n\nimport { weatherTool } from \"./tools\";\n\ntype Env = {\n  DB: D1Database;\n  OPENAI_API_KEY: string;\n};\n\nconst createWorker = (env: Env) => {\n  const memory = new Memory({\n    storage: new D1MemoryAdapter({\n      binding: env.DB,\n      tablePrefix: \"voltagent_memory\",\n    }),\n  });\n\n  const agent = new Agent({\n    name: \"Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    tools: [weatherTool],\n    memory,\n  });\n\n  const voltAgent = new VoltAgent({\n    agents: { agent },\n    serverless: serverlessHono(),\n  });\n\n  return voltAgent.serverless().toCloudflareWorker();\n};\n\nlet cached: ReturnType<typeof createWorker> | undefined;\n\nexport default {\n  fetch: (request: Request, env: Env, ctx: ExecutionContext) => {\n    if (!cached) {\n      cached = createWorker(env);\n    }\n\n    return cached.fetch(request, env, ctx);\n  },\n};\n```\n\n### Configuration Options\n\n| Option         | Type         | Description                                              |\n| -------------- | ------------ | -------------------------------------------------------- |\n| `binding`      | `D1Database` | Worker D1 binding (required)                             |\n| `tablePrefix`  | `string`     | Table name prefix (default: `voltagent_memory`)          |\n| `maxRetries`   | `number`     | Retry count for busy/locked DB operations (default: `3`) |\n| `retryDelayMs` | `number`     | Initial backoff delay in ms (default: `100`)             |\n| `debug`        | `boolean`    | Enable debug logging (default: `false`)                  |\n| `logger`       | `Logger`     | Optional logger for structured logging                   |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n- `${tablePrefix}_steps`\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new D1MemoryAdapter({ binding: env.DB }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Optional)\n\nD1 provides storage only. To enable semantic search, pair it with an embedding model string (for example, `openai/text-embedding-3-small`) and a vector adapter such as `InMemoryVectorAdapter`.\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n## Learn More\n\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","website/docs/agents/memory/in-memory.md":"---\ntitle: In-Memory Storage\nslug: /agents/memory/in-memory\n---\n\n# In-Memory Storage\n\n`InMemoryStorageAdapter` stores conversation history in application memory. Data is lost when the application restarts.\n\n## Default Behavior\n\nAgents use in-memory storage by default when no `memory` option is provided:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Uses InMemoryStorageAdapter automatically\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users with questions.\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\nOmitting `memory` does **not** disable memory. To make an agent stateless, set `memory: false`.\n\n## Explicit Configuration\n\nConfigure storage limits explicitly:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n## Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved in memory\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Development)\n\nCombine with `InMemoryVectorAdapter` for semantic search during development:\n\n```ts\nimport { Memory, InMemoryVectorAdapter, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n});\n```\n\nBoth storage and vectors are lost on restart. For persistent vectors, use `LibSQLVectorAdapter`.\n\n## Use Cases\n\n### Development & Testing\n\nTest agent logic without database setup:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst testAgent = new Agent({\n  name: \"Test Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new InMemoryStorageAdapter(),\n  }),\n});\n\n// Test conversations without persistence\nawait testAgent.generateText(\"Test message\", {\n  memory: {\n    userId: \"test-user\",\n    conversationId: \"test-conversation\",\n  },\n});\n```\n\n### Stateless Deployments\n\nServerless functions or ephemeral containers where persistence isn't needed:\n\n```ts\n// Cloud function handler\nexport async function handler(event) {\n  const agent = new Agent({\n    name: \"Serverless Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    // Default in-memory storage\n  });\n\n  return await agent.generateText(event.message, {\n    memory: {\n      userId: event.userId,\n      conversationId: event.sessionId,\n    },\n  });\n}\n```\n\n### Demos & Examples\n\nQuick prototypes without infrastructure dependencies.\n\n## Limitations\n\n- **No persistence** - All data lost on restart\n- **Memory usage** - Large message counts consume application memory\n- **Not for production** - Use persistent adapters for production applications\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n","website/docs/agents/memory/libsql.md":"---\ntitle: LibSQL / SQLite Memory\nslug: /agents/memory/libsql\n---\n\n# LibSQL / SQLite Memory\n\n`LibSQLMemoryAdapter` stores conversations in LibSQL, supporting local SQLite files, remote instances, and self-hosted `sqld`.\n\n## Installation\n\n```bash\nnpm install @voltagent/libsql\n```\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\n// Local SQLite\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"file:./.voltagent/memory.db\", // or \":memory:\" for ephemeral\n  }),\n});\n\n// Remote instance\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"libsql://your-database.example.io\",\n    authToken: process.env.LIBSQL_AUTH_TOKEN,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option        | Type     | Description                                                |\n| ------------- | -------- | ---------------------------------------------------------- |\n| `url`         | `string` | Connection URL (`file:<path>`, `libsql://`, or `:memory:`) |\n| `authToken`   | `string` | Auth token for remote instances (optional for local files) |\n| `tablePrefix` | `string` | Table name prefix (default: `voltagent_memory`)            |\n| `logger`      | `Logger` | Optional logger for debugging                              |\n\n### URL Formats\n\n- **Local SQLite**: `file:./.voltagent/memory.db` or `file:memory.db`\n- **In-memory (testing)**: `:memory:` or `file::memory:`\n- **Remote instance** (e.g., Turso): `libsql://your-database.example.io`\n- **Self-hosted sqld**: `libsql://your-server.com` or `https://your-server.com`\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nSchema migrations run automatically when updating VoltAgent versions.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    template: `# Context\\n- Key facts:\\n`,\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Vector Storage (Optional)\n\nUse `LibSQLVectorAdapter` for persistent vector storage:\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n```\n\n**Vector adapter options:**\n\n- Local file: Same URL as storage adapter\n- In-memory: `:memory:` (vectors lost on restart)\n- Turso: Same URL + auth token as storage adapter\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n**Use cases:**\n\n- Local development with SQLite files\n- Edge deployments with remote instances\n- Serverless environments\n- Simple deployments without separate database servers\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Examples\n\n### Development with Local SQLite\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Dev Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: \"file:./.voltagent/dev-memory.db\",\n    }),\n  }),\n});\n```\n\n### Remote Instance\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: process.env.LIBSQL_DATABASE_URL!,\n      authToken: process.env.LIBSQL_AUTH_TOKEN!,\n    }),\n  }),\n});\n```\n\n### Testing with In-Memory Database\n\n```ts\nconst testMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \":memory:\", // ephemeral\n  }),\n});\n```\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search with LibSQLVectorAdapter\n","website/docs/agents/memory/managed-memory.md":"---\ntitle: Managed Memory\nslug: /agents/memory/managed-memory\n---\n\n# Managed Memory\n\nVoltOps Managed Memory is a production-ready hosted memory service for VoltAgent. Create a database through the VoltOps Console and connect using API credentials - no infrastructure provisioning or schema management required.\n\n## Availability\n\n- **US Region**: Virginia (us-east-1)\n- **EU Region**: Germany (eu-central-1)\n\n## Installation\n\n```bash\nnpm install @voltagent/voltagent-memory\n```\n\n## Configuration\n\n### Automatic Setup (Recommended)\n\nGet your credentials from [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory) and set environment variables:\n\n<video controls width=\"100%\">\n  <source src=\"https://cdn.voltagent.dev/docs/voltagent-managed-memory.mp4\" type=\"video/mp4\" />\n  Your browser does not support the video tag.\n</video>\n\n```bash\n# .env\nVOLTAGENT_PUBLIC_KEY=pk_...\nVOLTAGENT_SECRET_KEY=sk_...\n```\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\n// Adapter automatically uses VoltOps credentials from environment\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nThe adapter checks `AgentRegistry` for a global `VoltOpsClient` instance configured from environment variables. This is the simplest setup - no need to instantiate `VoltOpsClient` manually.\n\n### Manual Setup\n\nPass a `VoltOpsClient` instance explicitly:\n\n```ts\nimport { Agent, Memory, VoltOpsClient } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY!,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY!,\n  // baseUrl: \"https://api.voltagent.dev\", // optional\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient, // explicit client\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nUse manual setup when:\n\n- Running multiple adapters with different credentials\n- Dynamically changing credentials at runtime\n- Testing with mock clients\n\n### Configuration Options\n\n| Option          | Type            | Required | Description                                                      |\n| --------------- | --------------- | -------- | ---------------------------------------------------------------- |\n| `databaseName`  | `string`        | Yes\\*    | Database name from VoltOps Console                               |\n| `databaseId`    | `string`        | Yes\\*    | Database ID from VoltOps API (alternative to `databaseName`)     |\n| `voltOpsClient` | `VoltOpsClient` | No       | Explicit VoltOps client (optional if credentials in environment) |\n| `debug`         | `boolean`       | No       | Enable debug logging (default: `false`)                          |\n\n\\*Either `databaseName` or `databaseId` is required.\n\n## Creating a Database\n\n1. Navigate to [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory)\n2. Click **Create Database**\n3. Enter a name and select region (US or EU)\n4. Copy the database credentials\n\nTo get your VoltOps API keys, go to **Settings** in the console to find your public and secret keys.\n\n## Features\n\n### Conversation Storage\n\nAll `StorageAdapter` methods are supported:\n\n- Message persistence (`addMessage`, `addMessages`, `getMessages`, `clearMessages`)\n- Conversation management (`createConversation`, `getConversation`, `updateConversation`, `deleteConversation`)\n- Working memory (`getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`)\n- Workflow state (`getWorkflowState`, `setWorkflowState`, `updateWorkflowState`)\n\n### Vector Storage (Optional)\n\nEnable semantic search with `ManagedMemoryVectorAdapter`:\n\n```ts\nimport { ManagedMemoryAdapter, ManagedMemoryVectorAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new ManagedMemoryVectorAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n```\n\nBoth adapters resolve credentials the same way (environment or explicit client). See [Semantic Search](./semantic-search.md) for usage.\n\n## Migration from Self-Hosted\n\n### Export from LibSQL\n\n```ts\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst localAdapter = new LibSQLMemoryAdapter({\n  url: \"file:./.voltagent/memory.db\",\n});\n\n// Export conversations for a user\nconst conversations = await localAdapter.getConversationsByUserId(\"user-123\");\n\nfor (const conv of conversations) {\n  const messages = await localAdapter.getMessages(\"user-123\", conv.id);\n  console.log(`Conversation ${conv.id}: ${messages.length} messages`);\n}\n```\n\n### Import to Managed Memory\n\n```ts\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst managedMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\n// Import conversation\nawait managedMemory.createConversation({\n  id: conv.id,\n  userId: conv.userId,\n  resourceId: conv.resourceId,\n  title: conv.title,\n  metadata: conv.metadata,\n});\n\n// Import messages\nawait managedMemory.addMessages(messages, conv.userId, conv.id);\n```\n\nBulk import/export APIs are planned for future releases.\n\n## Use Cases\n\n### Development & Prototyping\n\n```ts\n// No database setup required for pilots\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"dev-memory\",\n  }),\n});\n```\n\n### Multi-Region Deployment\n\n```ts\n// US database for North American users\nconst usMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-us\",\n  }),\n});\n\n// EU database for European users\nconst euMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-eu\",\n  }),\n});\n```\n\n### Team Collaboration\n\nMultiple developers connect to the same managed database using shared VoltOps credentials. Create separate databases for staging and production.\n\n## Limitations\n\n- **Regional latency**: Choose the region closest to your application servers\n- **Storage quotas**: Check VoltOps Console for plan-specific limits\n- **Credential rotation**: Update environment variables when rotating credentials\n\n## Comparison with Self-Hosted\n\n| Feature             | Managed Memory    | Self-Hosted               |\n| ------------------- | ----------------- | ------------------------- |\n| Setup time          | < 3 minutes       | Hours                     |\n| Schema management   | Automatic         | Manual migrations         |\n| Regional hosting    | US & EU           | DIY                       |\n| Credential rotation | API-based         | Manual                    |\n| Monitoring          | Console dashboard | DIY                       |\n| Cost                | Usage-based       | Infrastructure + ops time |\n\nProduction-ready for teams without database infrastructure or during rapid deployment.\n\n## Troubleshooting\n\n### Adapter initialization failed\n\n```\nError: Unable to locate managed memory database\n```\n\n**Solution**: Verify `databaseName` matches the name in VoltOps Console, or use `databaseId` instead.\n\n### VoltOps client not available\n\n```\nError: VoltOps client is not available for managed memory initialization\n```\n\n**Solution**: Ensure `VOLTAGENT_PUBLIC_KEY` and `VOLTAGENT_SECRET_KEY` environment variables are set, or pass `voltOpsClient` explicitly.\n\n### Enable debug logging\n\n```ts\nconst adapter = new ManagedMemoryAdapter({\n  databaseName: \"production-memory\",\n  debug: true, // Logs all API calls and metadata\n});\n```\n\n## Learn More\n\n- **[Semantic Search](./semantic-search.md)** - Enable vector search with managed memory\n- **[Working Memory](./working-memory.md)** - Configure working memory with managed storage\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres alternative\n","website/docs/agents/memory/overview.md":"---\ntitle: Overview\nslug: /agents/memory/overview\n---\n\n# Memory Overview\n\nVoltAgent's `Memory` class stores conversation history and optional semantic search vectors. Agents retrieve past messages before generating responses and persist new interactions after completion.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Cloudflare D1**  | `@voltagent/cloudflare-d1`    | Cloudflare D1 (SQLite) | Cloudflare Workers deployments   |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- Auto-creates conversations on first message\n- Configurable message limits (oldest pruned first)\n\n### Conversation Titles (Optional)\n\nWhen enabled, VoltAgent generates a concise title from the first user message. Title generation runs only when the conversation is created and does not overwrite existing titles.\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: true,\n});\n```\n\nCustom configuration:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: {\n    enabled: true,\n    model: \"gpt-4o-mini\", // default agent model\n    systemPrompt: \"Generate a short title (max 6 words).\",\n    temperature: null, // optional; default is 0, use null to omit\n    maxLength: 60,\n    maxOutputTokens: 128, // optional; default is 32\n  },\n});\n```\n\nNotes:\n\n- The agent's main model is used unless `generateTitle.model` is provided.\n- `generateTitle.model` accepts either a provider/model string or an AI SDK model instance.\n- Title generation sends `temperature: 0` by default. Set `generateTitle.temperature` to `null` to\n  omit the parameter for reasoning models such as OpenAI `o`-series and `gpt-5-mini`.\n- Reasoning models may need a higher `maxOutputTokens` value because reasoning tokens count toward\n  the output budget. Configure `generateTitle.maxOutputTokens` if the default `32` is too low.\n- If title generation fails, VoltAgent keeps the default conversation title and logs the cause at\n  `warn` level, including a hint to set `generateTitle.temperature` to `null` when appropriate.\n- Only the first user message is summarized.\n- If you create conversations manually via the Memory API, set `title` explicitly.\n\n### Conversation Steps\n\n- Every LLM/text/tool step can be recorded with metadata (operationId, agent/sub-agent IDs, usage, tool arguments/results).\n- VoltOps Observability consumes these records to render the Memory Explorer “Steps” tab and to correlate traces/logs with memory.\n- Adapters that implement the step APIs persist sub-agent activity alongside primary agent steps, so hierarchies stay visible.\n- If an adapter does not support steps, the console warns with “Conversation steps are not supported by this memory adapter.”\n- By default, agents checkpoint conversation progress during multi-step runs (`conversationPersistence.mode: \"step\"`).\n- Tool completion events (`tool-result`, `tool-error`) trigger immediate flushes by default (`flushOnToolResult: true`), reducing history loss risk on crashes or process interruption.\n- Use `conversationPersistence.mode: \"finish\"` if you want end-of-turn persistence only.\n\n### Semantic Search (Optional)\n\n- Requires `embedding` + `vector` adapters\n- Auto-embeds messages on save\n- Retrieves similar past messages by content, not recency\n- Merges semantic results with recent messages\n\n### Working Memory (Optional)\n\n- Stores compact context across conversation turns\n- Three formats: Markdown template, JSON schema (Zod), or free-form\n- Two scopes: `conversation` (default) or `user`\n- Agent exposes tools: `get_working_memory`, `update_working_memory`, `clear_working_memory`\n- If `memory.options.readOnly: true` is set per call, only `get_working_memory` is exposed and memory writes are skipped\n\n### Workflow State\n\n- Suspendable workflow checkpoint storage\n- Tracks execution state, context, suspension metadata\n\n## Agent Configuration\n\nAgents accept a `memory` option:\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\n\n// Default when omitted: built-in in-memory storage\nconst agent1 = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  // memory omitted (or undefined)\n});\n\n// Disable memory entirely\nconst agent2 = new Agent({\n  name: \"Stateless\",\n  model: \"openai/gpt-4o-mini\",\n  memory: false,\n});\n\n// Persistent storage\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent3 = new Agent({\n  name: \"Persistent\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  }),\n});\n```\n\n**`memory` option behavior**\n\n| Value                 | Behavior                                                                |\n| --------------------- | ----------------------------------------------------------------------- |\n| Omitted / `undefined` | Conversation memory is enabled with a built-in `InMemoryStorageAdapter` |\n| `false`               | Conversation memory is disabled                                         |\n| `new Memory({ ... })` | Conversation memory is enabled with your configured adapter             |\n\nFor stateless sub-agents, set `memory: false` on each sub-agent explicitly.\n\nWhen a supervisor delegates to sub-agents, delegated messages are tagged with sub-agent metadata so supervisor memory reads can filter sub-agent records from parent conversation context.\n\n### Global Defaults (VoltAgent)\n\nSet default memory instances once at the VoltAgent entrypoint. Defaults apply only when an agent or workflow does not specify `memory`. If nothing is configured, VoltAgent still falls back to built-in in-memory storage. An explicit `memory: false` on an agent disables memory and bypasses defaults.\n\n```ts\nimport { Memory, VoltAgent } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agentMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/agent.db\" }),\n});\n\nconst workflowMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/workflows.db\" }),\n});\n\nnew VoltAgent({\n  agentMemory,\n  workflowMemory,\n  // memory: sharedFallbackMemory,\n});\n```\n\n**Precedence**\n\n- Agents: agent `memory` > `agentMemory` > `memory` > built-in in-memory\n- Workflows: workflow `memory` > `workflowMemory` > `memory` > built-in in-memory\n\n## Usage with User and Conversation IDs\n\nProvide `memory.userId` and `memory.conversationId` in generation calls to scope memory:\n\n```ts\nconst response = await agent.generateText(\"What did we discuss yesterday?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n  },\n});\n```\n\n**Behavior:**\n\n- **Both provided**: Retrieves history for that specific thread\n- **Only userId**: New `conversationId` generated per call (fresh context each time)\n- **Neither provided**: Uses default user ID with new conversation ID\n\n## Examples\n\n### Managed Memory (Zero Setup)\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { VoltOpsClient } from \"@voltagent/core\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Semantic Search + Working Memory\n\n```ts\nimport { Agent, Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\",\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n      goals: z.array(z.string()).optional(),\n    }),\n  },\n});\n\nconst agent = new Agent({\n  name: \"Smart Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// Enable semantic search per call\nconst result = await agent.generateText(\"What preferences did I mention?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n    options: {\n      semanticMemory: {\n        enabled: true,\n        semanticLimit: 5,\n        semanticThreshold: 0.7,\n      },\n    },\n  },\n});\n```\n\n## Custom Adapters\n\nImplement the `StorageAdapter` interface to use custom databases:\n\n```ts\nimport type { StorageAdapter, UIMessage, Conversation, OperationContext } from \"@voltagent/core\";\n\nexport class MyStorageAdapter implements StorageAdapter {\n  async addMessage(\n    msg: UIMessage,\n    userId: string,\n    conversationId: string,\n    context?: OperationContext // Optional: access operation context\n  ): Promise<void> {\n    // Access user-provided context values\n    const tenantId = context?.context.get(\"tenantId\");\n\n    // Use logger and tracing\n    context?.logger.info(\"Storing message\", { userId, tenantId });\n\n    // Store message in your database\n  }\n\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: { limit?: number },\n    context?: OperationContext // Optional: access operation context\n  ): Promise<UIMessage[]> {\n    // Retrieve messages in chronological order (oldest first)\n    return [];\n  }\n\n  async createConversation(input: CreateConversationInput): Promise<Conversation> {\n    // Create conversation record\n  }\n\n  // ... implement remaining StorageAdapter methods\n}\n```\n\nRequired methods:\n\n- If you want Memory Explorer’s Steps tab (and Observability APIs) to work, make sure your adapter persists conversation steps by implementing the methods below. Otherwise VoltAgent will surface the “Conversation steps are not supported by this memory adapter.” warning.\n\n- Messages: `addMessage`, `addMessages`, `getMessages`, `clearMessages`\n- Conversations: `createConversation`, `getConversation`, `getConversations`, `getConversationsByUserId`, `queryConversations`, `updateConversation`, `deleteConversation`\n- Conversation steps: `saveConversationSteps`, `getConversationSteps`\n- Working memory: `getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`\n- Workflow state: `getWorkflowState`, `queryWorkflowRuns`, `setWorkflowState`, `updateWorkflowState`, `getSuspendedWorkflowStates`\n\nUse `queryWorkflowRuns({ workflowId?, status?, from?, to?, limit?, offset? })` for filtered/paginated listings (ordered by newest first).\n\n### Advanced: Context-Aware Adapters\n\nCustom adapters can access `OperationContext` for dynamic, per-request behavior:\n\n```ts\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log with tracing\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n    });\n\n    // Use scoped ID for data isolation\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Pass tenant per request\nawait agent.generateText(\"Query\", {\n  memory: {\n    userId: \"user-123\",\n  },\n  context: { tenantId: \"company-abc\" }, // Different tenant = different data\n});\n```\n\n**Use cases for context-aware adapters:**\n\n- **Multi-tenancy**: Isolate data by tenant using composite keys\n- **Audit logging**: Track all memory access with full context\n- **Access control**: Validate permissions before queries\n- **Dynamic behavior**: Adjust queries based on runtime values\n\nSee [Operation Context](../context.md#memory-adapters) for more examples and patterns.\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Semantic Search](./semantic-search.md)** - Retrieve messages by similarity\n- **[Working Memory](./working-memory.md)** - Maintain compact context across turns\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n- **[In-Memory Storage](./in-memory.md)** - Default ephemeral storage\n","website/docs/agents/memory/postgres.md":"---\ntitle: PostgreSQL Memory\nslug: /agents/memory/postgres\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# PostgreSQL Memory\n\n`PostgreSQLMemoryAdapter` stores conversations in PostgreSQL for production applications with existing Postgres infrastructure.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/postgres\n```\n\n  </TabItem>\n</Tabs>\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\n// Using connection string\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n    // or: \"postgresql://user:password@localhost:5432/mydb\"\n  }),\n});\n\n// Using connection object\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST || \"localhost\",\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: process.env.NODE_ENV === \"production\",\n    },\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option           | Type               | Description                                                                            |\n| ---------------- | ------------------ | -------------------------------------------------------------------------------------- |\n| `connection`     | `string \\| object` | Connection string or object with `host`, `port`, `database`, `user`, `password`, `ssl` |\n| `maxConnections` | `number`           | Connection pool size (default: `10`)                                                   |\n| `tablePrefix`    | `string`           | Table name prefix (default: `voltagent_memory`)                                        |\n| `debug`          | `boolean`          | Enable debug logging (default: `false`)                                                |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nPostgreSQL version 12 or higher recommended.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- Supports complex queries with filtering, pagination, and sorting\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // or \"conversation\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nStorage:\n\n- Conversation scope: `conversations.metadata.workingMemory`\n- User scope: `${tablePrefix}_users.metadata.workingMemory`\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### PostgreSQL Vector Storage\n\nStore vector embeddings directly in PostgreSQL for semantic search (no extensions required):\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter, PostgresVectorAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new PostgresVectorAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n});\n```\n\n**How It Works:**\n\n- **BYTEA Storage**: Vectors are stored as binary data using PostgreSQL's native `BYTEA` type\n- **In-Memory Similarity**: Cosine similarity is computed in-memory after loading vectors\n- **No Extensions**: Works with vanilla PostgreSQL 12+ without any extensions\n- **Automatic Setup**: Creates tables and indexes automatically on first use\n\n**Configuration Options:**\n\n```ts\nconst vectorAdapter = new PostgresVectorAdapter({\n  connection: process.env.DATABASE_URL!,\n\n  // Optional: customize table name (default: \"voltagent_vector\")\n  tablePrefix: \"custom_vector\",\n\n  // Optional: LRU cache size for frequently accessed vectors (default: 100)\n  cacheSize: 100,\n\n  // Optional: max vector dimensions (default: 1536)\n  maxVectorDimensions: 1536,\n});\n```\n\n**Benefits:**\n\n- **Unified Storage**: Keep messages and vectors in the same database\n- **No Extensions**: Works with any PostgreSQL instance (managed or self-hosted)\n- **Simple Setup**: No extension installation or special configuration needed\n- **Standard Tools**: Use familiar PostgreSQL monitoring and backup tools\n\n**Performance Considerations:**\n\nThis adapter loads all vectors into memory for similarity computation, which works well for:\n\n- Small to medium datasets (< 10,000 vectors)\n- Development and prototyping\n- Environments where extension installation is restricted\n\nFor large-scale production with millions of vectors, consider specialized vector databases or use `InMemoryVectorAdapter` with periodic persistence.\n\nSee [Semantic Search](./semantic-search.md) for detailed configuration and usage.\n\n## Production Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: true, // Enable SSL for production\n    },\n    maxConnections: 20, // Adjust based on load\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Production Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Advanced SSL Configuration\n\nFor databases requiring custom CA certificates or client certificates:\n\n```ts\nimport fs from \"fs\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: {\n        rejectUnauthorized: true,\n        ca: fs.readFileSync(\"/path/to/ca-certificate.crt\").toString(),\n        // Optional: client certificate authentication\n        key: fs.readFileSync(\"/path/to/client-key.key\").toString(),\n        cert: fs.readFileSync(\"/path/to/client-cert.crt\").toString(),\n      },\n    },\n    maxConnections: 20,\n  }),\n});\n```\n\nThe `ssl` option accepts either:\n\n- `boolean` - Simple SSL enable/disable\n- `ConnectionOptions` - Full TLS configuration object with support for:\n  - `ca` - Custom Certificate Authority\n  - `key` - Client private key\n  - `cert` - Client certificate\n  - `rejectUnauthorized` - Certificate validation control\n  - All other Node.js [TLS options](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)\n\n### Security\n\n- Use SSL connections in production (`ssl: true` or advanced config)\n- Store credentials and certificates in environment variables or secure secret management\n- Implement regular database backups\n- Adjust `maxConnections` based on concurrent usage\n\n**Use cases:**\n\n- Applications with existing Postgres infrastructure\n- High-availability requirements (replication, failover)\n- Complex SQL queries or analytics on conversation data\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","website/docs/agents/memory/semantic-search.md":"---\ntitle: Semantic Search\nslug: /agents/memory/semantic-search\n---\n\n# Semantic Search\n\nSemantic search retrieves past messages by similarity rather than recency. It requires an embedding adapter (text to vectors) and a vector adapter (storage and search).\n\n## Configuration\n\n```ts\nimport { Agent, Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }), // or InMemoryVectorAdapter() for dev\n  enableCache: true, // optional embedding cache\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nYou can pass an embedding config object to set adapter options:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: {\n    model: \"openai/text-embedding-3-small\",\n    normalize: true,\n  },\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n```\n\n### Available Adapters\n\n**Embedding:**\n\nMemory accepts an embedding adapter or a provider-qualified model string such as\n`\"openai/text-embedding-3-small\"`.\n\n- `AiSdkEmbeddingAdapter` - Wraps any AI SDK embedding model\n\n**Vector Storage:**\n\n- `InMemoryVectorAdapter` (`@voltagent/core`) - Development only\n- `LibSQLVectorAdapter` (`@voltagent/libsql`) - Persistent vectors in SQLite/LibSQL/Turso\n- `PostgresVectorAdapter` (`@voltagent/postgres`) - Persistent vectors in Postgres with pgvector\n- `ManagedMemoryVectorAdapter` (`@voltagent/voltagent-memory`) - VoltOps-hosted vectors\n\n## Usage\n\nEnable semantic search per generation call:\n\n```ts\nconst result = await agent.generateText(\"What pricing model did we discuss?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n    options: {\n      semanticMemory: {\n        enabled: true, // default: auto-enabled when vector support is present\n        semanticLimit: 5, // number of similar messages to retrieve\n        semanticThreshold: 0.7, // minimum similarity score (0-1)\n        mergeStrategy: \"append\", // \"prepend\" | \"append\" | \"interleave\"\n      },\n    },\n  },\n});\n```\n\n### Default Behavior\n\nWhen `embedding` and `vector` adapters are configured:\n\n- Semantic search auto-enables for calls with `memory.userId` and `memory.conversationId`\n- Default `semanticLimit`: 5 messages\n- Default `semanticThreshold`: 0.7\n- Default `mergeStrategy`: `\"append\"` (recent messages first, then similar messages)\n\n### Merge Strategies\n\n- **`append`** (default): `[recent messages] + [similar messages]` - preserves chronological order\n- **`prepend`**: `[similar messages] + [recent messages]` - emphasizes relevance\n- **`interleave`**: Alternates between similar and recent messages\n\n## How It Works\n\n### On Message Save\n\nWhen saving messages, the `Memory` class:\n\n1. Extracts text content from `UIMessage.parts`\n2. Generates embeddings via the embedding adapter\n3. Stores vectors with metadata:\n   - ID: `msg_${conversationId}_${message.id}`\n   - Metadata: `{ messageId, conversationId, userId, role, createdAt }`\n\n### On Message Retrieval\n\nWhen semantic search is enabled:\n\n1. Embed the current query\n2. Search for similar vectors using the vector adapter\n3. Retrieve matching messages by ID\n4. Merge with recent messages using the configured strategy\n5. Remove duplicates (messages in both sets)\n\n## Programmatic API\n\nDirect search without agent generation:\n\n```ts\n// Check if vectors are configured\nconst hasVectors = memory.hasVectorSupport(); // boolean\n\n// Search similar messages\nconst results = await memory.searchSimilar(\"pricing discussion\", {\n  limit: 10,\n  threshold: 0.8,\n  filter: { userId: \"user-123\", conversationId: \"thread-abc\" },\n});\n\nfor (const result of results) {\n  console.log(result.id, result.score, result.metadata);\n}\n```\n\n## Embedding Cache\n\nEnable caching to avoid re-embedding identical text:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  enableCache: true, // enable cache\n  cacheSize: 1000, // max entries (default: 1000)\n  cacheTTL: 3600000, // TTL in ms (default: 1 hour)\n});\n```\n\nThe cache stores `text → vector` mappings in memory with LRU eviction.\n\n## Vector Adapters\n\n### InMemoryVectorAdapter\n\n```ts\nimport { InMemoryVectorAdapter } from \"@voltagent/core\";\n\nconst vector = new InMemoryVectorAdapter();\n```\n\n- Uses cosine similarity\n- Supports metadata filtering\n- Lost on restart (use for development only)\n\n### LibSQLVectorAdapter\n\n```ts\nimport { LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\n// Local SQLite\nconst vector = new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" });\n\n// In-memory (testing)\nconst vector = new LibSQLVectorAdapter({ url: \":memory:\" });\n\n// Turso\nconst vector = new LibSQLVectorAdapter({\n  url: \"libsql://your-db.turso.io\",\n  authToken: process.env.TURSO_AUTH_TOKEN,\n});\n```\n\n- Stores vectors as BLOBs\n- Supports metadata filtering and thresholds\n- Persistent across restarts\n\n### ManagedMemoryVectorAdapter\n\n```ts\nimport { ManagedMemoryVectorAdapter } from \"@voltagent/voltagent-memory\";\n\nconst vector = new ManagedMemoryVectorAdapter({\n  databaseName: \"production-memory\",\n  // voltOpsClient optional (auto-resolves from environment)\n});\n```\n\n- VoltOps-hosted vectors with zero setup\n- See [Managed Memory](./managed-memory.md) for configuration\n\n## Example: Full Semantic Search Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  enableCache: true,\n});\n\nconst agent = new Agent({\n  name: \"Research Assistant\",\n  instructions: \"Help users recall past discussions and find relevant information.\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// Semantic search automatically enabled\nconst result = await agent.generateText(\n  \"What did we decide about the API authentication approach?\",\n  {\n    memory: {\n      userId: \"user-123\",\n      conversationId: \"project-alpha\",\n      options: {\n        semanticMemory: {\n          semanticLimit: 10,\n          semanticThreshold: 0.75,\n        },\n      },\n    },\n  }\n);\n```\n\n## Learn More\n\n- **[Working Memory](./working-memory.md)** - Maintain compact context across turns\n- **[Managed Memory](./managed-memory.md)** - Zero-setup vector storage\n- **[LibSQL / Turso](./libsql.md)** - Self-hosted vector storage\n","website/docs/agents/memory/supabase.md":"---\ntitle: Supabase Memory\nslug: /agents/memory/supabase\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Supabase Memory\n\n`SupabaseMemoryAdapter` stores conversations in Supabase Postgres for applications already using Supabase.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n</Tabs>\n\n## Database Setup\n\nRun this SQL in your Supabase SQL Editor (adjusts table prefix if needed):\n\n<details>\n<summary>Schema SQL (click to expand)</summary>\n\n```sql\n-- Users table (for user-scoped working memory)\nCREATE TABLE IF NOT EXISTS voltagent_memory_users (\n  id TEXT PRIMARY KEY,\n  metadata JSONB,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Conversations table\nCREATE TABLE IF NOT EXISTS voltagent_memory_conversations (\n  id TEXT PRIMARY KEY,\n  resource_id TEXT NOT NULL,\n  user_id TEXT NOT NULL,\n  title TEXT NOT NULL,\n  metadata JSONB NOT NULL,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Messages table (UIMessage format)\nCREATE TABLE IF NOT EXISTS voltagent_memory_messages (\n  conversation_id TEXT NOT NULL REFERENCES voltagent_memory_conversations(id) ON DELETE CASCADE,\n  message_id TEXT NOT NULL,\n  user_id TEXT NOT NULL,\n  role TEXT NOT NULL,\n  parts JSONB,\n  metadata JSONB,\n  format_version INTEGER DEFAULT 2,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  PRIMARY KEY (conversation_id, message_id)\n);\n\n-- Workflow states (for suspension/resume)\nCREATE TABLE IF NOT EXISTS voltagent_memory_workflow_states (\n  id TEXT PRIMARY KEY,\n  workflow_id TEXT NOT NULL,\n  workflow_name TEXT NOT NULL,\n  status TEXT NOT NULL,\n  suspension JSONB,\n  events JSONB,\n  output JSONB,\n  cancellation JSONB,\n  user_id TEXT,\n  conversation_id TEXT,\n  metadata JSONB,\n  created_at TIMESTAMPTZ NOT NULL,\n  updated_at TIMESTAMPTZ NOT NULL\n);\n\n-- Conversation steps (structured LLM/tool events)\nCREATE TABLE IF NOT EXISTS voltagent_memory_steps (\n  id TEXT PRIMARY KEY,\n  conversation_id TEXT NOT NULL REFERENCES voltagent_memory_conversations(id) ON DELETE CASCADE,\n  user_id TEXT NOT NULL,\n  agent_id TEXT NOT NULL,\n  agent_name TEXT,\n  operation_id TEXT,\n  step_index INTEGER NOT NULL,\n  type TEXT NOT NULL,\n  role TEXT NOT NULL,\n  content TEXT,\n  arguments JSONB,\n  result JSONB,\n  usage JSONB,\n  sub_agent_id TEXT,\n  sub_agent_name TEXT,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Indexes\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_conversations_user_id\n  ON voltagent_memory_conversations(user_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_conversations_resource_id\n  ON voltagent_memory_conversations(resource_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_messages_conversation_id\n  ON voltagent_memory_messages(conversation_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_messages_created_at\n  ON voltagent_memory_messages(created_at);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_workflow_states_workflow_id\n  ON voltagent_memory_workflow_states(workflow_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_workflow_states_status\n  ON voltagent_memory_workflow_states(status);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_steps_conversation\n  ON voltagent_memory_steps(conversation_id, step_index);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_steps_operation\n  ON voltagent_memory_steps(conversation_id, operation_id);\n```\n\n</details>\n\n## Credentials\n\nGet your Supabase credentials:\n\n1. Go to [Supabase Dashboard](https://app.supabase.com)\n2. Open your project\n3. Go to **Project Settings** → **API**\n4. Copy **Project URL** and **anon key**\n\nStore as environment variables: `SUPABASE_URL` and `SUPABASE_KEY`\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\n\n// Using URL and key\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n});\n\n// Using existing Supabase client\nimport { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseClient = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_KEY!);\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    client: supabaseClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option        | Type             | Description                                           |\n| ------------- | ---------------- | ----------------------------------------------------- |\n| `supabaseUrl` | `string`         | Supabase project URL (required if not using `client`) |\n| `supabaseKey` | `string`         | Supabase anon key (required if not using `client`)    |\n| `client`      | `SupabaseClient` | Existing Supabase client (alternative to URL/key)     |\n| `tableName`   | `string`         | Table name prefix (default: `voltagent_memory`)       |\n| `debug`       | `boolean`        | Enable debug logging (default: `false`)               |\n| `logger`      | `Logger`         | Optional logger for structured logging                |\n\n**Note**: Table prefix must match the SQL schema. If you use a custom `tableName`, update the SQL accordingly.\n\n## Features\n\n- Messages stored per `userId` and `conversationId`\n- Conversation steps persisted for Observability (Memory Explorer “Steps” tab, sub-agent visibility, usage metadata)\n- Supports complex queries with filtering, pagination, and sorting\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\n```ts\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md).\n\n### Semantic Search\n\n```ts\nimport { Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(), // or pgvector adapter\n});\n```\n\nSee [Semantic Search](./semantic-search.md).\n\n## Production Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\nimport { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseClient = createClient(\n  process.env.SUPABASE_URL!,\n  process.env.SUPABASE_KEY! // or service_role key for backend\n);\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    client: supabaseClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n**Security:**\n\n- Use `anon` key with Row Level Security (RLS) policies\n- Use `service_role` key only in secure backend environments\n- Store credentials in environment variables\n\n**Use cases:**\n\n- Applications already using Supabase\n- Projects leveraging Supabase Auth, Realtime, or Storage\n- Environments requiring RLS policies\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","website/docs/agents/memory/working-memory.md":"---\ntitle: Working Memory\nslug: /agents/memory/working-memory\n---\n\n# Working Memory\n\nWorking memory stores compact context across conversation turns. Unlike full message history, it tracks key facts, preferences, or goals that persist throughout interactions.\n\n## Configuration\n\nThree formats supported:\n\n1. **Markdown template** - Structured text with sections\n2. **JSON schema** - Validated structured data using Zod\n3. **Free-form** - Unstructured text\n\nScope options:\n\n- **`conversation`** (default) - Context per conversation thread\n- **`user`** - Context shared across all user conversations\n\n### Markdown Template\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // default\n    template: `\n# User Profile\n- Name:\n- Role:\n- Timezone:\n\n# Current Goals\n-\n\n# Preferences\n-\n`,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### JSON Schema\n\n```ts\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // persist across conversations\n    schema: z.object({\n      profile: z\n        .object({\n          name: z.string().optional(),\n          role: z.string().optional(),\n          timezone: z.string().optional(),\n        })\n        .optional(),\n      preferences: z.array(z.string()).optional(),\n      goals: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\n### Free-Form\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true, // no template or schema\n  },\n});\n```\n\n## Agent Integration\n\nWhen working memory is enabled, the agent:\n\n1. **Adds instructions to system prompt** - Includes current working memory content and usage guidelines\n2. **Exposes tools automatically**:\n   - `get_working_memory()` - Retrieve current content\n   - `update_working_memory(content)` - Update content (validated against schema if configured)\n   - `clear_working_memory()` - Clear content\n\nIf you run an operation with `options.memory.options.readOnly: true`, the agent only exposes\n`get_working_memory()` and skips write operations.\n\nThe agent manages working memory proactively based on conversation flow.\n\n## Update Modes\n\nTwo modes available:\n\n### Append Mode (Default)\n\nSafely merges new information with existing content:\n\n```ts\n// Existing working memory:\n// { profile: { name: \"Alice\" }, goals: [\"Learn TypeScript\"] }\n\n// Agent calls update_working_memory with:\n// { goals: [\"Build an API\"] }\n\n// Result (merged):\n// { profile: { name: \"Alice\" }, goals: [\"Learn TypeScript\", \"Build an API\"] }\n```\n\n**For JSON schemas:**\n\n- Objects: Deep merge\n- Arrays: Deduplicated concatenation\n- Primitives: Overwrite\n\n**For Markdown:**\n\n- New content appended with separator\n\n### Replace Mode\n\nOverwrites all existing content:\n\n```ts\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: { profile: { name: \"Bob\" } }, // existing data lost\n  options: { mode: \"replace\" },\n});\n```\n\nUse replace mode only when intentionally resetting all context.\n\n## Programmatic API\n\nDirect memory access without agent tools:\n\n```ts\n// Get current working memory\nconst content = await memory.getWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n});\nconsole.log(content); // string (JSON or Markdown)\n\n// Update (default: append mode)\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: { goals: [\"Complete onboarding\"] }, // object or string\n});\n\n// Update (replace mode)\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: \"Fresh context\",\n  options: { mode: \"replace\" },\n});\n\n// Clear\nawait memory.clearWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n});\n\n// Introspect configuration\nconst format = memory.getWorkingMemoryFormat(); // \"markdown\" | \"json\" | null\nconst template = memory.getWorkingMemoryTemplate(); // string | null\nconst schema = memory.getWorkingMemorySchema(); // ZodObject | null\n```\n\n## Storage Implementation\n\nWorking memory is stored differently per scope:\n\n**Conversation scope:**\n\n- Stored in `conversations.metadata.workingMemory` field\n- Isolated per conversation thread\n\n**User scope:**\n\n- Stored in `${tablePrefix}_users.metadata.workingMemory` field (adapter-specific)\n- Shared across all user conversations\n\nAll official adapters (LibSQL, Postgres, Supabase, Managed Memory) support both scopes.\n\n## Example: User-Scoped Preferences\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\nimport { z } from \"zod\";\n\nconst userPreferencesSchema = z.object({\n  name: z.string().optional(),\n  timezone: z.string().optional(),\n  communicationStyle: z.enum([\"formal\", \"casual\"]).optional(),\n  interests: z.array(z.string()).optional(),\n});\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // persist across all conversations\n    schema: userPreferencesSchema,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Personal Assistant\",\n  instructions: \"Adapt responses based on user preferences stored in working memory.\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// First conversation\nawait agent.generateText(\"I prefer casual communication and I'm into AI and music.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"conv-1\",\n  },\n});\n\n// Different conversation - agent remembers user preferences\nawait agent.generateText(\"What should I learn next?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"conv-2\", // different thread\n  },\n});\n```\n\n## Example: Conversation-Scoped Goals\n\n```ts\nconst projectMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // isolated per project\n    template: `\n# Project Context\n- Name:\n- Deadline:\n- Tech Stack:\n\n# Current Sprint\n- Goals:\n- Blockers:\n`,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Project Manager\",\n  instructions: \"Track project context and help with sprint planning.\",\n  model: \"openai/gpt-4o-mini\",\n  memory: projectMemory,\n});\n\n// Each project gets its own working memory\nawait agent.generateText(\"Let's plan the e-commerce project using Next.js.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"project-ecommerce\",\n  },\n});\n\nawait agent.generateText(\"For the analytics dashboard, we'll use React and D3.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"project-analytics\",\n  },\n});\n```\n\n## Best Practices\n\n1. **Use JSON schemas for structured data** - Ensures type safety and validation\n2. **Use Markdown templates for narrative context** - Better for summaries, notes, observations\n3. **Default to append mode** - Safer than replace; preserves existing data\n4. **User scope for preferences** - Name, timezone, communication style\n5. **Conversation scope for session data** - Goals, tasks, project details\n6. **Keep it compact** - Working memory supplements message history, not replaces it\n\n## Learn More\n\n- **[Semantic Search](./semantic-search.md)** - Retrieve relevant past messages\n- **[Managed Memory](./managed-memory.md)** - Zero-setup working memory storage\n- **[LibSQL / Turso](./libsql.md)** - Self-hosted working memory storage\n","website/docs/agents/message-types.md":"---\ntitle: Understanding Message Types\n---\n\n# Understanding Message Types\n\nVoltAgent works with three different message type formats, each serving a specific purpose in the agent lifecycle. Understanding when and how to use each type is essential for building effective AI applications.\n\n## Overview\n\nThe three message types are:\n\n1. **MessageContent** - Raw content format for LLM communication (from AI SDK)\n2. **UIMessage** - Frontend-friendly format with metadata (from AI SDK)\n3. **VoltAgentTextStreamPart** - Streaming format with SubAgent support (VoltAgent extension)\n\nThese types come from the underlying AI SDK architecture and VoltAgent's multi-agent features. Rather than being redundant, they serve different layers of your application.\n\n## The Three Message Types\n\n### 1. MessageContent (AI SDK Content Type)\n\n**MessageContent** represents the raw content that gets sent to language models. It's the `content` property of a `ModelMessage`.\n\n#### Type Definition\n\n```typescript\ntype MessageContent = UserContent | AssistantContent | ToolContent | string;\n\n// Examples:\ntype UserContent = string | Array<TextPart | ImagePart | FilePart>;\ntype AssistantContent = string | Array<TextPart | ToolCallPart>;\ntype ToolContent = Array<ToolResultPart>;\n```\n\n#### Format\n\n```typescript\n// Simple string\nconst content: MessageContent = \"Hello, how can I help you?\";\n\n// Structured array with multiple parts\nconst content: MessageContent = [\n  { type: \"text\", text: \"Check this image:\" },\n  { type: \"image\", image: \"data:image/png;base64,...\" },\n  { type: \"text\", text: \"What do you see?\" },\n];\n```\n\n#### When to Use\n\n- ✅ Creating `ModelMessage` objects for AI SDK\n- ✅ Direct LLM provider calls\n- ✅ Low-level message manipulation\n- ✅ Working with `message.content` property\n\n#### Common Patterns\n\n```typescript\nimport { generateText } from \"ai\";\n\n// Using MessageContent in direct AI SDK calls\nconst result = await generateText({\n  model: \"openai/gpt-4\",\n  messages: [\n    {\n      role: \"user\",\n      content: \"Hello!\", // MessageContent (string)\n    },\n    {\n      role: \"user\",\n      content: [\n        // MessageContent (array)\n        { type: \"text\", text: \"Describe this:\" },\n        { type: \"image\", image: imageData },\n      ],\n    },\n  ],\n});\n```\n\n### 2. UIMessage (AI SDK UI Format)\n\n**UIMessage** is the modern format designed for frontend applications, chat UIs, and memory systems. It includes metadata like message IDs and uses a `parts` array instead of `content`.\n\n#### Type Definition\n\n```typescript\ninterface UIMessage {\n  id: string;\n  role: \"user\" | \"assistant\" | \"system\";\n  parts: Array<TextUIPart | FileUIPart | ToolUIPart>;\n  createdAt?: Date;\n  metadata?: Record<string, unknown>;\n}\n```\n\n#### Format\n\n```typescript\nconst message: UIMessage = {\n  id: \"msg-123\",\n  role: \"user\",\n  parts: [\n    { type: \"text\", text: \"Hello, \" },\n    { type: \"file\", url: \"image.png\", mediaType: \"image/png\" },\n    { type: \"text\", text: \"what is this?\" },\n  ],\n  createdAt: new Date(),\n  metadata: { source: \"mobile\" },\n};\n```\n\n#### When to Use\n\n- ✅ Memory operations (`memory.getMessages()`, `memory.addMessage()`)\n- ✅ Agent hooks (`onPrepareMessages`, `onAfterGeneration`)\n- ✅ Chat UI rendering and state management\n- ✅ Message history and conversation management\n- ✅ Frontend/backend communication\n\n#### Common Patterns\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  memory: myMemory,\n\n  hooks: {\n    // UIMessages in hooks\n    onPrepareMessages: async ({ messages }) => {\n      // messages is UIMessage[]\n      const enhanced = messages.map((msg) => ({\n        ...msg,\n        parts: [...msg.parts, { type: \"text\", text: \" [verified]\" }],\n      }));\n\n      return { messages: enhanced };\n    },\n  },\n});\n\n// Getting messages from memory\nconst conversationHistory = await memory.getMessages(userId, conversationId);\n// Returns UIMessage[]\n\nconversationHistory.forEach((msg) => {\n  console.log(`[${msg.id}] ${msg.role}:`, msg.parts);\n});\n```\n\n#### Feedback metadata\n\nWhen feedback is enabled, VoltAgent attaches feedback metadata to assistant UI messages under `message.metadata.feedback`. This is how UIs can show thumbs up/down and submit feedback later.\n\n```ts\nconst feedback = message.metadata?.feedback as\n  | {\n      traceId?: string;\n      key?: string;\n      url?: string;\n      expiresAt?: string;\n      provided?: boolean;\n      providedAt?: string;\n      feedbackId?: string;\n    }\n  | undefined;\n\nconst alreadyProvided = Boolean(feedback?.provided || feedback?.providedAt || feedback?.feedbackId);\n\nif (feedback?.url && !alreadyProvided) {\n  console.log(\"Submit feedback to:\", feedback.url);\n}\n```\n\nAfter submitting feedback successfully, persist the provided-state in memory with `agent.markFeedbackProvided(...)` so the UI still hides feedback controls after conversation reload.\n\nSee [Feedback](/observability-docs/feedback) for the full flow and API examples.\n\n### 3. VoltAgentTextStreamPart (Streaming Extension)\n\n**VoltAgentTextStreamPart** extends AI SDK's `TextStreamPart` with SubAgent metadata, enabling multi-agent coordination during streaming.\n\n#### Type Definition\n\n```typescript\ntype VoltAgentTextStreamPart<TOOLS extends Record<string, any> = Record<string, any>> =\n  TextStreamPart<TOOLS> & {\n    /**\n     * Optional response message identifier (carried on start/step chunks).\n     */\n    messageId?: string;\n\n    /**\n     * Optional identifier for the subagent that generated this event\n     */\n    subAgentId?: string;\n\n    /**\n     * Optional name of the subagent that generated this event\n     */\n    subAgentName?: string;\n  };\n\n// TextStreamPart types include:\n// - text-start, text-delta, text-end\n// - reasoning-start, reasoning-delta, reasoning-end\n// - tool-input-start, tool-input-delta, tool-input-end\n// - tool-call, tool-result, tool-error\n// - start, start-step, finish-step, finish, abort, error, source, file, raw\n```\n\n#### Format\n\n```typescript\n// Regular streaming part\nconst streamPart: VoltAgentTextStreamPart = {\n  type: \"text-delta\",\n  textDelta: \"Hello\",\n};\n\n// With SubAgent metadata\nconst subAgentStreamPart: VoltAgentTextStreamPart = {\n  type: \"text-delta\",\n  textDelta: \"The answer is 42\",\n  subAgentId: \"agent-calculator\",\n  subAgentName: \"Calculator Agent\",\n};\n```\n\n#### When to Use\n\n- ✅ Streaming responses (`agent.streamText()`)\n- ✅ Real-time UI updates\n- ✅ Multi-agent coordination and event forwarding\n- ✅ Identifying which SubAgent is responding\n- ✅ Building collaborative agent UIs\n\n#### Common Patterns\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  subAgents: [writerAgent, editorAgent],\n});\n\n// Streaming with SubAgent tracking\nconst stream = await agent.streamText(\"Write and edit a blog post\");\n\nfor await (const part of stream.fullStream) {\n  if (part.type === \"text-delta\") {\n    // Check which SubAgent is responding\n    if (part.subAgentId) {\n      console.log(`[${part.subAgentName}]: ${part.textDelta}`);\n    } else {\n      console.log(`[Main Agent]: ${part.textDelta}`);\n    }\n  }\n}\n```\n\n## Message Flow Diagram\n\nHere's how messages flow through a typical VoltAgent application:\n\n```mermaid\ngraph TB\n    User[User Input] -->|string / UIMessage array / ModelMessage array| Agent[Agent]\n\n    Agent -->|normalizes to| UIMsg[UIMessage]\n    UIMsg -->|stored in| Memory[(Memory Storage)]\n    UIMsg -->|passed to| Hooks[Agent Hooks]\n\n    Hooks -->|onPrepareMessages| Transform[Message Transform]\n    Transform -->|UIMessage array| Convert[Convert to ModelMessage]\n\n    Convert -->|ModelMessage with MessageContent| LLM[Language Model]\n\n    LLM -->|streaming response| Stream[Stream Parts]\n    Stream -->|VoltAgentTextStreamPart| UI[Real-time UI]\n\n    Stream -->|on finish| Response[Final Response]\n    Response -->|UIMessage| Memory\n\n    SubAgent[SubAgents] -.->|add metadata| Stream\n\n    style UIMsg fill:#3b82f6,stroke:#1e40af,stroke-width:2px,color:#fff\n    style Stream fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#fff\n    style Memory fill:#8b5cf6,stroke:#6d28d9,stroke-width:2px,color:#fff\n    style LLM fill:#10b981,stroke:#059669,stroke-width:2px,color:#fff\n```\n\n## Converting Between Types\n\nVoltAgent and AI SDK provide utilities for converting between message formats:\n\n### MessageContent ↔ UIMessage\n\n```typescript\nimport { convertToModelMessages } from \"ai\";\nimport type { UIMessage } from \"ai\";\n\n// UIMessage[] → ModelMessage[] (which contains MessageContent)\nconst uiMessages: UIMessage[] = await memory.getMessages(userId, conversationId);\nconst modelMessages = await convertToModelMessages(uiMessages);\n\n// Access MessageContent from ModelMessage\nconst content = modelMessages[0].content; // MessageContent\n\n// Extract text from either format using helpers\nimport { extractText } from \"@voltagent/core\";\n\nconst text1 = extractText(content); // From MessageContent\nconst text2 = extractText(uiMessages[0]); // From UIMessage (overloaded!)\n```\n\n### Working with Both Formats\n\nVoltAgent's `messageHelpers` utilities support both formats:\n\n```typescript\nimport { extractText, hasImagePart, extractImageParts } from \"@voltagent/core\";\n\n// Works with MessageContent\nconst content: MessageContent = [\n  { type: \"text\", text: \"Hello\" },\n  { type: \"image\", image: \"data:...\" },\n];\n\nconst text = extractText(content); // \"Hello\"\nconst hasImage = hasImagePart(content); // true\n\n// Also works with UIMessage!\nconst message: UIMessage = {\n  id: \"msg-1\",\n  role: \"user\",\n  parts: [\n    { type: \"text\", text: \"Hello\" },\n    { type: \"file\", url: \"image.png\", mediaType: \"image/png\" },\n  ],\n} as UIMessage;\n\nconst text2 = extractText(message); // \"Hello\"\nconst hasImage2 = hasImagePart(message); // true\nconst images = extractImageParts(message); // FileUIPart[]\n```\n\nSee [Message Helpers](/docs/utils/message-helpers) for the complete API.\n\n## Common Patterns and Use Cases\n\n### Pattern 1: Memory + UIMessage\n\n**Use Case**: Store and retrieve conversation history\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  memory: myMemory,\n});\n\n// Generate response (automatically stores UIMessages in memory)\nawait agent.generateText({\n  prompt: \"Hello!\",\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n});\n\n// Retrieve conversation history\nconst history = await myMemory.getMessages(\"user-123\", \"conv-456\");\n// Returns UIMessage[]\n\n// Display in UI\nhistory.forEach((msg) => {\n  const text = extractText(msg);\n  console.log(`${msg.role}: ${text}`);\n});\n```\n\n### Pattern 2: Streaming + VoltAgentTextStreamPart\n\n**Use Case**: Real-time UI updates with SubAgent identification\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst coordinator = new Agent({\n  subAgents: [researchAgent, writerAgent],\n});\n\nconst stream = await coordinator.streamText(\"Research and write about AI\");\n\n// Track which agent is responding\nlet currentAgent = null;\n\nfor await (const part of stream.fullStream) {\n  if (part.type === \"text-delta\") {\n    // Detect agent switches\n    if (part.subAgentId && part.subAgentId !== currentAgent) {\n      currentAgent = part.subAgentId;\n      console.log(`\\n[Switching to: ${part.subAgentName}]`);\n    }\n\n    process.stdout.write(part.textDelta);\n  }\n}\n```\n\n### Pattern 3: Hooks + UIMessage Transformation\n\n**Use Case**: Add context or filter sensitive data\n\n```typescript\nimport { Agent, messageHelpers } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  hooks: {\n    onPrepareMessages: async ({ messages }) => {\n      // Add timestamps to user messages\n      const enhanced = messages.map((msg) => {\n        if (msg.role === \"user\") {\n          return messageHelpers.addTimestampToMessage(msg);\n        }\n        return msg;\n      });\n\n      return { messages: enhanced };\n    },\n\n    onAfterGeneration: async ({ response }) => {\n      // Filter sensitive data from assistant responses\n      const filtered = messageHelpers.mapMessageContent(response, (text) => {\n        // Redact SSN patterns\n        return text.replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, \"[REDACTED]\");\n      });\n\n      return { response: filtered };\n    },\n  },\n});\n```\n\n## Decision Tree: Which Type Should I Use?\n\n```\nAre you working with...\n\n┌─ Memory operations (store/retrieve)?\n│  └─> Use UIMessage\n│\n├─ Agent hooks (transform messages)?\n│  └─> Use UIMessage\n│\n├─ Streaming responses?\n│  └─> Use VoltAgentTextStreamPart\n│     └─ Need SubAgent tracking?\n│        └─> Yes: Check subAgentId/subAgentName properties\n│\n├─ Direct AI SDK calls (generateText/streamText)?\n│  └─> Use MessageContent (in ModelMessage.content)\n│\n├─ Chat UI rendering?\n│  └─> Use UIMessage\n│\n└─ Message content manipulation?\n   └─> Use messageHelpers (supports both MessageContent and UIMessage!)\n```\n\n## FAQ\n\n### Q: Why not just one unified message type?\n\n**A**: These types come from different layers of the AI SDK and serve different purposes:\n\n- **MessageContent** is optimized for LLM communication (minimal structure)\n- **UIMessage** is optimized for UIs and storage (includes metadata, IDs)\n- **VoltAgentTextStreamPart** is optimized for streaming (real-time, event-based)\n\nUnifying them would lose these optimizations and break compatibility with AI SDK.\n\n### Q: Do I need to convert between types manually?\n\n**A**: Usually not! VoltAgent handles conversions automatically:\n\n- Agent automatically converts UIMessages to ModelMessages when calling LLMs\n- Memory automatically stores responses as UIMessages\n- `messageHelpers` utilities work with both MessageContent and UIMessage\n\nYou only need manual conversion for advanced use cases.\n\n### Q: Which type does `memory.getMessages()` return?\n\n**A**: Always **UIMessage[]**. Memory operations use UIMessage format for:\n\n- Message IDs (tracking individual messages)\n- Metadata (timestamps, sources, etc.)\n- Frontend compatibility\n\n### Q: Can I use MessageContent with message helpers?\n\n**A**: Yes! All extractor and checker functions in `messageHelpers` support both:\n\n```typescript\nimport { extractText } from \"@voltagent/core\";\n\n// Both work!\nextractText(messageContent); // MessageContent\nextractText(uiMessage); // UIMessage\n```\n\n### Q: How do I know which SubAgent is responding in a stream?\n\n**A**: Check the `subAgentId` and `subAgentName` properties:\n\n```typescript\nfor await (const part of stream.fullStream) {\n  if (part.subAgentId) {\n    console.log(`SubAgent: ${part.subAgentName}`);\n  } else {\n    console.log(\"Main Agent\");\n  }\n}\n```\n\n### Q: Are these types specific to VoltAgent?\n\n**A**: No! **MessageContent** and **UIMessage** come from Vercel's AI SDK. Only **VoltAgentTextStreamPart** is a VoltAgent extension (adding `subAgentId` and `subAgentName` to AI SDK's `TextStreamPart`).\n\n## Related Documentation\n\n- [Message Helpers](/docs/utils/message-helpers) - Utilities for working with both MessageContent and UIMessage\n- [Memory Guide](/docs/agents/memory) - How memory uses UIMessage format\n- [Agent Hooks](/docs/agents/hooks) - UIMessage transformation in hooks\n- [API Streaming](/docs/api/streaming) - Working with VoltAgentTextStreamPart\n\n## Summary\n\n| Type                        | Source    | Primary Use Case  | Key Features                       |\n| --------------------------- | --------- | ----------------- | ---------------------------------- |\n| **MessageContent**          | AI SDK    | LLM communication | String or array, minimal structure |\n| **UIMessage**               | AI SDK    | UI & Memory       | Has `id`, `parts`, metadata        |\n| **VoltAgentTextStreamPart** | VoltAgent | Streaming         | Has `subAgentId`, `subAgentName`   |\n\n**Golden Rule**: Let VoltAgent handle conversions automatically. Use `messageHelpers` for content manipulation, and check the documentation for each API to know which type it expects.\n","website/docs/agents/middleware.md":"---\ntitle: Middleware\nslug: /agents/middleware\n---\n\n# Agent Middleware\n\nMiddleware runs before guardrails and hooks on each agent call. Use it to read or modify input and output, or to stop a call with a typed error.\n\n## Execution Order\n\n`generateText` and `generateObject`:\n\n1. Input middlewares\n2. Input guardrails\n3. Hooks (`onStart`)\n4. Model call (with retries and fallback)\n5. Output middlewares\n6. Output guardrails\n7. Hooks (`onEnd`)\n\n`streamText` and `streamObject`:\n\n1. Input middlewares\n2. Input guardrails\n3. Stream starts\n\nOutput middlewares do not run in streaming calls.\n\n## Configure Middleware\n\nAgent-level middlewares run before per-call middlewares. Per-call middlewares are appended to the list for that call.\n\n```ts\nimport { Agent, createInputMiddleware, createOutputMiddleware } from \"@voltagent/core\";\n\nconst normalizeInput = createInputMiddleware({\n  name: \"NormalizeInput\",\n  handler: ({ input }) => {\n    if (typeof input !== \"string\") return input;\n    return input.trim();\n  },\n});\n\nconst requireSignature = createOutputMiddleware<string>({\n  name: \"RequireSignature\",\n  handler: ({ output, abort }) => {\n    if (!output.includes(\"-- Support\")) {\n      abort(\"Missing signature\", { retry: true, metadata: { reason: \"signature\" } });\n    }\n    return output;\n  },\n});\n\nconst agent = new Agent({\n  name: \"Support\",\n  instructions: \"Answer support questions with short, direct replies.\",\n  model: \"openai/gpt-4o-mini\",\n  maxMiddlewareRetries: 1,\n  inputMiddlewares: [normalizeInput],\n  outputMiddlewares: [requireSignature],\n});\n```\n\nPer-call middleware:\n\n```ts\nawait agent.generateText(\"hi\", {\n  inputMiddlewares: [({ input }) => (typeof input === \"string\" ? `Customer: ${input}` : input)],\n});\n```\n\n## Middleware API\n\nInput middleware receives:\n\n- `input`: current input value\n- `originalInput`: input from the caller\n- `agent`: agent instance\n- `context`: operation context\n- `operation`: operation name (for example `generateText`)\n- `retryCount`: current middleware retry count\n- `abort(reason?, options?)`: stop the call or request a retry\n\nOutput middleware receives:\n\n- `output`: current output value\n- `originalOutput`: output from the model\n- `usage`, `finishReason`, `warnings`: model metadata when available\n- `agent`, `context`, `operation`, `retryCount`, `abort(...)`\n\nReturn a new value to replace the input or output; return `void` to keep the current value.\n\n## Retry Behavior\n\nMiddleware retries are controlled by `maxMiddlewareRetries`. The default is `0`.\n\n- Call `abort(\"reason\", { retry: true })` to request a retry.\n- Each retry restarts the full attempt: input middleware, guardrails, hooks, model selection, model retries/fallback, output middleware.\n- The retry reason is added as a system message for the next attempt.\n- `retryCount` starts at `0` and increments after each middleware-triggered retry.\n- If `maxMiddlewareRetries` is exceeded, the middleware error is returned to the caller.\n- In streaming calls, retry can only happen before the stream starts, so only input middlewares can trigger it.\n\nMiddleware retries are separate from model retries. Model retries use `maxRetries` and are evaluated per model call. For model fallback configuration, see [Retries and Fallbacks](/docs/agents/retries-fallback).\n","website/docs/agents/multi-modal.md":"# Multi-Modal Inputs\n\nAgents can process text, images, and files in a single request. This document covers how to send multi-modal inputs to agent methods.\n\n## Basic Usage\n\nAgent methods (`generateText`, `streamText`, `generateObject`, `streamObject`) accept three input formats:\n\n- `string` - Plain text input\n- `UIMessage[]` - AI SDK's UI message format (used in chat interfaces and streaming)\n- `BaseMessage[]` - AI SDK's model message format (alias for `ModelMessage`)\n\nBoth message formats are from the AI SDK and re-exported by VoltAgent. To send images or files, use either message array format with structured content.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport type { BaseMessage } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"vision-assistant\",\n  instructions: \"Analyze images and answer questions about them.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"What's in this image?\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/photo.jpg\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\nconsole.log(result.text);\n```\n\n## Image Formats\n\nImages can be provided as URLs, data URIs, or base64 strings.\n\n### Using URLs\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Describe this chart\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/chart.png\",\n      },\n    ],\n  },\n];\n```\n\n### Using Base64\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst imageBuffer = readFileSync(\"./photo.jpg\");\nconst base64Image = imageBuffer.toString(\"base64\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"What's in this photo?\" },\n      {\n        type: \"image\",\n        image: `data:image/jpeg;base64,${base64Image}`,\n        mediaType: \"image/jpeg\",\n      },\n    ],\n  },\n];\n```\n\n### Using Binary Data\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst imageBuffer = readFileSync(\"./photo.jpg\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Analyze this image\" },\n      {\n        type: \"image\",\n        image: new Uint8Array(imageBuffer),\n        mediaType: \"image/jpeg\",\n      },\n    ],\n  },\n];\n```\n\n## File Attachments\n\nFor non-image files (PDFs, documents), use the file content type.\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst pdfBuffer = readFileSync(\"./report.pdf\");\nconst base64Pdf = pdfBuffer.toString(\"base64\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Summarize this document\" },\n      {\n        type: \"file\",\n        url: `data:application/pdf;base64,${base64Pdf}`,\n        mediaType: \"application/pdf\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\n```\n\n## Multiple Images\n\nYou can include multiple images in a single message.\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Compare these two images\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/before.jpg\",\n      },\n      {\n        type: \"image\",\n        image: \"https://example.com/after.jpg\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\n```\n\n## Streaming with Images\n\nStreaming works the same way with multi-modal inputs.\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Describe this image in detail\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/landscape.jpg\",\n      },\n    ],\n  },\n];\n\nconst stream = await agent.streamText(messages);\n\nfor await (const chunk of stream.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n## Structured Output with Images\n\nUse `generateObject` to extract structured data from images.\n\n```ts\nimport { z } from \"zod\";\n\nconst receiptSchema = z.object({\n  total: z.number(),\n  items: z.array(\n    z.object({\n      name: z.string(),\n      price: z.number(),\n    })\n  ),\n  date: z.string(),\n});\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Extract the receipt details\" },\n      {\n        type: \"image\",\n        image: \"data:image/jpeg;base64,/9j/4AAQSkZJRg...\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateObject(messages, receiptSchema);\nconsole.log(result.object);\n// { total: 42.50, items: [...], date: \"2024-01-15\" }\n```\n\n## Content Part Types Reference\n\nThese types are from the AI SDK and re-exported by VoltAgent for convenience.\n\nWhen using message arrays, the `content` field can be:\n\n- A `string` for text-only messages\n- An array of content parts for multi-modal messages\n\n### Text Part\n\n```ts\n{\n  type: \"text\",\n  text: \"Your text here\"\n}\n```\n\n### Image Part\n\n```ts\n{\n  type: \"image\",\n  image: string | URL | Uint8Array,  // URL, data URI, base64, or binary\n  mediaType?: string,                 // \"image/jpeg\", \"image/png\", etc.\n  alt?: string                        // Alternative text description\n}\n```\n\n### File Part\n\n```ts\n{\n  type: \"file\",\n  url: string,           // Absolute URL or data URI\n  mediaType: string      // \"application/pdf\", \"text/csv\", etc.\n}\n```\n\nFor complete type definitions, see the [AI SDK documentation](https://sdk.vercel.ai/docs/reference/ai-sdk-core/model-message).\n\n## Model Support\n\nNot all models support vision or file inputs. Check your provider's documentation.\n\nVision-capable models include:\n\n- **OpenAI**: `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`\n- **Anthropic**: `claude-3-5-sonnet`, `claude-3-opus`, `claude-3-haiku`\n- **Google**: `gemini-1.5-pro`, `gemini-1.5-flash`\n\nImage format support, size limits, and costs vary by provider. Refer to your model provider's documentation for details.\n\n## Error Handling\n\n```ts\ntry {\n  const messages: BaseMessage[] = [\n    {\n      role: \"user\",\n      content: [\n        { type: \"text\", text: \"What's in this image?\" },\n        {\n          type: \"image\",\n          image: \"https://example.com/image.jpg\",\n        },\n      ],\n    },\n  ];\n\n  const result = await agent.generateText(messages);\n  console.log(result.text);\n} catch (error) {\n  if (error.message.includes(\"vision\")) {\n    console.error(\"This model does not support image inputs\");\n  } else {\n    console.error(\"Error:\", error);\n  }\n}\n```\n\n## VoltOps Console\n\nThe [VoltOps Console](https://console.voltagent.dev/) includes a chat interface with file upload support.\n\nClick the attachment button (📎) to upload images or files. The console converts uploaded files to the appropriate message format (base64 data URIs) and sends them to your agent.\n\nThis allows you to test multi-modal agents without writing code for file handling.\n","website/docs/agents/overview.md":"---\ntitle: Overview\nslug: /agents/overview\n---\n\n# Agent Overview\n\nAn agent in VoltAgent wraps a language model with instructions, tools, memory, and other capabilities. You create an agent instance, then call its methods to generate responses or stream output.\n\nThere are two ways to use agents in VoltAgent:\n\n1. **Direct method calls** - Call agent methods (`generateText`, `streamText`) from your application code\n2. **REST API** - Use VoltAgent's HTTP server to expose agents as REST endpoints\n\nThis document covers both approaches, starting with the basics.\n\n## Creating an Agent\n\nAn agent requires three properties: a name, instructions, and a model.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n```\n\nYou can also use a model string instead of importing a provider:\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n```\n\nThe `instructions` property defines the agent's behavior. The `model` can be an ai-sdk `LanguageModel` or a `provider/model` string resolved by VoltAgent. See [Model Router & Registry](/docs/getting-started/model-router) for details.\n\nTo configure fallback lists and per-model retries, see [Retries and Fallbacks](/docs/agents/retries-fallback).\nTo run pre-guardrail input/output handlers, see [Middleware](/docs/agents/middleware).\n\n## Using Agents: Direct Method Calls\n\nAgents have two core methods for generating responses:\n\n### Text Generation\n\nUse these methods when you need text responses.\n\n**generateText** - Returns the complete text response at once.\n\n```ts\nconst result = await agent.generateText(\"What is TypeScript?\");\nconsole.log(result.text);\n```\n\n**streamText** - Streams text chunks as they're generated (for real-time UIs).\n\n```ts\nconst stream = await agent.streamText(\"Explain async/await\");\n\nfor await (const chunk of stream.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n### Streaming Features\n\nWhen using `streamText`, you can access detailed events and final values.\n\n#### fullStream for Detailed Events\n\nUse `fullStream` to receive detailed streaming events including tool calls, reasoning steps, and completion status.\n\n```ts\nconst response = await agent.streamText(\"Write a story\");\n\nfor await (const chunk of response.fullStream) {\n  switch (chunk.type) {\n    case \"reasoning-start\":\n      console.log(\"\\nReasoning started\");\n      break;\n    case \"reasoning-delta\":\n      process.stdout.write(chunk.delta ?? chunk.text ?? \"\");\n      break;\n    case \"reasoning-end\":\n      console.log(\"\\nReasoning completed\");\n      break;\n    case \"text-delta\":\n      process.stdout.write(chunk.delta ?? chunk.text ?? \"\");\n      break;\n    case \"tool-call\":\n      console.log(`\\nUsing tool: ${chunk.toolName}`);\n      break;\n    case \"tool-result\":\n      console.log(`Tool completed: ${chunk.toolName}`);\n      break;\n    case \"finish\":\n      console.log(`\\nDone! Tokens: ${chunk.usage?.totalTokens}`);\n      break;\n  }\n}\n```\n\n#### Promise-based Properties\n\nAccess final values as Promises that resolve when streaming completes.\n\n```ts\nconst response = await agent.streamText(\"Explain async/await\");\n\n// Process stream\n(async () => {\n  for await (const chunk of response.textStream) {\n    process.stdout.write(chunk);\n  }\n})();\n\n// Access final values (resolve when stream completes)\nconst [fullText, finalStepUsage, totalUsage, finishReason] = await Promise.all([\n  response.text, // Promise<string>\n  response.usage, // Promise<LanguageModelUsage> for the final step\n  response.totalUsage, // Promise<LanguageModelUsage> across all steps\n  response.finishReason, // Promise<string>\n]);\n\nconsole.log(`\\nTotal: ${fullText.length} chars, ${totalUsage?.totalTokens} tokens`);\nconsole.log(`Final step tokens: ${finalStepUsage?.totalTokens}`);\n```\n\n### Feedback (optional)\n\nIf you have VoltOps API keys configured, you can enable feedback per agent or per call. VoltAgent creates a short-lived feedback token and attaches it to the assistant message metadata and the result object.\n\n```ts\nconst result = await agent.generateText(\"Summarize this trace\", {\n  feedback: {\n    key: \"satisfaction\",\n    feedbackConfig: {\n      type: \"categorical\",\n      categories: [\n        { value: 1, label: \"Satisfied\" },\n        { value: 0, label: \"Unsatisfied\" },\n      ],\n    },\n  },\n});\n\nconsole.log(result.feedback?.url);\n```\n\n`result.feedback` may also include `provided`, `providedAt`, and `feedbackId` so UI clients can hide feedback controls once submitted.\n\nIf the feedback key is already registered, you can pass only `key` and let the stored config populate the token.\n\n```ts\nconst result = await agent.generateText(\"Quick rating?\", {\n  feedback: { key: \"satisfaction\" },\n});\n```\n\nTo persist \"already submitted\" state across memory reloads, you can use the result helper:\n\n```ts\nconst feedbackId = \"feedback-id-from-ingestion-response\"; // returned by your feedback ingestion API response\n\nif (result.feedback && !result.feedback.isProvided()) {\n  await result.feedback.markFeedbackProvided({\n    feedbackId, // optional\n  });\n}\n```\n\nYou can still call `agent.markFeedbackProvided(...)` directly if you prefer explicit control.\n\nThese helpers require memory-backed conversations (`userId` and `conversationId`) and are typically called from your backend after feedback ingestion succeeds.\n\nFor end-to-end examples (SDK, API, and useChat), see [Feedback](/observability-docs/feedback).\n\n### Structured Data Generation\n\nUse `output` with `generateText`/`streamText` to get structured data while still using tools and all agent capabilities.\n`generateObject` and `streamObject` are deprecated in VoltAgent 2.x.\n\n```ts\nimport { Output } from \"ai\";\nimport { z } from \"zod\";\n\nconst recipeSchema = z.object({\n  name: z.string(),\n  ingredients: z.array(z.string()),\n  steps: z.array(z.string()),\n  prepTime: z.number(),\n});\n\n// With generateText - supports tool calling\nconst result = await agent.generateText(\"Create a pasta recipe\", {\n  output: Output.object({ schema: recipeSchema }),\n});\nconsole.log(result.output); // { name: \"...\", ingredients: [...], ... }\n\n// With streamText - stream partial objects while using tools\nconst stream = await agent.streamText(\"Create a detailed recipe\", {\n  output: Output.object({ schema: recipeSchema }),\n});\n\nfor await (const partial of stream.partialOutputStream ?? []) {\n  console.log(partial); // Incrementally built object\n}\n\n// Constrained text generation\nconst haiku = await agent.generateText(\"Write a haiku about coding\", {\n  output: Output.text(),\n});\nconsole.log(haiku.output);\n```\n\n### Summarization\n\nSummarization inserts a system summary and keeps the last N non-system messages before each model call. Configure it with the `summarization` option on the agent. See [Summarization](./summarization.md) for configuration and storage details.\n\n### Conversation Persistence\n\nVoltAgent persists conversation messages and step records while a run is executing. By default, it uses step-level checkpoints, which is safer for long multi-step tool chains.\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users reliably.\",\n  model: \"openai/gpt-4o-mini\",\n  conversationPersistence: {\n    mode: \"step\", // \"step\" (default) or \"finish\"\n    debounceMs: 200, // default\n    flushOnToolResult: true, // default\n  },\n});\n```\n\nYou can also override this per call:\n\n```ts\nawait agent.generateText(\"Run the workflow\", {\n  memory: {\n    options: {\n      conversationPersistence: {\n        mode: \"finish\",\n      },\n    },\n  },\n});\n```\n\n`mode: \"step\"` schedules persistence during execution and flushes immediately on tool completion by default. `mode: \"finish\"` keeps the legacy behavior of persisting only at operation completion.\n\n### Input Types\n\nAll methods accept either a string or an array of messages:\n\n```ts\n// String input\nawait agent.generateText(\"Hello\");\n\n// Message array (for multimodal or conversation history)\nawait agent.generateText([{ role: \"user\", content: \"What's in this image?\" }]);\n```\n\n## Using Agents: REST API\n\nVoltAgent can expose agents through HTTP endpoints. This lets you call agents from any client (web, mobile, etc.).\n\n### Server Setup\n\nCreate a `VoltAgent` instance with a server provider:\n\n```ts\nimport { VoltAgent, Agent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst agent = new Agent({\n  name: \"assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n});\n\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer(), // Starts on port 3141 by default\n});\n```\n\n### Available Endpoints\n\nThe server exposes the following REST endpoints:\n\n#### Text Generation\n\n- `POST /agents/:id/text` - Generate complete text response (synchronous)\n- `POST /agents/:id/stream` - Stream raw fullStream events (SSE)\n- `POST /agents/:id/chat` - Stream UI messages for useChat hook (SSE)\n\nIf you need clients to reconnect after refresh and continue the same response, enable resumable streaming. See [Resumable Streaming](/docs/agents/resumable-streaming/).\n\n#### Structured Data\n\n- `POST /agents/:id/object` - Generate structured object (synchronous)\n- `POST /agents/:id/stream-object` - Stream object generation (SSE)\n\n**Endpoint comparison:**\n\n| Endpoint         | Method | Response Type | Use Case                                                |\n| ---------------- | ------ | ------------- | ------------------------------------------------------- |\n| `/text`          | POST   | JSON          | Complete text response at once                          |\n| `/stream`        | POST   | SSE           | Raw stream events (text/reasoning/tool/lifecycle parts) |\n| `/chat`          | POST   | SSE           | UI message stream for ai-sdk's useChat hook             |\n| `/object`        | POST   | JSON          | Complete structured object at once                      |\n| `/stream-object` | POST   | SSE           | Streaming partial objects                               |\n\n### Structured Output via HTTP\n\nYou can use `output` with the text endpoints (`/text`, `/stream`, `/chat`) to get structured data while maintaining tool calling capabilities. Add the `output` field to the `options` object in your request:\n\n```typescript\nconst response = await fetch(\"http://localhost:3141/agents/assistant/text\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    input: \"Create a recipe\",\n    options: {\n      output: {\n        type: \"object\",\n        schema: {\n          type: \"object\",\n          properties: {\n            name: { type: \"string\" },\n            ingredients: { type: \"array\", items: { type: \"string\" } },\n          },\n          required: [\"name\"],\n        },\n      },\n    },\n  }),\n});\n\nconst data = await response.json();\nconsole.log(data.data.output); // Structured object\n```\n\nFor detailed API reference and examples, see [Agent Endpoints - output](../api/endpoints/agents.md#using-output-for-structured-generation).\n\n### Calling from Next.js API Route\n\nExample Next.js API route using the SDK (not direct REST calls):\n\n```ts\n// app/api/chat/route.ts\nimport { agent } from \"@/voltagent\";\n\nexport async function POST(req: Request) {\n  const { messages, conversationId, userId } = await req.json();\n\n  const result = await agent.streamText(messages, {\n    memory: {\n      conversationId,\n      userId,\n    },\n  });\n\n  return result.toUIMessageStreamResponse();\n}\n```\n\nTo call the REST API directly from a client:\n\n```ts\n// Client-side code\nconst response = await fetch(\"http://localhost:3141/agents/assistant/chat\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    messages: [{ role: \"user\", content: \"Hello\" }],\n  }),\n});\n\n// Stream response (SSE)\nconst reader = response.body.getReader();\n// ... process stream chunks\n```\n\n## Constructor Options\n\nYou can configure agents with additional options:\n\n```typescript\nconst agent = new Agent({\n  // Required\n  name: \"MyAgent\", // Agent identifier\n  instructions: \"You are a helpful assistant\", // Behavior guidelines\n  model: \"openai/gpt-4o\", // Model string or ai-sdk LanguageModel\n\n  // Optional\n  id: \"custom-id\", // Unique ID (auto-generated if not provided)\n  purpose: \"Customer support agent\", // Agent purpose for supervisor context\n  tools: [weatherTool, searchTool], // Available tools\n  memory: memoryStorage, // Memory instance (omit -> built-in in-memory, false -> disable)\n  context: new Map([\n    // Default context for all operations\n    [\"environment\", \"production\"],\n  ]),\n  maxSteps: 10, // Maximum tool-use iterations\n  temperature: 0.7, // Default creativity (overridable per call)\n  maxOutputTokens: 512, // Default token limit (overridable per call)\n  subAgents: [researchAgent], // Sub-agents for delegation\n  supervisorConfig: {\n    // Supervisor behavior config\n    systemMessage: \"Custom supervisor instructions\",\n    includeAgentsMemory: true,\n  },\n\n  // Additional constructor parameters\n  hooks: createHooks({ onStart, onEnd }), // Lifecycle event handlers\n  retriever: new PineconeRetriever(), // RAG retriever\n  voice: new ElevenLabsVoice(), // Voice configuration\n  markdown: true, // Enable markdown formatting\n  voltOpsClient: new VoltOpsClient({\n    // Observability & prompt management\n    publicKey: \"...\",\n    secretKey: \"...\",\n  }),\n  maxHistoryEntries: 1000, // Max history entries to store\n});\n```\n\n## Advanced Features\n\nAgents support additional capabilities through configuration options. Each feature is briefly explained below with a link to detailed documentation.\n\n### Memory\n\nMemory stores conversation history so agents can reference past messages. If `memory` is omitted, agents use built-in in-memory storage. Use `memory: false` for stateless behavior, or configure a persistent adapter for long-term storage.\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n\nconst agent = new Agent({\n  name: \"Agent with Memory\",\n  model: \"openai/gpt-4o\",\n  memory,\n});\n```\n\n[Memory documentation](./memory/overview.md)\n\n### Tools\n\nTools let agents call functions to fetch data, perform actions, or interact with external systems. The LLM decides when to use tools based on the user's input.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather for a location\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => {\n    // Call weather API\n    return { temperature: 72, conditions: \"sunny\" };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Weather Assistant\",\n  instructions: \"Answer weather questions using the get_weather tool.\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n});\n```\n\n[Tools documentation](./tools.md)\n\n#### Using Agents as Tools\n\nAgents can be converted to tools and used by other agents:\n\n```ts\nconst writerAgent = new Agent({\n  id: \"writer\",\n  purpose: \"Writes blog posts\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst editorAgent = new Agent({\n  id: \"editor\",\n  purpose: \"Edits content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Coordinator uses them as tools\nconst coordinator = new Agent({\n  tools: [writerAgent.toTool(), editorAgent.toTool()],\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\nThis is useful when the LLM should decide which agents to call based on the request.\n\n### Guardrails\n\nGuardrails run before and after the model call to validate inputs or adjust outputs. Input guardrails receive the request text, while output guardrails can rewrite or block the response before it reaches the caller.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Guarded Assistant\",\n  instructions: \"Answer briefly.\",\n  model: \"openai/gpt-4o-mini\",\n  inputGuardrails: [\n    {\n      id: \"reject-empty\",\n      name: \"Reject Empty Prompts\",\n      handler: async ({ inputText }) => {\n        if (inputText.trim().length === 0) {\n          return {\n            pass: false,\n            action: \"block\",\n            message: \"Prompt cannot be empty.\",\n          };\n        }\n        return { pass: true };\n      },\n    },\n  ],\n  outputGuardrails: [\n    {\n      id: \"trim-output\",\n      name: \"Trim Whitespace\",\n      handler: async ({ output }) => ({\n        pass: true,\n        action: \"modify\",\n        modifiedOutput: typeof output === \"string\" ? output.trim() : output,\n      }),\n    },\n  ],\n});\n\nconst response = await agent.generateText(\"  Give me one fact about Mars.  \");\nconsole.log(response.text); // \"Give me one fact about Mars.\" (trimmed)\n```\n\n[Learn more about Guardrails](../guardrails/built-in.md)\n\n### Sub-Agents\n\nSub-agents let you delegate tasks to specialized agents. The parent agent can call sub-agents through an automatically generated `delegate_task` tool.\n\n```ts\nconst researchAgent = new Agent({\n  name: \"Researcher\",\n  instructions: \"Research topics thoroughly.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst writerAgent = new Agent({\n  name: \"Writer\",\n  instructions: \"Write clear, concise content.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst coordinator = new Agent({\n  name: \"Coordinator\",\n  instructions: \"Delegate research to Researcher and writing to Writer.\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researchAgent, writerAgent],\n});\n```\n\n#### Event Filtering\n\nWhen streaming with sub-agents, by default only `tool-call` and `tool-result` events are forwarded from sub-agents to reduce noise.\n\n**Enable all event types:**\n\n```ts\nconst coordinator = new Agent({\n  name: \"Coordinator\",\n  instructions: \"Coordinate between agents.\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researchAgent, writerAgent],\n  supervisorConfig: {\n    fullStreamEventForwarding: {\n      types: [\n        \"tool-call\",\n        \"tool-result\",\n        \"text-start\",\n        \"text-delta\",\n        \"text-end\",\n        \"reasoning-start\",\n        \"reasoning-delta\",\n        \"reasoning-end\",\n        \"source\",\n        \"error\",\n        \"finish\",\n      ],\n    },\n  },\n});\n\n// Now receive all sub-agent events\nconst response = await coordinator.streamText(\"Research and write about AI\");\nfor await (const chunk of response.fullStream) {\n  if (chunk.subAgentId && chunk.subAgentName) {\n    console.log(`[${chunk.agentPath?.join(\" > \") ?? chunk.subAgentName}] ${chunk.type}`);\n  }\n}\n```\n\n[Sub-Agents documentation](./subagents.md)\n\n### Hooks\n\nHooks let you run code at specific points in the agent lifecycle: before/after operations, before/after tool calls, etc. Use hooks for logging, monitoring, or custom logic.\n\n```ts\nimport { createHooks } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onStart: async ({ agent, context }) => {\n    console.log(`${agent.name} started`);\n  },\n  onEnd: async ({ agent, output, error }) => {\n    if (error) {\n      console.error(\"Error:\", error);\n    } else {\n      console.log(\"Completed:\", output);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: \"Answer questions.\",\n  model: \"openai/gpt-4o\",\n  hooks,\n});\n```\n\n[Hooks documentation](./hooks.md)\n\n### Prompt Management\n\nInstructions can be static strings, dynamic functions, or managed remotely via VoltOps. Dynamic instructions let you change agent behavior based on runtime context.\n\n```ts\n// Static instructions\nconst agent1 = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions.\",\n  model: \"openai/gpt-4o\",\n});\n\n// Dynamic instructions\nconst agent2 = new Agent({\n  name: \"Assistant\",\n  instructions: ({ context }) => {\n    const tier = context.get(\"tier\") || \"free\";\n    return tier === \"premium\" ? \"Provide detailed answers.\" : \"Provide concise answers.\";\n  },\n  model: \"openai/gpt-4o\",\n});\n```\n\n[Prompt Management documentation](./prompts.md)\n\n### Dynamic Agents\n\nAgent properties (instructions, model, tools) can be functions that evaluate at runtime based on context. This enables multi-tenant apps, role-based access, or per-user model selection.\n\n```ts\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: ({ context }) => {\n    const role = context.get(\"role\");\n    return role === \"admin\" ? \"Admin instructions.\" : \"User instructions.\";\n  },\n  model: ({ context }) => {\n    const tier = context.get(\"tier\");\n    return tier === \"premium\" ? \"openai/gpt-4o\" : \"openai/gpt-4o-mini\";\n  },\n});\n\nawait agent.generateText(\"Hello\", {\n  context: new Map([\n    [\"role\", \"admin\"],\n    [\"tier\", \"premium\"],\n  ]),\n});\n```\n\n[Dynamic Agents documentation](./dynamic-agents.md)\n\n### Operation Context\n\nPass request-specific data (like request IDs, user info) between hooks and tools using the operation context `Map`. Context is scoped to a single operation.\n\n```ts\nconst hooks = createHooks({\n  onStart: async ({ context }) => {\n    context.context.set(\"requestId\", `req-${Date.now()}`);\n  },\n});\n\nconst tool = createTool({\n  name: \"logger\",\n  parameters: z.object({ message: z.string() }),\n  execute: async (params, oc) => {\n    const requestId = oc?.context?.get(\"requestId\");\n    console.log(`[${requestId}] ${params.message}`);\n  },\n});\n```\n\n[Context documentation](./context.md)\n\n### Retriever (RAG)\n\nRetrievers fetch relevant context from external knowledge bases before calling the LLM. Implement the `BaseRetriever` interface to connect vector databases or document stores.\n\n```ts\nclass SimpleRetriever extends BaseRetriever {\n  async retrieve(input: string | BaseMessage[]): Promise<string> {\n    // Fetch relevant documents from vector DB or knowledge base\n    return \"Retrieved context goes here\";\n  }\n}\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer using retrieved context.\",\n  model: \"openai/gpt-4o\",\n  retriever: new SimpleRetriever(),\n});\n```\n\n[Retriever documentation](../rag/overview.md)\n\n### Models and Providers\n\nVoltAgent can use [ai-sdk](https://sdk.vercel.ai) models directly, but model strings are the default. Switch providers by changing the model string (or import another provider).\n\n```ts\nconst agent1 = new Agent({\n  name: \"OpenAI Agent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"Answer questions.\",\n});\n\nconst agent2 = new Agent({\n  name: \"Anthropic Agent\",\n  model: \"anthropic/claude-3-5-sonnet\",\n  instructions: \"Answer questions.\",\n});\n```\n\n[Providers and Models documentation](../getting-started/providers-models.md)\n\n### Call Settings\n\nConfigure generation behavior per-call with parameters like `temperature`, `maxOutputTokens`, `topP`, etc.\n\n```ts\nawait agent.generateText(\"Write a story\", {\n  temperature: 0.8,\n  maxOutputTokens: 500,\n  topP: 0.9,\n  seed: 12345,\n  // Provider-specific options\n  providerOptions: {\n    someProviderSpecificOption: {\n      foo: \"bar\",\n    },\n  },\n});\n```\n\n### Markdown Formatting\n\nEnable automatic markdown formatting in text responses by setting `markdown: true`. The agent will use markdown syntax (headings, lists, bold text, etc.) in its responses.\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n  markdown: true,\n});\n\nconst result = await agent.generateText(\"Explain how to make tea.\");\n// Response will be formatted with markdown\n```\n\n### Step Control (maxSteps)\n\n`maxSteps` limits how many LLM calls (steps) an agent can make in a single operation. Each tool call consumes a step. Default is 5.\n\n```ts\n// Set maxSteps at agent level\nconst agent = new Agent({\n  name: \"Agent\",\n  model: \"openai/gpt-4o\",\n  maxSteps: 5, // Default for all operations\n});\n\n// Or override per-call\nawait agent.generateText(\"Question\", { maxSteps: 3 });\n```\n\n### Cancellation (AbortController)\n\nCancel long-running operations using `AbortController`. Pass the signal to agent methods.\n\n```ts\nconst abortController = new AbortController();\n\nsetTimeout(() => abortController.abort(), 5000); // Cancel after 5s\n\ntry {\n  const response = await agent.generateText(\"Long task...\", {\n    abortSignal: abortController.signal,\n  });\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Cancelled\");\n  }\n}\n```\n\n[Cancellation documentation](./cancellation.md)\n\n### MCP (Model Context Protocol)\n\nConnect to external MCP servers to use their tools as if they were local tools.\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    myServer: {\n      type: \"http\",\n      url: \"https://mcp-server.example.com\",\n    },\n  },\n});\n\nconst mcpTools = await mcpConfig.getTools();\n\nconst agent = new Agent({\n  name: \"Agent\",\n  model: \"openai/gpt-4o\",\n  tools: mcpTools,\n});\n```\n\n[MCP documentation](./mcp/mcp.md)\n\n### Voice\n\nAdd speech-to-text and text-to-speech capabilities using voice providers.\n\n```ts\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\nconst voice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n  ttsModel: \"tts-1\",\n  voice: \"alloy\",\n});\n\nconst agent = new Agent({\n  name: \"Voice Assistant\",\n  model: \"openai/gpt-4o\",\n  voice,\n});\n\nconst result = await agent.generateText(\"Tell a story\");\nconst audioStream = await agent.voice.speak(result.text);\n```\n\n[Voice documentation](./voice.md)\n\n## Error Handling\n\nWrap agent calls in try-catch blocks. Errors during streaming are recorded in history and can be observed via the `onError` callback.\n\n## Next Steps\n\nSee the links above for detailed documentation on each feature. For working examples, visit the [examples repository](https://github.com/voltagent/voltagent/tree/main/examples).\n","website/docs/agents/plan-agent.md":"---\ntitle: PlanAgent\nslug: /agents/plan-agent\n---\n\n# PlanAgent\n\nPlanAgent is a higher-level agent that adds built-in planning, task delegation, filesystem tools, and summarization on top of the standard `Agent`. Use it when you want multi-step execution with explicit plans and tighter control over context.\n\n## Default Agent (baseline)\n\nStart with a normal `Agent` if you only need straightforward responses.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst result = await agent.generateText(\"What is VoltAgent?\");\nconsole.log(result.text);\n```\n\n## PlanAgent quickstart\n\nPlanAgent works like `Agent`, but adds planning, filesystem, and subagent tooling by default.\n\n```ts\nimport { PlanAgent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst internetSearch = createTool({\n  name: \"internet_search\",\n  description: \"Search the web for information\",\n  parameters: z.object({\n    query: z.string().describe(\"Search query\"),\n    maxResults: z.number().optional().default(5),\n  }),\n  execute: async ({ query, maxResults }) => {\n    const response = await fetch(\"https://api.tavily.com/search\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        Authorization: `Bearer ${process.env.TAVILY_API_KEY}`,\n      },\n      body: JSON.stringify({ query, maxResults }),\n    });\n    return response.json();\n  },\n});\n\nconst agent = new PlanAgent({\n  name: \"Researcher\",\n  systemPrompt: \"You are an expert researcher. Produce clear, sourced answers.\",\n  model: \"openai/gpt-4o\",\n  tools: [internetSearch],\n});\n\nconst result = await agent.generateText(\"What is VoltAgent?\");\nconsole.log(result.text);\n```\n\n## Dynamic systemPrompt\n\n`systemPrompt` can be a function that resolves per request. It receives `{ context, prompts }` and can return a string or a VoltOps prompt payload.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"DynamicPlanner\",\n  model: openai(\"gpt-4o\"),\n  systemPrompt: async ({ context }) => {\n    const tenant = context.get(\"tenant\") ?? \"default\";\n    return `Tenant: ${tenant}. Keep answers concise.`;\n  },\n});\n\nconst context = new Map<string | symbol, unknown>();\ncontext.set(\"tenant\", \"acme\");\n\nconst result = await agent.generateText(\"Summarize the roadmap\", { context });\nconsole.log(result.text);\n```\n\nIf you return a chat prompt:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"DynamicChatPlanner\",\n  model: openai(\"gpt-4o\"),\n  systemPrompt: async () => ({\n    type: \"chat\",\n    messages: [{ role: \"system\", content: \"You are a precise planner.\" }],\n  }),\n});\n```\n\nPlanAgent still appends its base prompt and any extension prompts.\n\n## Built-in capabilities\n\n### Planning with write_todos\n\nPlanAgent injects a planning prompt and a `write_todos` tool. The agent is expected to create a plan before using tools or producing long answers.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Planner\",\n  systemPrompt: \"Plan carefully before acting.\",\n  model: \"openai/gpt-4o\",\n  planning: {\n    systemPrompt: \"Always plan any multi-step task. Keep 4-8 concise steps and update as you go.\",\n  },\n});\n```\n\nDisable planning if you want a lighter agent:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"NoPlan\",\n  systemPrompt: \"Answer quickly.\",\n  model: \"openai/gpt-4o\",\n  planning: false,\n});\n```\n\n### State and persistence\n\nPlanAgent stores its todo list, filesystem state, and summaries per conversation. When a conversation continues, it injects the latest state back into the system prompt so the model can keep working from the same plan and files.\n\n### Filesystem tools\n\nPlanAgent provides `ls`, `read_file`, `write_file`, `edit_file`, `glob`, and `grep`. The default backend is an in-memory filesystem, but you can use a real filesystem backend.\n\n```ts\nimport { NodeFilesystemBackend } from \"@voltagent/core\";\n\nconst agent = new PlanAgent({\n  name: \"FileAgent\",\n  systemPrompt: \"Use the filesystem to store and retrieve context.\",\n  model: \"openai/gpt-4o\",\n  filesystem: {\n    backend: new NodeFilesystemBackend({\n      rootDir: process.cwd(),\n      virtualMode: true,\n    }),\n  },\n});\n```\n\n### Task tool and subagents\n\nPlanAgent adds a `task` tool that can delegate work to subagents for isolated, multi-step tasks.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate deep research tasks to subagents.\",\n  model: \"openai/gpt-4o\",\n  subagents: [\n    {\n      name: \"research-analyst\",\n      description: \"Deep research and synthesis\",\n      systemPrompt: \"Research thoroughly and return a concise report.\",\n      tools: [internetSearch],\n    },\n  ],\n});\n```\n\nYou can also tune the task tool behavior:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  task: {\n    taskDescription: \"Use subagents for complex, independent work.\",\n    maxSteps: 6,\n  },\n});\n```\n\nTo forward subagent stream events when you call `planAgent.streamText(...)`, configure the task's supervisor config:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  task: {\n    supervisorConfig: {\n      fullStreamEventForwarding: {\n        types: [\"tool-call\", \"tool-result\", \"text-delta\"],\n      },\n    },\n  },\n});\n```\n\nPlanAgent also adds a default `general-purpose` subagent unless you disable it:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  generalPurposeAgent: false,\n});\n```\n\n### Summarization\n\nPlanAgent can automatically summarize older conversation history and keep recent messages in context.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Summarizer\",\n  systemPrompt: \"Keep context tight.\",\n  model: \"openai/gpt-4o\",\n  summarization: {\n    triggerTokens: 120_000,\n    keepMessages: 6,\n    maxOutputTokens: 600,\n  },\n});\n```\n\nDisable it when you want full context preserved:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"NoSummary\",\n  systemPrompt: \"Keep full context.\",\n  model: \"openai/gpt-4o\",\n  summarization: false,\n});\n```\n\n### Tool result eviction\n\nLarge tool outputs can be offloaded to the filesystem to keep the model context small.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Evictor\",\n  systemPrompt: \"Store large tool outputs on the filesystem.\",\n  model: \"openai/gpt-4o\",\n  toolResultEviction: {\n    enabled: true,\n    tokenLimit: 20000,\n  },\n});\n```\n\n### Extensions\n\nUse `extensions` to add new toolkits, prompts, or hooks without forking PlanAgent.\n\n```ts\nimport type { PlanAgentExtension } from \"@voltagent/core\";\n\nconst debugExtension: PlanAgentExtension = {\n  name: \"debug\",\n  apply: () => ({\n    systemPrompt: \"Log key decisions to the user.\",\n  }),\n};\n\nconst agent = new PlanAgent({\n  name: \"Extended\",\n  systemPrompt: \"Be helpful.\",\n  model: \"openai/gpt-4o\",\n  extensions: [debugExtension],\n});\n```\n\n## Configuration reference (high-level)\n\n- `systemPrompt`: static string or dynamic function for per-request prompts\n- `planning`: configure or disable planning (`write_todos`)\n- `filesystem`: configure backend and tool prompts\n- `task`: configure task tool prompts and limits\n- `subagents`: custom subagent definitions\n- `generalPurposeAgent`: add/remove the default general-purpose subagent\n- `summarization`: configure summarization triggers and model\n- `toolResultEviction`: offload large tool results to filesystem\n- `extensions`: attach custom tooling or hooks\n\nUse PlanAgent when your workloads require explicit plans, context offloading, or structured delegation to subagents.\n","website/docs/agents/prompts.md":"# Agent Instructions\n\n## Overview\n\nVoltAgent supports three approaches for defining agent instructions. Each approach addresses different requirements around flexibility, team collaboration, and deployment workflows.\n\n### The Three Approaches\n\n| Approach                 | Definition                    | Context Access | Team Workflow    |\n| ------------------------ | ----------------------------- | -------------- | ---------------- |\n| **Static Instructions**  | Hardcoded string              | No             | Code-based       |\n| **Dynamic Instructions** | Function with runtime context | Yes            | Code-based       |\n| **VoltOps Management**   | Externally managed prompts    | Yes            | Platform-managed |\n\n## Static Instructions\n\nStatic instructions are literal strings assigned to the `instructions` property.\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: \"You are a customer support agent. Help users with their questions.\",\n});\n```\n\n**Type signature:**\n\n```typescript\ninstructions: string;\n```\n\n### When to Use\n\nUse static instructions when:\n\n- Agent behavior is consistent across all interactions\n- No runtime context is needed\n- Instructions rarely change\n- Team members edit prompts through code reviews\n\nAvoid when:\n\n- Different users need different behavior\n- Instructions depend on runtime data (user tier, time, location)\n- Non-technical team members need to edit prompts\n- You need prompt versioning outside of code commits\n\n## Dynamic Instructions\n\nDynamic instructions are functions that receive runtime context and return instructions.\n\n### Returning Strings\n\nFunctions can return a plain string based on context:\n\n```typescript\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ context }) => {\n    const userTier = context.get(\"userTier\") || \"basic\";\n\n    if (userTier === \"premium\") {\n      return \"You are a premium customer support agent. Provide detailed explanations and prioritize this customer's requests.\";\n    }\n    return \"You are a customer support agent. Provide helpful but concise answers.\";\n  },\n});\n```\n\n**Using with context:**\n\n```typescript\nconst premiumContext = new Map();\npremiumContext.set(\"userTier\", \"premium\");\n\nconst response = await agent.generateText(\"I need help\", {\n  context: premiumContext,\n});\n```\n\n### Returning PromptContent Objects\n\nFunctions can also return `PromptContent` objects for text or chat-based instructions.\n\n**Text type:**\n\n```typescript\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ context }) => {\n    return {\n      type: \"text\",\n      text: \"You are a customer support agent.\",\n    };\n  },\n});\n```\n\n**Chat type with multiple messages:**\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"ChatAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async () => {\n    return {\n      type: \"chat\",\n      messages: [\n        {\n          role: \"system\",\n          content: \"You are a helpful assistant.\",\n        },\n        {\n          role: \"user\",\n          content: \"Hello!\",\n        },\n        {\n          role: \"assistant\",\n          content: \"Hi! How can I help you today?\",\n        },\n      ],\n    };\n  },\n});\n```\n\n**Chat type with provider-specific options:**\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"CachedAgent\",\n  model: \"anthropic/claude-3-7-sonnet-20250219\",\n  instructions: async () => {\n    return {\n      type: \"chat\",\n      messages: [\n        {\n          role: \"system\",\n          content: \"Long system prompt that should be cached...\",\n          providerOptions: {\n            anthropic: {\n              cacheControl: { type: \"ephemeral\", ttl: \"5m\" },\n            },\n          },\n        },\n      ],\n    };\n  },\n});\n```\n\n**Type signature:**\n\n```typescript\ninstructions: (options: DynamicValueOptions) => Promise<string | PromptContent>;\n\ninterface DynamicValueOptions {\n  context: Map<string | symbol, unknown>;\n  headers?: Record<string, string>;\n  prompts: PromptHelper;\n}\n\ninterface PromptContent {\n  type: \"text\" | \"chat\";\n  text?: string;\n  messages?: ChatMessage[];\n}\n```\n\n### When to Use\n\nUse dynamic instructions when:\n\n- Agent behavior depends on user properties (tier, role, preferences)\n- Instructions need runtime data (time, location, session state)\n- Different tenants require different behavior\n- Conditional logic determines instruction content\n\nAvoid when:\n\n- Multiple non-technical stakeholders need to edit prompts\n- You need prompt version history outside of code\n- Collaborative prompt editing is required\n- Prompts should update without deploying code\n\n## VoltOps Prompt Management\n\nVoltOps separates prompt content from application code. Prompts are created and versioned in the VoltOps platform, then fetched at runtime.\n\nFor complete documentation on VoltOps Prompt Management, see:\n\n- [Prompt Management Overview](/prompt-engineering-docs/) - Creating, versioning, labels, and managing prompts\n- [Usage Guide](/prompt-engineering-docs/usage/) - Integration with agents, caching, variables, and error handling\n\n### Quick Example\n\n```typescript\nimport { Agent, VoltAgent, VoltOpsClient } from \"@voltagent/core\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ prompts }) => {\n    return await prompts.getPrompt({\n      promptName: \"customer-support-prompt\",\n      label: \"production\",\n      variables: { companyName: \"VoltAgent Corp\" },\n    });\n  },\n});\n\nnew VoltAgent({\n  agents: { agent },\n  voltOpsClient: voltOpsClient,\n});\n```\n\n### When to Use\n\nUse VoltOps when:\n\n- Non-technical team members edit prompts\n- Audit trails and approval workflows are required\n- Multiple environments need different prompt versions\n- Prompt analytics and monitoring are needed\n\nAvoid when:\n\n- External dependencies are not acceptable\n- Offline operation is required\n\n## Comparison\n\n| Feature                   | Static      | Dynamic     | VoltOps            |\n| ------------------------- | ----------- | ----------- | ------------------ |\n| **Type**                  | String      | Function    | External platform  |\n| **Context Access**        | No          | Yes         | Yes (via function) |\n| **Runtime Flexibility**   | None        | Full        | Full               |\n| **Team Collaboration**    | Code review | Code review | Platform UI        |\n| **Version Control**       | Git         | Git         | VoltOps + Git      |\n| **Non-technical Editing** | No          | No          | Yes                |\n| **Analytics**             | No          | No          | Yes                |\n| **Offline Support**       | Yes         | Yes         | No                 |\n| **External Dependency**   | No          | No          | Yes                |\n\n## Examples\n\n**Static (solo developer, consistent behavior):**\n\n```typescript\nconst agent = new Agent({\n  instructions: \"You are a code reviewer. Focus on security and performance.\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n**Dynamic (user-specific behavior):**\n\n```typescript\nconst agent = new Agent({\n  instructions: async ({ context }) => {\n    const tier = context.get(\"tier\");\n    return tier === \"premium\"\n      ? \"You are a premium support agent with deep technical expertise.\"\n      : \"You are a support agent providing efficient solutions.\";\n  },\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n**VoltOps (team collaboration, versioning):**\n\n```typescript\nconst agent = new Agent({\n  instructions: async ({ prompts }) => {\n    return await prompts.getPrompt({\n      promptName: \"customer-support-agent\",\n      label: process.env.NODE_ENV === \"production\" ? \"production\" : \"development\",\n    });\n  },\n  model: \"openai/gpt-4o-mini\",\n  voltOpsClient: voltOpsClient,\n});\n```\n","website/docs/agents/providers.md":"---\ntitle: Using Models\nslug: /agents/providers\n---\n\n# Using Models with Agents\n\nVoltAgent 1.x has no `llm` provider prop. Set `model` to either:\n\n- An ai-sdk `LanguageModel` (imported from a provider package), or\n- A model string like `openai/gpt-4o-mini` resolved by the built-in model router.\n\n## Quick Example\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"my-agent\",\n  instructions: \"Helpful and concise\",\n  model: \"anthropic/claude-3-5-sonnet\",\n});\n\nconst res = await agent.generateText(\"Say hi\", { temperature: 0.2 });\nconsole.log(res.text);\n```\n\nYou can also pass a `LanguageModel` directly:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport { anthropic } from \"@ai-sdk/anthropic\";\n\nconst agent = new Agent({\n  name: \"my-agent\",\n  instructions: \"Helpful and concise\",\n  model: anthropic(\"claude-3-5-sonnet\"),\n});\n```\n\n## Provider Options (IntelliSense)\n\nUse `providerOptions` for provider-specific settings in `generateText`, `streamText`, `generateObject`, and `streamObject` calls.\n\n```ts\nawait agent.generateText(\"Draft a summary\", {\n  temperature: 0.3,\n  maxOutputTokens: 300,\n  providerOptions: {\n    openai: {\n      reasoningEffort: \"medium\",\n      textVerbosity: \"low\",\n    },\n    anthropic: {\n      sendReasoning: true,\n      cacheControl: { type: \"ephemeral\" },\n    },\n    google: {\n      thinkingConfig: {\n        thinkingBudget: 1024,\n      },\n    },\n    xai: {\n      reasoningEffort: \"medium\",\n    },\n  },\n});\n```\n\nTypeScript IntelliSense is available for `openai`, `anthropic`, `google`, and `xai` option objects.\n\n## Provider Selection\n\nUse any ai-sdk provider package you have configured (OpenAI, Anthropic, Google, Groq, Mistral, Vertex, Bedrock, etc.), or use model strings.\nFor installation and an up-to-date model matrix, see:\n\n**[Providers & Models](/docs/getting-started/providers-models)**\n\nFor how model strings are resolved and how environment variables map, see:\n\n**[Model Router & Registry](/docs/getting-started/model-router)**\n\n## Retries and Fallbacks\n\nFor retry counts and fallback lists, see:\n\n**[Retries and Fallbacks](/docs/agents/retries-fallback)**\n","website/docs/agents/resumable-streaming.md":"---\ntitle: Resumable Streaming\nslug: /agents/resumable-streaming\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Resumable Streaming\n\nResumable streaming lets a client reconnect to an in-flight stream (for example after a refresh) and continue receiving the same response. VoltAgent provides this via `@voltagent/resumable-streams`.\n\n<video controls loop muted playsInline style={{width: '100%', height: 'auto'}}>\n\n  <source src=\"https://cdn.voltagent.dev/docs/resumable-stream.mp4\" type=\"video/mp4\" />\n  Your browser does not support the video tag.\n</video>\n\n## Install\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n    ```bash\n    npm install @voltagent/resumable-streams\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/resumable-streams\n    ```\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n    ```bash\n    pnpm add @voltagent/resumable-streams\n    ```\n  </TabItem>\n</Tabs>\n\n## How it works\n\nResumable streaming is split into two storages:\n\n1. **Stream store**: stores stream chunks and pub/sub messages.\n2. **Active stream store**: maps `userId + \"-\" + conversationId` -> `streamId`.\n\nWhen a stream starts, a new `streamId` is created, the stream store persists the output, and the active stream store is updated. When the client reconnects, the active stream store is used to find the `streamId`, and the stream store resumes from the last position. When the stream finishes, the active stream store entry is cleared.\n\n## Basic usage (Hono server)\n\nCreate a Redis-backed store, build the adapter, and pass it to the Hono server.\n\n```ts\nimport { Agent, VoltAgent } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst resumableStream = await createResumableStreamAdapter({ streamStore });\n\nconst agent = new Agent({\n  id: \"assistant\",\n  name: \"Resumable Stream Agent\",\n  instructions: \"You are a helpful assistant.\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer({\n    resumableStream: { adapter: resumableStream },\n  }),\n});\n```\n\nBy default, the adapter uses `streamStore` as the `activeStreamStore`.  \nFor production, use a shared stream store (Redis) so multiple instances can resume.\n\n### Enabling resumable streams\n\nResumable streams are **opt-in**. You must set:\n\n- `options.resumableStream: true`\n- `options.conversationId`\n- `options.userId`\n\n```ts\nawait fetch(`${baseUrl}/agents/${agentId}/chat`, {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: `{\"input\":\"Hello!\",\"options\":{\"conversationId\":\"conv-1\",\"userId\":\"user-1\",\"resumableStream\":true}}`,\n});\n```\n\n### Server-level default (optional)\n\nIf you want resumable streaming enabled by default, set it at the server level and omit the per-request flag. You can still opt out by sending `options.resumableStream: false`.\n\n```ts\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer({\n    resumableStream: {\n      adapter: resumableStream,\n      defaultEnabled: true,\n    },\n  }),\n});\n```\n\n### Resume endpoint\n\nThe Hono server exposes:\n\n- `GET /agents/:id/chat/:conversationId/stream?userId=...`\n\nIf there is no active stream, it returns `204`. `userId` is required.\n\n## Store options\n\n### VoltOps managed store\n\n`createResumableStreamVoltOpsStore` stores streams in VoltOps. It uses:\n\n- the global `VoltOpsClient` if one is already configured by `VoltAgent`, or\n- your explicit `voltOpsClient`, or\n- `VOLTAGENT_PUBLIC_KEY` + `VOLTAGENT_SECRET_KEY` from env.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamVoltOpsStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamVoltOpsStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\nIf you need a custom base URL, pass `baseUrl` or set `VOLTAGENT_API_BASE_URL`.\n\nCustom VoltOps client:\n\n```ts\nimport { VoltOpsClient } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamVoltOpsStore,\n} from \"@voltagent/resumable-streams\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst streamStore = await createResumableStreamVoltOpsStore({ voltOpsClient });\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### Redis store (recommended)\n\n`createResumableStreamRedisStore` reads `REDIS_URL` or `KV_URL` by default.  \nIf you need a custom connection string, pass your own Redis clients as `publisher` and `subscriber`.\n\n```ts\nimport { createClient } from \"redis\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\n// Option A: use REDIS_URL or KV_URL from env\nconst streamStore = await createResumableStreamRedisStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n\n// Option B: pass your own clients (custom connection string)\nconst publisher = createClient({ url: \"redis://localhost:6379\" });\nconst subscriber = createClient({ url: \"redis://localhost:6379\" });\nconst customStreamStore = await createResumableStreamRedisStore({ publisher, subscriber });\nconst adapter = await createResumableStreamAdapter({ streamStore: customStreamStore });\n```\n\nIf you pass custom clients, you must connect them yourself.\n\n### Memory store (dev/test)\n\n`createResumableStreamMemoryStore` keeps everything in-process.  \nData is lost on restart and not shared across instances.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamMemoryStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamMemoryStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### Generic store (custom backend)\n\n`createResumableStreamGenericStore` accepts your own `publisher`/`subscriber`.  \nUse this for non-Redis backends that still provide pub/sub + KV semantics.\n\n```ts\nimport type {\n  ResumableStreamPublisher,\n  ResumableStreamSubscriber,\n} from \"@voltagent/resumable-streams\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamGenericStore,\n} from \"@voltagent/resumable-streams\";\n\nconst publisher: ResumableStreamPublisher = {\n  async connect() {},\n  async publish(channel, message) {\n    return 1;\n  },\n  async set(key, value, options) {\n    return \"OK\";\n  },\n  async get(key) {\n    return null;\n  },\n  async incr(key) {\n    return 1;\n  },\n};\n\nconst subscriber: ResumableStreamSubscriber = {\n  async connect() {},\n  async subscribe(channel, callback) {\n    return 1;\n  },\n  async unsubscribe(channel) {},\n};\n\nconst streamStore = await createResumableStreamGenericStore({\n  publisher,\n  subscriber,\n  keyPrefix: \"voltagent\",\n  waitUntil: null,\n});\n\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n## Next.js + AI SDK (useChat)\n\nThis section mirrors the `with-nextjs-resumable-stream` example. Full project:  \nhttps://github.com/VoltAgent/voltagent/tree/main/examples/with-nextjs-resumable-stream\n\n### 1) Adapter helper\n\nCreate the adapter once and reuse it across routes.\n\n```ts\n// lib/resumable-stream.ts\nimport type { ResumableStreamAdapter } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\nimport { after } from \"next/server\";\n\nlet adapterPromise: Promise<ResumableStreamAdapter> | undefined;\n\nexport function getResumableStreamAdapter() {\n  if (!adapterPromise) {\n    adapterPromise = (async () => {\n      const streamStore = await createResumableStreamRedisStore({ waitUntil: after });\n      return createResumableStreamAdapter({ streamStore });\n    })();\n  }\n\n  return adapterPromise;\n}\n```\n\n### 2) POST route (create stream)\n\n```ts\n// app/api/chat/route.ts\nimport { getResumableStreamAdapter } from \"@/lib/resumable-stream\";\nimport { agent } from \"@/voltagent\";\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\nimport { setWaitUntil } from \"@voltagent/core\";\nimport { after } from \"next/server\";\n\nexport async function POST(req: Request) {\n  const body = await req.json();\n  const { message, messages, options } = body;\n  const conversationId = options.conversationId;\n  const userId = options.userId;\n  const input = message ?? messages;\n\n  setWaitUntil(after);\n\n  const adapter = await getResumableStreamAdapter();\n  const session = createResumableChatSession({\n    adapter,\n    conversationId,\n    userId,\n    agentId: agent.id,\n  });\n\n  await session.clearActiveStream();\n\n  const result = await agent.streamText(input, {\n    userId,\n    conversationId,\n  });\n\n  return result.toUIMessageStreamResponse({\n    consumeSseStream: session.consumeSseStream,\n    onFinish: session.onFinish,\n  });\n}\n```\n\n### 3) GET route (resume stream)\n\n```ts\n// app/api/chat/[id]/stream/route.ts\nimport { getResumableStreamAdapter } from \"@/lib/resumable-stream\";\nimport { agent } from \"@/voltagent\";\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\n\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params;\n  const userId = new URL(request.url).searchParams.get(\"userId\") as string;\n\n  const adapter = await getResumableStreamAdapter();\n  const session = createResumableChatSession({\n    adapter,\n    conversationId: id,\n    userId,\n    agentId: agent.id,\n  });\n\n  return session.resumeResponse();\n}\n```\n\n### 4) Client-side resume\n\n```tsx\n// components/chat-interface.tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\n\nconst { messages, sendMessage } = useChat({\n  id: chatId,\n  messages: initialMessages,\n  resume: true,\n  transport: new DefaultChatTransport({\n    api: \"/api/chat\",\n    prepareSendMessagesRequest: ({ id, messages }) => ({\n      body: {\n        message: messages[messages.length - 1],\n        options: {\n          conversationId: id,\n          userId,\n        },\n      },\n    }),\n    prepareReconnectToStreamRequest: ({ id }) => ({\n      api: `/api/chat/${id}/stream?userId=${encodeURIComponent(userId)}`,\n    }),\n  }),\n});\n```\n\n## Custom usage (no AI SDK)\n\nUse `createResumableChatSession` when you control the SSE pipeline.\n\n```ts\nimport {\n  createResumableChatSession,\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n\nconst sseHeaders = {\n  \"Content-Type\": \"text/event-stream\",\n  \"Cache-Control\": \"no-cache\",\n  Connection: \"keep-alive\",\n};\n\nexport async function POST(req: Request) {\n  const { options, agentId, text } = await req.json();\n  const conversationId = options.conversationId;\n  const userId = options.userId;\n\n  const session = createResumableChatSession({\n    adapter,\n    conversationId,\n    userId,\n    agentId,\n    resumeHeaders: sseHeaders,\n  });\n\n  const stream = new ReadableStream<string>({\n    start(controller) {\n      controller.enqueue(`data: ${text}\\n\\n`);\n      controller.close();\n    },\n  });\n\n  const [persistStream, responseStream] = stream.tee();\n  await session.createStream(persistStream);\n\n  return new Response(responseStream.pipeThrough(new TextEncoderStream()), {\n    headers: sseHeaders,\n  });\n}\n```\n\nIf you want JSON payloads in SSE, serialize them with your preferred helper.\n\n## Route helpers (optional)\n\n`createResumableChatHandlers` can generate `POST`/`GET` handlers.  \nDefaults:\n\n- `conversationId`: `body.options.conversationId` (GET uses `params.id`)\n- `userId`: `body.options.userId` (GET uses `?userId=...`)\n\n## Custom stores and adapters\n\nYou have two extension points:\n\n- Implement `ResumableStreamStore` and pass it to the adapter.\n- Implement `ResumableStreamAdapter` directly for full control.\n\n`ResumableStreamStore` handles stream chunk storage.  \nIf it also implements `ResumableStreamActiveStore`, it can be used as `activeStreamStore`.  \nOtherwise, pass a separate `activeStreamStore`.\n\n## Important notes\n\n- Keying rule: `userId + \"-\" + conversationId`. `userId` is required.  \n  If you do not have a user identity, generate a stable id (for example `crypto.randomUUID()`).\n- When resumable streaming is enabled, the abort signal is removed.  \n  If you need abortable streams, do not use `resumableStream`.\n- Resumable streaming does not persist messages. For message persistence, enable memory.  \n  See [Memory](../agents/memory.md).\n\n## VoltOps plan limits (managed store)\n\nWhen you use the VoltOps managed store, concurrent resumable streams are limited per project:\n\n- Free: 1 concurrent stream\n- Core: 100 concurrent streams\n- Pro: 1000 concurrent streams\n\n## API reference (@voltagent/resumable-streams)\n\n### createResumableStreamAdapter\n\nBuilds the adapter used by servers or custom routes.\n\nProps:\n\n- `streamStore` (required): a `ResumableStreamStore` for stream chunks.\n- `activeStreamStore` (optional): overrides the active stream index.  \n  If omitted, the adapter uses `streamStore` when it implements `ResumableStreamActiveStore`.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore({ keyPrefix: \"voltagent\" });\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### createResumableStreamRedisStore\n\nCreates a Redis-backed stream store.\n\nProps:\n\n- `keyPrefix` (optional): prefix for Redis keys.\n- `waitUntil` (optional): keep-alive hook for serverless environments.\n- `publisher` (optional): Redis publisher client.\n- `subscriber` (optional): Redis subscriber client.\n\n```ts\nimport { createClient } from \"redis\";\nimport { createResumableStreamRedisStore } from \"@voltagent/resumable-streams\";\n\n// Option A: use REDIS_URL or KV_URL from env\nconst streamStore = await createResumableStreamRedisStore();\n\n// Option B: custom clients (custom connection string)\nconst publisher = createClient({ url: \"redis://localhost:6379\" });\nconst subscriber = createClient({ url: \"redis://localhost:6379\" });\nconst customStreamStore = await createResumableStreamRedisStore({ publisher, subscriber });\n```\n\nIf you pass custom clients, you must connect them yourself.\n\n### createResumableStreamVoltOpsStore\n\nCreates a managed store backed by VoltOps.\n\nProps:\n\n- `voltOpsClient` (optional)\n- `publicKey` / `secretKey` (optional, used if `voltOpsClient` is not provided)\n- `baseUrl` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport { createResumableStreamVoltOpsStore } from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamVoltOpsStore({\n  baseUrl: \"https://api.voltagent.dev\",\n});\n```\n\n### createResumableStreamMemoryStore\n\nCreates an in-process stream store (dev/test).\n\nProps:\n\n- `keyPrefix` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport { createResumableStreamMemoryStore } from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamMemoryStore({ keyPrefix: \"local\" });\n```\n\n### createResumableStreamGenericStore\n\nCreates a store from a custom pub/sub + KV backend.\n\nProps:\n\n- `publisher` (required)\n- `subscriber` (required)\n- `keyPrefix` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport type {\n  ResumableStreamPublisher,\n  ResumableStreamSubscriber,\n} from \"@voltagent/resumable-streams\";\nimport { createResumableStreamGenericStore } from \"@voltagent/resumable-streams\";\n\nconst publisher: ResumableStreamPublisher = {\n  async connect() {},\n  async publish(channel, message) {\n    return 1;\n  },\n  async set(key, value, options) {\n    return \"OK\";\n  },\n  async get(key) {\n    return null;\n  },\n  async incr(key) {\n    return 1;\n  },\n};\n\nconst subscriber: ResumableStreamSubscriber = {\n  async connect() {},\n  async subscribe(channel, callback) {\n    return 1;\n  },\n  async unsubscribe(channel) {},\n};\n\nconst streamStore = await createResumableStreamGenericStore({\n  publisher,\n  subscriber,\n  keyPrefix: \"voltagent\",\n  waitUntil: null,\n});\n```\n\n### createMemoryResumableStreamActiveStore\n\nCreates an in-memory active stream index. Use this to override `activeStreamStore`.\n\n```ts\nimport {\n  createMemoryResumableStreamActiveStore,\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst activeStreamStore = createMemoryResumableStreamActiveStore();\nconst adapter = await createResumableStreamAdapter({ streamStore, activeStreamStore });\n```\n\n### createResumableChatSession\n\nBuilds a session helper for custom route handlers.\n\nProps:\n\n- `adapter` (required)\n- `conversationId` (required)\n- `userId` (required)\n- `agentId` (optional)\n- `logger` (optional)\n- `resumeHeaders` (optional): headers for SSE resume responses\n\n```ts\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\n\nconst session = createResumableChatSession({\n  adapter,\n  conversationId,\n  userId,\n  agentId,\n});\n\nconst result = await agent.streamText(input, { conversationId, userId });\nreturn result.toUIMessageStreamResponse({\n  consumeSseStream: session.consumeSseStream,\n  onFinish: session.onFinish,\n});\n```\n\n```ts\nreturn session.resumeResponse();\n```\n\n### createResumableChatHandlers\n\nGenerates `POST` and `GET` handlers.\n\nProps:\n\n- `agent` (required)\n- `adapter` (required)\n- `waitUntil` (optional)\n- `logger` (optional)\n- `agentId` (optional)\n- `sendReasoning` (optional)\n- `sendSources` (optional)\n- `resolveInput` (optional)\n- `resolveConversationId` (optional)\n- `resolveUserId` (optional)\n- `resolveOptions` (optional)\n\n```ts\nimport { createResumableChatHandlers } from \"@voltagent/resumable-streams\";\n\nconst { POST, GET } = createResumableChatHandlers({\n  agent,\n  adapter,\n  resolveConversationId: ({ body }) => body.options.conversationId,\n  resolveUserId: ({ body }) => body.options.userId,\n});\n\nexport { POST, GET };\n```\n\n## Type contracts\n\n### ResumableStreamStore\n\n- `createNewResumableStream(streamId, makeStream, skipCharacters?)`\n- `resumeExistingStream(streamId, skipCharacters?)`\n\n### ResumableStreamActiveStore\n\n- `getActiveStreamId(context)`\n- `setActiveStreamId(context, streamId)`\n- `clearActiveStream(context)`\n\n### ResumableStreamAdapter\n\n- `createStream({ conversationId, userId, stream, agentId? })`\n- `resumeStream(streamId)`\n- `getActiveStreamId(context)`\n- `clearActiveStream(context)`\n\n### ResumableStreamPublisher / ResumableStreamSubscriber\n\nPublisher methods:\n\n- `connect()`\n- `publish(channel, message)`\n- `set(key, value, { EX })` (TTL in seconds)\n- `get(key)`\n- `incr(key)`\n\nSubscriber methods:\n\n- `connect()`\n- `subscribe(channel, callback)`\n- `unsubscribe(channel)`\n","website/docs/agents/retries-fallback.md":"---\ntitle: Retries and Fallbacks\nslug: /agents/retries-fallback\n---\n\n# Retries and Fallbacks\n\nVoltAgent supports per-call retries and ordered model fallback lists. Retries are handled by VoltAgent with exponential backoff. After retries are exhausted for a model, VoltAgent attempts the next model in the list.\n\n## Configure Retries\n\nYou can set a default retry count at the agent level, then override it per model or per call.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Support\",\n  instructions: \"Resolve tickets quickly.\",\n  model: \"openai/gpt-4o-mini\",\n  maxRetries: 3,\n});\n\n// Per-call override\nawait agent.generateText(\"Summarize this ticket\", { maxRetries: 0 });\n```\n\n- `maxRetries` is the number of retry attempts for a single model call (total attempts = `maxRetries + 1`).\n- Priority order: per-call `maxRetries` > per-model `maxRetries` > agent `maxRetries` > default `3`.\n- `0` disables retries for that call.\n- Retry delay uses exponential backoff: 1s, 2s, 4s, 8s (max 10s).\n- Errors with `isRetryable: false` skip retries; fallback can still run.\n\n## Configure Fallbacks\n\nProvide a list of models. VoltAgent tries them in order until one succeeds.\nEach entry can have its own retry count and can be toggled on/off.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport { anthropic } from \"@ai-sdk/anthropic\";\n\nconst agent = new Agent({\n  name: \"FallbackAgent\",\n  instructions: \"Be concise.\",\n  model: [\n    { id: \"primary\", model: \"openai/gpt-4o-mini\", maxRetries: 2 },\n    { id: \"secondary\", model: anthropic(\"claude-3-5-sonnet\"), maxRetries: 1 },\n    { id: \"tertiary\", model: \"google/gemini-2.0-flash\", enabled: true },\n  ],\n});\n```\n\nNotes:\n\n- The first enabled model is the primary.\n- `maxRetries` falls back to the agent default if omitted.\n- `enabled: false` lets you keep a model configured but temporarily disabled.\n\n## How Fallback Works\n\nVoltAgent tries each enabled model in order:\n\n1. Resolve the model (dynamic or static).\n2. Execute the call with that model, retrying up to `maxRetries` on error.\n3. If retries are exhausted, move to the next model.\n\nFallback **does not** trigger for:\n\n- Abort or bail errors.\n- Guardrail blocks.\n- Tool execution errors.\n\nStreaming behavior:\n\n- Retries and fallback happen only if the stream fails before the first output chunk.\n- If a stream fails after output starts, the error is surfaced to the caller and the stream is not restarted.\n\n## Middleware Retries\n\nMiddleware retries are separate from model retries and fallback. A middleware can call `abort(\"reason\", { retry: true })` to restart the full attempt.\n\n- Controlled by `maxMiddlewareRetries` (agent-level or per-call).\n- Each retry reruns the full pipeline: middleware, guardrails, hooks, model selection, model retries, and fallback.\n- In streaming calls, only input middlewares can trigger a retry before the stream starts.\n\nSee [Middleware](/docs/agents/middleware) for configuration and API details.\n\n## Real-World Scenarios\n\n### 1. Provider Outage During a Traffic Spike\n\nThe primary provider returns 5xx errors during a traffic spike. Configure a fallback provider with its own retry count.\n\n```ts\nconst agent = new Agent({\n  name: \"LaunchAssistant\",\n  instructions: \"Handle onboarding questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n    { model: \"anthropic/claude-3-5-sonnet\", maxRetries: 2 },\n  ],\n});\n```\n\nExpected behavior:\n\n- Primary errors are retried up to `maxRetries`.\n- After retries, the secondary provider is attempted.\n\n### 2. Rate Limits During Peak Hours\n\nThe primary model returns 429 during peak hours. Set `maxRetries: 0` on the primary and allow retries on the fallback.\n\n```ts\nconst agent = new Agent({\n  name: \"PeakTrafficAgent\",\n  instructions: \"Answer pricing questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 0 },\n    { model: \"groq/llama-3.3-70b-versatile\", maxRetries: 2 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The primary fails without retry delay.\n- The fallback is attempted and can retry if needed.\n\n### 3. Timeouts in One Region\n\nRequests in a region start timing out. Configure a regional primary with a global fallback.\n\n```ts\nconst agent = new Agent({\n  name: \"RegionalAgent\",\n  instructions: \"Support users in APAC.\",\n  model: [\n    { model: \"google/gemini-2.0-flash\", maxRetries: 1 },\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n  ],\n});\n```\n\nExpected behavior:\n\n- Timeouts on the primary move the call to the fallback after retries.\n- The fallback handles calls that the primary fails.\n\n### 4. Compliance or Data Residency Constraints\n\nSome users require a specific provider for data residency. Select the primary model dynamically and keep a fallback entry.\n\n```ts\nconst agent = new Agent({\n  name: \"ComplianceAgent\",\n  instructions: \"Handle regulated requests.\",\n  model: [\n    {\n      model: async ({ context }) => {\n        const region = (context.get(\"region\") as string) || \"us\";\n        return region === \"eu\" ? \"mistral/mistral-large-latest\" : \"openai/gpt-4o-mini\";\n      },\n      maxRetries: 1,\n    },\n    { model: \"anthropic/claude-3-5-sonnet\", maxRetries: 1 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The model selection uses `context` values on each call.\n- If the selected model fails, the fallback is attempted.\n\n### 5. Cost Control with Fallback\n\nUse a lower-cost primary model and a second model for error cases.\n\n```ts\nconst agent = new Agent({\n  name: \"CostAwareAgent\",\n  instructions: \"Handle receipts and billing questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n    { model: \"openai/gpt-4o\", maxRetries: 0 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The primary handles successful calls.\n- The fallback is only used when the primary errors.\n\n## Troubleshooting Tips\n\n- If fallbacks never trigger, check for guardrail blocks or tool errors.\n- If retries are skipped, check whether the provider marks the error as `isRetryable: false`.\n- If all configured models are disabled, VoltAgent throws `MODEL_LIST_EMPTY`.\n- For long streaming sessions, consider application-level retry on stream errors.\n- Keep the fallback list short to avoid long end-to-end latency when providers are degraded.\n","website/docs/agents/subagents.md":"---\ntitle: Sub-agents\nslug: /agents/sub-agents\n---\n\n# Sub-agents\n\nSub-agents are agents that work under a supervisor agent to handle specific tasks. This architecture allows you to create agent workflows where each sub-agent focuses on a specific domain, coordinated by a supervisor.\n\n## Why Use Sub-agents?\n\n- **Task delegation**: Assign specific tasks to agents configured for particular domains (e.g., coding, translation, data analysis)\n- **Workflow orchestration**: Build multi-step workflows by delegating tasks to appropriate agents\n- **Code organization**: Break down complex problems into smaller components\n- **Modularity**: Add or swap agents without disrupting the entire system\n\n## Creating and Using Sub-agents\n\n### Creating Individual Agents\n\nCreate the agents that will serve as sub-agents:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Create an agent for content creation\nconst contentCreatorAgent = new Agent({\n  name: \"ContentCreator\",\n  instructions: \"Creates short text content on requested topics\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Create an agent for formatting\nconst formatterAgent = new Agent({\n  name: \"Formatter\",\n  instructions: \"Formats and styles text content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Give sub-agents a concise purpose to control what the supervisor sees\nconst summarizerAgent = new Agent({\n  name: \"Summarizer\",\n  purpose: \"Summarize long support tickets\",\n  instructions:\n    \"Read the conversation and produce a concise summary highlighting blockers and owners\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n### Creating a Supervisor Agent\n\nPass the agents in the `subAgents` array during supervisor initialization:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst supervisorAgent = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinates between content creation and formatting agents\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [contentCreatorAgent, formatterAgent, summarizerAgent],\n});\n```\n\n:::tip Advanced Sub-agent Configuration\nBy default, sub-agents use the `streamText` method. You can specify different methods like `generateText`, `generateObject`, or `streamObject` with custom schemas and options.\n\nSee: [Advanced Configuration](#advanced-configuration)\n:::\n\n## Customizing Supervisor Behavior\n\nSupervisor agents use an automatically generated system message that includes guidelines for managing sub-agents. Customize this behavior using the `supervisorConfig` option.\n\n### Purpose vs. Instructions (what the supervisor sees)\n\n- The supervisor lists sub-agents in a `<specialized_agents>` block using the sub-agent `purpose` when provided; if `purpose` is missing, it falls back to `instructions`, and if both are missing it uses `\"Dynamic instructions\"`.\n- Set a short `purpose` to avoid leaking long/verbose instructions into the supervisor’s prompt and to keep the prompt small. The full `instructions` still run for the sub-agent itself when delegated.\n- If you leave `purpose` empty, expect the supervisor prompt to include the entire `instructions` string for that sub-agent.\n\n:::info Default System Message\nSee the [generateSupervisorSystemMessage implementation](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/subagent/index.ts#L131) on GitHub.\n:::\n\n:::note Type Safety\nThe `supervisorConfig` option is only available when `subAgents` are provided. TypeScript will prevent you from using `supervisorConfig` on agents without sub-agents.\n:::\n\n### Basic Supervisor Configuration\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst supervisorAgent = new Agent({\n  name: \"Content Supervisor\",\n  instructions: \"Coordinate content creation workflow\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    // Add custom guidelines to the default ones\n    customGuidelines: [\n      \"Always thank the user at the end\",\n      \"Keep responses concise and actionable\",\n      \"Prioritize user experience\",\n    ],\n\n    // Control whether prior sub-agent interactions are injected into supervisor prompt\n    includeAgentsMemory: true, // default: true\n  },\n});\n```\n\n### Stream Event Forwarding Configuration\n\nControl which events from sub-agents are forwarded to the parent stream. By default, only `tool-call` and `tool-result` events are forwarded.\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Content Supervisor\",\n  instructions: \"Coordinate content creation workflow\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    // Configure which sub-agent events to forward\n    fullStreamEventForwarding: {\n      // Default: ['tool-call', 'tool-result']\n      types: [\n        \"tool-call\",\n        \"tool-result\",\n        \"text-delta\",\n        \"reasoning-start\",\n        \"reasoning-delta\",\n        \"reasoning-end\",\n        \"source\",\n        \"error\",\n        \"finish\",\n      ],\n    },\n  },\n});\n```\n\n**Common Configurations:**\n\n```ts\n// Minimal - Only tool events (default)\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result'],\n}\n\n// Text + Tools - Include text generation\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result', 'text-delta'],\n}\n\n// Full visibility - All events including reasoning\nfullStreamEventForwarding: {\n  types: [\n    'tool-call',\n    'tool-result',\n    'text-delta',\n    'reasoning-start',\n    'reasoning-delta',\n    'reasoning-end',\n    'source',\n    'error',\n    'finish'\n  ],\n}\n\n// Clean tool names - No agent prefix (add prefix manually when consuming events if desired)\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result'],\n}\n```\n\nThis configuration balances stream performance and information detail for sub-agent interactions.\n\n### Error Handling Configuration\n\nControl how the supervisor handles sub-agent failures.\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate between agents\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [dataProcessor, analyzer],\n\n  supervisorConfig: {\n    // Control whether stream errors throw exceptions\n    throwOnStreamError: false, // default: false\n\n    // Control whether error messages appear in empty responses\n    includeErrorInEmptyResponse: true, // default: true\n  },\n});\n```\n\n#### Configuration Options\n\n**`throwOnStreamError`** (boolean, default: `false`)\n\n- When `false`: Stream errors are caught and returned as error results with `status: \"error\"`\n- When `true`: Stream errors throw exceptions that must be caught with try/catch\n- Set to `true` to handle errors at a higher level or trigger retry logic\n\n**`includeErrorInEmptyResponse`** (boolean, default: `true`)\n\n- When `true`: Error messages are included in the response when no content was generated\n- When `false`: Returns empty string in result, but still marks status as \"error\"\n- Set to `false` to handle error messaging yourself\n\n#### Common Error Handling Patterns\n\n**Default - Graceful Error Handling:**\n\n```ts\n// Errors are returned as results with helpful messages\nsupervisorConfig: {\n  throwOnStreamError: false,\n  includeErrorInEmptyResponse: true,\n}\n\n// Usage:\nconst result = await supervisor.streamText(\"Process data\");\n// If sub-agent fails:\n// result contains error message like \"Error in DataProcessor: Stream failed\"\n```\n\n:::info Retry Behavior\nVoltAgent retries model calls based on `maxRetries`. `throwOnStreamError` controls how stream errors are surfaced; it does not change retry or fallback behavior.\n:::\n\n**Silent Errors - Custom Messaging:**\n\n```ts\n// Errors don't include automatic messages\nsupervisorConfig: {\n  includeErrorInEmptyResponse: false,\n}\n\n// Usage with custom error handling:\nconst result = await supervisor.streamText(\"Process data\");\nfor await (const event of result.fullStream) {\n  if (event.type === \"error\") {\n    // Provide custom user-friendly error message\n    console.log(\"We're having trouble processing your request. Please try again.\");\n  }\n}\n```\n\n**Production Setup - Error Tracking:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: false, // Don't crash the app\n  includeErrorInEmptyResponse: true, // Help with debugging\n\n  // Capture error events for monitoring\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result', 'error'],\n  },\n}\n```\n\n#### Error Handling in Practice\n\nThe supervisor's behavior when a sub-agent encounters an error depends on your configuration:\n\n```ts\n// Example: Sub-agent fails during stream\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [unreliableAgent],\n  supervisorConfig: {\n    throwOnStreamError: false,\n    includeErrorInEmptyResponse: true,\n  },\n});\n\n// The supervisor handles the failure\nconst response = await supervisor.streamText(\"Do something risky\");\n\n// Check the response\nif (response.status === \"error\") {\n  console.log(\"Sub-agent failed:\", response.error);\n  // response.result contains: \"Error in UnreliableAgent: [error details]\"\n} else {\n  // Process successful response\n}\n```\n\n#### Using with fullStream\n\nWhen using `fullStream`, the configuration controls what you receive from sub-agents. VoltAgent\nforwards metadata onto each forwarded chunk so you can attribute every event:\n\n- `subAgentId` / `subAgentName`: the sub-agent that produced the chunk\n- `executingAgentId` / `executingAgentName`: same as above (reserved for nested handoffs)\n- `parentAgentId` / `parentAgentName`: the supervisor that forwarded the chunk\n- `agentPath`: ordered names from supervisor → executing agent\n\nThis metadata is only on the streamed events (fullStream / UI streams); it is **not** sent back to\nthe model provider or injected into the LLM messages.\n\n```ts\n// Stream with full event details\nconst result = await supervisorAgent.streamText(\"Create and edit content\");\n\n// Process different event types\nfor await (const event of result.fullStream) {\n  if (event.type === \"tool-call\") {\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Tool called: ${event.toolName}`);\n    }\n  } else if (event.type === \"tool-result\") {\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Tool result:`, event.output);\n    }\n  } else if (event.type === \"text-delta\") {\n    // Only appears if included in types array\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Text: ${event.text ?? event.delta ?? \"\"}`);\n    }\n  }\n}\n```\n\n#### Filtering Sub-agent Events\n\nIdentify which events come from sub-agents by checking for `subAgentId` and `subAgentName` properties:\n\n```ts\nconst result = await supervisorAgent.streamText(\"Create and edit content\");\n\nfor await (const event of result.fullStream) {\n  if (event.subAgentId && event.subAgentName) {\n    console.log(\n      `Event from sub-agent ${event.subAgentName} (path: ${event.agentPath?.join(\" > \")})`\n    );\n    console.log(`  Type: ${event.type}`);\n    console.log(`  Payload:`, {\n      toolName: event.toolName,\n      output: event.output,\n      text: (event as { text?: string }).text,\n    });\n    continue;\n  }\n\n  // This is from the supervisor agent itself\n  console.log(`Supervisor event: ${event.type}`);\n}\n```\n\nThis allows you to:\n\n- Distinguish between supervisor and sub-agent events\n- Filter events by specific sub-agent\n- Apply different handling logic based on the event source\n\n#### Filtering Historical Conversations\n\nSub-agent metadata is also persisted in memory, so you can apply the same filtering logic when you replay a conversation later:\n\n```ts\nconst messages = await memory.getMessages(userId, conversationId);\n\nconst writerMessages = messages.filter(\n  (message) => message.metadata?.subAgentName === \"WriterAgent\"\n);\n\nfor (const message of writerMessages) {\n  console.log(`[${message.metadata?.subAgentName}]`, message.parts);\n}\n```\n\nEvery `UIMessage` in memory now includes the `metadata.subAgentId` and `metadata.subAgentName` fields, making it easy to render supervisor history the same way you render live streams.\n\n### Complete System Message Override\n\nProvide a custom `systemMessage` to replace the default template:\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Custom Supervisor\",\n  instructions: \"This will be ignored when systemMessage is provided\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    systemMessage: `\nYou are a content manager named \"ContentBot\".\n\nYour team:\n- Writer: Creates original content\n- Editor: Reviews and improves content\n\nYour workflow:\n1. Analyze user requests\n2. Use delegate_task to assign work to appropriate specialists\n3. Coordinate between specialists as needed\n4. Provide final responses\n5. Maintain a professional tone\n\nRemember: Use the delegate_task tool to assign tasks to your specialists.\n    `.trim(),\n\n    // Control memory inclusion even with custom system message\n    includeAgentsMemory: true,\n  },\n});\n```\n\n### Quick Usage\n\n**Add custom rules:**\n\n```ts\nsupervisorConfig: {\n  customGuidelines: [\"Verify sources\", \"Include confidence levels\"];\n}\n```\n\n**Override entire system message:**\n\n```ts\nsupervisorConfig: {\n  systemMessage: \"You are TaskBot. Use delegate_task(task, [agentNames]) to assign work.\";\n}\n```\n\n**Control memory:**\n\n```ts\nsupervisorConfig: {\n  includeAgentsMemory: false; // Exclude prior sub-agent interactions from supervisor prompt (default: true)\n}\n```\n\n:::note `includeAgentsMemory` vs `memory: false`\n`includeAgentsMemory` only controls prompt construction for the supervisor. It does not disable conversation memory inside each sub-agent. If you need a stateless sub-agent, set `memory: false` on that sub-agent.\n:::\n\n**Stateless sub-agent:**\n\n```ts\nconst writerAgent = new Agent({\n  name: \"Writer\",\n  instructions: \"Write concise drafts.\",\n  model: \"openai/gpt-4o-mini\",\n  memory: false,\n});\n```\n\n**Configure event forwarding:**\n\n```ts\nsupervisorConfig: {\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result', 'text-delta'], // Control which events to forward\n  }\n}\n```\n\n**Handle errors gracefully:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: false, // Return errors as results (default)\n  includeErrorInEmptyResponse: true // Include error details in response (default)\n}\n```\n\n**Throw exceptions for custom error handling:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: true; // Throw exceptions on sub-agent failures\n}\n```\n\n## How Sub-agents Work\n\nThe supervisor agent delegates tasks to its sub-agents using the automatically provided `delegate_task` tool.\n\n1. A user sends a request to the supervisor agent.\n2. The supervisor's LLM analyzes the request and its system prompt (which lists available sub-agents).\n3. Based on the task, the supervisor decides which sub-agent(s) to use.\n4. The supervisor uses the `delegate_task` tool to hand off the task(s).\n\n### The `delegate_task` Tool\n\nThis tool is automatically added to supervisor agents and handles delegation.\n\n- **Name**: `delegate_task`\n- **Description**: \"Delegate a task to one or more specialized agents\"\n- **Parameters**:\n  - `task` (string, required): The task description to be delegated\n  - `targetAgents` (array of strings, required): Sub-agent names to delegate the task to. The supervisor can delegate to multiple agents simultaneously\n  - `context` (object, optional): Additional context needed by the sub-agent(s)\n- **Execution**:\n  - Finds the sub-agent instances based on the provided names\n  - Calls the `handoffTask` (or `handoffToMultiple`) method internally\n  - Tags delegated sub-agent messages with metadata so supervisor memory reads can exclude sub-agent records\n  - Passes the supervisor's agent ID (`parentAgentId`) and history entry ID (`parentHistoryEntryId`) for observability\n- **Returns**:\n  - **Always returns an array** of result objects (even for single agent):\n    ```ts\n    [\n      {\n        agentName: string; // Name of the sub-agent that executed the task\n        response: string; // The text result returned by the sub-agent\n        usage?: UsageInfo; // Token usage information\n        bailed?: boolean; // True if onHandoffComplete called bail()\n      },\n      // ... more results if multiple agents were targeted\n    ]\n    ```\n\n  When `bailed: true`, the supervisor's execution is terminated immediately and the subagent's response is returned to the user. See [Early Termination (Bail)](#early-termination-bail) for details.\n\n5. Sub-agents process their delegated tasks independently. They can use their own tools or delegate further if they are also supervisors.\n6. Each sub-agent returns its result to the `delegate_task` tool execution context.\n7. The supervisor receives the results from the `delegate_task` tool.\n8. The supervisor synthesizes the final response based on its instructions and the received results.\n\n## Complete Example\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Create agents\nconst writer = new Agent({\n  name: \"Writer\",\n  instructions: \"Write creative stories\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst translator = new Agent({\n  name: \"Translator\",\n  instructions: \"Translate text accurately\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Create supervisor\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate story writing and translation\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writer, translator],\n});\n\n// Use it\nconst result = await supervisor.streamText(\"Write a story about AI and translate to Spanish\");\n\nfor await (const chunk of result.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n**What happens:**\n\n1. Supervisor analyzes request\n2. Calls `delegate_task` → Writer creates story\n3. Calls `delegate_task` → Translator translates\n4. Combines results and responds\n\n## Using Hooks\n\nVoltAgent provides hooks to monitor and control the supervisor/subagent workflow:\n\n### `onHandoff` Hook\n\nTriggered when delegation begins:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [writer, translator],\n  hooks: {\n    onHandoff: ({ agent, sourceAgent }) => {\n      console.log(`${sourceAgent.name} → ${agent.name}`);\n    },\n  },\n});\n```\n\n### `onHandoffComplete` Hook\n\nTriggered when a subagent completes execution. This hook enables **early termination (bail)** to optimize token usage:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [dataAnalyzer, reportGenerator],\n  hooks: {\n    onHandoffComplete: async ({ agent, sourceAgent, result, messages, usage, context, bail }) => {\n      // Bail if subagent produced final output\n      if (agent.name === \"Report Generator\") {\n        context.logger?.info(\"Final report ready, bailing\");\n        bail(); // Skip supervisor processing\n      }\n    },\n  },\n});\n```\n\nSee [Early Termination (Bail)](#early-termination-bail) below for detailed usage.\n\n## Context Sharing\n\nSub-agents automatically inherit the supervisor's context:\n\n```ts\n// Supervisor passes context\nconst response = await supervisor.streamText(\"Task\", {\n  context: new Map([[\"projectId\", \"123\"]]),\n});\n\n// Sub-agent receives it automatically\nconst subAgent = new Agent({\n  hooks: {\n    onStart: (context) => {\n      const projectId = context.context.get(\"projectId\"); // \"123\"\n    },\n  },\n});\n```\n\n## Early Termination (Bail)\n\n### The Problem\n\nIn supervisor/subagent workflows, subagents **always** return to the supervisor for processing, even when they generate final outputs (like JSON structures or reports) that need no additional handling. This wastes tokens:\n\n```\nCurrent flow:\nSupervisor → SubAgent (generates 2K token JSON) → Supervisor (processes JSON) → User\n                                                    ↑ Wastes ~2K tokens\n```\n\n**Example impact:**\n\n- Without bail: ~2,650 tokens per request\n- With bail: ~560 tokens per request\n- **Savings: 79%** (~$0.020 per request)\n\n### The Solution\n\nThe `onHandoffComplete` hook allows supervisors to intercept subagent results and **bail** (skip supervisor processing) when the subagent produces final output:\n\n```\nNew flow:\nSupervisor → SubAgent → bail() → User ✅\n```\n\n### Basic Usage\n\nCall `bail()` in the `onHandoffComplete` hook to terminate early:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Workout Supervisor\",\n  subAgents: [exerciseAgent, workoutBuilder],\n  hooks: {\n    onHandoffComplete: async ({ agent, result, bail, context }) => {\n      // Workout Builder produces final JSON - no processing needed\n      if (agent.name === \"Workout Builder\") {\n        context.logger?.info(\"Final output received, bailing\");\n        bail(); // Skip supervisor, return directly to user\n      }\n      // Default: continue to supervisor for processing\n    },\n  },\n});\n```\n\n### Conditional Bail Logic\n\nBail based on agent name, result size, or content:\n\n```ts\nhooks: {\n  onHandoffComplete: async ({ agent, result, bail, context }) => {\n    // By agent name\n    if (agent.name === \"Report Generator\") {\n      bail();\n      return;\n    }\n\n    // By result size (save tokens)\n    if (result.length > 2000) {\n      context.logger?.warn(\"Large result, bailing to save tokens\");\n      bail();\n      return;\n    }\n\n    // By result content\n    if (result.includes(\"FINAL_OUTPUT\")) {\n      bail();\n      return;\n    }\n\n    // Default: continue to supervisor\n  },\n}\n```\n\n### Transform Before Bail\n\nOptionally transform the result before bailing:\n\n```ts\nhooks: {\n  onHandoffComplete: async ({ agent, result, bail }) => {\n    if (agent.name === \"Report Generator\") {\n      // Add metadata before returning\n      const transformed = `# Final Report\\n\\n${result}\\n\\n---\\nGenerated at: ${new Date().toISOString()}`;\n      bail(transformed); // Bail with transformed result\n    }\n  },\n}\n```\n\n### Hook Parameters\n\n```ts\ninterface OnHandoffCompleteHookArgs {\n  agent: Agent; // Target agent (subagent)\n  sourceAgent: Agent; // Source agent (supervisor)\n  result: string; // Subagent's output\n  messages: UIMessage[]; // Full conversation messages\n  usage?: UsageInfo; // Token usage info\n  context: OperationContext; // Operation context\n  bail: (transformedResult?: string) => void; // Call to bail\n}\n```\n\n### Accessing Bailed Results\n\nWhen a subagent bails, the **subagent's result** is returned to the user (not the supervisor's):\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [\n    createSubagent({\n      agent: workoutBuilder,\n      method: \"generateObject\",\n      schema: WorkoutSchema,\n    }),\n  ],\n  hooks: {\n    onHandoffComplete: async ({ agent, bail }) => {\n      if (agent.name === \"Workout Builder\") {\n        bail(); // Return workout JSON directly\n      }\n    },\n  },\n});\n\nconst result = await supervisor.generateText(\"Create workout\");\nconsole.log(result.text); // Contains workout JSON, not supervisor's processing\n```\n\n### Supported Methods\n\nBail works with methods that support tools:\n\n- ✅ `generateText` - Aborts execution, returns bailed result\n- ✅ `streamText` - Aborts stream immediately, returns bailed result\n- ❌ `generateObject` - No tool support, bail not applicable\n- ❌ `streamObject` - No tool support, bail not applicable\n\n### Stream Event Visibility with Bail\n\nWhen using bail with `toUIMessageStream()` or consuming `fullStream` events, you need to configure which events are forwarded from subagents.\n\n**Default Behavior:**\n\nBy default, only `tool-call` and `tool-result` events are forwarded from subagents. This means **subagent text chunks are NOT visible** in the stream:\n\n```ts\n// Default configuration (implicit)\nsupervisorConfig: {\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result'], // ⚠️ text-delta NOT included\n  }\n}\n```\n\n**To See Subagent Text Output:**\n\nWhen a subagent bails and produces text output, you must explicitly include `text-delta` in the forwarded event types:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [workoutBuilder],\n  supervisorConfig: {\n    fullStreamEventForwarding: {\n      types: [\"tool-call\", \"tool-result\", \"text-delta\"], // ✅ Include text-delta\n    },\n  },\n  hooks: {\n    onHandoffComplete: ({ agent, bail }) => {\n      if (agent.name === \"Workout Builder\") {\n        bail(); // Subagent text will be visible in stream\n      }\n    },\n  },\n});\n```\n\n**Consuming Bailed Subagent Output:**\n\nWhen using `toUIMessageStream()`, subagent text appears as `data-subagent-stream` events that are automatically grouped and rendered in the UI:\n\n```ts\nconst result = await supervisor.streamText(\"Create workout\");\n\nfor await (const message of result.toUIMessageStream()) {\n  // Message parts include grouped subagent output; each subagent chunk carries:\n  // subAgentId, subAgentName, executingAgent*, parentAgent*, agentPath\n  // These parts are emitted as `data-subagent-stream` entries.\n}\n```\n\n**Event Types Reference:**\n\n| Event Type                                              | Description                              | Default         |\n| ------------------------------------------------------- | ---------------------------------------- | --------------- |\n| `tool-call`                                             | Tool invocations                         | ✅ Included     |\n| `tool-result`                                           | Tool results                             | ✅ Included     |\n| `text-delta`                                            | Text chunk generation                    | ❌ NOT included |\n| `reasoning-start` / `reasoning-delta` / `reasoning-end` | Model reasoning lifecycle (if available) | ❌ NOT included |\n| `source`                                                | Retrieved sources (if available)         | ❌ NOT included |\n| `error`                                                 | Error events                             | ❌ NOT included |\n| `finish`                                                | Stream completion                        | ❌ NOT included |\n\n### Observability\n\nBailed subagents are tracked in observability with visual indicators:\n\n**Logging:**\n\n```\n[INFO] Supervisor bailed after handoff\n  supervisor: Workout Supervisor\n  subagent: Workout Builder\n  transformed: false\n  resultLength: 450\n```\n\n**OpenTelemetry Attributes:**\n\nBoth supervisor and subagent spans get attributes:\n\n```ts\n// Supervisor span attributes\n{\n  \"bailed\": true,\n  \"bail.subagent\": \"Workout Builder\",\n  \"bail.transformed\": false\n}\n\n// Subagent span attributes\n{\n  \"bailed\": true,\n  \"bail.supervisor\": \"Workout Supervisor\",\n  \"bail.transformed\": false\n}\n```\n\n### Use Cases\n\nPerfect for scenarios where specialized subagents generate final outputs:\n\n1. **JSON/Structured data generators** - Workout builders, report generators, data exporters\n2. **Large content producers** - Document creators, extensive data analysis\n3. **Token optimization** - Skip processing for expensive results\n4. **Business logic** - Conditional routing based on result characteristics\n\n### Best Practices\n\n**✅ DO:**\n\n- Bail when subagent produces final, ready-to-use output\n- Use conditional logic to bail selectively\n- Log bail decisions for debugging\n- Transform results before bailing when needed\n\n**❌ DON'T:**\n\n- Bail on every subagent (defeats supervisor purpose)\n- Bail when supervisor needs to process/combine results\n- Forget to handle non-bailed flow (default case)\n\n````\n\n## Step Control\n\nControl workflow steps with `maxSteps`:\n\n```ts\nconst supervisor = new Agent({\n  subAgents: [writer, editor],\n  maxSteps: 20, // Inherited by all sub-agents\n});\n\n// Override per request\nconst result = await supervisor.generateText(\"Task\", { maxSteps: 10 });\n````\n\n**Default:** `10 × number_of_sub-agents` (prevents infinite loops)\n\n## Observability\n\nSub-agent operations are automatically linked to their supervisor for traceability in monitoring tools.\n\n## Advanced Configuration\n\nUse different execution methods for sub-agents:\n\n```ts\nimport { createSubagent } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst AnalysisSchema = z.object({\n  insights: z.array(z.string()),\n  confidence: z.number().min(0).max(1),\n});\n\nconst supervisor = new Agent({\n  subAgents: [\n    writer, // Default streamText\n    createSubagent({\n      agent: analyzer,\n      method: \"generateObject\",\n      schema: AnalysisSchema,\n      options: { temperature: 0.1 },\n    }),\n  ],\n});\n```\n\n**Available methods:**\n\n- `streamText` (default) - Real-time text streaming\n- `generateText` - Text generation\n- `generateObject` - Structured data with Zod schema\n- `streamObject` - Streaming structured data\n\n## Dynamic Sub-agents\n\nAdd sub-agents after initialization:\n\n```ts\nsupervisor.addSubAgent(newAgent);\n```\n\n## Remove Sub-agents\n\n```ts\nsupervisor.removeSubAgent(agentId);\n```\n\n## Troubleshooting\n\n**Sub-agent not being called?**\n\n- Check agent names match exactly\n- Make supervisor instructions explicit about when to delegate\n- Use `onHandoff` hook to debug delegation flow\n","website/docs/agents/summarization.md":"---\ntitle: Summarization\nslug: /agents/summarization\n---\n\n# Summarization\n\nSummarization shortens long conversations by inserting a system summary and keeping the last N non-system messages. It is configured per agent.\n\n## Configuration\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n  summarization: {\n    enabled: true,\n    triggerTokens: 120_000,\n    keepMessages: 6,\n    maxOutputTokens: 800,\n    systemPrompt: \"Summarize the conversation for the next step.\",\n    model: \"openai/gpt-4o-mini\",\n  },\n});\n```\n\nOptions:\n\n- `enabled`: Enable or disable summarization.\n- `triggerTokens`: Token estimate threshold. The estimate uses a simple character-to-token heuristic.\n- `keepMessages`: Number of most recent non-system messages kept after summarization.\n- `maxOutputTokens`: Token budget for the summary generation call.\n- `systemPrompt`: Prompt used for summary generation. If omitted, a default system prompt is used.\n- `model`: Optional model for summarization; defaults to the agent model.\n\n## How Summarization Runs\n\nOn each request, VoltAgent:\n\n1. Removes any previous summary system message (identified by `<agent_summary>` markers).\n2. Calculates a token estimate for non-system messages.\n3. If `nonSystemMessages.length > keepMessages` and the token estimate meets `triggerTokens`, it generates a summary.\n4. Stores the summary state and injects a system message that contains the summary.\n5. Returns `[systemMessages, summaryMessage, tailMessages]` to the model.\n\nIf summary generation fails, the original sanitized messages are used.\n\nThe injected summary system message looks like:\n\n```text\n<agent_summary>\n...summary text...\n</agent_summary>\n```\n\n## Storage Behavior\n\nSummary state is stored under the conversation metadata key `agent` when memory is enabled and `conversationId` is present. The state includes:\n\n- `summary`\n- `summaryMessageCount`\n- `summaryUpdatedAt`\n\nIf memory is disabled or `conversationId` is missing, summary state is kept in process memory and resets on restart.\n\nIf you manage conversation metadata, reserve the `metadata.agent` key for VoltAgent summarization state.\n\n## When To Use It\n\nUse summarization when conversation history grows past model limits or when token usage needs to be capped. Avoid it when you require full message fidelity for audits or deterministic replay.\n\n## Notes for Framework Integrations\n\n- Pass `conversationId` consistently to keep summaries across requests.\n- If you render messages in a UI, filter summary system messages by the `<agent_summary>` marker.\n- If you hook into `onPrepareMessages`, that hook runs after summarization and receives the summary-injected message list.\n","website/docs/agents/tools.md":"---\ntitle: Tools\nslug: /agents/tools\n---\n\n# Tools\n\nTools enable agents to interact with external systems, APIs, databases, and perform specific actions beyond generating text. An agent uses its model to decide when to call tools based on their descriptions and the current context.\n\n## Creating a Tool\n\nUse `createTool` to define a tool with type-safe parameters and execution logic.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather for a location\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  execute: async ({ location }) => {\n    // Call weather API\n    const response = await fetch(`https://api.weather.com/current?city=${location}`);\n    const data = await response.json();\n\n    return {\n      location,\n      temperature: data.temp,\n      conditions: data.conditions,\n    };\n  },\n});\n```\n\nEach tool has:\n\n- **name**: Unique identifier\n- **description**: Explains what the tool does (the model uses this to decide when to call it)\n- **parameters**: Input schema defined with Zod\n- **execute**: Function that runs when the tool is called\n- **providerOptions** (optional): Provider-specific options for advanced features\n- **tags** (optional): Optional user-defined tags for organizing or labeling tools.\n\nThe `execute` function's parameter types are automatically inferred from the Zod schema, providing full IntelliSense support.\n\n## Tool Hooks\n\nYou can attach tool-specific hooks to observe or post-process a tool result. Tool hooks run before agent-level hooks, and agent `onToolEnd` can still override the output afterward.\n\n**Hook parameters:**\n\n- `onStart`: `{ tool, args, options }`\n- `onEnd`: `{ tool, args, output, error, options }` (return `{ output }` to override the result)\n\n> Overrides are re-validated if the tool has an `outputSchema`. For streaming tools (AsyncIterable), overrides apply only to the final output.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst summarizeTool = createTool({\n  name: \"summarize_text\",\n  description: \"Summarize text with a hard cap\",\n  parameters: z.object({ text: z.string() }),\n  execute: async ({ text }) => text,\n  hooks: {\n    onStart: ({ tool }) => {\n      console.log(`[tool] ${tool.name} starting`);\n    },\n    onEnd: ({ output }) => {\n      if (typeof output === \"string\") {\n        return { output: output.slice(0, 500) };\n      }\n    },\n  },\n});\n```\n\n## Streaming Tool Results (Preliminary)\n\nIf your tool can provide progress or intermediate status, return an `AsyncIterable` from `execute`.\nEach `yield` is emitted as a preliminary tool result, and the **last yielded value** is treated as the final result.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherOutputSchema = z.discriminatedUnion(\"status\", [\n  z.object({\n    status: z.literal(\"loading\"),\n    text: z.string(),\n    weather: z.undefined().optional(),\n  }),\n  z.object({\n    status: z.literal(\"success\"),\n    text: z.string(),\n    weather: z.object({\n      location: z.string(),\n      temperature: z.number(),\n    }),\n  }),\n]);\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a location\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  outputSchema: weatherOutputSchema,\n  async *execute({ location }) {\n    yield {\n      status: \"loading\" as const,\n      text: `Getting weather for ${location}`,\n      weather: undefined,\n    };\n\n    await new Promise((resolve) => setTimeout(resolve, 3000));\n\n    const temperature = 72 + Math.floor(Math.random() * 21) - 10;\n\n    yield {\n      status: \"success\" as const,\n      text: `The weather in ${location} is ${temperature}F`,\n      weather: { location, temperature },\n    };\n  },\n});\n```\n\n### Tool Tags\n\nTags are optional string labels that help organize and categorize tools.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Example: Database tools with tags\nconst queryTool = createTool({\n  name: \"query_database\",\n  description: \"Execute a read-only SQL query\",\n  tags: [\"database\", \"read-only\", \"sql\"],\n  parameters: z.object({\n    query: z.string().describe(\"SQL query to execute\"),\n  }),\n  execute: async ({ query }) => {\n    // Execute query\n    return { results: [] };\n  },\n});\n\nconst updateTool = createTool({\n  name: \"update_record\",\n  description: \"Update database records\",\n  tags: [\"database\", \"write\", \"sql\", \"destructive\"],\n  parameters: z.object({\n    table: z.string().describe(\"Table name\"),\n    id: z.string().describe(\"Record ID\"),\n    data: z.record(z.unknown()).describe(\"Data to update\"),\n  }),\n  execute: async ({ table, id, data }) => {\n    // Update record\n    return { success: true };\n  },\n});\n\n// Example: External API tools\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather data\",\n  tags: [\"weather\", \"external-api\", \"read-only\"],\n  parameters: z.object({\n    location: z.string().describe(\"City name\"),\n  }),\n  execute: async ({ location }) => {\n    // Fetch weather\n    return { temperature: 72, conditions: \"sunny\" };\n  },\n});\n```\n\nTags are included in OpenTelemetry spans and visible in the VoltAgent observability dashboard, making it easy to analyze tool usage patterns and debug agent behavior.\n\nTool tags also flow through the REST tool endpoints (`GET /tools` and `POST /tools/:name/execute` responses), so you can build frontend tooling or access control based on tags.\n\n### Provider-Specific Options\n\nYou can pass provider-specific options to enable advanced features like caching. Currently supported providers include Anthropic, OpenAI, Google, and others.\n\n#### Anthropic Cache Control\n\nAnthropic's prompt caching can reduce costs and latency for repeated tool calls:\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst cityAttractionsTool = createTool({\n  name: \"get_city_attractions\",\n  description: \"Get tourist attractions for a city\",\n  parameters: z.object({\n    city: z.string().describe(\"The city name\"),\n  }),\n  providerOptions: {\n    anthropic: {\n      cacheControl: { type: \"ephemeral\" },\n    },\n  },\n  execute: async ({ city }) => {\n    return await fetchAttractions(city);\n  },\n});\n```\n\nThe `cacheControl` option tells Anthropic to cache the tool definition, improving performance for subsequent calls. Learn more about [Anthropic's cache control](https://ai-sdk.dev/providers/ai-sdk-providers/anthropic#cache-control).\n\n## Using Tools with Agents\n\nAdd tools when creating an agent. The model decides when to use them based on the user's input and tool descriptions.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Weather Assistant\",\n  instructions: \"An assistant that provides weather information\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n});\n\n// The model calls the tool when appropriate\nconst response = await agent.generateText(\"What's the weather in Paris?\");\nconsole.log(response.text);\n// \"The current weather in Paris is 22°C and sunny.\"\n```\n\n### Using Multiple Tools\n\nAgents can use multiple tools together to answer complex queries.\n\n```ts\nconst calculatorTool = createTool({\n  name: \"calculate\",\n  description: \"Perform mathematical calculations\",\n  parameters: z.object({\n    expression: z.string().describe(\"The mathematical expression to evaluate\"),\n  }),\n  execute: async ({ expression }) => {\n    // Use a safe math parser in production\n    const result = eval(expression);\n    return { result };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Multi-Tool Assistant\",\n  instructions: \"An assistant that can check weather and perform calculations\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool, calculatorTool],\n});\n\nconst response = await agent.generateText(\"What's the weather in Paris? Also, what is 24 * 7?\");\n// The model uses both tools and combines the results\n```\n\n### Dynamic Tool Registration\n\nAdd tools to an agent after creation or provide them per request.\n\n```ts\n// Add tools after agent creation\nagent.addTools([calculatorTool]);\n\n// Provide tools for a specific request only\nconst response = await agent.generateText(\"Calculate 123 * 456\", {\n  tools: [calculatorTool],\n});\n```\n\n## Accessing Operation Context\n\nThe `execute` function receives a `ToolExecuteOptions` object as its second parameter, which extends `Partial<OperationContext>` and provides access to all operation metadata and control mechanisms.\n\n```ts\nconst debugTool = createTool({\n  name: \"log_debug_info\",\n  description: \"Logs debugging information\",\n  parameters: z.object({\n    message: z.string().describe(\"Debug message to log\"),\n  }),\n  execute: async (args, options) => {\n    // Access tool context (tool-specific metadata)\n    const { name, callId, messages } = options?.toolContext || {};\n    console.log(\"Tool name:\", name);\n    console.log(\"Tool call ID:\", callId);\n    console.log(\"Message history length:\", messages?.length);\n\n    // Access operation metadata directly from options\n    console.log(\"Operation ID:\", options?.operationId);\n    console.log(\"User ID:\", options?.userId);\n    console.log(\"Conversation ID:\", options?.conversationId);\n\n    // Access the original input\n    console.log(\"Original input:\", options?.input);\n\n    // Access user-defined context Map\n    const customValue = options?.context?.get(\"customKey\");\n\n    // Use the operation-scoped logger with tool name\n    options?.logger?.info(`${name}: Logging ${args.message}`);\n\n    // Check if operation is still active\n    if (!options?.isActive) {\n      throw new Error(\"Operation has been cancelled\");\n    }\n\n    return `Logged: ${args.message}`;\n  },\n});\n```\n\nThe `options` parameter includes all `OperationContext` fields plus an **optional** `toolContext` object:\n\n**Tool execution context (`toolContext?` - optional):**\n\n> **Note:** `toolContext` is always populated when your tool is called from a VoltAgent agent. It may be `undefined` when called from external systems (e.g., MCP servers). Always use optional chaining: `options?.toolContext?.name`.\n\n- `toolContext.name`: Name of the tool being executed\n- `toolContext.callId`: Unique identifier for this specific tool call (from AI SDK)\n- `toolContext.messages`: Message history at the time of tool call (from AI SDK)\n- `toolContext.abortSignal`: Abort signal for detecting cancellation (from AI SDK)\n\n**From OperationContext:**\n\n- `operationId`: Unique identifier for this operation\n- `userId`: Optional user identifier\n- `conversationId`: Optional conversation identifier\n- `workspace`: Workspace instance configured on the agent (if available)\n- `context`: Map for user-provided context values\n- `systemContext`: Map for internal system values\n- `isActive`: Whether the operation is still active\n- `input`: The original input (string, UIMessage[], or BaseMessage[])\n- `abortController`: AbortController for cancelling the operation\n- `logger`: Execution-scoped logger with full context\n- `traceContext`: OpenTelemetry trace context\n- `elicitation`: Optional function for requesting user input\n\n> - Since `ToolExecuteOptions` extends `Partial<OperationContext>`, you can name the second parameter anything you like (`options`, `context`, `ctx`, etc.)\n\nExample: reading workspace content inside a custom tool call:\n\n```ts\nconst readWorkspaceData = createTool({\n  name: \"read_workspace_data\",\n  description: \"Read a workspace file and inspect sandbox output\",\n  parameters: z.object({ path: z.string() }),\n  execute: async ({ path }, options) => {\n    const workspace = options?.workspace;\n    if (!workspace) {\n      return \"No workspace configured\";\n    }\n\n    const content = await workspace.filesystem.read(path);\n    const sandboxResult = await workspace.sandbox?.execute({\n      command: \"cat\",\n      args: [path],\n      operationContext: options as any,\n    });\n\n    return `filesystem=${content}\\nsandbox=${sandboxResult?.stdout ?? \"\"}`;\n  },\n});\n```\n\n## Cancellation with AbortController\n\nTools can respond to cancellation signals and cancel the entire operation when needed.\n\n```ts\nconst searchTool = createTool({\n  name: \"search_web\",\n  description: \"Search the web for information\",\n  parameters: z.object({\n    query: z.string().describe(\"The search query\"),\n  }),\n  execute: async (args, options) => {\n    const abortController = options?.abortController;\n    const signal = abortController?.signal;\n\n    // Check if already aborted\n    if (signal?.aborted) {\n      throw new Error(\"Search was cancelled before it started\");\n    }\n\n    // Tool can trigger abort to cancel the entire operation\n    if (args.query.includes(\"forbidden\")) {\n      abortController?.abort(\"Forbidden query detected\");\n      throw new Error(\"Search query contains forbidden terms\");\n    }\n\n    try {\n      // Pass signal to fetch for cancellation support\n      const response = await fetch(`https://api.search.com?q=${args.query}`, { signal });\n\n      // Abort based on response\n      if (!response.ok && response.status === 429) {\n        abortController?.abort(\"Rate limit exceeded\");\n        throw new Error(\"API rate limit exceeded\");\n      }\n\n      return await response.json();\n    } catch (error) {\n      if (error.name === \"AbortError\") {\n        throw new Error(\"Search was cancelled during execution\");\n      }\n      throw error;\n    }\n  },\n});\n```\n\nWhen calling an agent with an abort signal:\n\n```ts\nimport { isAbortError } from \"@voltagent/core\";\n\nconst abortController = new AbortController();\n\n// Set timeout to abort after 30 seconds\nsetTimeout(() => abortController.abort(\"Operation timeout\"), 30000);\n\ntry {\n  const response = await agent.generateText(\"Search for the latest AI developments\", {\n    abortSignal: abortController.signal,\n  });\n  console.log(response.text);\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Operation was cancelled:\", error.message);\n  } else {\n    console.error(\"Error:\", error);\n  }\n}\n```\n\n## Tool Execution Approval (needsApproval)\n\nSome tools should not run automatically because they trigger sensitive actions (payments, file changes, command execution, etc). Set `needsApproval` to require explicit user approval before the tool executes.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst runCommandTool = createTool({\n  name: \"runCommand\",\n  description: \"Run a shell command\",\n  parameters: z.object({\n    command: z.string().describe(\"Command to execute\"),\n  }),\n  needsApproval: true,\n  execute: async ({ command }) => {\n    // Execute safely\n    return { ok: true, command };\n  },\n});\n```\n\n`needsApproval` can also be a function for dynamic policies:\n\n```ts\nconst processPaymentTool = createTool({\n  name: \"processPayment\",\n  description: \"Charge a customer\",\n  parameters: z.object({\n    amount: z.number(),\n    customerId: z.string(),\n  }),\n  needsApproval: async ({ amount }) => amount > 1000,\n  execute: async ({ amount, customerId }) => {\n    return { success: true, amount, customerId };\n  },\n});\n```\n\n### How the Approval Flow Works\n\n1. The model calls a tool with `needsApproval`.\n2. VoltAgent returns a tool part with `state: \"approval-requested\"` instead of executing.\n3. Your UI asks the user to approve or deny.\n4. Send a tool approval response back to VoltAgent.\n5. If approved, the tool executes in the next step. If denied, the model sees the denial and can respond accordingly.\n\n### UI Implementation (useChat)\n\nVoltAgent's `/agents/:id/chat` stream is compatible with AI SDK `useChat`, so you can render the approval request and send a response from the UI.\n\n```tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses } from \"ai\";\n\nfunction Chat() {\n  const { messages, addToolApprovalResponse } = useChat({\n    transport: new DefaultChatTransport({ api: \"/agents/weather/chat\" }),\n    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,\n  });\n\n  return (\n    <div>\n      {messages.map((message) => (\n        <div key={message.id}>\n          {message.parts.map((part) => {\n            if (part.type === \"tool-runCommand\") {\n              if (part.state === \"approval-requested\") {\n                return (\n                  <div key={part.toolCallId}>\n                    <p>Approve command: {part.input.command}?</p>\n                    <button\n                      onClick={() =>\n                        addToolApprovalResponse({\n                          id: part.approval.id,\n                          approved: true,\n                        })\n                      }\n                    >\n                      Approve\n                    </button>\n                    <button\n                      onClick={() =>\n                        addToolApprovalResponse({\n                          id: part.approval.id,\n                          approved: false,\n                          reason: \"User denied\",\n                        })\n                      }\n                    >\n                      Deny\n                    </button>\n                  </div>\n                );\n              }\n            }\n            return null;\n          })}\n        </div>\n      ))}\n    </div>\n  );\n}\n```\n\nIf you do not use `sendAutomaticallyWhen`, call `sendMessage` manually after approval to continue the flow.\n\n## Client-Side Tools\n\nClient-side tools execute in the browser or client application instead of on the server. They're useful for accessing browser APIs, user permissions, or client-specific features.\n\nFor a complete working setup, see the example: [with-client-side-tools](https://github.com/VoltAgent/voltagent/tree/main/examples/with-client-side-tools).\n\n### What Makes a Tool Client-Side?\n\nA tool without an `execute` function is automatically client-side.\n\n```ts\n// Client-side tool (no execute function)\nconst getLocationTool = createTool({\n  name: \"getLocation\",\n  description: \"Get the user's current location\",\n  parameters: z.object({}),\n  // No execute = client-side\n});\n\n// Another client-side tool\nconst readClipboardTool = createTool({\n  name: \"readClipboard\",\n  description: \"Read content from the user's clipboard\",\n  parameters: z.object({}),\n});\n\n// Server-side tool (has execute)\nconst getWeatherTool = createTool({\n  name: \"getWeather\",\n  description: \"Get current weather for a city\",\n  parameters: z.object({\n    city: z.string().describe(\"City name\"),\n  }),\n  execute: async ({ city }) => {\n    const response = await fetch(`https://api.weather.com/current?city=${city}`);\n    return await response.json();\n  },\n});\n```\n\n### Handling Client-Side Tools\n\nUse the `onToolCall` callback with `useChat` to handle client-side tool execution.\n\n```tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport type { ClientSideToolResult } from \"@voltagent/core\";\nimport { useState, useEffect, useCallback } from \"react\";\n\nfunction Chat() {\n  const [result, setResult] = useState<ClientSideToolResult | null>(null);\n\n  const handleToolCall = useCallback(async ({ toolCall }) => {\n    // Handle automatic execution for getLocation\n    if (toolCall.toolName === \"getLocation\") {\n      navigator.geolocation.getCurrentPosition(\n        (position) => {\n          setResult({\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            output: {\n              latitude: position.coords.latitude,\n              longitude: position.coords.longitude,\n              accuracy: position.coords.accuracy,\n            },\n          });\n        },\n        (error) => {\n          setResult({\n            state: \"output-error\",\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            errorText: error.message,\n          });\n        }\n      );\n    }\n  }, []);\n\n  const { messages, sendMessage, addToolResult, status } = useChat({\n    transport: new DefaultChatTransport({ api: \"/api/chat\" }),\n    onToolCall: handleToolCall,\n  });\n\n  // Send results back to the model\n  useEffect(() => {\n    if (!result) return;\n    addToolResult(result);\n  }, [result, addToolResult]);\n\n  return (\n    <div>\n      {messages.map((message) => (\n        <div key={message.id}>{/* Render message */}</div>\n      ))}\n    </div>\n  );\n}\n```\n\n### Interactive Client-Side Tools\n\nFor tools requiring user interaction, render UI components:\n\n```tsx\nfunction ReadClipboardTool({\n  callId,\n  state,\n  addToolResult,\n}: {\n  callId: string;\n  state?: string;\n  addToolResult: (res: ClientSideToolResult) => void;\n}) {\n  if (state === \"input-streaming\" || state === \"input-available\") {\n    return (\n      <div>\n        <p>Allow access to your clipboard?</p>\n        <button\n          onClick={async () => {\n            try {\n              const text = await navigator.clipboard.readText();\n              addToolResult({\n                tool: \"readClipboard\",\n                toolCallId: callId,\n                output: { content: text },\n              });\n            } catch {\n              addToolResult({\n                state: \"output-error\",\n                tool: \"readClipboard\",\n                toolCallId: callId,\n                errorText: \"Clipboard access denied\",\n              });\n            }\n          }}\n        >\n          Yes\n        </button>\n        <button\n          onClick={() =>\n            addToolResult({\n              state: \"output-error\",\n              tool: \"readClipboard\",\n              toolCallId: callId,\n              errorText: \"Access denied\",\n            })\n          }\n        >\n          No\n        </button>\n      </div>\n    );\n  }\n  return <div>readClipboard: {state}</div>;\n}\n```\n\n**Important**: You must call `addToolResult` to send the tool result back to the model. Without this, the model considers the tool call a failure.\n\n## Agent Tool Hooks\n\nHooks let you respond to tool execution events for logging, UI updates, or additional actions. Use `onToolError` when you need to customize the error payload before it is returned to the model. For tool-level hooks (per tool), see the **Tool Hooks** section above.\n\n```ts\nimport { Agent, createHooks, isAbortError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ agent, tool, context, args }) {\n    console.log(`Tool starting: ${tool.name}`);\n    console.log(`Agent: ${agent.name}`);\n    console.log(`Operation ID: ${context.operationId}`);\n    console.log(`Arguments:`, args);\n  },\n\n  onToolEnd({ agent, tool, output, error, context }) {\n    console.log(`Tool completed: ${tool.name}`);\n\n    if (error) {\n      if (isAbortError(error)) {\n        console.log(`Tool was aborted: ${error.message}`);\n      } else {\n        console.error(`Tool failed: ${error.message}`);\n      }\n    } else {\n      console.log(`Result:`, output);\n    }\n  },\n\n  onToolError({ tool, originalError }) {\n    const maybeAxios = (originalError as any).isAxiosError === true;\n    if (!maybeAxios) {\n      return;\n    }\n\n    return {\n      output: {\n        error: true,\n        message: originalError.message,\n        code: (originalError as any).code,\n        status: (originalError as any).response?.status,\n      },\n    };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Assistant with Tool Hooks\",\n  instructions: \"An assistant that logs tool execution\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n  hooks: hooks,\n});\n```\n\n### Using Tags with Hooks\n\nTool tags can be accessed in hooks for implementing custom logic like access control or conditional behavior:\n\n```ts\nimport { createHooks, ToolDeniedError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ tool, context }) {\n    // Check if tool has destructive tag and user has permission\n    if (tool.tags?.includes(\"destructive\")) {\n      const userRole = context.context.get(\"userRole\");\n      if (userRole !== \"admin\") {\n        throw new ToolDeniedError({\n          toolName: tool.name,\n          message: \"Admin permission required for destructive operations\",\n          code: \"TOOL_FORBIDDEN\",\n        });\n      }\n    }\n\n    // Log tools by category\n    if (tool.tags?.includes(\"external-api\")) {\n      console.log(`Calling external API: ${tool.name}`);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Controlled Assistant\",\n  instructions: \"An assistant with tag-based access control\",\n  model: \"openai/gpt-4o\",\n  tools: [queryTool, updateTool, weatherTool],\n  hooks: hooks,\n});\n\n// Usage with context\nconst response = await agent.generateText(\"Update the user record\", {\n  context: { userRole: \"admin\" }, // Admin can use destructive tools\n});\n```\n\n### Policy Enforcement with ToolDeniedError\n\nThrow `ToolDeniedError` from a hook to block a tool and immediately stop the entire agent operation.\n\n```ts\nimport { createHooks, ToolDeniedError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ tool, context }) {\n    const plan = context.context.get(\"userPlan\");\n\n    // Block expensive tools for non-pro users\n    if (tool.name === \"search_web\" && plan !== \"pro\") {\n      throw new ToolDeniedError({\n        toolName: tool.name,\n        message: \"Pro plan required to use web search.\",\n        code: \"TOOL_PLAN_REQUIRED\",\n        httpStatus: 402,\n      });\n    }\n  },\n});\n```\n\nCatching the denial:\n\n```ts\nimport { isToolDeniedError } from \"@voltagent/core\";\n\ntry {\n  const res = await agent.generateText(\"Please search the web\", { hooks });\n} catch (err) {\n  if (isToolDeniedError(err)) {\n    console.log(\"Tool denied:\", {\n      tool: err.name,\n      status: err.httpStatus,\n      code: err.code,\n      message: err.message,\n    });\n    // Show upgrade UI, redirect, etc.\n  } else {\n    console.error(\"Operation failed:\", err);\n  }\n}\n```\n\nAllowed `code` values:\n\n- `TOOL_ERROR`\n- `TOOL_FORBIDDEN`\n- `TOOL_PLAN_REQUIRED`\n- `TOOL_QUOTA_EXCEEDED`\n- Custom codes (e.g., `\"TOOL_REGION_BLOCKED\"`)\n\n### Multi-modal Tool Results\n\nTools can return images and media content to the LLM using the `toModelOutput` function. This enables visual workflows where tools can provide screenshots, generated images, or other media for the LLM to analyze.\n\n**Supported Providers:** Anthropic, OpenAI\n\n#### Screenshot Tool Example\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\nimport fs from \"fs\";\n\nconst screenshotTool = createTool({\n  name: \"take_screenshot\",\n  description: \"Takes a screenshot of the screen\",\n  parameters: z.object({\n    region: z.string().optional().describe(\"Region to capture\"),\n  }),\n  execute: async ({ region }) => {\n    // Take screenshot and return base64\n    const imageData = fs.readFileSync(\"./screenshot.png\").toString(\"base64\");\n\n    return {\n      type: \"image\",\n      data: imageData,\n      timestamp: new Date().toISOString(),\n    };\n  },\n  // Convert output to multi-modal content for LLM\n  toModelOutput: ({ output }) => ({\n    type: \"content\",\n    value: [\n      {\n        type: \"text\",\n        text: `Screenshot captured at ${output.timestamp}`,\n      },\n      {\n        type: \"media\",\n        data: output.data,\n        mediaType: \"image/png\",\n      },\n    ],\n  }),\n});\n```\n\n#### Return Formats\n\nThe `toModelOutput` function can return different content types:\n\n**Text only:**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"text\",\n  value: \"Operation completed successfully\",\n});\n```\n\n**JSON data:**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"json\",\n  value: { status: \"success\", data: output },\n});\n```\n\n**Multi-modal (text + image):**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"content\",\n  value: [\n    { type: \"text\", text: \"Here is the image:\" },\n    { type: \"media\", data: output.base64, mediaType: \"image/png\" },\n  ],\n});\n```\n\n**Error handling:**\n\n```ts\ntoModelOutput: ({ output }) => {\n  if (output.error) {\n    return {\n      type: \"error-text\",\n      value: `Failed: ${output.error}`,\n    };\n  }\n  return {\n    type: \"text\",\n    value: output.result,\n  };\n};\n```\n\nLearn more: [AI SDK Multi-modal Tool Results](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#multi-modal-tool-results)\n\n## Best Practices\n\n### Clear Descriptions\n\nProvide clear descriptions for tools and parameters. The model relies on these to understand when and how to use tools.\n\n**Bad:**\n\n```ts\nconst badTool = createTool({\n  name: \"search\",\n  description: \"Searches things\", // Too vague\n  parameters: z.object({\n    q: z.string(), // No description\n  }),\n  execute: async (args) => {\n    /* ... */\n  },\n});\n```\n\n**Good:**\n\n```ts\nconst goodTool = createTool({\n  name: \"search_web\",\n  description:\n    \"Searches the web for current information. Use when you need recent or factual information not in your training data.\",\n  parameters: z.object({\n    query: z.string().describe(\"The search query. Be specific about what information is needed.\"),\n    results_count: z\n      .number()\n      .min(1)\n      .max(10)\n      .optional()\n      .describe(\"Number of results to return. Defaults to 3.\"),\n  }),\n  execute: async (args) => {\n    /* ... */\n  },\n});\n```\n\n### Error Handling\n\nImplement error handling that provides useful feedback to the model.\n\n```ts\nexecute: async (args) => {\n  try {\n    const result = await performOperation(args);\n    return result;\n  } catch (error) {\n    throw new Error(`Failed to process request: ${error.message}`);\n  }\n};\n```\n\n### Timeout Handling\n\nFor long-running operations, implement timeouts using `AbortController`.\n\n```ts\nexecute: async (args, options) => {\n  const timeoutController = new AbortController();\n  const timeoutId = setTimeout(() => {\n    timeoutController.abort(\"Operation timed out\");\n  }, 5000);\n\n  try {\n    // Listen to parent abort if provided\n    const parentController = options?.abortController;\n    if (parentController?.signal) {\n      parentController.signal.addEventListener(\"abort\", () => {\n        timeoutController.abort(\"Parent operation aborted\");\n        clearTimeout(timeoutId);\n      });\n    }\n\n    const result = await fetch(url, {\n      signal: timeoutController.signal,\n    });\n    clearTimeout(timeoutId);\n    return result;\n  } catch (error) {\n    clearTimeout(timeoutId);\n    throw error;\n  }\n};\n```\n\n## MCP (Model Context Protocol) Support\n\nVoltAgent supports the [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol/mcp), allowing agents to connect with external MCP-compatible servers and tools.\n\n### Using MCP Tools\n\nConnect to MCP servers and use their tools with your agents:\n\n```ts\nimport { Agent, MCPConfiguration } from \"@voltagent/core\";\n\n// Configure MCP servers\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    // HTTP server\n    browserTools: {\n      type: \"http\",\n      url: \"https://your-mcp-server.example.com/browser\",\n    },\n    // Local stdio server\n    localAI: {\n      type: \"stdio\",\n      command: \"python\",\n      args: [\"local_ai_server.py\"],\n    },\n  },\n});\n\n// Get tools grouped by server\nconst toolsets = await mcpConfig.getToolsets();\nconst browserToolsOnly = toolsets.browserTools.getTools();\n\n// Or get all tools combined\nconst allMcpTools = await mcpConfig.getTools();\n\n// Create agent with MCP tools\nconst agent = new Agent({\n  name: \"MCP-Enhanced Assistant\",\n  description: \"Assistant with MCP tools\",\n  model: \"openai/gpt-4o\",\n  tools: allMcpTools,\n});\n```\n\nFor detailed MCP setup and usage, see the [MCP documentation](./mcp/mcp.md).\n","website/docs/agents/voice.md":"---\ntitle: Voice\nslug: /agents/voice\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Voice\n\nThe `@voltagent/voice` package provides text-to-speech (TTS) and speech-to-text (STT) capabilities through various providers. It allows your applications to speak text and transcribe audio with minimal setup.\n\n## Installation\n\nInstall the package using your preferred package manager:\n\n<Tabs>\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/voice\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/voice\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/voice\n```\n\n  </TabItem>\n</Tabs>\n\n## Supported Providers\n\n- **OpenAI**: High-quality voices and transcription.\n- **ElevenLabs**: Realistic, customizable voices.\n- **xsAI**: Lightweight OpenAI-compatible voice API.\n\n## Basic Usage\n\nFirst, initialize a voice provider instance.\n\n### Initialize a Voice Provider\n\n```typescript\n// Initialize with OpenAI\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\nconst openAIVoice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY, // Ensure API key is set in environment variables\n  ttsModel: \"tts-1\",\n  voice: \"alloy\", // Available voices: alloy, echo, fable, onyx, nova, shimmer\n});\n\n// Or initialize with ElevenLabs\nimport { ElevenLabsVoiceProvider } from \"@voltagent/voice\";\n\nconst elevenLabsVoice = new ElevenLabsVoiceProvider({\n  apiKey: process.env.ELEVENLABS_API_KEY, // Ensure API key is set\n  ttsModel: \"eleven_multilingual_v2\",\n  voice: \"Rachel\", // Example voice ID\n});\n\n// Or initialize with xsAI\nimport { XsAIVoiceProvider } from \"@voltagent/voice\";\n\nconst xsAIVoice = new XsAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY!,\n  ttsModel: \"tts-1\",\n  voice: \"alloy\",\n  // If you are not using OpenAI, simply specify the `baseURL`\n});\n```\n\n**Note:** It's recommended to manage API keys securely, for example, using environment variables.\n\n### Text-to-Speech (TTS)\n\nConvert text into an audio stream. You can then process this stream, for example, by saving it to a file.\n\n```typescript\nimport { createWriteStream } from \"node:fs\";\nimport { PassThrough } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\"; // Use pipeline for better error handling\n\n// --- Example 1: Basic Speak and Save to File ---\n\nconsole.log(\"Generating audio...\");\n// Get the audio stream for the text\nconst audioStream = await openAIVoice.speak(\"Hello from VoltAgent!\");\n\nconsole.log(\"Saving audio to output.mp3...\");\n// Create a file stream to write the audio\nconst fileStream = createWriteStream(\"output.mp3\");\n\ntry {\n  // Pipe the audio stream to the file stream and wait for completion\n  await pipeline(audioStream, fileStream);\n  console.log(\"Audio successfully saved to output.mp3\");\n} catch (error) {\n  console.error(\"Failed to save audio:\", error);\n}\n\n// --- Example 2: Speak with Options and Save ---\n\nconsole.log(\"Generating custom audio...\");\nconst customAudioStream = await elevenLabsVoice.speak(\"Speaking faster now.\", {\n  // Provider-specific options can be passed here.\n  // For OpenAI, you might use:\n  // voice: \"nova\", // Override the default voice\n  // speed: 1.5,    // Adjust speaking speed (1.0 is default)\n});\n\nconsole.log(\"Saving custom audio to custom_output.mp3...\");\nconst customFileStream = createWriteStream(\"custom_output.mp3\");\n\ntry {\n  // Pipe the custom audio stream to the file stream\n  await pipeline(customAudioStream, customFileStream);\n  console.log(\"Custom audio successfully saved to custom_output.mp3\");\n} catch (error) {\n  console.error(\"Failed to save custom audio:\", error);\n}\n\n// Note: The audioStream is a standard Node.js Readable stream.\n// You can pipe it to other destinations or process it directly.\n```\n\n### Speech-to-Text (STT)\n\nTranscribe audio from a stream into text.\n\n```typescript\nimport { createReadStream } from \"node:fs\";\n\n// Ensure you have an audio file (e.g., input.mp3)\nconst audioFileStream = createReadStream(\"input.mp3\");\n\n// Basic transcription with OpenAI\nconst transcribedText = await openAIVoice.listen(audioFileStream);\nconsole.log(\"Transcription:\", transcribedText);\n\n// Transcription with options (e.g., language hint)\nconst transcribedSpanish = await openAIVoice.listen(audioFileStream, {\n  language: \"es\", // Specify the language code\n});\nconsole.log(\"Spanish Transcription:\", transcribedSpanish);\n\n// Streaming transcription (Provider-dependent, e.g., OpenAI)\ntry {\n  const streamingTranscription = await openAIVoice.listen(audioFileStream, {\n    stream: true, // Enable streaming\n  });\n\n  for await (const chunk of streamingTranscription) {\n    // Process transcription chunks as they arrive\n    // The structure of 'chunk' depends on the provider\n    console.log(\"Stream Chunk:\", chunk);\n  }\n} catch (error) {\n  console.error(\"Streaming transcription failed:\", error);\n}\n```\n\n### List Available Voices\n\nFetch the list of voices supported by the provider.\n\n```typescript\n// Using the initialized openAIVoice instance\nconst availableVoices = await openAIVoice.getVoices();\nconsole.log(\"Available OpenAI Voices:\", availableVoices);\n/* Example Output:\n[\n  { id: 'alloy', name: 'Alloy', language: 'en', gender: 'neutral' },\n  { id: 'echo', name: 'Echo', language: 'en', gender: 'male' },\n  // ... other voices\n]\n*/\n\nconst elevenLabsVoices = await elevenLabsVoice.getVoices();\nconsole.log(\"Available ElevenLabs Voices:\", elevenLabsVoices); // Structure might differ\n```\n\n## Integrating with Agent\n\nYou can integrate the voice provider directly into an `Agent` instance.\n\n```typescript\nimport { Agent, OpenAIAgentProvider } from \"@voltagent/core\";\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\n// Initialize the voice provider\nconst voice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n  ttsModel: \"tts-1\",\n  voice: \"nova\",\n});\n\n// Initialize the agent provider\nconst provider = new OpenAIAgentProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n});\n\n// Create the agent, passing the voice instance\nconst voiceEnabledAgent = new Agent({\n  name: \"VoiceBot\",\n  instructions: \"An agent that can speak.\",\n  provider,\n  model: \"openai/gpt-4o\", // Choose an appropriate model\n  voice: voice, // Assign the voice provider here\n});\n\n// Now the agent instance potentially uses the voice capabilities\n// (Specific usage within the agent depends on future VoltAgent features or custom implementations)\n\n// Example: Manually trigger speech from agent's response\n// const response = await voiceEnabledAgent.generateText(\"Tell me a short story.\");\n// if (voiceEnabledAgent.voice) {\n//   const audio = await voiceEnabledAgent.voice.speak(response.text);\n//   // Pipe audio to output\n//   audio.pipe(createWriteStream(\"story.mp3\"));\n// }\n```\n\n**Note:** The direct integration of `voice` within the `Agent`'s core methods (`generateText`, `streamText`, etc.) for automatic speaking/listening is under development. Currently, you can access the `agent.voice` property to manually trigger TTS or STT.\n\n## Event Handling\n\nListen for events emitted by the voice provider instance.\n\n```typescript\n// Using the initialized openAIVoice instance\n\n// Listen for the start of speech synthesis\nopenAIVoice.on(\"speaking\", (data) => {\n  console.log(`[Speaking Started] Text: \"${data.text}\"`);\n});\n\n// Listen for the start of transcription\nopenAIVoice.on(\"listening\", (data) => {\n  // data might contain information about the audio stream\n  console.log(\"[Listening Started]\");\n});\n\n// Handle errors\nopenAIVoice.on(\"error\", (errorData) => {\n  console.error(\n    `[Voice Error] Code: ${errorData.code}, Message: ${errorData.message}`,\n    errorData.details\n  );\n});\n\n// Trigger an action that emits events\ntry {\n  await openAIVoice.speak(\"Testing event listeners.\");\n  // const audioInput = createReadStream(\"input.mp3\");\n  // await openAIVoice.listen(audioInput);\n} catch (error) {\n  // Errors caught here might also be emitted via the 'error' event\n  console.error(\"Direct action failed:\", error);\n}\n```\n\n## Creating a Custom Voice Provider\n\nExtend the `BaseVoiceProvider` to integrate other TTS/STT services.\n\n```typescript\nimport {\n  BaseVoiceProvider,\n  VoiceProviderEvents,\n  VoiceMetadata,\n  VoiceSpeakOptions,\n  VoiceListenOptions,\n} from \"@voltagent/voice\";\nimport { PassThrough, Readable } from \"node:stream\"; // Import Readable\n\n// Define your custom provider options\ntype MyCustomProviderOptions = {\n  apiKey: string;\n  region?: string;\n  // Add other necessary options\n};\n\n// Define potential errors specific to your provider\nenum MyCustomErrorCode {\n  AuthError = \"AUTH_ERROR\",\n  ApiError = \"API_ERROR\",\n  InvalidInput = \"INVALID_INPUT\",\n}\n\nexport class MyCustomVoiceProvider extends BaseVoiceProvider<\n  MyCustomProviderOptions,\n  VoiceProviderEvents\n> {\n  private readonly apiKey: string;\n  private readonly region: string;\n  // Add client instances or other state needed\n\n  constructor(options: MyCustomProviderOptions) {\n    super(options); // Pass options to the base class\n    this.apiKey = options.apiKey;\n    this.region = options.region || \"default-region\";\n    // Initialize your API client here\n  }\n\n  /**\n   * Connect to the service if needed (e.g., WebSockets).\n   */\n  async connect(): Promise<void> {\n    console.log(\"Connecting to Custom Voice Service...\");\n    // Add connection logic if required\n    // Simulating async operation\n    await new Promise((resolve) => setTimeout(resolve, 100));\n    console.log(\"Connected.\");\n  }\n\n  /**\n   * Disconnect from the service.\n   */\n  disconnect(): void {\n    console.log(\"Disconnecting from Custom Voice Service...\");\n    // Add disconnection logic\n  }\n\n  /**\n   * Send audio data (e.g., for streaming STT).\n   */\n  async send(audioData: Readable): Promise<void> {\n    console.log(\"Sending audio data chunk...\");\n    // Implementation for sending streaming audio\n    // Read from audioData stream and send to your API\n    // Example: audioData.pipe(yourApiStream);\n    await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate async send\n  }\n\n  /**\n   * Convert text to speech.\n   */\n  async speak(\n    text: string, // Assuming text is always string for simplicity here\n    options?: VoiceSpeakOptions // Use standardized options type\n  ): Promise<Readable> {\n    // Return standard Readable stream\n    this.emit(\"speaking\", { text }); // Emit standard event\n\n    try {\n      console.log(`Speaking text: \"${text}\" with voice: ${options?.voice}`);\n      // --- Your TTS API Call Logic ---\n      // const apiResponse = await yourTTSClient.synthesize({\n      //   text: text,\n      //   voice: options?.voice || 'default-voice',\n      //   apiKey: this.apiKey,\n      //   // map other options like speed if applicable\n      // });\n      // -----------------------------\n\n      // Simulate receiving audio data from API\n      await new Promise((resolve) => setTimeout(resolve, 500));\n      const audioBuffer = Buffer.from(`Simulated audio for: ${text}`);\n\n      // Return audio data as a Readable stream\n      const passThrough = new PassThrough();\n      passThrough.end(audioBuffer);\n      return passThrough;\n    } catch (error: any) {\n      const errorMessage = error.message || \"Unknown TTS error\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError, // Use your specific error codes\n        details: error,\n      });\n      throw new Error(`Custom TTS failed: ${errorMessage}`); // Re-throw a standard error\n    }\n  }\n\n  /**\n   * Convert speech to text.\n   */\n  async listen(\n    audio: Readable, // Expect a Readable stream\n    options?: VoiceListenOptions // Use standardized options type\n  ): Promise<string | AsyncIterable<any>> {\n    // Return string or stream based on options\n    this.emit(\"listening\", { audio }); // Emit standard event\n\n    try {\n      console.log(`Listening with language: ${options?.language}, stream: ${options?.stream}`);\n\n      // --- Your STT API Call Logic ---\n      // Handle both standard and streaming modes\n      if (options?.stream) {\n        // Implement streaming transcription logic\n        // Example: const transcriptStream = yourSTTClient.startStream(audio, { language: options.language });\n        // return transcriptStream; // Return the async iterable from your client\n\n        // --- Placeholder for streaming ---\n        async function* generateChunks() {\n          yield { delta: \"Simulated \" };\n          await new Promise((resolve) => setTimeout(resolve, 200));\n          yield { delta: \"streamed \" };\n          await new Promise((resolve) => setTimeout(resolve, 200));\n          yield { delta: \"transcript.\" };\n        }\n        return generateChunks();\n        // --- End Placeholder ---\n      } else {\n        // Implement non-streaming transcription logic\n        // Example: const result = await yourSTTClient.transcribe(audio, { language: options?.language });\n        // return result.text;\n\n        // --- Placeholder for non-streaming ---\n        // Simulate reading the stream and getting a result\n        let fullAudio = Buffer.alloc(0);\n        for await (const chunk of audio) {\n          fullAudio = Buffer.concat([fullAudio, chunk]);\n        }\n        await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate API call\n        return \"Simulated full transcript of audio.\";\n        // --- End Placeholder ---\n      }\n      // -----------------------------\n    } catch (error: any) {\n      const errorMessage = error.message || \"Unknown STT error\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError, // Use your specific error codes\n        details: error,\n      });\n      throw new Error(`Custom STT failed: ${errorMessage}`); // Re-throw a standard error\n    }\n  }\n\n  /**\n   * Fetch available voices from your service.\n   */\n  async getVoices(): Promise<VoiceMetadata[]> {\n    try {\n      // --- Your API Call Logic to Get Voices ---\n      // const apiVoices = await yourClient.listVoices({ apiKey: this.apiKey });\n      // return apiVoices.map(v => ({ id: v.id, name: v.name, language: v.lang, gender: v.gender }));\n      // ---------------------------------------\n\n      // --- Placeholder ---\n      await new Promise((resolve) => setTimeout(resolve, 100)); // Simulate API call\n      return [\n        { id: \"custom-voice-1\", name: \"Custom Voice One\", language: \"en\", gender: \"female\" },\n        { id: \"custom-voice-2\", name: \"Custom Voice Two\", language: \"es\", gender: \"male\" },\n      ];\n      // --- End Placeholder ---\n    } catch (error: any) {\n      const errorMessage = error.message || \"Failed to fetch custom voices\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError,\n        details: error,\n      });\n      // Return empty array or throw, depending on desired behavior\n      return [];\n    }\n  }\n}\n\n// Example usage of the custom provider\n// const myVoice = new MyCustomVoiceProvider({ apiKey: \"YOUR_CUSTOM_API_KEY\" });\n// const audio = await myVoice.speak(\"Hello from my custom provider!\");\n// audio.pipe(createWriteStream(\"custom_output.mp3\"));\n```\n\nThis updated custom provider example uses standardized types like `VoiceSpeakOptions`, `VoiceListenOptions`, `VoiceMetadata`, and `Readable` stream types for better consistency and type safety. It also includes more detailed placeholders for API logic and error handling.\n","website/docs/agents/voltagent-instance.md":"---\ntitle: VoltAgent Instance\nsidebar_label: VoltAgent Instance\n---\n\n# VoltAgent Instance\n\n`VoltAgent` is the application entrypoint. It wires agents and workflows together, applies global defaults, and optionally starts an HTTP or serverless provider.\n\n## Basic Setup\n\n```ts\nimport { Agent, VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst assistant = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users get answers fast.\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nnew VoltAgent({\n  agents: { assistant },\n  server: honoServer({ port: 3141 }),\n});\n```\n\n## What VoltAgent Manages\n\n- Registers agents and workflows for API access and observability.\n- Applies global defaults (logger, observability, memory, conversation persistence).\n- Boots HTTP or serverless providers.\n- Registers triggers, MCP servers, and A2A servers.\n- Coordinates graceful shutdown for servers and telemetry.\n\n## Registering Agents and Workflows\n\nYou can register instances on startup or later at runtime.\n\n```ts\nimport { VoltAgent } from \"@voltagent/core\";\n\nconst app = new VoltAgent({\n  agents: { assistant },\n  workflows: { onboardingWorkflow },\n});\n\napp.registerAgent(anotherAgent);\napp.registerWorkflow(anotherWorkflow);\n```\n\nTo register triggers at runtime:\n\n```ts\napp.registerTriggers(triggers);\n```\n\n## Memory Defaults\n\nSet default memory once at the VoltAgent entrypoint. Defaults apply only when an agent or workflow does not specify `memory`. Omitting `memory` does not disable it; it still resolves to configured defaults (or built-in in-memory). An explicit `memory: false` on an agent disables memory entirely.\n\n```ts\nimport { Memory, VoltAgent } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agentMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/agent.db\" }),\n});\n\nconst workflowMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/workflows.db\" }),\n});\n\nnew VoltAgent({\n  agentMemory,\n  workflowMemory,\n  // memory: sharedFallbackMemory,\n});\n```\n\n**Precedence**\n\n- Agents: agent `memory` > `agentMemory` > `memory` > built-in in-memory\n- Workflows: workflow `memory` > `workflowMemory` > `memory` > built-in in-memory\n\nSee [Memory Overview](/docs/agents/memory/overview) for adapter options.\n\n## Conversation Persistence Defaults\n\nSet a default persistence strategy for agents that do not explicitly define `conversationPersistence`.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  agentConversationPersistence: {\n    mode: \"step\", // default\n    debounceMs: 200, // default\n    flushOnToolResult: true, // default\n  },\n});\n```\n\n**Precedence**\n\n- Per-call `options.memory.options.conversationPersistence` (preferred)\n- Per-call `options.conversationPersistence` (deprecated)\n- Agent `conversationPersistence`\n- VoltAgent `agentConversationPersistence`\n- Built-in defaults (`mode: \"step\"`, `debounceMs: 200`, `flushOnToolResult: true`)\n\n## Observability and Logging\n\nVoltAgent creates an OpenTelemetry-based observability instance by default. You can provide your own instance or logger if you want full control.\n\n```ts\nimport { VoltAgent, VoltAgentObservability } from \"@voltagent/core\";\n\nconst observability = new VoltAgentObservability({\n  serviceName: \"my-voltagent-app\",\n});\n\nnew VoltAgent({\n  agents: { assistant },\n  observability,\n});\n```\n\nSee [Observability Overview](/docs/observability/overview) and [Logging](/docs/observability/logging) for details.\n\nIf you already have a `VoltOpsClient` or a custom logger, pass them once and VoltAgent shares them across agents and workflows.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  voltOpsClient,\n  logger,\n});\n```\n\n## Server and Serverless Providers\n\nUse `server` to expose HTTP endpoints, or `serverless` for fetch-based runtimes.\n\n```ts\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst app = new VoltAgent({\n  agents: { assistant },\n  server: honoServer({ port: 3141, enableSwaggerUI: true }),\n});\n```\n\nSee [API Overview](/docs/api/overview) for the full endpoint list.\n\nFor HTTP servers, use `startServer()` and `stopServer()` if you need manual control:\n\n```ts\nawait app.startServer();\nawait app.stopServer();\n```\n\nYou can read the server instance directly:\n\n```ts\nconst serverInstance = app.getServerInstance();\n```\n\nIf you are using a serverless provider, you can access it with `serverless()`:\n\n```ts\nimport { serverlessHono } from \"@voltagent/serverless-hono\";\n\nconst serverlessApp = new VoltAgent({\n  agents: { assistant },\n  serverless: serverlessHono(),\n});\n\nconst serverlessProvider = serverlessApp.serverless();\n```\n\n## Triggers and Protocol Servers\n\nVoltAgent can register triggers and protocol servers alongside your agents.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  triggers,\n  mcpServers,\n  a2aServers,\n});\n```\n\nSee [Triggers](/docs/triggers) and [MCP](/docs/agents/mcp) for setup details.\nFor A2A servers, see [A2A Server](/docs/agents/a2a/a2a-server).\n\n## Runtime Accessors and Shutdown\n\nVoltAgent exposes accessors for registered components and a graceful shutdown helper.\n\n```ts\nconst agents = app.getAgents();\nconst workflows = app.getWorkflows();\nconst observability = app.getObservability();\n\nawait app.shutdown();\n```\n\n## Dependency Update Checks\n\n`checkDependencies` controls a background dependency update check intended for Node runtimes. It is skipped in serverless or edge environments, and the current core implementation is a no-op placeholder.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  checkDependencies: false,\n});\n```\n","website/docs/tools/overview.md":"---\ntitle: Overview\n---\n\n# Tools & Toolkits\n\nVoltAgent allows you to extend the capabilities of your AI agents by providing them with **Tools**. Tools enable agents to interact with external APIs, perform calculations, access databases, or execute virtually any custom code. This guide covers how to define and use individual tools and the new `Toolkit` concept for managing related tools.\n\n## Defining a Single Tool\n\nThe most basic way to define a tool is using the `createTool` helper function (or instantiating the `Tool` class directly).\n\nA tool requires:\n\n- `name`: A unique name for the tool (used by the LLM to call it).\n- `description`: A clear description of what the tool does (used by the LLM to decide when to use it).\n- `parameters`: A Zod schema defining the input arguments the tool expects.\n- `execute`: An asynchronous function that contains the tool's logic, taking the validated arguments as input.\n- `outputSchema` (optional): A Zod schema defining the expected output format. When provided, the tool's output will be validated against this schema.\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define a simple weather tool\nconst getWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches the current weather for a given location.\",\n  parameters: z.object({\n    location: z.string().describe(\"The city and state, e.g., San Francisco, CA\"),\n  }),\n  execute: async ({ location }) => {\n    // In a real scenario, you would call a weather API here\n    console.log(`Fetching weather for ${location}...`);\n    if (location.toLowerCase().includes(\"tokyo\")) {\n      return { temperature: \"15°C\", condition: \"Cloudy\" };\n    }\n    return { temperature: \"22°C\", condition: \"Sunny\" };\n  },\n});\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that can fetch weather information.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeatherTool], // Add the tool to the agent\n});\n\n// Now the agent can use the 'get_weather' tool when asked about weather.\n```\n\n## Accessing Operation Context in Tools\n\nTools can access operation metadata, user context, and control mechanisms through the second parameter:\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Tool that uses operation context\nconst contextAwareWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches weather with user preferences\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  execute: async ({ location }, options) => {\n    // Access user-defined context\n    const units = options?.context?.get(\"preferredUnits\") || \"celsius\";\n    const userId = options?.userId;\n\n    // Use operation-scoped logger\n    options?.logger?.info(`Fetching weather for ${location} in ${units}`);\n\n    // Check abort signal\n    if (options?.abortSignal?.aborted) {\n      throw new Error(\"Request was cancelled\");\n    }\n\n    // Call weather API with user preferences\n    const response = await fetch(\n      `https://api.weather.com/current?city=${location}&units=${units}`,\n      { signal: options?.abortSignal }\n    );\n\n    return await response.json();\n  },\n});\n\n// Use the tool with context\nconst context = new Map();\ncontext.set(\"preferredUnits\", \"fahrenheit\");\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that respects user preferences\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [contextAwareWeatherTool],\n});\n\nconst response = await agent.generateText(\"What's the weather in Paris?\", {\n  userId: \"user123\",\n  context,\n});\n```\n\nThe `options` parameter includes:\n\n- **Operation metadata**: `operationId`, `userId`, `conversationId`\n- **User context**: `context` (Map for custom data)\n- **Control mechanisms**: `abortController`, `abortSignal`\n- **Logging**: `logger` (operation-scoped logger)\n- **AI SDK data**: `toolCallId`, `messages`\n\nFor more details, see the [Tools documentation](/docs/agents/tools#accessing-operation-context).\n\n## Tool Output Schema Validation\n\nVoltAgent supports optional output schema validation for tools. This feature ensures that tool outputs conform to a predefined structure, providing several benefits:\n\n- **Type Safety**: Tool outputs are typed based on the schema\n- **Runtime Validation**: Invalid outputs are caught immediately\n- **Error Recovery**: When validation fails, the LLM receives an error message and can retry with corrected output\n- **Consistency**: All tool responses follow the same structure\n- **Documentation**: Output schemas serve as API contracts\n\n### Example with Output Schema\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define the output schema\nconst weatherOutputSchema = z.object({\n  location: z.string(),\n  temperature: z.number(),\n  condition: z.enum([\"sunny\", \"cloudy\", \"rainy\", \"snowy\"]),\n  humidity: z.number().min(0).max(100),\n  forecast: z.object({\n    high: z.number(),\n    low: z.number(),\n    description: z.string(),\n  }),\n});\n\n// Create a tool with output validation\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather with forecast\",\n  parameters: z.object({\n    location: z.string().describe(\"City name\"),\n  }),\n  outputSchema: weatherOutputSchema, // Optional output schema\n  execute: async ({ location }) => {\n    // This output will be validated against weatherOutputSchema\n    return {\n      location,\n      temperature: 22,\n      condition: \"sunny\",\n      humidity: 65,\n      forecast: {\n        high: 25,\n        low: 18,\n        description: \"Clear skies throughout the day\",\n      },\n    };\n  },\n});\n```\n\n### How Output Validation Works\n\n1. When a tool is executed, its output is validated against the `outputSchema` if provided\n2. If validation succeeds, the validated output is returned\n3. If validation fails, an error object is returned to the LLM:\n   ```json\n   {\n     \"error\": true,\n     \"message\": \"Output validation failed: Expected number, received string\",\n     \"validationErrors\": [...],\n     \"actualOutput\": {...}\n   }\n   ```\n4. The LLM can see the validation error and potentially fix the issue by calling the tool again\n\n### Best Practices\n\n- Use output schemas for tools that need consistent response formats\n- Keep schemas focused and avoid overly complex nested structures\n- Use descriptive error messages in your schemas with `.describe()`\n- Consider making some fields optional with `.optional()` for flexibility\n- Output schemas are completely optional - tools without them work as before\n\n## Grouping Tools with Toolkits\n\nOften, several tools work together logically. For instance, tools for step-by-step reasoning (`think`, `analyze`) or a set of tools interacting with the same API. For these scenarios, VoltAgent provides **Toolkits**.\n\nA `Toolkit` allows you to:\n\n1.  **Group related tools:** Keep your tool management organized.\n2.  **Define shared instructions:** Provide common guidance to the LLM on how to use all tools within the toolkit.\n3.  **Control instruction injection:** Decide if the toolkit's shared instructions should be automatically added to the agent's system prompt.\n\n### Defining a Toolkit\n\nA `Toolkit` is an object with the following structure:\n\n```typescript\nimport { createTool, createToolkit, type Tool, type Toolkit } from \"@voltagent/core\";\n\nconst myCalculatorToolkit = createToolkit({\n  name: \"calculator_toolkit\",\n  description: \"Tools for performing basic arithmetic operations.\",\n  // Optional instructions for the LLM\n  instructions: `Use these tools for calculations. Always use 'add' for addition, 'subtract' for subtraction.`,\n  // Set to true to add the above instructions to the system prompt\n  addInstructions: true,\n  tools: [\n    createTool({\n      /* ... definition for 'add' tool ... */\n    }),\n    createTool({\n      /* ... definition for 'subtract' tool ... */\n    }),\n    // ... other calculator tools\n  ],\n});\n```\n\n**Important:** With the introduction of Toolkits, individual `Tool` instances no longer have their own `instructions` or `addInstructions` properties. Instructions are managed at the Toolkit level.\n\n### Adding Tools and Toolkits to an Agent\n\nThe `tools` option in the `Agent` constructor now accepts an array containing both individual `Tool` objects and `Toolkit` objects. The `ToolManager` handles both seamlessly.\n\n```typescript\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool, createToolkit, type Toolkit } from \"@voltagent/core\";\n// ... import other tools and toolkits ...\n\nconst agent = new Agent({\n  name: \"MultiToolAgent\",\n  instructions: \"An agent with various tools and toolkits.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [\n    getWeatherTool, // Add an individual tool\n    myCalculatorToolkit, // Add a toolkit\n    openai.tools.webSearch(), // Add a provider-defined tool\n    // ... other tools or toolkits\n  ],\n});\n```\n\n### Automatic Instructions\n\nWhen an agent is initialized, its `getSystemMessage` method checks all the `Toolkit`s provided in the `tools` array. If a `Toolkit` has `addInstructions: true` and defines an `instructions` string, those instructions will be automatically appended to the agent's base description, forming part of the final system prompt sent to the LLM.\n\n## Provider-defined Tools\n\nSome providers expose their own tools (via the Vercel AI SDK).\n\n- They can be standalone tools or live inside Toolkits and are subject to name-conflict checks when adding toolkits.\n- They are not executable on your server via the usual `Tool.execute` handler — the provider manages them.\n\n## Next Steps\n\n- See the [Reasoning Tools](/docs/tools/reasoning-tool/) documentation for a practical example of a pre-built toolkit.\n- Explore creating your own tools to connect agents to your specific data sources and APIs.\n","website/docs/tools/tool-routing.md":"---\ntitle: Tool Routing\n---\n\n# Tool Routing\n\nTool routing keeps the full tool pool hidden and exposes two system tools to the model:\n\n- `searchTools` returns tool metadata and schemas for a query.\n- `callTool` executes a tool by name with validated arguments.\n\nOnly `searchTools`, `callTool`, and any tools listed in `toolRouting.expose` are visible to the model.\n\n## How Tool Routing Works\n\n- Tool routing builds a hidden pool from `toolRouting.pool` or all registered tools (by default).\n- The model calls `searchTools` with the user request and receives tool details.\n- The model calls `callTool` with the exact tool name and schema-compliant arguments.\n- Pool tools stay hidden unless explicitly exposed.\n\n## Quick Setup With Embedding Search\n\nThis configuration enables embedding-based tool search and exposes only `searchTools` and `callTool` to the model.\n\n```ts\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    temperatureC: 22,\n    condition: \"sunny\",\n  }),\n});\n\nconst getTimeZone = createTool({\n  name: \"get_time_zone\",\n  description: \"Get the time zone offset for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    timeZone: \"UTC+1\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Tool Routing Agent\",\n  instructions:\n    \"When you need a tool, call searchTools with the user request, then call callTool with the exact tool name and schema-compliant arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeather, getTimeZone],\n  toolRouting: {\n    embedding: \"openai/text-embedding-3-small\",\n    topK: 2,\n  },\n});\n```\n\nNotes:\n\n- If `toolRouting.pool` is not set, the pool defaults to all tools registered in the agent.\n- If `toolRouting.expose` is not set, only `searchTools` and `callTool` are visible to the model.\n\n## Tool Pool and Exposed Tools\n\nUse `pool` to define the hidden tool set and `expose` to keep a subset visible.\n\n```ts\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({ location: z.string() }),\n  execute: async ({ location }) => ({ location, temperatureC: 22 }),\n});\n\nconst getStatus = createTool({\n  name: \"get_status\",\n  description: \"Return the service status\",\n  parameters: z.object({}),\n  execute: async () => ({ status: \"ok\" }),\n});\n\nconst agent = new Agent({\n  name: \"Support Agent\",\n  instructions:\n    \"Use searchTools to find tool schemas, then call callTool with validated arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    pool: [getWeather],\n    expose: [getStatus],\n  },\n});\n```\n\n## Enforcing Search Before Call\n\nBy default, `callTool` requires the tool to appear in a prior `searchTools` result.\nYou can disable this behavior:\n\n```ts\nconst agent = new Agent({\n  name: \"Relaxed Agent\",\n  instructions: \"Use searchTools before callTool when possible.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    pool: [\n      /* tools and toolkits */\n    ],\n    enforceSearchBeforeCall: false,\n  },\n});\n```\n\n## Embedding Configuration\n\n`toolRouting.embedding` accepts either a model string or an object with extra options.\n\n- `\"openai/text-embedding-3-small\"`\n- `\"text-embedding-3-small\"`\n\nProvider-qualified strings use the same registry and type list as agent model strings.\n\n```ts\nconst agent = new Agent({\n  name: \"Custom Embedding Agent\",\n  instructions:\n    \"Use searchTools to discover tool schemas, then call callTool with the right arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: {\n      model: \"openai/text-embedding-3-small\",\n      topK: 3,\n      toolText: (tool) => {\n        const tags = tool.tags?.join(\", \") ?? \"\";\n        return [tool.name, tool.description, tags].filter(Boolean).join(\"\\n\");\n      },\n    },\n  },\n});\n```\n\nThe embedding search caches vectors in memory. The cache is reset when the process restarts.\nA tool text change for a given tool name triggers a new embedding for that tool.\n\n## Per-Call Overrides\n\nYou can disable tool routing for a single call or provide a temporary routing config.\n\n```ts\nconst result = await agent.generateText(\"List tools\", {\n  toolRouting: false,\n});\n\nconst routed = await agent.generateText(\"What time is it in Berlin?\", {\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    topK: 1,\n  },\n});\n```\n\n## Hooks, Approval, and Error Handling\n\n- `needsApproval`, input/output guardrails, and tool hooks still run when tools are executed via `callTool`.\n- If a tool is not in the pool, `callTool` returns a tool error.\n- `callTool` validates arguments against the target schema and surfaces validation errors for retries.\n- Provider tools and MCP tools can be pooled; execution still runs through approvals and hooks.\n\n## Observability\n\nTool routing adds spans for selection and embedding steps:\n\n- `tool.search.selection:*` spans include `tool.search.candidates`, `tool.search.selection.count`, and selected names.\n- `tool.search.embedding:*` spans include `embedding.model`, `embedding.dimensions`, and cache hit/miss counts.\n\nThese spans appear under the `searchTools` tool span in the execution trace.\n\n## PlanAgent\n\n`PlanAgent` accepts `toolRouting` in its options. It passes routing config to its internal agent.\n\n```ts\nimport { PlanAgent } from \"@voltagent/core\";\n\nconst agent = new PlanAgent({\n  name: \"Planning Agent\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n  },\n});\n```\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# VoltAgent\n\nVoltAgent is an open-source TypeScript framework for building and orchestrating AI agents.\n\n## What is VoltAgent?\n\nVoltAgent is a comprehensive framework that enables developers to build sophisticated AI agents with memory, tools, observability, and sub-agent capabilities. It provides direct AI SDK integration with comprehensive OpenTelemetry tracing built-in.\n\n## Overview\n\n- View [`docs/structure.md`](./docs/structure.md) for the repository structure and organization\n- View [`docs/tooling.md`](./docs/tooling.md) for development tools and utilities\n- View [`docs/testing.md`](./docs/testing.md) for testing guidelines and commands\n- View [`docs/linting.md`](./docs/linting.md) for code formatting and linting\n\n## Validating Changes\n\nTo validate your changes you can run the following commands:\n\n```bash\npnpm test:all        # Run all tests\npnpm build:all       # Build all packages\npnpm lint            # Run linting checks\npnpm lint:fix        # Auto-fix linting issues\n```\n\n## Important Notes for AI Agents\n\n1. **Always check existing patterns** before implementing new features\n2. **Use the established registry patterns** for agent and tool management\n3. **Maintain type safety** - this is a TypeScript-first codebase\n4. **Follow the monorepo structure** - changes may impact multiple packages\n5. **Test your changes** - ensure all tests pass before committing\n\n## Gotchas\n\n- **JSON.stringify** SHOULD NEVER BE USED, used the `safeStringify` function instead, imported from `@voltagent/internal`\n","isInternal":false,"tokens":336,"sizeBytes":1519},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"AGENTS.md","isInternal":false,"tokens":3,"sizeBytes":9},{"name":"agents.mdc","path":".cursor/rules/agents.mdc","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/.cursor/rules/agents.mdc","title":"Cursor Rule: agents","category":"cursor-rule","format":"mdc","content":"---\ndescription: \nglobs: \nalwaysApply: true\n---\nALWAYS read the `AGENTS.md` in the root of the repository and apply the instructions to the current task.\n","frontmatter":{"description":null,"globs":null,"alwaysApply":true},"isInternal":false,"tokens":38,"sizeBytes":154},{"name":"schema.md","path":"examples/with-workspace/workspace/skills/data-analysis/references/schema.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/data-analysis/references/schema.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# CSV Schema\n\nExpected columns:\n\n- customer_id: unique identifier\n- segment: smb | mid-market | enterprise\n- mrr: monthly recurring revenue (number)\n- region: NA | EU | APAC\n- plan: Starter | Pro | Enterprise\n","isInternal":false,"tokens":53,"sizeBytes":209},{"name":"SKILL.md","path":"examples/with-workspace/workspace/skills/data-analysis/SKILL.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/data-analysis/SKILL.md","title":"Workspace Data Analyst","category":"anthropic-skill","format":"markdown","content":"---\nname: Workspace Data Analyst\ndescription: Analyze CSV files in the workspace and summarize insights.\nversion: \"1.0.0\"\ntags:\n  - data\n  - csv\n  - analysis\nreferences:\n  - references/schema.md\nassets:\n  - assets/sample.csv\n---\n\nWhen analyzing CSV data:\n\n1. Load the file and inspect headers.\n2. Summarize totals, averages, and outliers.\n3. Provide a short insight summary and recommended next steps.\n","frontmatter":{"name":"Workspace Data Analyst","description":"Analyze CSV files in the workspace and summarize insights.","version":"1.0.0","tags":["data","csv","analysis"],"references":["references/schema.md"],"assets":["assets/sample.csv"]},"isInternal":false,"tokens":98,"sizeBytes":402},{"name":"request-mocking.md","path":"examples/with-workspace/workspace/skills/playwright-cli/references/request-mocking.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/references/request-mocking.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n","isInternal":false,"tokens":549,"sizeBytes":2182},{"name":"running-code.md","path":"examples/with-workspace/workspace/skills/playwright-cli/references/running-code.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/references/running-code.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.loading', { state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.result', { timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const [download] = await Promise.all([\n    page.waitForEvent('download'),\n    page.click('a.download-link')\n  ]);\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.click('.maybe-missing', { timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.fill('input[name=email]', 'user@example.com');\n  await page.fill('input[name=password]', 'secret');\n  await page.click('button[type=submit]');\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n","isInternal":false,"tokens":1263,"sizeBytes":5273},{"name":"session-management.md","path":"examples/with-workspace/workspace/skills/playwright-cli/references/session-management.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/references/session-management.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n","isInternal":false,"tokens":939,"sizeBytes":3999},{"name":"storage-state.md","path":"examples/with-workspace/workspace/skills/playwright-cli/references/storage-state.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/references/storage-state.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1735689600,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1735689600\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n","isInternal":false,"tokens":1249,"sizeBytes":5198},{"name":"test-generation.md","path":"examples/with-workspace/workspace/skills/playwright-cli/references/test-generation.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/references/test-generation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Test Generation\n\nGenerate Playwright test code automatically as you interact with the browser.\n\n## How It Works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into your test files.\n\n## Example Workflow\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n## Building a Test File\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"login flow\", async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto(\"https://example.com/login\");\n  await page.getByRole(\"textbox\", { name: \"Email\" }).fill(\"user@example.com\");\n  await page.getByRole(\"textbox\", { name: \"Password\" }).fill(\"password123\");\n  await page.getByRole(\"button\", { name: \"Sign In\" }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n## Best Practices\n\n### 1. Use Semantic Locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator(\"#submit-btn\").click();\n```\n\n### 2. Explore Before Recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### 3. Add Assertions Manually\n\nGenerated code captures actions but not assertions. Add expectations in your test:\n\n```typescript\n// Generated action\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Manual assertion\nawait expect(page.getByText(\"Success\")).toBeVisible();\n```\n","isInternal":false,"tokens":563,"sizeBytes":2411},{"name":"tracing.md","path":"examples/with-workspace/workspace/skills/playwright-cli/references/tracing.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/references/tracing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category        | Details                                            |\n| --------------- | -------------------------------------------------- |\n| **Actions**     | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM**         | Full DOM snapshot before/after each action         |\n| **Screenshots** | Visual state at each step                          |\n| **Network**     | All requests, responses, headers, bodies, timing   |\n| **Console**     | All console.log, warn, error messages              |\n| **Timing**      | Precise timing for each operation                  |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature                 | Trace       | Video       | Screenshot       |\n| ----------------------- | ----------- | ----------- | ---------------- |\n| **Format**              | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection**      | Yes         | No          | No               |\n| **Network details**     | Yes         | No          | No               |\n| **Step-by-step replay** | Yes         | Continuous  | Single frame     |\n| **File size**           | Medium      | Large       | Small            |\n| **Best for**            | Debugging   | Demos       | Quick capture    |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n","isInternal":false,"tokens":859,"sizeBytes":3852},{"name":"video-recording.md","path":"examples/with-workspace/workspace/skills/playwright-cli/references/video-recording.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/references/video-recording.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Start recording\nplaywright-cli video-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop demo.webm\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-stop recordings/login-flow-2024-01-15.webm\nplaywright-cli video-stop recordings/checkout-test-run-42.webm\n```\n\n## Tracing vs Video\n\n| Feature  | Video                | Tracing                                  |\n| -------- | -------------------- | ---------------------------------------- |\n| Output   | WebM file            | Trace file (viewable in Trace Viewer)    |\n| Shows    | Visual recording     | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis                      |\n| Size     | Larger               | Smaller                                  |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n","isInternal":false,"tokens":265,"sizeBytes":1253},{"name":"SKILL.md","path":"examples/with-workspace/workspace/skills/playwright-cli/SKILL.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-workspace/workspace/skills/playwright-cli/SKILL.md","title":"playwright-cli","category":"anthropic-skill","format":"markdown","content":"---\nname: playwright-cli\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(playwright-cli:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\nplaywright-cli fill e5 \"user@example.com\"\nplaywright-cli drag e2 e8\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\nplaywright-cli snapshot --filename=after-click.yaml\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli network\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start\nplaywright-cli video-stop video.webm\n```\n\n### Install\n\n```bash\nplaywright-cli install --skills\nplaywright-cli install-browser\n```\n\n### Configuration\n\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n# Connect to browser via extension\nplaywright-cli open --extension\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n### Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli network\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Specific tasks\n\n- **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n- **Running Playwright code** [references/running-code.md](references/running-code.md)\n- **Browser session management** [references/session-management.md](references/session-management.md)\n- **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n- **Test generation** [references/test-generation.md](references/test-generation.md)\n- **Tracing** [references/tracing.md](references/tracing.md)\n- **Video recording** [references/video-recording.md](references/video-recording.md)\n","frontmatter":{"name":"playwright-cli","description":"Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.","allowed-tools":"Bash(playwright-cli:*)","allowedTools":["Bash(playwright-cli:*)"]},"allowedTools":["Bash(playwright-cli:*)"],"isInternal":false,"tokens":1555,"sizeBytes":6639},{"name":"overview.md","path":"website/docs/tools/overview.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/tools/overview.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Overview\n---\n\n# Tools & Toolkits\n\nVoltAgent allows you to extend the capabilities of your AI agents by providing them with **Tools**. Tools enable agents to interact with external APIs, perform calculations, access databases, or execute virtually any custom code. This guide covers how to define and use individual tools and the new `Toolkit` concept for managing related tools.\n\n## Defining a Single Tool\n\nThe most basic way to define a tool is using the `createTool` helper function (or instantiating the `Tool` class directly).\n\nA tool requires:\n\n- `name`: A unique name for the tool (used by the LLM to call it).\n- `description`: A clear description of what the tool does (used by the LLM to decide when to use it).\n- `parameters`: A Zod schema defining the input arguments the tool expects.\n- `execute`: An asynchronous function that contains the tool's logic, taking the validated arguments as input.\n- `outputSchema` (optional): A Zod schema defining the expected output format. When provided, the tool's output will be validated against this schema.\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define a simple weather tool\nconst getWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches the current weather for a given location.\",\n  parameters: z.object({\n    location: z.string().describe(\"The city and state, e.g., San Francisco, CA\"),\n  }),\n  execute: async ({ location }) => {\n    // In a real scenario, you would call a weather API here\n    console.log(`Fetching weather for ${location}...`);\n    if (location.toLowerCase().includes(\"tokyo\")) {\n      return { temperature: \"15°C\", condition: \"Cloudy\" };\n    }\n    return { temperature: \"22°C\", condition: \"Sunny\" };\n  },\n});\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that can fetch weather information.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeatherTool], // Add the tool to the agent\n});\n\n// Now the agent can use the 'get_weather' tool when asked about weather.\n```\n\n## Accessing Operation Context in Tools\n\nTools can access operation metadata, user context, and control mechanisms through the second parameter:\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Tool that uses operation context\nconst contextAwareWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches weather with user preferences\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  execute: async ({ location }, options) => {\n    // Access user-defined context\n    const units = options?.context?.get(\"preferredUnits\") || \"celsius\";\n    const userId = options?.userId;\n\n    // Use operation-scoped logger\n    options?.logger?.info(`Fetching weather for ${location} in ${units}`);\n\n    // Check abort signal\n    if (options?.abortSignal?.aborted) {\n      throw new Error(\"Request was cancelled\");\n    }\n\n    // Call weather API with user preferences\n    const response = await fetch(\n      `https://api.weather.com/current?city=${location}&units=${units}`,\n      { signal: options?.abortSignal }\n    );\n\n    return await response.json();\n  },\n});\n\n// Use the tool with context\nconst context = new Map();\ncontext.set(\"preferredUnits\", \"fahrenheit\");\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that respects user preferences\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [contextAwareWeatherTool],\n});\n\nconst response = await agent.generateText(\"What's the weather in Paris?\", {\n  userId: \"user123\",\n  context,\n});\n```\n\nThe `options` parameter includes:\n\n- **Operation metadata**: `operationId`, `userId`, `conversationId`\n- **User context**: `context` (Map for custom data)\n- **Control mechanisms**: `abortController`, `abortSignal`\n- **Logging**: `logger` (operation-scoped logger)\n- **AI SDK data**: `toolCallId`, `messages`\n\nFor more details, see the [Tools documentation](/docs/agents/tools#accessing-operation-context).\n\n## Tool Output Schema Validation\n\nVoltAgent supports optional output schema validation for tools. This feature ensures that tool outputs conform to a predefined structure, providing several benefits:\n\n- **Type Safety**: Tool outputs are typed based on the schema\n- **Runtime Validation**: Invalid outputs are caught immediately\n- **Error Recovery**: When validation fails, the LLM receives an error message and can retry with corrected output\n- **Consistency**: All tool responses follow the same structure\n- **Documentation**: Output schemas serve as API contracts\n\n### Example with Output Schema\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define the output schema\nconst weatherOutputSchema = z.object({\n  location: z.string(),\n  temperature: z.number(),\n  condition: z.enum([\"sunny\", \"cloudy\", \"rainy\", \"snowy\"]),\n  humidity: z.number().min(0).max(100),\n  forecast: z.object({\n    high: z.number(),\n    low: z.number(),\n    description: z.string(),\n  }),\n});\n\n// Create a tool with output validation\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather with forecast\",\n  parameters: z.object({\n    location: z.string().describe(\"City name\"),\n  }),\n  outputSchema: weatherOutputSchema, // Optional output schema\n  execute: async ({ location }) => {\n    // This output will be validated against weatherOutputSchema\n    return {\n      location,\n      temperature: 22,\n      condition: \"sunny\",\n      humidity: 65,\n      forecast: {\n        high: 25,\n        low: 18,\n        description: \"Clear skies throughout the day\",\n      },\n    };\n  },\n});\n```\n\n### How Output Validation Works\n\n1. When a tool is executed, its output is validated against the `outputSchema` if provided\n2. If validation succeeds, the validated output is returned\n3. If validation fails, an error object is returned to the LLM:\n   ```json\n   {\n     \"error\": true,\n     \"message\": \"Output validation failed: Expected number, received string\",\n     \"validationErrors\": [...],\n     \"actualOutput\": {...}\n   }\n   ```\n4. The LLM can see the validation error and potentially fix the issue by calling the tool again\n\n### Best Practices\n\n- Use output schemas for tools that need consistent response formats\n- Keep schemas focused and avoid overly complex nested structures\n- Use descriptive error messages in your schemas with `.describe()`\n- Consider making some fields optional with `.optional()` for flexibility\n- Output schemas are completely optional - tools without them work as before\n\n## Grouping Tools with Toolkits\n\nOften, several tools work together logically. For instance, tools for step-by-step reasoning (`think`, `analyze`) or a set of tools interacting with the same API. For these scenarios, VoltAgent provides **Toolkits**.\n\nA `Toolkit` allows you to:\n\n1.  **Group related tools:** Keep your tool management organized.\n2.  **Define shared instructions:** Provide common guidance to the LLM on how to use all tools within the toolkit.\n3.  **Control instruction injection:** Decide if the toolkit's shared instructions should be automatically added to the agent's system prompt.\n\n### Defining a Toolkit\n\nA `Toolkit` is an object with the following structure:\n\n```typescript\nimport { createTool, createToolkit, type Tool, type Toolkit } from \"@voltagent/core\";\n\nconst myCalculatorToolkit = createToolkit({\n  name: \"calculator_toolkit\",\n  description: \"Tools for performing basic arithmetic operations.\",\n  // Optional instructions for the LLM\n  instructions: `Use these tools for calculations. Always use 'add' for addition, 'subtract' for subtraction.`,\n  // Set to true to add the above instructions to the system prompt\n  addInstructions: true,\n  tools: [\n    createTool({\n      /* ... definition for 'add' tool ... */\n    }),\n    createTool({\n      /* ... definition for 'subtract' tool ... */\n    }),\n    // ... other calculator tools\n  ],\n});\n```\n\n**Important:** With the introduction of Toolkits, individual `Tool` instances no longer have their own `instructions` or `addInstructions` properties. Instructions are managed at the Toolkit level.\n\n### Adding Tools and Toolkits to an Agent\n\nThe `tools` option in the `Agent` constructor now accepts an array containing both individual `Tool` objects and `Toolkit` objects. The `ToolManager` handles both seamlessly.\n\n```typescript\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool, createToolkit, type Toolkit } from \"@voltagent/core\";\n// ... import other tools and toolkits ...\n\nconst agent = new Agent({\n  name: \"MultiToolAgent\",\n  instructions: \"An agent with various tools and toolkits.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [\n    getWeatherTool, // Add an individual tool\n    myCalculatorToolkit, // Add a toolkit\n    openai.tools.webSearch(), // Add a provider-defined tool\n    // ... other tools or toolkits\n  ],\n});\n```\n\n### Automatic Instructions\n\nWhen an agent is initialized, its `getSystemMessage` method checks all the `Toolkit`s provided in the `tools` array. If a `Toolkit` has `addInstructions: true` and defines an `instructions` string, those instructions will be automatically appended to the agent's base description, forming part of the final system prompt sent to the LLM.\n\n## Provider-defined Tools\n\nSome providers expose their own tools (via the Vercel AI SDK).\n\n- They can be standalone tools or live inside Toolkits and are subject to name-conflict checks when adding toolkits.\n- They are not executable on your server via the usual `Tool.execute` handler — the provider manages them.\n\n## Next Steps\n\n- See the [Reasoning Tools](/docs/tools/reasoning-tool/) documentation for a practical example of a pre-built toolkit.\n- Explore creating your own tools to connect agents to your specific data sources and APIs.\n","frontmatter":{"title":"Overview"},"isInternal":false,"tokens":2200,"sizeBytes":9797},{"name":"tool-routing.md","path":"website/docs/tools/tool-routing.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/tools/tool-routing.md","title":"Tools Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Tool Routing\n---\n\n# Tool Routing\n\nTool routing keeps the full tool pool hidden and exposes two system tools to the model:\n\n- `searchTools` returns tool metadata and schemas for a query.\n- `callTool` executes a tool by name with validated arguments.\n\nOnly `searchTools`, `callTool`, and any tools listed in `toolRouting.expose` are visible to the model.\n\n## How Tool Routing Works\n\n- Tool routing builds a hidden pool from `toolRouting.pool` or all registered tools (by default).\n- The model calls `searchTools` with the user request and receives tool details.\n- The model calls `callTool` with the exact tool name and schema-compliant arguments.\n- Pool tools stay hidden unless explicitly exposed.\n\n## Quick Setup With Embedding Search\n\nThis configuration enables embedding-based tool search and exposes only `searchTools` and `callTool` to the model.\n\n```ts\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    temperatureC: 22,\n    condition: \"sunny\",\n  }),\n});\n\nconst getTimeZone = createTool({\n  name: \"get_time_zone\",\n  description: \"Get the time zone offset for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    timeZone: \"UTC+1\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Tool Routing Agent\",\n  instructions:\n    \"When you need a tool, call searchTools with the user request, then call callTool with the exact tool name and schema-compliant arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeather, getTimeZone],\n  toolRouting: {\n    embedding: \"openai/text-embedding-3-small\",\n    topK: 2,\n  },\n});\n```\n\nNotes:\n\n- If `toolRouting.pool` is not set, the pool defaults to all tools registered in the agent.\n- If `toolRouting.expose` is not set, only `searchTools` and `callTool` are visible to the model.\n\n## Tool Pool and Exposed Tools\n\nUse `pool` to define the hidden tool set and `expose` to keep a subset visible.\n\n```ts\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({ location: z.string() }),\n  execute: async ({ location }) => ({ location, temperatureC: 22 }),\n});\n\nconst getStatus = createTool({\n  name: \"get_status\",\n  description: \"Return the service status\",\n  parameters: z.object({}),\n  execute: async () => ({ status: \"ok\" }),\n});\n\nconst agent = new Agent({\n  name: \"Support Agent\",\n  instructions:\n    \"Use searchTools to find tool schemas, then call callTool with validated arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    pool: [getWeather],\n    expose: [getStatus],\n  },\n});\n```\n\n## Enforcing Search Before Call\n\nBy default, `callTool` requires the tool to appear in a prior `searchTools` result.\nYou can disable this behavior:\n\n```ts\nconst agent = new Agent({\n  name: \"Relaxed Agent\",\n  instructions: \"Use searchTools before callTool when possible.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    pool: [\n      /* tools and toolkits */\n    ],\n    enforceSearchBeforeCall: false,\n  },\n});\n```\n\n## Embedding Configuration\n\n`toolRouting.embedding` accepts either a model string or an object with extra options.\n\n- `\"openai/text-embedding-3-small\"`\n- `\"text-embedding-3-small\"`\n\nProvider-qualified strings use the same registry and type list as agent model strings.\n\n```ts\nconst agent = new Agent({\n  name: \"Custom Embedding Agent\",\n  instructions:\n    \"Use searchTools to discover tool schemas, then call callTool with the right arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: {\n      model: \"openai/text-embedding-3-small\",\n      topK: 3,\n      toolText: (tool) => {\n        const tags = tool.tags?.join(\", \") ?? \"\";\n        return [tool.name, tool.description, tags].filter(Boolean).join(\"\\n\");\n      },\n    },\n  },\n});\n```\n\nThe embedding search caches vectors in memory. The cache is reset when the process restarts.\nA tool text change for a given tool name triggers a new embedding for that tool.\n\n## Per-Call Overrides\n\nYou can disable tool routing for a single call or provide a temporary routing config.\n\n```ts\nconst result = await agent.generateText(\"List tools\", {\n  toolRouting: false,\n});\n\nconst routed = await agent.generateText(\"What time is it in Berlin?\", {\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    topK: 1,\n  },\n});\n```\n\n## Hooks, Approval, and Error Handling\n\n- `needsApproval`, input/output guardrails, and tool hooks still run when tools are executed via `callTool`.\n- If a tool is not in the pool, `callTool` returns a tool error.\n- `callTool` validates arguments against the target schema and surfaces validation errors for retries.\n- Provider tools and MCP tools can be pooled; execution still runs through approvals and hooks.\n\n## Observability\n\nTool routing adds spans for selection and embedding steps:\n\n- `tool.search.selection:*` spans include `tool.search.candidates`, `tool.search.selection.count`, and selected names.\n- `tool.search.embedding:*` spans include `embedding.model`, `embedding.dimensions`, and cache hit/miss counts.\n\nThese spans appear under the `searchTools` tool span in the execution trace.\n\n## PlanAgent\n\n`PlanAgent` accepts `toolRouting` in its options. It passes routing config to its internal agent.\n\n```ts\nimport { PlanAgent } from \"@voltagent/core\";\n\nconst agent = new PlanAgent({\n  name: \"Planning Agent\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n  },\n});\n```\n","frontmatter":{"title":"Tool Routing"},"isInternal":false,"tokens":1420,"sizeBytes":5841},{"name":"a2a-server.md","path":"website/docs/agents/a2a/a2a-server.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/a2a/a2a-server.md","title":"Subagent: a2a-server","category":"subagent-persona","format":"markdown","content":"---\ntitle: A2A Server\ndescription: Step-by-step guide for exposing VoltAgent over the Agent-to-Agent protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent A2A Server Quick Start\n\nFollow the steps below to let other agents talk to your VoltAgent application over the Agent-to-Agent (A2A) protocol.\n\n## Install Packages\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n</Tabs>\n\n`@voltagent/a2a-server` exposes VoltAgent agents via JSON-RPC. `@voltagent/server-hono` wires the routes into the default Hono server (feel free to swap it out for another framework later).\n\n## Create An A2A Server Instance\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  description: \"VoltAgent A2A example\",\n  provider: {\n    organization: \"Acme\",\n    url: \"https://acme.example\",\n  },\n});\n```\n\nThe server metadata feeds the discovery card served from `/.well-known/{serverId}/agent-card.json`. The card's `url` field points to `/a2a/{serverId}`, and when the card is fetched over HTTP VoltAgent returns that URL as an absolute address.\n\n## Register The Server With VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { a2aServer } from \"./a2a/server\";\nimport { assistant } from \"./agents/assistant\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  a2aServers: {\n    a2aServer,\n  },\n  server: honoServer({ port: 3141 }),\n});\n```\n\nWith this in place, VoltAgent automatically exposes:\n\n- `GET /.well-known/{serverId}/agent-card.json`\n- `POST /a2a/{serverId}`\n\nThe JSON-RPC handler accepts `message/send`, `message/stream`, `tasks/get`, and `tasks/cancel` requests.\n\n## Available Endpoints\n\n| Method | Path                                      | Description                                                                                                      |\n| ------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |\n| `GET`  | `/.well-known/{serverId}/agent-card.json` | Returns the discovery card for the specified A2A server. The card's `url` points to `/a2a/{serverId}`.           |\n| `POST` | `/a2a/{serverId}`                         | Accepts JSON-RPC 2.0 requests. Supported methods: `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`. |\n\nExample JSON-RPC payload for `message/send`:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    \"message\": {\n      \"kind\": \"message\",\n      \"role\": \"user\",\n      \"messageId\": \"msg-1\",\n      \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }]\n    }\n  }\n}\n```\n\nYou can include an optional `context` object alongside the JSON-RPC payload to forward metadata such as `userId`, `sessionId`, or arbitrary `metadata` fields:\n\n```json\n{\n  \"context\": {\n    \"userId\": \"user-42\",\n    \"sessionId\": \"chat-1\",\n    \"metadata\": { \"tenant\": \"acme\" }\n  },\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    /* ... */\n  }\n}\n```\n\nThe context values surface inside the agent call so you can track callers or reuse an existing conversation identifier. `context.userId` is forwarded directly as the VoltAgent `userId` option, while `sessionId` and any nested `metadata` keys are merged into the agent's `options.context` map together with the request-level `params.metadata` and per-message `message.metadata` fields.\n\nIf you cannot modify the JSON body, send the same object as a JSON-encoded query parameter. VoltAgent reads `context` (or the legacy `runtimeContext`) from the query string and merges it with the body-provided values, letting the body override duplicated keys while metadata objects are combined.\n\n```bash\ncurl \\\n  -X POST \"http://localhost:3141/a2a/support-agent?context=%7B%22userId%22%3A%22user-42%22%7D\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": { \"message\": { \"kind\": \"message\", \"role\": \"user\", \"messageId\": \"msg-1\", \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }] } }\n}'\n```\n\n## Configure Which Agents Are Exposed (Optional)\n\nYou can publish agents that are not part of the primary VoltAgent registry or filter the combined list before answering A2A requests:\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\nimport { assistant } from \"./agents/assistant\";\nimport { shadowAgent } from \"./agents/shadow\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  agents: {\n    \"shadow-agent\": shadowAgent,\n  },\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n});\n```\n\nConfigured agents appear alongside registry entries. The filter receives the merged list and can drop or reorder entries before discovery and JSON-RPC calls run.\n\nVoltAgent already formats Server-Sent Events when you use `@voltagent/server-hono`, so no extra work is needed for streaming clients. If you forward chunks manually, serialize each payload with `safeStringify` and prepend `\\x1E` before the JSON string.\n\n## Provide A Different Task Store (Optional)\n\nBy default, `A2AServer` uses an in-memory store. Provide your own implementation (Redis, Postgres, KV…) by implementing the `TaskStore` interface:\n\n```ts title=\"src/a2a/redis-store.ts\"\nimport { createClient } from \"redis\";\nimport type { TaskRecord, TaskStore } from \"@voltagent/a2a-server\";\n\nexport class RedisTaskStore implements TaskStore {\n  private client = createClient({ url: process.env.REDIS_URL });\n\n  async load({ agentId, taskId }: { agentId: string; taskId: string }): Promise<TaskRecord | null> {\n    const raw = await this.client.get(`${agentId}::${taskId}`);\n    return raw ? (JSON.parse(raw) as TaskRecord) : null;\n  }\n\n  async save({ agentId, data }: { agentId: string; data: TaskRecord }): Promise<void> {\n    await this.client.set(`${agentId}::${data.id}`, JSON.stringify(data));\n  }\n}\n```\n\nPass the store during initialisation:\n\n```ts title=\"src/a2a/server.ts\"\nimport { RedisTaskStore } from \"./redis-store\";\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { assistant } from \"../agents/assistant\";\n\nconst redisTaskStore = new RedisTaskStore();\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n});\n\nexport const voltAgent = new VoltAgent({\n  agents: { assistant },\n  a2aServers: { a2aServer },\n  server: honoServer({ port: 3141 }),\n});\n\na2aServer.initialize({\n  agentRegistry: voltAgent.agentRegistry,\n  taskStore: redisTaskStore,\n});\n```\n\nRemember to implement TTL or pruning inside your custom store if you need to cap history growth.\n\n## Run The Example Smoke Test\n\nCreate the example project and run its smoke test to ensure everything is wired correctly:\n\n```bash\nnpm create voltagent-app@latest -- --example with-a2a-server\ncd with-a2a-server\npnpm test:smoke\n```\n\nThe test sends a `message/send`, streams a `message/stream`, and exercises `tasks/cancel` to verify streaming cancellation propagation.\n\n## Troubleshooting checklist\n\n- **404 for discovery card**: ensure the `serverId` in `/.well-known/{serverId}/agent-card.json` matches `A2AServer({ id })`, or the normalized `name` when `id` is omitted. It does not come from `VoltAgent({ agents: { ... } })` or the `a2aServers` map key.\n- **Unexpected JSON in SSE**: confirm you are stripping the `\\x1E` prefix before parsing the JSON payload.\n- **Cancellation not propagating**: verify you call `tasks/cancel` with the task ID from the stream and that your TaskStore preserves the `activeCancellations` set.\n","frontmatter":{"title":"A2A Server","description":"Step-by-step guide for exposing VoltAgent over the Agent-to-Agent protocol."},"isInternal":false,"tokens":2102,"sizeBytes":8162},{"name":"cancellation.md","path":"website/docs/agents/cancellation.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/cancellation.md","title":"Subagent: cancellation","category":"subagent-persona","format":"markdown","content":"---\ntitle: Cancellation\nslug: /agents/cancellation\n---\n\n# Cancellation\n\nVoltAgent implements cancellation through the standard `AbortController` API. Pass an `AbortController` instance to agent methods to stop LLM generation, tool execution, and multi-agent workflows.\n\n## Basic Cancellation\n\nCreate an `AbortController` and pass it to agent methods:\n\n```typescript\nimport { Agent, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst abortController = new AbortController();\n\n// Cancel after 5 seconds\nsetTimeout(() => {\n  abortController.abort(\"Timeout: Operation took too long\");\n}, 5000);\n\ntry {\n  const response = await agent.generateText(\"Write a detailed analysis...\", {\n    abortController,\n  });\n  console.log(response.text);\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Operation cancelled:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## How Cancellation Works\n\nWhen you provide an `AbortController` to an agent method:\n\n1. The signal is passed to the LLM provider\n2. Tools receive the `OperationContext` containing the `abortController`\n3. Subagents inherit the parent's `abortController`\n4. All operations check the signal state before proceeding\n\nCancellation propagates through the entire operation chain for clean shutdown at every level.\n\n:::note Parameter Deprecation\nThe `signal` parameter is deprecated. Use `abortController` instead. The `signal` parameter will be removed in a future version.\n:::\n\n## Streaming Cancellation\n\nCancellation works with streaming responses:\n\n```typescript\nconst abortController = new AbortController();\n\n// User can cancel anytime\ndocument.getElementById(\"stop-btn\")?.addEventListener(\"click\", () => {\n  abortController.abort(\"User requested stop\");\n});\n\nconst response = await agent.streamText(\"Generate a long report...\", {\n  abortController,\n});\n\ntry {\n  for await (const chunk of response.textStream) {\n    process.stdout.write(chunk);\n  }\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"\\nStream cancelled\");\n  }\n}\n```\n\nWith `fullStream`, you receive detailed cancellation feedback:\n\n```typescript\nconst response = await agent.streamText(\"Complex task...\", {\n  abortController,\n});\n\nfor await (const event of response.fullStream) {\n  if (event.type === \"error\" && isAbortError(event.error)) {\n    console.log(\"Cancelled during:\", event.context);\n    break;\n  }\n  // Process other events\n}\n```\n\n## Tool Cancellation\n\nTools receive the `OperationContext` as the second parameter. Access `abortController` through the context to respond to or trigger cancellation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst dataProcessingTool = createTool({\n  name: \"process_data\",\n  description: \"Process large datasets\",\n  parameters: z.object({\n    dataset: z.string(),\n    operation: z.string(),\n  }),\n  execute: async (args, context) => {\n    const abortController = context?.abortController;\n    const signal = abortController?.signal;\n\n    // Check if already aborted\n    if (signal?.aborted) {\n      return { error: \"Operation was already cancelled\" };\n    }\n\n    // Tool can trigger abort based on conditions\n    if (args.dataset === \"restricted\") {\n      abortController?.abort(\"Access to restricted dataset denied\");\n      return { error: \"Dataset access denied\" };\n    }\n\n    // Pass signal to async operations\n    try {\n      const response = await fetch(`/api/process/${args.dataset}`, {\n        method: \"POST\",\n        body: JSON.stringify({ operation: args.operation }),\n        signal,\n      });\n\n      return await response.json();\n    } catch (error) {\n      if (error.name === \"AbortError\") {\n        return { error: \"Processing cancelled\" };\n      }\n      throw error;\n    }\n  },\n});\n```\n\n## Multi-Agent Cancellation\n\nIn supervisor-subagent architectures, the abort signal propagates to all subagents:\n\n```typescript\nconst researcher = new Agent({\n  name: \"Researcher\",\n  instructions: \"Research topics thoroughly\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst writer = new Agent({\n  name: \"Writer\",\n  instructions: \"Write detailed content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate research and writing\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researcher, writer],\n});\n\nconst abortController = new AbortController();\n\n// Cancel supervisor and any active subagent operations\nsetTimeout(() => {\n  abortController.abort(\"Deadline reached\");\n}, 10000);\n\nconst response = await supervisor.streamText(\"Research and write about quantum computing\", {\n  abortController,\n});\n```\n\nWhen the supervisor is cancelled:\n\n- Any active `delegate_task` operations stop\n- Subagents inherit the parent's `abortController`\n- All tool executions within subagents are cancelled\n- The entire workflow stops\n\n## Timeout Implementation\n\nImplement timeouts for operations:\n\n```typescript\nconst abortController = new AbortController();\n\n// Set a timeout to abort after 30 seconds\nconst timeoutId = setTimeout(() => {\n  abortController.abort(\"Operation timeout - exceeded 30 seconds\");\n}, 30000);\n\ntry {\n  const response = await agent.generateText(\"Complex analysis...\", {\n    abortController,\n  });\n\n  // Clear timeout if operation completes\n  clearTimeout(timeoutId);\n  console.log(response.text);\n} catch (error) {\n  clearTimeout(timeoutId); // Always clear the timeout\n\n  if (isAbortError(error)) {\n    console.log(\"Operation timed out:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## Error Detection with Hooks\n\nAgent hooks can detect and handle abort errors:\n\n```typescript\nimport { createHooks, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ error, context }) => {\n      if (isAbortError(error)) {\n        // Handle abort - error.name === \"AbortError\"\n        console.log(\"Operation aborted:\", error.message);\n\n        // Cleanup resources\n        await cleanup(context);\n\n        // Log metrics\n        await logAbortMetrics({\n          operationId: context.operationId,\n          reason: error.message,\n          duration: Date.now() - context.startTime,\n        });\n      } else if (error) {\n        // Handle other errors\n        console.error(\"Operation failed:\", error);\n      }\n    },\n  }),\n});\n```\n\n## REST API Cancellation\n\nClients can cancel REST API requests by closing the connection:\n\n```typescript\n// Client-side cancellation\nconst controller = new AbortController();\n\nconst response = await fetch(\"http://localhost:3141/agents/my-agent/stream\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    input: \"Generate a comprehensive report\",\n  }),\n  signal: controller.signal,\n});\n\n// Cancel button\ndocument.getElementById(\"cancel\")?.addEventListener(\"click\", () => {\n  controller.abort();\n});\n\n// Process stream\nconst reader = response.body?.getReader();\nif (reader) {\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      // Process chunk\n    }\n  } catch (error) {\n    if (error.name === \"AbortError\") {\n      console.log(\"Stream cancelled\");\n    }\n  }\n}\n```\n\nThe server detects client disconnection and aborts the agent operation.\n\n## Cancellation States\n\nOperations can be in different states regarding cancellation:\n\n```typescript\nconst abortController = new AbortController();\n\n// Check if aborted\nif (abortController.signal.aborted) {\n  console.log(\"Already aborted\");\n}\n\n// Listen for abort\nabortController.signal.addEventListener(\"abort\", () => {\n  console.log(\"Just aborted:\", abortController.signal.reason);\n});\n\n// Trigger abort with reason\nabortController.abort(\"User cancelled\");\n```\n\n## Implementation Patterns\n\n### Graceful Shutdown\n\nFor individual agent operations, manually handle shutdown signals:\n\n```typescript\nasync function processWithGracefulShutdown(agent: Agent, input: string) {\n  const abortController = new AbortController();\n\n  // Handle shutdown signals\n  const shutdownHandler = () => {\n    console.log(\"Shutting down...\");\n    abortController.abort(\"System shutdown\");\n  };\n\n  process.on(\"SIGINT\", shutdownHandler);\n  process.on(\"SIGTERM\", shutdownHandler);\n\n  try {\n    const response = await agent.generateText(input, {\n      abortController,\n    });\n    return response.text;\n  } finally {\n    process.off(\"SIGINT\", shutdownHandler);\n    process.off(\"SIGTERM\", shutdownHandler);\n  }\n}\n```\n\n### VoltAgent-Level Shutdown\n\nWhen using the `VoltAgent` class with a server, shutdown is handled automatically:\n\n```typescript\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst voltAgent = new VoltAgent({\n  agents: { myAgent },\n  server: honoServer({ port: 3141 }),\n});\n\n// VoltAgent automatically registers SIGINT/SIGTERM handlers\n// For programmatic shutdown:\nawait voltAgent.shutdown(); // Stops server, workflows, and telemetry\n```\n\nVoltAgent automatically registers SIGINT/SIGTERM handlers that clean up the server, suspend workflows, and shutdown telemetry. See [Server Architecture - Graceful Shutdown](../api/server-architecture.md#graceful-shutdown) for details.\n\n### Concurrent Operations with Cancellation\n\n```typescript\nasync function processMultiple(agent: Agent, inputs: string[]) {\n  const abortController = new AbortController();\n\n  // Cancel all if any fails\n  const promises = inputs.map(async (input) => {\n    try {\n      return await agent.generateText(input, { abortController });\n    } catch (error) {\n      abortController.abort(\"One operation failed\");\n      throw error;\n    }\n  });\n\n  return Promise.all(promises);\n}\n```\n\n## Next Steps\n\n- Learn about [Tools](./tools.md) and how they handle cancellation\n- Explore [Sub-Agents](./subagents.md) for multi-agent cancellation patterns\n- See [Hooks](./hooks.md) for cancellation detection and handling\n","frontmatter":{"title":"Cancellation","slug":"/agents/cancellation"},"isInternal":false,"tokens":2208,"sizeBytes":10046},{"name":"context.md","path":"website/docs/agents/context.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/context.md","title":"Subagent: context","category":"subagent-persona","format":"markdown","content":"---\ntitle: Operation Context (context)\nslug: /agents/context\ndescription: Pass custom data through agent operations using context.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Operation Context (`context`)\n\nThe `context` field in `OperationContext` is a Map that passes custom data throughout a single agent operation. All components (hooks, tools, retrievers, sub-agents) can read and write to this shared data structure during execution.\n\n## Basic Concept\n\nHere's how `context` flows through an agent operation:\n\n```\nYou → Agent → Hooks → Tools → Retrievers → Memory → Sub-Agents\n     ↑                                                        ↓\n     ← ← ← ← ← ← context flows everywhere ← ← ← ← ← ← ← ← ←\n```\n\n## Initialize context\n\nYou can provide initial data in two ways:\n\n### Method 1: Set Default Context in Constructor\n\nSet default `context` when creating the agent. This context is used for all operations unless overridden at execution time:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\n// Set default context at agent creation\nconst defaultContext = new Map();\ndefaultContext.set(\"environment\", \"production\");\ndefaultContext.set(\"projectId\", \"my-project\");\n\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  context: defaultContext, // Default context for all operations\n});\n\n// Uses default context automatically\nconst response1 = await agent.generateText(\"Hello!\");\nconsole.log(\"Environment:\", response1.context?.get(\"environment\")); // \"production\"\n\n// Override with execution context (replaces default completely)\nconst response2 = await agent.generateText(\"Debug this\", {\n  context: new Map([\n    [\"environment\", \"development\"],\n    [\"projectId\", \"my-project\"],\n  ]),\n});\nconsole.log(\"Environment:\", response2.context?.get(\"environment\")); // \"development\"\nconsole.log(\"Project ID:\", response2.context?.get(\"projectId\")); // \"my-project\"\n```\n\n### Method 2: Pass Context During Execution\n\nPass `context` when calling the agent to provide operation-specific data:\n\n```typescript\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Pass context when calling the agent\nconst executionContext = new Map();\nexecutionContext.set(\"language\", \"English\");\n\nconst response = await agent.generateText(\"Hello!\", {\n  context: executionContext,\n});\n// Now you can access the data from the response\nconsole.log(\"Language:\", response.context?.get(\"language\"));\n```\n\n## Sending User Information\n\nYou can pass user information to the agent using the `context`. This information is automatically picked up by the tracing system and associated with the trace.\n\nSupported fields:\n\n- `user.name`: User's full name\n- `user.email`: User's email address\n- `user.avatar`: User's avatar URL (optional, falls back to Gravatar using email if not provided)\n- `user.metadata`: Additional metadata object\n\n<Tabs>\n  <TabItem value=\"sdk\" label=\"SDK\" default>\n\nWhen using the SDK, pass user information in the `context` Map:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({ ... });\n\nconst context = new Map();\ncontext.set(\"user.name\", \"John Doe\");\ncontext.set(\"user.email\", \"john@example.com\");\ncontext.set(\"user.avatar\", \"https://example.com/avatar.jpg\");\ncontext.set(\"user.metadata\", { plan: \"pro\" });\n\nawait agent.generateText(\"Hello\", { userId: \"user_123\", context });\n```\n\n  </TabItem>\n  <TabItem value=\"api\" label=\"REST API\">\n\nWhen using the REST API, you can pass user information in the `context` object. You can use either a nested `user` object or flat keys.\n\n**Option 1: Nested User Object**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user\": {\n      \"name\": \"John Doe\",\n      \"email\": \"john@example.com\",\n      \"avatar\": \"https://example.com/avatar.jpg\",\n      \"metadata\": { \"plan\": \"pro\" }\n    }\n  }\n}\n```\n\n**Option 2: Flat Keys**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user.name\": \"John Doe\",\n    \"user.email\": \"john@example.com\",\n    \"user.avatar\": \"https://example.com/avatar.jpg\",\n    \"user.metadata\": { \"plan\": \"pro\" }\n  }\n}\n```\n\n  </TabItem>\n</Tabs>\n\n## Hooks Access context\n\nHooks can read and write to `context`:\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"HookAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Read data that was passed in\n      const language = context.context.get(\"language\");\n      console.log(`Starting operation for language: ${language}`);\n\n      // Add new data\n      context.context.set(\"requestId\", `req-${Date.now()}`);\n      context.context.set(\"startTime\", new Date().toISOString());\n    },\n    onEnd: (context) => {\n      // Read data from context\n      const requestId = context.context.get(\"requestId\");\n      const startTime = context.context.get(\"startTime\");\n      console.log(`Request ${requestId} completed (started at ${startTime})`);\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\n\nawait agent.generateText(\"Hello!\", { context });\n```\n\n## Tools Access Context\n\nTools receive the full operation context through the second parameter (`options`), which includes all `OperationContext` fields directly accessible:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst loggerTool = createTool({\n  name: \"log_message\",\n  description: \"Logs a message with user context\",\n  parameters: z.object({\n    message: z.string(),\n  }),\n  execute: async ({ message }, options) => {\n    // Access user-defined context directly\n    const language = options?.context?.get(\"language\");\n    const requestId = options?.context?.get(\"requestId\");\n\n    // Access operation metadata\n    const userId = options?.userId;\n    const operationId = options?.operationId;\n\n    // Use the operation-scoped logger\n    options?.logger?.info(`[${requestId}] User ${userId}: ${message}`);\n\n    console.log(`[${requestId}] Language ${language}: ${message}`);\n\n    // Write to context\n    const ctx = options?.context;\n    if (ctx) {\n      const logs = ctx.get(\"logs\") || [];\n      logs.push({\n        message,\n        timestamp: new Date().toISOString(),\n        userId,\n        operationId,\n      });\n      ctx.set(\"logs\", logs);\n    }\n\n    return `Message logged for language ${language}`;\n  },\n});\n\nconst agentWithTool = new Agent({\n  name: \"ToolAgent\",\n  model: \"openai/gpt-4o\",\n  tools: [loggerTool],\n  instructions: \"Use the log_message tool to log what the user says.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\ncontext.set(\"requestId\", \"req-456\");\n\nconst response = await agentWithTool.generateText(\"Log this: Hello world!\", {\n  context,\n  userId: \"user123\",\n});\n\n// Check what was logged\nconst logs = response.context?.get(\"logs\");\nconsole.log(\"All logs:\", logs);\n```\n\n### What's Available in Tool Options?\n\nThe `options` parameter in tool `execute` functions combines `ToolExecuteOptions` and `Partial<OperationContext>`:\n\n**From OperationContext:**\n\n- `operationId` - Unique operation identifier\n- `userId` - User identifier (if provided)\n- `conversationId` - Conversation identifier (if provided)\n- `workspace` - Workspace instance configured on the agent (if available)\n- `context` - User-defined context Map (read/write)\n- `systemContext` - Internal system context Map\n- `logger` - Operation-scoped logger with full context\n- `abortController` - For operation cancellation\n- `traceContext` - OpenTelemetry trace context\n- `input` - Original input to the agent\n- `isActive` - Whether operation is still active\n\n**Tool-specific context (from `toolContext?` - optional):**\n\n> **Note:** `toolContext` is always populated when your tool is called from a VoltAgent agent. It may be `undefined` when called externally.\n\n- `toolContext?.name` - Name of the tool being executed\n- `toolContext?.callId` - Unique identifier for this tool call (from AI SDK)\n- `toolContext?.messages` - Message history at tool call time (from AI SDK)\n- `toolContext?.abortSignal` - Abort signal for cancellation (from AI SDK)\n\n```typescript\n// Example: Authorization check using context\nconst sensitiveDataTool = createTool({\n  name: \"get_sensitive_data\",\n  description: \"Retrieves sensitive user data\",\n  parameters: z.object({\n    dataType: z.string(),\n  }),\n  execute: async ({ dataType }, options) => {\n    // Check user role from context\n    const userRole = options?.context?.get(\"userRole\");\n\n    if (userRole !== \"admin\") {\n      throw new Error(\"Unauthorized: Admin role required\");\n    }\n\n    // Log access with full context including tool name\n    const toolName = options?.toolContext?.name;\n    options?.logger?.warn(`${toolName}: Sensitive data accessed: ${dataType}`, {\n      userId: options?.userId,\n      operationId: options?.operationId,\n    });\n\n    return await fetchSensitiveData(dataType);\n  },\n});\n```\n\n## Access Input and Output in Context\n\nThe `OperationContext` provides access to both the input and output of the current operation, making debugging and observability much easier.\n\n### Accessing Input\n\nThe `input` field contains the original input provided to the agent operation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst logTool = createTool({\n  name: \"log_input\",\n  description: \"Logs the original user input\",\n  parameters: z.object({}),\n  execute: async (args, options) => {\n    const input = options?.operationContext?.input;\n    console.log(\"Original input:\", input);\n    // input can be: string, UIMessage[], or BaseMessage[]\n\n    return \"Input logged successfully\";\n  },\n});\n```\n\n### Accessing Output\n\nThe `output` field contains the generated response (only available after generation completes, mainly useful in hooks):\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onEnd: async ({ context }) => {\n    // Access both input and output\n    console.log(\"User asked:\", context.input);\n    console.log(\"Agent responded:\", context.output);\n\n    // Output type depends on the method used:\n    // - generateText/streamText: string\n    // - generateObject/streamObject: object\n\n    // Log complete interaction\n    const interaction = {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    };\n\n    console.log(\"Complete interaction:\", interaction);\n  },\n});\n```\n\n### Use Cases\n\nThe `input` and `output` fields are particularly useful for:\n\n- **Audit Logging**: Track what users asked and what the agent responded\n- **Analytics**: Analyze input/output patterns across conversations\n- **Debugging**: Trace issues by comparing input to output\n- **Quality Assurance**: Monitor agent responses for quality control\n- **Custom Metrics**: Calculate response time, token efficiency, etc.\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst auditHooks = createHooks({\n  onEnd: async ({ context, output }) => {\n    // Log complete audit trail\n    await auditLog.save({\n      timestamp: context.startTime,\n      operationId: context.operationId,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      input: context.input,\n      output: context.output,\n      finalStepUsage: output?.usage,\n      totalUsage: output?.totalUsage,\n    });\n  },\n});\n\nconst auditedAgent = new Agent({\n  name: \"Audited Assistant\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  hooks: auditHooks,\n});\n```\n\n## OperationContext Properties\n\nThe `OperationContext` type (defined in [types.ts](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/types.ts)) contains multiple properties beyond the user-managed `context` Map. Understanding these properties helps with debugging, observability, and advanced use cases.\n\n### User-Managed Fields\n\nThese fields are set by you and used to identify and track operations:\n\n```typescript\ninterface OperationContext {\n  // User data storage - read and write custom values\n  readonly context: Map<string | symbol, unknown>;\n\n  // Optional user identifier\n  userId?: string;\n\n  // Optional conversation identifier\n  conversationId?: string;\n\n  // Workspace configured on the agent (if any)\n  workspace?: Workspace;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst response = await agent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n  context: new Map([[\"language\", \"en\"]]),\n});\n\n// Access in hooks or tools\nconsole.log(operationContext.userId); // \"user-123\"\nconsole.log(operationContext.conversationId); // \"conv-456\"\nconsole.log(operationContext.context.get(\"language\")); // \"en\"\nconsole.log(operationContext.workspace?.id); // e.g. \"demo-workspace\"\n```\n\n### Operation Metadata (Read-Only)\n\nThese fields are automatically set by the framework and provide metadata about the current operation:\n\n```typescript\ninterface OperationContext {\n  // Unique identifier for this operation\n  readonly operationId: string;\n\n  // When the operation started\n  startTime: Date;\n\n  // Whether the operation is still active\n  isActive: boolean;\n\n  // Parent agent ID if this is a sub-agent execution\n  parentAgentId?: string;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst agent = new Agent({\n  name: \"TrackedAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ context }) => {\n      const duration = Date.now() - context.startTime.getTime();\n      console.log(`Operation ${context.operationId} took ${duration}ms`);\n\n      if (context.parentAgentId) {\n        console.log(`Executed as sub-agent of ${context.parentAgentId}`);\n      }\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n```\n\n### System Fields (Advanced)\n\nThese fields are used internally by the framework. Most users don't need to interact with them directly:\n\n```typescript\ninterface OperationContext {\n  // Internal state management (used by framework)\n  readonly systemContext: Map<string | symbol, unknown>;\n\n  // Scoped logger with operation context\n  logger: Logger;\n\n  // OpenTelemetry span management\n  traceContext: AgentTraceContext;\n\n  // Full conversation history including tool calls\n  conversationSteps?: StepWithContent[];\n\n  // Cancellation control\n  abortController: AbortController;\n\n  // Error to throw when operation is aborted\n  cancellationError?: CancellationError;\n\n  // Bridge for requesting user input (human-in-the-loop)\n  elicitation?: (request: unknown) => Promise<unknown>;\n}\n```\n\n**System Context vs User Context:**\n\n- **`context`**: Your data - use this for sharing information between components\n- **`systemContext`**: Framework internals - stores stream writers, internal state, etc.\n\nThe framework uses `systemContext` to pass internal state between components. For example, when a sub-agent streams data, the parent agent's stream writer is stored in `systemContext` (see [subagent/index.ts:145-148](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/subagent/index.ts#L145-L148)).\n\n### Input/Output (Automatically Set)\n\nThese fields capture the operation's input and output:\n\n```typescript\ninterface OperationContext {\n  // Original input provided to the agent\n  input?: string | UIMessage[] | BaseMessage[];\n\n  // Generated response (text or object)\n  output?: string | object;\n}\n```\n\nThese fields are documented in detail in the \"Access Input and Output in Context\" section below.\n\n### Operation Lifecycle Example\n\nHere's how to use metadata fields for observability:\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst observableAgent = new Agent({\n  name: \"ObservableAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: ({ context }) => {\n      // Use the scoped logger\n      context.logger.info(\"Operation started\", {\n        operationId: context.operationId,\n        userId: context.userId,\n        conversationId: context.conversationId,\n      });\n\n      // Store start metrics\n      context.context.set(\"metrics\", {\n        startTime: context.startTime,\n        operationId: context.operationId,\n      });\n    },\n    onEnd: ({ context, output }) => {\n      const metrics = context.context.get(\"metrics\") as any;\n      const duration = Date.now() - context.startTime.getTime();\n\n      context.logger.info(\"Operation completed\", {\n        operationId: context.operationId,\n        duration,\n        inputType: typeof context.input,\n        outputType: typeof context.output,\n        isActive: context.isActive,\n      });\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// The logger automatically includes userId, conversationId, and operationId\nconst response = await observableAgent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n});\n```\n\n### Cancellation with AbortController\n\nThe `abortController` field allows operation cancellation:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"CancellableAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Create an abort controller\nconst abortController = new AbortController();\n\n// Pass the signal to the agent\nconst responsePromise = agent.generateText(\"Write a long story\", {\n  abortSignal: abortController.signal,\n});\n\n// Cancel after 1 second\nsetTimeout(() => {\n  abortController.abort(\"User cancelled\");\n}, 1000);\n\ntry {\n  const response = await responsePromise;\n} catch (error) {\n  if (error.name === \"AbortError\") {\n    console.log(\"Operation was cancelled\");\n  }\n}\n```\n\nThe agent creates an internal `AbortController` and cascades the external signal to it (see [agent.ts:3385-3395](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3385-L3395)). Sub-agents automatically inherit the parent's abort controller.\n\n## Retrievers Store References\n\nRetrievers can store source information in `context`:\n\n```typescript\nimport { BaseRetriever } from \"@voltagent/core\";\n\nclass SimpleRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    // Simulate finding documents\n    const foundDocs = [\n      { title: \"VoltAgent Guide\", url: \"https://docs.example.com\" },\n      { title: \"Agent Tutorial\", url: \"https://tutorial.example.com\" },\n    ];\n\n    // Store references in context\n    if (options?.context) {\n      options.context.set(\"references\", foundDocs);\n      options.context.set(\"searchQuery\", input);\n    }\n\n    // Return content for LLM\n    return foundDocs.map((doc) => `${doc.title}: Some helpful content...`).join(\"\\n\");\n  }\n}\n\nconst agentWithRetriever = new Agent({\n  name: \"RetrievalAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new SimpleRetriever(),\n  instructions: \"Answer using retrieved information.\",\n});\n\n// Usage\nconst response = await agentWithRetriever.generateText(\"How do I use VoltAgent?\");\n\nconsole.log(\"Answer:\", response.text);\nconsole.log(\"Search query:\", response.context?.get(\"searchQuery\"));\nconsole.log(\"References:\", response.context?.get(\"references\"));\n```\n\n## Sub-Agents Automatically Inherit Context\n\nWhen a supervisor delegates to sub-agents, the parent's `OperationContext` is passed down. The sub-agent receives the same `context` Map reference (not a copy), so modifications are visible to both parent and sub-agent.\n\n```typescript\n// Worker agent - automatically receives supervisor's context\nconst workerAgent = new Agent({\n  name: \"WorkerAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Automatically gets context from supervisor\n      const projectId = context.context.get(\"projectId\");\n      const language = context.context.get(\"language\");\n      console.log(`Worker starting for project ${projectId}, language ${language}`);\n\n      // Can add its own data too\n      context.context.set(\"workerStartTime\", new Date().toISOString());\n    },\n  }),\n  instructions: \"You are a worker that processes tasks.\",\n});\n\n// Supervisor agent\nconst supervisorAgent = new Agent({\n  name: \"SupervisorAgent\",\n  model: \"openai/gpt-4o\",\n  subAgents: [workerAgent],\n  hooks: createHooks({\n    onStart: (context) => {\n      // Set up project context\n      context.context.set(\"projectId\", `project-${Date.now()}`);\n      context.context.set(\"supervisorId\", \"supervisor-001\");\n    },\n  }),\n  instructions: \"You supervise tasks. Delegate work to WorkerAgent when needed.\",\n});\n\n// Usage\nconst initialContext = new Map();\ninitialContext.set(\"language\", \"English\");\ninitialContext.set(\"priority\", \"high\");\n\nconst response = await supervisorAgent.generateText(\"Please delegate this task to the worker\", {\n  context: initialContext,\n});\n\n// Final context includes data from both supervisor and worker\nconsole.log(\"Project ID:\", response.context?.get(\"projectId\"));\nconsole.log(\"Worker start time:\", response.context?.get(\"workerStartTime\"));\n```\n\n### Context Inheritance Priority\n\nWhen creating an `OperationContext`, the framework follows this priority (see [agent.ts:3343-3371](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3343-L3371)):\n\n1. **Parent context** (if sub-agent): Use parent's context Map reference\n2. **Runtime context** (if provided at execution): Use the provided Map\n3. **Agent-level context** (if set in constructor): Use the agent's default Map\n4. **New Map**: Create an empty Map if none exists\n\nThe framework then fills in missing keys from lower-priority sources without overriding existing values. This means:\n\n- Sub-agents see all parent context data\n- Parent's values take precedence over runtime and agent-level defaults\n- Runtime values take precedence over agent-level defaults\n\n**Important**: The context Map reference is preserved, not cloned. This enables bidirectional updates - changes made by sub-agents are visible to the parent.\n\nFor more details on sub-agent architecture, see the [Sub-Agents guide](./subagents.md).\n\n## Complete Flow Example\n\nHere's how all pieces work together:\n\n```typescript\nimport { Agent, createHooks, createTool, BaseRetriever } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Simple retriever\nclass BasicRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    if (options.context) {\n      options.context.set(\"references\", [{ title: \"Document 1\", source: \"knowledge-base\" }]);\n    }\n    return \"Retrieved content about the topic\";\n  }\n}\n\n// Simple tool\nconst counterTool = createTool({\n  name: \"increment_counter\",\n  description: \"Increments a counter\",\n  parameters: z.object({}),\n  execute: async (_, options) => {\n    const ctx = options?.context;\n    if (ctx) {\n      const count = (ctx.get(\"counter\") || 0) + 1;\n      ctx.set(\"counter\", count);\n      return `Counter is now: ${count}`;\n    }\n    return \"Counter incremented\";\n  },\n});\n\n// Agent with everything\nconst fullAgent = new Agent({\n  name: \"FullAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new BasicRetriever(),\n  tools: [counterTool],\n  hooks: createHooks({\n    onStart: (context) => {\n      console.log(\"🚀 Operation started\");\n      context.context.set(\"operationId\", `op-${Date.now()}`);\n    },\n    onEnd: (context) => {\n      const opId = context.context.get(\"operationId\");\n      const counter = context.context.get(\"counter\");\n      const references = context.context.get(\"references\");\n\n      console.log(\"✅ Operation completed\");\n      console.log(`Operation ID: ${opId}`);\n      console.log(`Counter final value: ${counter}`);\n      console.log(`References found: ${references?.length || 0}`);\n    },\n  }),\n  instructions: \"Use tools and retrieval to help users. Always increment the counter.\",\n});\n\n// Usage showing the complete flow\nasync function demonstrateFlow() {\n  const initialContext = new Map();\n  initialContext.set(\"language\", \"English\");\n\n  const response = await fullAgent.generateText(\n    \"Use the increment tool and search for information\",\n    { context: initialContext }\n  );\n\n  console.log(\"Text:\", response.text);\n  const finalContext = response.context;\n  for (const [key, value] of finalContext.entries()) {\n    console.log(`${String(key)}: ${JSON.stringify(value)}`);\n  }\n}\n```\n\n## Key Points\n\n### Context Access by Component\n\n1. **Hooks**: Access via `context.context` in `onStart`/`onEnd` callbacks\n2. **Tools**: Access via `options.operationContext.context` in `execute` function\n3. **Retrievers**: Access via `options.context` in `retrieve` method\n4. **Sub-Agents**: Automatically receive parent's `context` Map reference\n5. **Response**: Access final state via `response.context`\n6. **Dynamic Values**: Constructor context is available in dynamic instructions, model, and tools functions\n\n### OperationContext Fields\n\nThe `OperationContext` contains:\n\n- **User-managed**: `context` (Map), `userId`, `conversationId`, `workspace`\n- **Metadata**: `operationId`, `startTime`, `isActive`, `parentAgentId`\n- **System**: `systemContext`, `logger`, `traceContext`, `conversationSteps`, `abortController`, `cancellationError`, `elicitation`\n- **Input/Output**: `input`, `output` (automatically set)\n\n### Context Initialization Priority\n\n1. Parent context (for sub-agents) - highest priority\n2. Runtime context (execution parameter)\n3. Agent-level context (constructor)\n4. New empty Map - lowest priority\n\nLower-priority sources only fill in missing keys; they don't override existing values. The Map reference is preserved (not cloned) to enable bidirectional updates between parent and sub-agents.\n\n### Tips\n\n**Extend rather than replace default context:**\n\n```typescript\nconst agent = new Agent({\n  context: new Map([[\"env\", \"production\"]]),\n  // ...\n});\n\n// Extend the default context\nconst extendedContext = new Map(agent.context);\nextendedContext.set(\"requestId\", \"req-123\");\n\nawait agent.generateText(\"Hello\", { context: extendedContext });\n```\n\n**Use metadata fields for observability:**\n\n```typescript\nhooks: createHooks({\n  onEnd: ({ context }) => {\n    console.log(\n      `Operation ${context.operationId} completed in ${Date.now() - context.startTime.getTime()}ms`\n    );\n  },\n});\n```\n\n## Memory Adapters\n\nCustom memory adapters can access `OperationContext` to implement cross-cutting concerns like multi-tenancy, logging, and access control without modifying core adapter logic.\n\n### Example: Multi-Tenant Data Isolation\n\nCustom memory adapters can read user-provided context to implement tenant-scoped data access:\n\n```typescript\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext, GetMessagesOptions, UIMessage } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant ID from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID is required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log access for audit trail\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n      traceId: context.traceContext.getTraceId(),\n    });\n\n    // Use scoped user ID to isolate tenant data\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Tenant A: user-123's data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-abc\"]]), // Stores as \"company-abc:user-123\"\n});\n\n// Tenant B: same user ID, but different tenant = different data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-xyz\"]]), // Stores as \"company-xyz:user-123\"\n});\n```\n\n**What this enables:**\n\n- **Data isolation** between tenants using composite keys\n- **Per-request tenant context** without adapter reconfiguration\n- **Audit logging** with full operation context\n- **Flexible access control** based on runtime values\n\nAll memory adapters (InMemory, Postgres, LibSQL, Supabase) support `OperationContext` as an optional parameter.\n","frontmatter":{"title":"Operation Context (context)","slug":"/agents/context","description":"Pass custom data through agent operations using context."},"isInternal":false,"tokens":6503,"sizeBytes":28502},{"name":"dynamic-agents.md","path":"website/docs/agents/dynamic-agents.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/dynamic-agents.md","title":"Subagent: dynamic-agents","category":"subagent-persona","format":"markdown","content":"---\ntitle: Dynamic Agents\nslug: /agents/dynamic-agents\n---\n\n# Dynamic Agents\n\nDynamic agents use functions instead of static values for `instructions`, `model`, and `tools`. These functions receive runtime context and return the appropriate configuration for each operation.\n\nWhen you call `generateText()` or `streamText()`, the agent resolves dynamic values using the `context` map you provide. This lets you configure agent behavior per-request based on user roles, subscription tiers, language preferences, or any custom logic.\n\n## Use Cases\n\nDynamic agents work well for:\n\n- Multi-tenant applications where different users need different capabilities\n- Role-based access control where admins get different tools than regular users\n- Subscription tiers where premium users get access to different models\n- Internationalization where responses adapt to user's language\n- A/B testing where different users get different configurations\n\n## How Dynamic Resolution Works\n\nWhen you provide a function instead of a static value, the agent calls it during each operation:\n\n1. You call `agent.generateText(input, { context })`\n2. Agent invokes your function with `DynamicValueOptions`\n3. Function receives `{ context, prompts }` and returns the value\n4. Agent uses the resolved value for that operation\n\nThe resolution happens in [agent.ts:2583-2606](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L2583-L2606) via the `resolveValue()` method. Dynamic functions are called on every operation, so keep them synchronous or fast.\n\n### Type System\n\nDynamic values use the `DynamicValue<T>` type:\n\n```typescript\ntype DynamicValue<T> = (options: DynamicValueOptions) => Promise<T> | T;\n\ninterface DynamicValueOptions {\n  context: Map<string | symbol, unknown>; // Runtime context you provide\n  headers?: Record<string, string>; // HTTP request headers, when called through a server adapter\n  prompts: PromptHelper; // VoltOps prompt management\n}\n```\n\nThe agent defines three specific dynamic types:\n\n- `InstructionsDynamicValue`: `string | DynamicValue<string | PromptContent>`\n- `ModelDynamicValue<T>`: `T | DynamicValue<T>`\n- `ToolsDynamicValue`: `(Tool | Toolkit)[] | DynamicValue<(Tool | Toolkit)[]>`\n\n## Basic Example\n\nHere's a dynamic agent that changes instructions based on user role:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst dynamicAgent = new Agent({\n  name: \"Adaptive Assistant\",\n\n  // Dynamic instructions based on user context\n  instructions: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n    const language = (context.get(\"language\") as string) || \"English\";\n\n    if (role === \"admin\") {\n      return `You are an admin assistant with special privileges. Respond in ${language}.`;\n    } else {\n      return `You are a helpful assistant. Respond in ${language}.`;\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Dynamic Properties\n\nYou can make `instructions`, `model`, and `tools` dynamic. Each is resolved independently during operation execution.\n\n### Dynamic Instructions\n\nInstructions can be a string or a function that returns a string (or `PromptContent` when using VoltOps).\n\n```ts\nconst agent = new Agent({\n  name: \"Multilingual Support Agent\",\n\n  instructions: ({ context }) => {\n    const language = (context.get(\"language\") as string) || \"English\";\n    const supportTier = (context.get(\"supportTier\") as string) || \"basic\";\n\n    let baseInstructions = `You are a customer support agent. Respond in ${language}.`;\n\n    if (supportTier === \"premium\") {\n      baseInstructions += \" You can offer advanced troubleshooting and priority support.\";\n    } else {\n      baseInstructions += \" Provide standard support within basic guidelines.\";\n    }\n\n    return baseInstructions;\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n### Request Headers\n\nWhen an agent is called through the built-in HTTP endpoints, VoltAgent exposes the request headers to dynamic `instructions`, `model`, and `tools` functions as `headers`. Header names are normalized to lowercase.\n\n```ts\nconst agent = new Agent({\n  name: \"Tenant Agent\",\n  instructions: \"You are a tenant-aware assistant.\",\n  model: ({ headers }) => {\n    const tenantId = headers?.[\"x-tenant-id\"];\n\n    return tenantId === \"enterprise\" ? \"openai/gpt-4o\" : \"openai/gpt-4o-mini\";\n  },\n  tools: ({ headers }) => {\n    const token = headers?.authorization;\n\n    return token ? [createTenantTool(token)] : [];\n  },\n});\n```\n\nFor direct in-process calls, pass `requestHeaders`:\n\n```ts\nawait agent.generateText(\"Hello\", {\n  requestHeaders: {\n    authorization: \"Bearer token\",\n    \"x-tenant-id\": \"tenant-1\",\n  },\n});\n```\n\n### Dynamic Models\n\nThe model can be a static `LanguageModel`, a `provider/model` string, or a function returning either. The agent resolves the model at [agent.ts:1672](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1672) before each generation call.\n\n```ts\nconst agent = new Agent({\n  name: \"Tier-Based Assistant\",\n  instructions: \"You are a helpful assistant.\",\n\n  // Different models for different subscription tiers\n  model: ({ context }) => {\n    const tier = (context.get(\"tier\") as string) || \"free\";\n\n    switch (tier) {\n      case \"premium\":\n        return \"openai/gpt-4o\";\n      case \"pro\":\n        return \"openai/gpt-4o-mini\";\n      default:\n        return \"openai/gpt-3.5-turbo\";\n    }\n  },\n});\n```\n\n### Dynamic Tools\n\nTools can be a static array or a function returning an array. The agent stores dynamic tool functions separately in the `dynamicTools` property ([agent.ts:337](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L337)) and resolves them at [agent.ts:1673](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1673).\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Regular user tool\nconst basicTool = createTool({\n  name: \"get_help\",\n  description: \"Get basic help information\",\n  parameters: z.object({\n    topic: z.string().describe(\"Help topic\"),\n  }),\n  execute: async ({ topic }) => {\n    return `Here's basic help about ${topic}`;\n  },\n});\n\n// Admin-only tool\nconst adminTool = createTool({\n  name: \"admin_action\",\n  description: \"Perform administrative actions\",\n  parameters: z.object({\n    action: z.string().describe(\"Admin action to perform\"),\n  }),\n  execute: async ({ action }) => {\n    return `Admin action performed: ${action}`;\n  },\n});\n\nconst agent = new Agent({\n  name: \"Role-Based Agent\",\n  instructions: \"You are an assistant with role-based capabilities.\",\n\n  // Different tools based on user role\n  tools: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n\n    if (role === \"admin\") {\n      return [basicTool, adminTool]; // Admins get both tools\n    } else {\n      return [basicTool]; // Regular users get basic tools only\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Execution\n\nPass `context` as an option to any generation method. The agent uses this context to resolve dynamic values:\n\n```ts\n// Create context with relevant information\nconst context = new Map<string, unknown>();\ncontext.set(\"role\", \"admin\");\ncontext.set(\"language\", \"Spanish\");\ncontext.set(\"tier\", \"premium\");\ncontext.set(\"company\", \"TechCorp\");\n\n// Generate response with context\nconst response = await dynamicAgent.generateText(\"Help me manage the system settings\", {\n  context,\n});\n\nconsole.log(response.text);\n// The agent will respond in Spanish, with admin capabilities,\n// using the premium model, and have access to admin tools\n```\n\nYou can also use it with streaming:\n\n```ts\nconst streamResponse = await dynamicAgent.streamText(\"What products are available?\", {\n  context: new Map([\n    [\"role\", \"customer\"],\n    [\"language\", \"French\"],\n    [\"tier\", \"basic\"],\n  ]),\n});\n\nfor await (const chunk of streamResponse.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n## REST API Usage\n\nYou can pass `context` via the REST API to trigger dynamic resolution over HTTP.\n\n### API Request Format\n\nPass the `context` object within the `options` field of your API request:\n\n```json\n{\n  \"input\": \"Your message to the agent\",\n  \"options\": {\n    \"context\": {\n      \"role\": \"admin\",\n      \"language\": \"Spanish\",\n      \"tier\": \"premium\"\n    },\n    \"temperature\": 0.7,\n    \"maxTokens\": 500\n  }\n}\n```\n\n### REST API Examples\n\n**Basic Text Generation:**\n\n```bash\n# Admin user requesting help in Spanish\ncurl -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/text \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"I need to update system settings\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"admin\",\n           \"language\": \"Spanish\",\n           \"tier\": \"enterprise\"\n         }\n       }\n     }'\n```\n\n**Streaming with Real-time Context:**\n\n```bash\n# Stream response for different user roles\ncurl -N -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/stream \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"Write a summary of our quarterly results\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"manager\",\n           \"department\": \"finance\",\n           \"accessLevel\": \"confidential\",\n           \"language\": \"English\"\n         }\n       }\n     }'\n```\n\n## VoltOps Integration\n\nThe VoltOps dashboard lets you test dynamic agents by setting context values through a visual interface.\n\n<!-- GIF PLACEHOLDER: VoltOps Dynamic Agents Demo -->\n\n![VoltOps Dynamic Agents Demo](https://cdn.voltagent.dev/docs/user-context-demo.gif)\n\nYou can:\n\n- Set context values through a form\n- Test different user roles and configurations\n- Monitor how dynamic properties change based on context\n- Debug dynamic agent logic\n\n## Best Practices\n\n### Context Validation\n\nValidate context values and provide defaults:\n\n```ts\ninstructions: ({ context }) => {\n  // Always provide defaults and validate types\n  const role = (context.get(\"role\") as string) || \"user\";\n  const language = (context.get(\"language\") as string) || \"English\";\n\n  // Validate known values\n  const validRoles = [\"admin\", \"support\", \"customer\"];\n  const actualRole = validRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${actualRole} assistant. Respond in ${language}.`;\n};\n```\n\n### Performance\n\nDynamic functions run on every operation. Avoid async operations or heavy computation:\n\n```ts\n// ✅ Good - Fast synchronous logic\nmodel: ({ context }) => {\n  const tier = context.get('tier') as string;\n  return tier === 'premium' ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n},\n\n// ❌ Avoid - API calls or database queries\nmodel: async ({ context }) => {\n  const userProfile = await fetchUserFromDatabase(context.get('userId'));\n  return determineModelFromProfile(userProfile);\n}\n```\n\n### Security\n\nUse allowlists for security-sensitive values:\n\n```ts\ninstructions: ({ context }) => {\n  const role = context.get(\"role\") as string;\n\n  // ✅ Use allowlists for security-sensitive operations\n  const allowedRoles = [\"admin\", \"support\", \"customer\"];\n  const safeRole = allowedRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${safeRole} assistant.`;\n};\n```\n\n### Context Structure\n\nUse consistent context key naming across your application:\n\n```ts\n// ✅ Consistent naming convention\nconst createUserContext = (user: User) => {\n  const context = new Map<string, unknown>();\n  context.set(\"user.id\", user.id);\n  context.set(\"user.role\", user.role);\n  context.set(\"user.tier\", user.subscriptionTier);\n  context.set(\"user.language\", user.preferredLanguage);\n  context.set(\"request.timestamp\", Date.now());\n  return context;\n};\n```\n\n## Advanced Patterns\n\n### Conditional Resolution\n\nMake properties dynamic only when context requires it:\n\n```ts\nconst agent = new Agent({\n  name: \"Conditionally Dynamic Agent\",\n\n  // Static instructions most of the time\n  instructions: \"You are a helpful assistant.\",\n\n  // But dynamic model when user context is provided\n  model: ({ context }) => {\n    // If no tier specified, use default static model\n    if (!context.has(\"tier\")) {\n      return \"openai/gpt-4o-mini\"; // Default model\n    }\n\n    // Otherwise, choose based on tier\n    const tier = context.get(\"tier\") as string;\n    return tier === \"premium\" ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n  },\n\n  // model is selected dynamically above\n});\n```\n\n### Context Between Operations\n\nPass the same context to related operations for consistency:\n\n```ts\n// Main agent with context\nconst parentResponse = await dynamicAgent.generateText(\"Create a summary\", {\n  context: myContext,\n});\n\n// Sub-agent inherits the same context\nconst detailResponse = await detailAgent.generateText(\n  \"Provide more details\",\n  { context: myContext } // Same context for consistency\n);\n```\n\n## Error Handling\n\nWrap dynamic logic in try-catch and provide fallbacks:\n\n```ts\nmodel: ({ context }) => {\n  try {\n    const tier = context.get(\"tier\") as string;\n\n    if (tier === \"premium\") {\n      return \"openai/gpt-4o\";\n    }\n    return \"openai/gpt-3.5-turbo\";\n  } catch (error) {\n    console.warn(\"Error in dynamic model selection:\", error);\n    // Fallback to safe default\n    return \"openai/gpt-3.5-turbo\";\n  }\n};\n```\n","frontmatter":{"title":"Dynamic Agents","slug":"/agents/dynamic-agents"},"isInternal":false,"tokens":3105,"sizeBytes":13151},{"name":"hooks.md","path":"website/docs/agents/hooks.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/hooks.md","title":"Subagent: hooks","category":"subagent-persona","format":"markdown","content":"---\ntitle: Hooks\nslug: /agents/hooks\n---\n\n# Agent Hooks\n\nHooks intercept specific points in the agent execution pipeline. Use them to observe or modify behavior during agent operations: before a request starts, when preparing messages for the LLM, before and after tool execution, or after completion.\n\n## Defining Hooks\n\nDefine hooks using the `createHooks` helper function. Pass the resulting object to the Agent constructor, or to individual method calls like `generateText` and `streamText`.\n\n```ts\nimport {\n  Agent,\n  createHooks,\n  messageHelpers,\n  type AgentTool,\n  type AgentOperationOutput,\n  type VoltAgentError,\n  type OnStartHookArgs,\n  type OnEndHookArgs,\n  type OnPrepareMessagesHookArgs,\n  type OnPrepareModelMessagesHookArgs,\n  type OnToolStartHookArgs,\n  type OnToolEndHookArgs,\n  type OnToolErrorHookArgs,\n  type OnHandoffHookArgs,\n} from \"@voltagent/core\";\n\n// Define a collection of hooks using the helper\nconst myAgentHooks = createHooks({\n  /**\n   * Called before the agent starts processing a request.\n   */\n  onStart: async (args: OnStartHookArgs) => {\n    const { agent, context } = args;\n    console.log(`[Hook] Agent ${agent.name} starting interaction at ${new Date().toISOString()}`);\n    console.log(`[Hook] Operation ID: ${context.operationId}`);\n  },\n\n  /**\n   * Called after VoltAgent sanitizes UI messages but before the LLM receives them.\n   * `rawMessages` contains the unsanitized list for inspection or metadata recovery.\n   */\n  onPrepareMessages: async (args: OnPrepareMessagesHookArgs) => {\n    const { messages, rawMessages, context } = args;\n    console.log(`Preparing ${messages.length} sanitized messages for LLM`);\n\n    // Add timestamp to each message\n    const timestamp = new Date().toLocaleTimeString();\n    const enhanced = messages.map((msg) => messageHelpers.addTimestampToMessage(msg, timestamp));\n\n    if (rawMessages) {\n      // Access raw message structure for audit logging\n      console.debug(`First raw message parts:`, rawMessages[0]?.parts);\n    }\n\n    return { messages: enhanced };\n  },\n\n  /**\n   * Called after UI messages are converted into provider-specific ModelMessage objects.\n   */\n  onPrepareModelMessages: async (args: OnPrepareModelMessagesHookArgs) => {\n    const { modelMessages, uiMessages } = args;\n    console.log(`Model payload contains ${modelMessages.length} messages`);\n\n    // Inject a system message if none exists\n    if (!modelMessages.some((msg) => msg.role === \"system\")) {\n      return {\n        modelMessages: [\n          {\n            role: \"system\",\n            content: [{ type: \"text\", text: \"Operate within safety budget\" }],\n          },\n          ...modelMessages,\n        ],\n      };\n    }\n\n    return {};\n  },\n\n  /**\n   * Called after the agent completes a request (success or failure).\n   */\n  onEnd: async (args: OnEndHookArgs) => {\n    const { agent, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Agent ${agent.name} finished with error:`, error.message);\n      console.error(`[Hook] Error Details:`, JSON.stringify(error, null, 2));\n    } else if (output) {\n      console.log(`[Hook] Agent ${agent.name} finished successfully.`);\n      // Log aggregate usage or inspect output type\n      if (\"totalUsage\" in output && output.totalUsage) {\n        console.log(`[Hook] Total Token Usage: ${output.totalUsage.totalTokens}`);\n      }\n      if (\"text\" in output && output.text) {\n        console.log(`[Hook] Final text length: ${output.text.length}`);\n      }\n      if (\"object\" in output && output.object) {\n        console.log(`[Hook] Final object keys: ${Object.keys(output.object).join(\", \")}`);\n      }\n    }\n  },\n\n  /**\n   * Called before a tool executes.\n   */\n  onToolStart: async (args: OnToolStartHookArgs) => {\n    const { agent, tool, context, args: toolArgs } = args;\n    console.log(`[Hook] Agent ${agent.name} starting tool: ${tool.name}`);\n    console.log(`[Hook] Tool arguments:`, toolArgs);\n  },\n\n  /**\n   * Called after a tool completes or throws an error.\n   */\n  onToolEnd: async (args: OnToolEndHookArgs) => {\n    const { agent, tool, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Tool ${tool.name} failed:`, error.message);\n      console.error(`[Hook] Tool Error Details:`, JSON.stringify(error, null, 2));\n    } else {\n      console.log(`[Hook] Tool ${tool.name} completed with result:`, output);\n    }\n  },\n\n  /**\n   * Called only when a tool throws, before VoltAgent serializes the error payload.\n   */\n  onToolError: async (args: OnToolErrorHookArgs) => {\n    const { tool, originalError } = args;\n    console.error(`[Hook] Raw tool error from ${tool.name}:`, originalError.message);\n\n    // Return { output } to customize what gets sent back to the model.\n    return {\n      output: {\n        error: true,\n        message: \"External API request failed\",\n      },\n    };\n  },\n\n  /**\n   * Called when a task is handed off from a source agent to this agent.\n   */\n  onHandoff: async (args: OnHandoffHookArgs) => {\n    const { agent, sourceAgent } = args;\n    console.log(`[Hook] Task handed off from ${sourceAgent.name} to ${agent.name}`);\n  },\n});\n\n// Pass hooks to the Agent constructor\nconst agentWithHooks = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\n// Or define hooks inline\nconst agentWithInlineHooks = new Agent({\n  name: \"Inline Hooks Agent\",\n  instructions: \"Another assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: {\n    onStart: async ({ agent, context }) => {\n      /* ... */\n    },\n    onEnd: async ({ agent, output, error, context }) => {\n      /* ... */\n    },\n  },\n});\n```\n\n## Passing Hooks to Methods\n\nPass hooks to `generateText`, `streamText`, `generateObject`, or `streamObject` to run hooks for that specific invocation only.\n\n:::warning\nMethod-level hooks do not override agent-level hooks. Both will execute. For most hooks, the method-level hook runs first, then the agent-level hook. For `onPrepareMessages` and `onPrepareModelMessages`, the method-level hook replaces the agent-level hook entirely.\n:::\n\n```ts\nconst agent = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\nawait agent.generateText(\"Hello, how are you?\", {\n  hooks: {\n    onEnd: async ({ context }) => {\n      console.log(\"End of generation for this invocation!\");\n    },\n  },\n});\n```\n\nFor example, store conversation history only for specific endpoints:\n\n```ts\nconst agent = new Agent({\n  name: \"Translation Agent\",\n  instructions: \"A translation agent that translates text from English to French\",\n  model: \"openai/gpt-4o\",\n});\n\n// for the translate endpoint, we don't want to store the conversation history\napp.post(\"/api/translate\", async (req, res) => {\n  const result = await agent.generateText(req.body.text);\n  return result;\n});\n\n// for the chat endpoint, we want to store the conversation history\napp.post(\"/api/translate/chat\", async (req, res) => {\n  const result = await agent.streamText(req.body.text, {\n    hooks: {\n      onEnd: async ({ context }) => {\n        await chatStore.save({\n          conversationId: context.conversationId,\n          messages: context.steps,\n        });\n      },\n    },\n  });\n\n  return result.textStream;\n});\n```\n\n## Available Hooks\n\nAll hooks receive a single argument object containing relevant information.\n\n### Choosing the right message hook\n\n| Hook                     | Stage                                                              | When to use                                                                                           |\n| ------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `onPrepareMessages`      | Operates on sanitized `UIMessage[]` built from memory + user input | Transform messages using helper functions, redact content, or access `rawMessages` for logging.       |\n| `onPrepareModelMessages` | Runs after `convertToModelMessages` on `ModelMessage[]`            | Apply provider-specific adjustments, inject system directives, or modify the final payload structure. |\n\nUse `onPrepareMessages` for general transformations and `onPrepareModelMessages` for provider-specific adjustments. Both hooks can be used together; VoltAgent applies them in that order.\n\n### `onStart`\n\n- **Triggered:** Before the agent begins processing a request (`generateText`, `streamText`, etc.).\n- **Argument Object (`OnStartHookArgs`):** `{ agent: Agent, context: OperationContext }`\n- **Use Cases:** Initialization logic, request logging, setting up request-scoped resources.\n\n```ts\n// Example: Log the start of an operation\nonStart: async ({ agent, context }) => {\n  console.log(`Agent ${agent.name} starting operation ${context.operationId}`);\n};\n```\n\n### `onPrepareMessages`\n\n- **Triggered:** After VoltAgent assembles conversation history and sanitizes UI messages, before conversion to provider-specific payloads.\n- **Argument Object (`OnPrepareMessagesHookArgs`):** `{ messages: UIMessage[], rawMessages?: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Transform sanitized messages (add timestamps, redact content), or access `rawMessages` for logging or metadata recovery.\n- **Return:** `{ messages: UIMessage[] }` to replace the sanitized list, or an empty object to keep the original.\n- **Notes:**\n  - `messages` contains sanitized UI messages safe for the LLM.\n  - `rawMessages` contains the full structure before sanitization, useful for logging or metadata reconstruction.\n\n```ts\nonPrepareMessages: async ({ messages, rawMessages }) => {\n  const tagged = messages.map((msg) =>\n    messageHelpers.addTimestampToMessage(msg, new Date().toISOString())\n  );\n\n  if (rawMessages) {\n    auditTrail.write(rawMessages); // your own analytics sink\n  }\n\n  return { messages: tagged };\n};\n```\n\n### `onPrepareModelMessages`\n\n- **Triggered:** After UI messages are converted via `convertToModelMessages`, immediately before the provider receives them.\n- **Argument Object (`OnPrepareModelMessagesHookArgs`):** `{ modelMessages: ModelMessage[], uiMessages: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Apply provider-specific transformations, inject final system messages, compress conversation length, or add structured metadata.\n- **Return:** `{ modelMessages: ModelMessage[] }` with a replacement list, or an empty object to keep the original.\n- **Tip:** Use `onPrepareMessages` for general transformations, and this hook for provider-specific adjustments.\n\n```ts\nonPrepareModelMessages: async ({ modelMessages }) => {\n  // Force the last message to include a \"speak clearly\" reminder for voice models\n  const last = modelMessages.at(-1);\n  if (last && last.role === \"user\" && Array.isArray(last.content)) {\n    last.content.push({ type: \"text\", text: \"Please answer succinctly.\" });\n  }\n\n  return { modelMessages };\n};\n```\n\n### `onEnd`\n\n- **Triggered:** After the agent completes processing a request (success or failure).\n- **Argument Object (`OnEndHookArgs`):** `{ agent: Agent, output: AgentOperationOutput | undefined, error: VoltAgentError | undefined, conversationId: string, context: OperationContext }`\n- **Use Cases:** Cleanup, logging, analyzing output or errors, recording usage statistics, storing conversation history.\n- **Note:** The `output` structure depends on the method called. Check for `text` or `object` fields. `error` contains the structured `VoltAgentError` on failure.\n\n```ts\n// Example: Log the outcome of an operation and store conversation history\nonEnd: async ({ agent, output, error, conversationId, context }) => {\n  if (error) {\n    console.error(`Agent ${agent.name} operation ${context.operationId} failed: ${error.message}`);\n    console.log(`User input: \"${context.historyEntry.input}\"`);\n    // Only user input available on error (no assistant response)\n  } else {\n    // Check output type if needed\n    if (output && \"text\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with text output.`\n      );\n    } else if (output && \"object\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with object output.`\n      );\n    } else {\n      console.log(`Agent ${agent.name} operation ${context.operationId} succeeded.`);\n    }\n\n    // Access input and output directly from context\n    console.log(\"Request input:\", context.input);\n    console.log(\"Generated output:\", context.output);\n\n    // Log the complete conversation flow\n    console.log(`Conversation flow:`, {\n      user: context.historyEntry.input,\n      assistant: context.steps, // the assistant steps\n      totalMessages: context.steps.length,\n      toolInteractions: context.steps.flatMap((s) => s.toolInvocations || []).length,\n      toolsUsed: context.steps.flatMap((s) => s.toolInvocations || []).map((t) => t.toolName),\n    });\n\n    // Log complete interaction using input/output\n    console.log(\"Full interaction:\", {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    });\n\n    // Log usage if available\n    if (output?.totalUsage) {\n      console.log(`  Total usage: ${output.totalUsage.totalTokens} tokens`);\n    }\n  }\n};\n```\n\n### `onRetry`\n\n- **Triggered:** When VoltAgent schedules a retry for an LLM attempt or a middleware abort.\n- **Argument Object (`OnRetryHookArgs`):** Includes `source`, `operation`, `context`, and retry details.\n- **Use Cases:** Retry metrics, alerts, tracking retry sources.\n\n```ts\nonRetry: async (args) => {\n  if (args.source === \"llm\") {\n    console.log(\n      `LLM retry ${args.nextAttempt}/${args.maxRetries + 1} for ${args.modelName} (${args.operation})`\n    );\n  } else {\n    console.log(\n      `Middleware retry ${args.retryCount + 1}/${args.maxRetries + 1} for ${args.middlewareId ?? \"unknown\"}`\n    );\n  }\n};\n```\n\n### `onFallback`\n\n- **Triggered:** When VoltAgent selects the next model candidate.\n- **Argument Object (`OnFallbackHookArgs`):** Includes `stage`, `fromModel`, `fromModelIndex`, `nextModel`, and `error`.\n- **Use Cases:** Tracking model failover, audit logs, fallback diagnostics.\n\n```ts\nonFallback: async ({ stage, fromModel, nextModel, operation }) => {\n  console.warn(\n    `Fallback (${stage}) from ${fromModel} to ${nextModel ?? \"next\"} during ${operation}`\n  );\n};\n```\n\n### `onToolStart`\n\n- **Triggered:** Before an agent executes a tool.\n- **Argument Object (`OnToolStartHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool being executed\n  - `args`: Tool input arguments\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Use Cases:** Logging tool usage, inspecting tool arguments, or validating inputs before execution.\n\n```ts\n// Example: Log tool invocation with arguments\nonToolStart: async ({ agent, tool, args, context }) => {\n  console.log(`Agent ${agent.name} invoking tool '${tool.name}'`);\n  console.log(`Tool arguments:`, args);\n};\n```\n\n### `onToolEnd`\n\n- **Triggered:** After a tool execution completes or throws an error.\n- **Argument Object (`OnToolEndHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that completed\n  - `output`: Tool output (undefined on error)\n  - `error`: VoltAgentError when tool throws (undefined on success)\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** `{ output }` to replace the tool result. The replacement is validated again if the tool has an `outputSchema`.\n- **Use Cases:** Logging tool results or errors, post-processing output, triggering actions based on success or failure.\n\n```ts\n// Example: Log the result or error of a tool execution\nonToolEnd: async ({ agent, tool, output, error, context }) => {\n  if (error) {\n    console.error(\n      `Tool '${tool.name}' failed in operation ${context.operationId}: ${error.message}`\n    );\n  } else {\n    console.log(\n      `Tool '${tool.name}' succeeded in operation ${context.operationId}. Result:`,\n      output\n    );\n  }\n};\n```\n\n### `onToolError`\n\n- **Triggered:** Only when a tool throws, before VoltAgent builds its default serialized error result.\n- **Argument Object (`OnToolErrorHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that failed\n  - `args`: Tool input arguments\n  - `error`: Structured `VoltAgentError` for the tool failure\n  - `originalError`: Original thrown error normalized to an `Error` instance\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** Optional `{ output }` replacement payload. If omitted, VoltAgent returns its default serialized error payload.\n- **Use Cases:** Centralized error normalization (for example Axios), redacting sensitive fields, shrinking error payload size before it is sent to the model.\n\n```ts\nonToolError: async ({ tool, error, originalError }) => {\n  const maybeAxios = (originalError as any).isAxiosError === true;\n  if (!maybeAxios) {\n    return;\n  }\n\n  return {\n    output: {\n      error: true,\n      name: error.name,\n      message: originalError.message,\n      code: (originalError as any).code,\n      status: (originalError as any).response?.status,\n    },\n  };\n};\n```\n\n### Tool-level hooks (per tool)\n\nTool hooks run for a specific tool instance and are called before/after execution. Tool-level `onEnd` runs before agent-level `onToolEnd`. If both return `{ output }`, the agent hook wins. Any override is re-validated when `outputSchema` is present.\n\n**Tool hook parameters:**\n\n- `onStart`: `{ tool, args, options }`\n- `onEnd`: `{ tool, args, output, error, options }` (return `{ output }` to override)\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst normalizeTool = createTool({\n  name: \"normalize_text\",\n  description: \"Normalize and trim text\",\n  parameters: z.object({ text: z.string() }),\n  execute: async ({ text }) => text,\n  hooks: {\n    onStart: ({ tool }) => {\n      console.log(`[tool] ${tool.name} starting`);\n    },\n    onEnd: ({ output }) => {\n      if (typeof output === \"string\") {\n        return { output: output.trim() };\n      }\n    },\n  },\n});\n```\n\n### `onHandoff`\n\n- **Triggered:** When one agent delegates a task to another agent via the `delegate_task` tool.\n- **Argument Object (`OnHandoffHookArgs`):** `{ agent: Agent, sourceAgent: Agent }`\n- **Use Cases:** Tracking workflow in multi-agent systems, logging agent collaboration.\n\n```ts\n// Example: Log agent handoffs\nonHandoff: async ({ agent, sourceAgent }) => {\n  console.log(`Task handed off from agent '${sourceAgent.name}' to agent '${agent.name}'`);\n};\n```\n\n## Hook Execution and Error Handling\n\n- **Async Execution:** Hooks can be `async` functions. VoltAgent awaits completion before proceeding. Long-running operations in hooks add latency to agent response time.\n- **Error Handling:** Errors thrown inside hooks may interrupt agent execution. Use `try...catch` within hooks or design them to be reliable.\n- **Hook Merging:** When hooks are passed to both the Agent constructor and a method call:\n  - Most hooks (`onStart`, `onEnd`, `onError`, `onHandoff`, `onToolStart`, `onToolEnd`, `onToolError`, `onStepFinish`, `onRetry`, `onFallback`) execute both: method-level first, then agent-level.\n  - Message hooks (`onPrepareMessages`, `onPrepareModelMessages`) do not merge: method-level replaces agent-level entirely.\n\n## Additional Hooks\n\n- **`onError`**: Called when an error occurs during agent execution. Receives `{ agent: Agent, error: Error, context: OperationContext }`.\n- **`onToolError`**: Called when a tool throws, before default tool error serialization. Receives `{ agent, tool, args, error, originalError, context }` and can return `{ output }`.\n- **`onStepFinish`**: Called after each step in multi-step agent execution. Receives `{ agent: Agent, step: any, context: OperationContext }`.\n- **`onRetry`**: Called when VoltAgent schedules a retry. Receives `{ source, operation, ... }` with retry metadata.\n- **`onFallback`**: Called when VoltAgent selects the next model candidate. Receives `{ stage, fromModel, nextModel, error, ... }`.\n\n## Common Use Cases\n\n1.  **Logging & Observability**: Track execution steps, timings, inputs, outputs, and errors.\n2.  **Analytics**: Collect usage data (token counts, tool usage frequency, success/error rates).\n3.  **Message Transformation**: Modify messages before they reach the LLM or provider.\n4.  **State Management**: Initialize or clean up request-specific resources.\n5.  **Workflow Orchestration**: Trigger external actions based on agent events.\n6.  **UI Integration**: Convert `OperationContext` to messages for the Vercel AI SDK using `@voltagent/vercel-ui`.\n\n## Examples\n\n### Message Transformation with onPrepareMessages\n\nTransform messages before they reach the LLM using `onPrepareMessages` and message helper functions:\n\n```ts\nimport { Agent, createHooks, messageHelpers } from \"@voltagent/core\";\n\nconst enhancedHooks = createHooks({\n  onPrepareMessages: async ({ messages, context }) => {\n    const enhanced = messages.map((msg) => {\n      // Add timestamps to user messages\n      if (msg.role === \"user\") {\n        const timestamp = new Date().toLocaleTimeString();\n        msg = messageHelpers.addTimestampToMessage(msg, timestamp);\n      }\n\n      // Redact sensitive data\n      msg = messageHelpers.mapMessageContent(msg, (text) => {\n        text = text.replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, \"[SSN-REDACTED]\");\n        text = text.replace(/\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g, \"[CC-REDACTED]\");\n        return text;\n      });\n\n      return msg;\n    });\n\n    // Add context based on user\n    if (context.context?.get && context.context.get(\"userId\")) {\n      const systemContext = {\n        role: \"system\" as const,\n        content: `User ID: ${context.context.get(\"userId\")}. Provide personalized responses.`,\n      };\n      enhanced.unshift(systemContext);\n    }\n\n    return { messages: enhanced };\n  },\n\n  onEnd: async ({ output, context }) => {\n    console.log(`Messages processed for operation ${context.operationId}`);\n    if (output?.totalUsage) {\n      console.log(`Tokens used: ${output.totalUsage.totalTokens}`);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Privacy-Aware Assistant\",\n  instructions: \"A helpful assistant that protects user privacy\",\n  model: \"openai/gpt-4o-mini\",\n  hooks: enhancedHooks,\n});\n\n// User message: \"My SSN is 123-45-6789\"\n// LLM receives: \"[10:30:45] My SSN is [SSN-REDACTED]\"\n```\n\n### Inspecting Output in onEnd\n\nThe `output` parameter structure depends on the agent method called. Check for `text` or `object` fields:\n\nFor token accounting, VoltAgent exposes the AI SDK usage contract directly:\n\n- `output.usage` is the final step usage, matching AI SDK `result.usage`.\n- `output.totalUsage` is the aggregate usage across all steps, matching AI SDK `result.totalUsage`.\n- `output.steps?.[index].usage` is the per-step usage, matching AI SDK `result.steps[].usage`.\n\nIn a single-step run, `usage` and `totalUsage` are usually the same. In a multi-step tool run, use `totalUsage` for total cost or billing, `usage` for the final model step, and `steps[].usage` for step-level analysis.\n\n```ts\nconst hooks = createHooks({\n  onEnd: async ({ output }) => {\n    if (!output) return; // operation failed or was aborted\n\n    const finalStepUsage = output.usage;\n    const aggregateUsage = output.totalUsage;\n    const perStepUsage = output.steps?.map((step) => step.usage);\n\n    console.log({\n      finalStepTokens: finalStepUsage?.totalTokens,\n      aggregateTokens: aggregateUsage?.totalTokens,\n      stepTokens: perStepUsage?.map((usage) => usage.totalTokens),\n    });\n\n    // Handle text results\n    if (\"text\" in output && output.text) {\n      console.log(\"Final text:\", output.text);\n      return;\n    }\n\n    // Handle object results\n    if (\"object\" in output && output.object) {\n      console.log(\"Final object keys:\", Object.keys(output.object));\n    }\n  },\n});\n```\n","frontmatter":{"title":"Hooks","slug":"/agents/hooks"},"isInternal":false,"tokens":5730,"sizeBytes":24401},{"name":"authorization.md","path":"website/docs/agents/mcp/authorization.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/mcp/authorization.md","title":"Subagent: authorization","category":"subagent-persona","format":"markdown","content":"---\ntitle: MCP Authorization\ndescription: Add access control to MCP tools with VoltAgent's authorization layer\n---\n\n# MCP Authorization\n\nVoltAgent provides an authorization layer for MCP (Model Context Protocol) tools. This allows you to control which tools users can see and execute based on roles, permissions, or any custom logic.\n\n## Overview\n\nThe MCP authorization layer supports two modes:\n\n1. **Tool Discovery Filtering** (`filterOnDiscovery`) - Hide tools from users who don't have permission to use them\n2. **Execution-Time Checks** (`checkOnExecution`) - Verify permissions before each tool call\n\nYou can enable either or both modes depending on your security requirements.\n\n## Quick Start\n\nAdd authorization with the `can` function:\n\n```typescript\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    myServer: {\n      type: \"http\",\n      url: \"http://localhost:3000/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, action, userId, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      // Admin-only tools\n      if (toolName === \"delete_item\" && !roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete items\" };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n\n// Get tools with authorization context\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"], department: \"engineering\" },\n});\n```\n\n## The `can` Function\n\nThe `can` function receives authorization parameters and returns whether access is allowed.\n\n### Parameters\n\n```typescript\ninterface MCPCanParams {\n  /** Tool name (without server prefix) */\n  toolName: string;\n  /** Server/resource identifier */\n  serverName: string;\n  /** The action being authorized: \"discovery\" or \"execution\" */\n  action: \"discovery\" | \"execution\";\n  /** Tool arguments (only available for \"execution\" action) */\n  arguments?: Record<string, unknown>;\n  /** User identifier */\n  userId?: string;\n  /** User-defined context Map */\n  context?: Map<string | symbol, unknown>;\n}\n```\n\n### Return Value\n\nReturn a boolean or an object with `allowed` and optional `reason`:\n\n```typescript\n// Simple boolean\nreturn true;\nreturn false;\n\n// Object with reason (shown in error message when denied)\nreturn { allowed: true };\nreturn { allowed: false, reason: \"Insufficient permissions\" };\n```\n\n## When Actions Are Called\n\nThe `action` parameter tells you whether the check is for listing tools or executing a tool.\n\n### Discovery Action\n\nWhen you call `getTools()` with `filterOnDiscovery: true`, the `can` function is called for each tool with `action: \"discovery\"`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    filterOnDiscovery: true,\n  },\n});\n\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n```\n\n**What `can` receives (for each tool on the server):**\n\n```json\n{\n  \"toolName\": \"list_expenses\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"delete_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\nNote: `arguments` is `undefined` during discovery since no tool is being executed yet.\n\n### Execution Action\n\nWhen a tool is executed (via agent interaction or direct call) with `checkOnExecution: true`, the `can` function is called with `action: \"execution\"` and the tool's `arguments`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n\nconst tools = await mcp.getTools();\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o\",\n  tools,\n});\n\n// When user says \"add an expense for $50 coffee\"\n// and the agent decides to call the add_expense tool:\nawait agent.generateText(\"Add an expense for $50 coffee\", {\n  userId: \"user-123\",\n  context: new Map([[\"roles\", [\"manager\"]]]),\n});\n```\n\n**What `can` receives when the agent calls `add_expense`:**\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"execution\",\n  \"arguments\": {\n    \"amount\": 50,\n    \"description\": \"coffee\"\n  },\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n### Different Logic per Action\n\nYou can use different authorization logic depending on the action:\n\n```typescript\ncan: async ({ toolName, action, arguments: args, userId, context }) => {\n  const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n  if (action === \"discovery\") {\n    // During getTools(): hide admin tools from non-admins\n    if (toolName.startsWith(\"admin_\") && !roles.includes(\"admin\")) {\n      return false; // Tool won't appear in the list\n    }\n  }\n\n  if (action === \"execution\") {\n    // During tool execution: check specific permissions\n    if (toolName === \"delete_expense\") {\n      if (!roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete expenses\" };\n      }\n    }\n\n    // Check argument-based permissions (only available during execution)\n    if (toolName === \"transfer_funds\" && args?.amount > 10000) {\n      if (!roles.includes(\"senior_manager\")) {\n        return { allowed: false, reason: \"Transfers over $10,000 require senior manager approval\" };\n      }\n    }\n  }\n\n  return true;\n};\n```\n\n## Authorization Context\n\nWhen calling `getTools()` or during tool execution, provide authorization context:\n\n```typescript\ninterface MCPAuthorizationContext {\n  userId?: string;\n  context?: Map<string | symbol, unknown> | Record<string, unknown>;\n}\n\n// Example: pass context to getTools()\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: {\n    roles: [\"admin\"],\n    tenantId: \"acme-corp\",\n    permissions: [\"read\", \"write\"],\n  },\n});\n```\n\nThe context is converted to a `Map` and passed to your `can` function.\n\n## Examples\n\n### Role-Based Access\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      const toolPermissions: Record<string, string[]> = {\n        list_items: [\"user\", \"manager\", \"admin\"],\n        create_item: [\"user\", \"manager\", \"admin\"],\n        delete_item: [\"admin\"],\n        admin_panel: [\"admin\"],\n      };\n\n      const allowedRoles = toolPermissions[toolName] ?? [];\n      const hasPermission = roles.some((role) => allowedRoles.includes(role));\n\n      if (!hasPermission) {\n        return { allowed: false, reason: `Requires one of: ${allowedRoles.join(\", \")}` };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Attribute-Based Access Control\n\nUse tool arguments for attribute-based access control (only available during execution):\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, action, arguments: args, userId }) => {\n      // arguments is only available during execution\n      if (action === \"execution\" && toolName === \"get_user_data\") {\n        const requestedUserId = args?.userId as string;\n        if (requestedUserId !== userId) {\n          return { allowed: false, reason: \"Can only access your own data\" };\n        }\n      }\n\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n```\n\n## Policy-Based Authorization with Cerbos\n\nFor production applications, use a dedicated authorization service like [Cerbos](https://cerbos.dev/). Cerbos is an open-source authorization layer that lets you define access policies in YAML files.\n\nSee the full working example: [examples/with-cerbos](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos)\n\n### Project Structure\n\n```\nwith-cerbos/\n├── docker-compose.yml    # Cerbos PDP container\n├── policies/\n│   └── mcp_tools.yaml    # Authorization policies\n├── src/\n│   ├── index.ts          # Agent with MCP authorization\n│   └── mcp-server.ts     # MCP server with tools\n└── package.json\n```\n\n### Step 1: Start Cerbos PDP\n\nCreate `docker-compose.yml`:\n\n```yaml\nservices:\n  cerbos:\n    image: ghcr.io/cerbos/cerbos:latest\n    ports:\n      - \"3593:3593\" # gRPC\n      - \"3592:3592\" # HTTP\n    volumes:\n      - ./policies:/policies\n    command: [\"server\", \"--config=/conf.yaml\"]\n    configs:\n      - source: cerbos-config\n        target: /conf.yaml\n\nconfigs:\n  cerbos-config:\n    content: |\n      server:\n        httpListenAddr: \":3592\"\n        grpcListenAddr: \":3593\"\n      storage:\n        driver: disk\n        disk:\n          directory: /policies\n```\n\nStart with `docker compose up -d`.\n\n### Step 2: Define Policies\n\nCreate `policies/mcp_tools.yaml`:\n\n```yaml\napiVersion: api.cerbos.dev/v1\nresourcePolicy:\n  version: \"default\"\n  resource: \"mcp::expenses\"\n  rules:\n    # All users can list expenses\n    - actions: [\"list_expenses\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\", \"user\"]\n\n    # Only users can add expenses\n    - actions: [\"add_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"user\"]\n\n    # Managers and admins can approve/reject\n    - actions: [\"approve_expense\", \"reject_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\"]\n\n    # Only admins can delete\n    - actions: [\"delete_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\"]\n```\n\nThe policy structure:\n\n- `resource`: `mcp::expenses` matches the `serverName` in your MCP configuration\n- `actions`: Tool names (e.g., `list_expenses`, `add_expense`)\n- `roles`: Which roles can perform the action\n- `effect`: `EFFECT_ALLOW` or `EFFECT_DENY`\n\n### Step 3: Connect to Cerbos\n\n```typescript\nimport { GRPC } from \"@cerbos/grpc\";\nimport { Agent, MCPConfiguration, VoltAgent } from \"@voltagent/core\";\nimport type { MCPCanParams } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst cerbos = new GRPC(\"localhost:3593\", { tls: false });\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    expenses: {\n      type: \"http\",\n      url: \"http://localhost:3142/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, serverName, action, userId, context }: MCPCanParams) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [\"user\"];\n\n      const result = await cerbos.checkResource({\n        principal: {\n          id: userId ?? \"anonymous\",\n          roles,\n        },\n        resource: {\n          kind: `mcp::${serverName}`,\n          id: serverName,\n        },\n        actions: [toolName],\n      });\n\n      const allowed = result.isAllowed(toolName) ?? false;\n\n      return {\n        allowed,\n        reason: allowed ? undefined : `Access denied for ${toolName}`,\n      };\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Step 4: Create the Agent\n\n```typescript\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n\nconst agent = new Agent({\n  name: \"Finance Assistant\",\n  instructions: `You are a finance assistant that helps users manage expenses.\nAvailable actions depend on the user's role.`,\n  model: \"openai/gpt-4o-mini\",\n  tools,\n});\n\nnew VoltAgent({\n  agents: { agent },\n  server: honoServer(),\n});\n```\n\n### How It Works\n\n1. **Tool Discovery**: When `getTools()` is called, each tool is checked against Cerbos. Tools the user can't access are filtered out.\n\n2. **Tool Execution**: When the agent calls a tool, Cerbos verifies the user has permission before execution.\n\n3. **Policy Evaluation**: Cerbos matches the request against policies:\n   - `principal.roles` → matches policy `roles`\n   - `resource.kind` → matches policy `resource`\n   - `actions[0]` → matches policy `actions`\n\n### Example Flow\n\nUser with role `[\"manager\"]` calls `getTools()`:\n\n| Tool              | Cerbos Check                      | Result      |\n| ----------------- | --------------------------------- | ----------- |\n| `list_expenses`   | manager in [admin, manager, user] | ✅ Allowed  |\n| `add_expense`     | manager in [user]                 | ❌ Filtered |\n| `approve_expense` | manager in [admin, manager]       | ✅ Allowed  |\n| `delete_expense`  | manager in [admin]                | ❌ Filtered |\n\nThe manager sees only `list_expenses` and `approve_expense` in their tool list.\n\n## Error Handling\n\nWhen authorization fails at execution time, an `MCPAuthorizationError` is thrown:\n\n```typescript\nimport { MCPAuthorizationError } from \"@voltagent/core\";\n\ntry {\n  const result = await agent.generateText(\"Delete all expenses\", {\n    userId: \"user-123\",\n    context: new Map([[\"roles\", [\"user\"]]]),\n  });\n} catch (error) {\n  if (error instanceof MCPAuthorizationError) {\n    console.log(`Access denied for tool: ${error.toolName}`);\n    console.log(`Server: ${error.serverName}`);\n    console.log(`Reason: ${error.reason}`);\n  }\n}\n```\n\n## Configuration Reference\n\n### MCPAuthorizationConfig\n\n```typescript\ninterface MCPAuthorizationConfig {\n  /**\n   * Authorization function to check tool access.\n   */\n  can: MCPCanFunction;\n\n  /**\n   * Filter tools on discovery (getTools/getToolsets).\n   * When true, unauthorized tools are hidden from the tool list.\n   * @default false\n   */\n  filterOnDiscovery?: boolean;\n\n  /**\n   * Check authorization on execution (callTool).\n   * When true, each tool call is verified before execution.\n   * @default true\n   */\n  checkOnExecution?: boolean;\n}\n```\n\n## See Also\n\n- [MCP Configuration](./mcp.md) - Basic MCP setup\n- [MCP Server](./mcp-server.md) - Building MCP servers\n- [Cerbos Example](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos) - Full Cerbos integration example\n","frontmatter":{"title":"MCP Authorization","description":"Add access control to MCP tools with VoltAgent's authorization layer"},"isInternal":false,"tokens":3577,"sizeBytes":14365},{"name":"mcp-server.md","path":"website/docs/agents/mcp/mcp-server.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/mcp/mcp-server.md","title":"Subagent: mcp-server","category":"subagent-persona","format":"markdown","content":"---\ntitle: MCP Server\ndescription: Quick start guide for exposing VoltAgent over the Model Context Protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent MCP Server\n\nFollow these steps to expose your VoltAgent project over MCP so you can browse tools, prompts, and resources from VoltOps Console (`https://console.voltagent.dev/mcp`) or an MCP-compatible IDE.\n\n## 1. Install the MCP server package\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/mcp-server\n    ```\n  </TabItem>\n</Tabs>\n\n## 2. Create an MCP server instance\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP example\",\n});\n```\n\n> 📘 **Tip:** When you expose agents through MCP, their `purpose` field becomes the tool description shown in clients. Keep it short and user-facing (fallback is the agent instructions if `purpose` is empty).\n\nThis minimal configuration:\n\n- Names the server `voltagent-example` (used in URLs and IDE listings).\n- Registers server metadata so clients can discover it even before you add tools/agents.\n- Enables all transports (`stdio`, `http`, `sse`) by default. Override `protocols` to disable transports you do not need.\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  protocols: {\n    stdio: true,\n    http: false,\n    sse: false,\n  },\n});\n```\n\nThe snippet above shows how to run in stdio-only mode (ideal for IDE integrations or command-line tooling).\n\n## 3. Register the server with VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { mcpServer } from \"./mcp/server\";\nimport { assistant } from \"./agents/assistant\";\nimport { expenseApprovalWorkflow } from \"./workflows/expense\";\nimport { logger } from \"./logger\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  workflows: {\n    expenseApprovalWorkflow,\n  },\n  mcpServers: {\n    mcpServer,\n  },\n  server: honoServer({ port: 3141 }),\n  logger,\n});\n```\n\n- `mcpServers` registers your MCP server alongside the agent/workflow registries.\n- Passing `server: honoServer(...)` makes VoltAgent expose MCP HTTP and SSE routes automatically. With the server running, visit [`https://console.voltagent.dev/mcp`](https://console.voltagent.dev/mcp) to browse, invoke, and debug tools.\n\n## 4. Find the MCP endpoint (HTTP + SSE)\n\nWhen you start the VoltAgent server, MCP transport endpoints are exposed per server ID:\n\n- **Streamable HTTP**: `POST /mcp/{serverId}/mcp`\n- **SSE** (if enabled): `GET /mcp/{serverId}/sse` and `POST /mcp/{serverId}/messages`\n\nThe `{serverId}` is a normalized version of the `name` (lowercased, spaces replaced) and may get a numeric suffix if there are duplicates. You can always discover it via the registry endpoint:\n\n```bash\ncurl http://localhost:3141/mcp/servers\n```\n\nExample response (trimmed):\n\n```json\n{\n  \"servers\": [\n    {\n      \"id\": \"voltagent-example\",\n      \"name\": \"voltagent-example\",\n      \"version\": \"0.1.0\"\n    }\n  ]\n}\n```\n\nOnce you have the `id`, the HTTP MCP URL becomes:\n\n```\nhttp://localhost:3141/mcp/voltagent-example/mcp\n```\n\n## 5. Connect via stdio (local process)\n\nSTDIO does not use an HTTP URL. The MCP client spawns your server process and communicates over stdin/stdout. In the startup log you should see a line like:\n\n```\nSTDIO  uses stdin/stdout. Example client: { type: \"stdio\", command: \"node\", args: [\"dist/index.js\"] }\n```\n\nClient example (MCPClient):\n\n```ts\nimport { MCPClient } from \"@voltagent/core\";\n\nconst client = new MCPClient({\n  clientInfo: { name: \"local-client\", version: \"0.1.0\" },\n  server: {\n    type: \"stdio\",\n    command: \"node\",\n    args: [\"dist/index.js\"],\n    cwd: process.cwd(),\n    env: { ...process.env },\n  },\n});\n\nawait client.connect();\nconst tools = await client.listTools();\n```\n\nReplace `command`/`args` with the entry that starts your VoltAgent + MCP server (for example `pnpm start -- --stdio` or `node dist/index.js` after build).\n\n## 6. Connect via HTTP\n\nUse the MCP URL in `MCPConfiguration`:\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    local: {\n      type: \"http\",\n      url: \"http://localhost:3141/mcp/voltagent-example/mcp\",\n    },\n  },\n});\n\nconst tools = await mcpConfig.getTools();\n```\n\n## 7. Optional: add MCP-only entries\n\nSometimes you want MCP clients to see helpers that are not (yet) registered with VoltAgent. Provide them as keyed objects (just like the main `VoltAgent` config) via the `agents`, `workflows`, or `tools` fields to append entries that live only on the MCP side:\n\n```ts title=\"src/mcp/server.ts\"\nimport { Agent, createTool, createWorkflowChain } from \"@voltagent/core\";\nimport { MCPServer } from \"@voltagent/mcp-server\";\nimport { z } from \"zod\";\n\nconst statusTool = createTool({\n  name: \"status\",\n  description: \"Return the current time\",\n  parameters: z.object({}),\n  async execute() {\n    return { status: \"ok\", time: new Date().toISOString() };\n  },\n});\n\nconst supportAgent = new Agent({\n  name: \"Support Agent\",\n  purpose: \"Route customer tickets to the correct queue.\",\n  instructions:\n    \"Use internal knowledge to triage customer tickets and respond with routing guidance.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [statusTool],\n});\n\nconst incidentWorkflow = createWorkflowChain({\n  id: \"incident-triage\",\n  name: \"Incident Triage\",\n  purpose: \"Placeholder entry for an external workflow.\",\n  input: z.object({ ticketId: z.string() }),\n}).andThen({\n  id: \"acknowledge\",\n  execute: async ({ data }) => ({ ...data, acknowledged: true }),\n});\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  tools: {\n    statusTool,\n  },\n  agents: {\n    supportAgent,\n  },\n  workflows: {\n    incidentWorkflow,\n  },\n});\n```\n\nThese configured entries behave like regular VoltAgent components for MCP clients, they appear in listings and can be invoked-yet they are not registered with the main `VoltAgent` instance.\n\n## 8. Optional: filter exposed agents/workflows/tools\n\nBy default, every agent, workflow, and tool registered with `VoltAgent` is visible to MCP clients. Provide filter functions when you need to hide or reorder the registry output for a specific transport:\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n  filterWorkflows: ({ items }) => items,\n  filterTools: ({ items }) => items,\n});\n```\n\nFilters receive the list of components sourced from the VoltAgent registries (plus any configured additions) and must return the array you want to expose. They are intended for pruning or sorting, use the `agents`/`workflows`/`tools` fields when you need to introduce brand-new entries.\n\n## 9. Optional: stream prompts and resources\n\nMCP clients can ask a server for structured prompt templates (`prompts/list`, `prompts/get`) and arbitrary resources (`resources/list`, `resources/read`). VoltAgent lets you seed static content and/or forward to a dynamic source via the new `adapters` field:\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\n// Replace these placeholders with your own services or data sources\nconst voltOps = {\n  prompts: {\n    list: async () => [\n      {\n        name: \"triage\",\n        description: \"Short ticket triage message\",\n        arguments: [],\n      },\n    ],\n    get: async (name: string, version?: string) => ({\n      description: `Prompt ${name}`,\n      messages: [{ role: \"user\", content: { type: \"text\", text: \"Summarise ticket {{id}}\" } }],\n      version,\n    }),\n  },\n};\n\nconst knowledgeBase = {\n  list: async () => [\n    {\n      uri: \"volt://docs/runbook\",\n      name: \"On-call runbook\",\n      description: \"Operational checklist\",\n      mimeType: \"text/markdown\",\n    },\n  ],\n  read: async (uri: string) => ({\n    uri,\n    mimeType: \"text/markdown\",\n    text: \"# On-call runbook\\n...\",\n  }),\n};\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP with prompts/resources\",\n  adapters: {\n    prompts: {\n      listPrompts: async () => voltOps.prompts.list(),\n      getPrompt: async (params) => voltOps.prompts.get(params.name, params.version),\n    },\n    resources: {\n      listResources: async () => knowledgeBase.list(),\n      readResource: async (uri) => knowledgeBase.read(uri),\n    },\n  },\n});\n```\n\n- The `adapters` block forwards MCP requests to any backend (VoltOps Prompt Manager, a documentation service, your own REST API).\n- When you change external data, call `await mcpServer.notifyPromptListChanged()` or `await mcpServer.notifyResourceListChanged()` so connected IDEs receive the standard `list_changed` notifications. If you update a specific resource at runtime, invoke `await mcpServer.notifyResourceUpdated(\"volt://docs/runbook\")` to push an incremental update only to subscribers.\n- If an adapter provides a `sendRequest` method, MCP clients can make `elicitation/create` calls. VoltAgent forwards these requests to the adapter so you can collect data from the user and return an `ElicitResult`. Tools can consume the bridge via `operationContext.elicitation`:\n\n```ts\nconst confirmAction = createTool({\n  name: \"confirm-action\",\n  description: \"Ask the operator to approve a risky step\",\n  parameters: z.object({ description: z.string() }),\n  async execute(args, operationContext) {\n    const handler = operationContext?.elicitation;\n\n    if (!handler) {\n      throw new Error(\"Elicitation bridge unavailable\");\n    }\n\n    return handler({\n      schema: {\n        type: \"object\",\n        properties: {\n          confirmed: { type: \"boolean\" },\n        },\n        required: [\"confirmed\"],\n      },\n      message: `Approve the following action: ${args.description}`,\n    });\n  },\n});\n```\n\nThat’s it! Your VoltAgent stack now speaks MCP. Start the agent, open VoltOps Console (or your preferred MCP client), and you’ll see the server listed with its tools, prompts, resources, and workflows ready to debug.\n","frontmatter":{"title":"MCP Server","description":"Quick start guide for exposing VoltAgent over the Model Context Protocol."},"isInternal":false,"tokens":2682,"sizeBytes":10692},{"name":"mcp.md","path":"website/docs/agents/mcp/mcp.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/mcp/mcp.md","title":"Subagent: mcp","category":"subagent-persona","format":"markdown","content":"---\ntitle: MCP Client\nslug: /agents/mcp\n---\n\n# Model Context Protocol (MCP)\n\nThe [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is a standardized protocol for AI agents to interact with external tools and services. VoltAgent implements MCP client capabilities, enabling agents to access functionalities like filesystem operations, browser automation, database interactions, and external AI models.\n\n## Transport Types\n\nVoltAgent supports four transport types for MCP connections:\n\n- **`http`**: Attempts streamable HTTP first, automatically falls back to SSE if not supported\n- **`streamable-http`**: Direct streamable HTTP transport (no fallback)\n- **`sse`**: Server-Sent Events transport\n- **`stdio`**: Standard input/output for local processes\n\n## Getting Started with MCPConfiguration\n\n`MCPConfiguration` manages connections to MCP servers and provides their tools to your agents.\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\nimport path from \"node:path\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    // HTTP with automatic fallback\n    github: {\n      type: \"http\",\n      url: \"https://api.githubcopilot.com/mcp\",\n      timeout: 15000, // Optional, default: 30000ms\n    },\n\n    // Streamable HTTP (no fallback)\n    reddit: {\n      type: \"streamable-http\",\n      url: \"https://mcp.composio.dev/reddit/your-api-key-here\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      sessionId: \"optional-session-id\", // Optional\n      timeout: 20000,\n    },\n\n    // SSE transport\n    linear: {\n      type: \"sse\",\n      url: \"https://mcp.linear.app/sse\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      eventSourceInit: { withCredentials: true },\n      timeout: 25000,\n    },\n\n    // stdio for local processes\n    filesystem: {\n      type: \"stdio\",\n      command: \"npx\",\n      args: [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        path.join(process.env.HOME || \"\", \"Desktop\"),\n      ],\n      cwd: process.env.HOME,\n      env: { NODE_ENV: \"production\" },\n      timeout: 10000,\n    },\n  },\n});\n```\n\n## Working with MCP Tools\n\nRetrieve tools from configured MCP servers as standard `Tool` objects compatible with VoltAgent agents.\n\n### Get All Tools as Flat Array\n\n`getTools()` returns all tools from all servers in a single array:\n\n```ts\nconst allTools = await mcpConfig.getTools();\n\n// allTools is Tool<any>[]\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n### Get Tools Organized by Server\n\n`getToolsets()` returns tools grouped by server name:\n\n```ts\nconst toolsets = await mcpConfig.getToolsets();\n\n// toolsets.filesystem is a ToolsetWithTools object\nconst filesystemTools = toolsets.filesystem.getTools();\n\nconst response = await agent.generateText(\"List all files in my Desktop folder\", {\n  userId: \"user123\",\n  tools: filesystemTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Event Handling\n\nAccess individual clients to listen for events:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\nif (clients.reddit) {\n  clients.reddit.on(\"connect\", () => {\n    console.log(\"Connected to Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"disconnect\", () => {\n    console.log(\"Disconnected from Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"error\", (error) => {\n    console.error(\"Reddit MCP error:\", error.message);\n  });\n\n  clients.reddit.on(\"toolCall\", (name, args, result) => {\n    console.log(`Tool ${name} called with:`, args);\n  });\n}\n```\n\nAvailable events: `connect`, `disconnect`, `error`, `toolCall`\n\n## Cleanup\n\nDisconnect clients when done to terminate processes and free resources:\n\n```ts\nawait mcpConfig.disconnect();\n```\n\nThis is especially important for `stdio` servers, which spawn child processes.\n\n## Adding MCP Tools to an Agent\n\nAdd MCP tools at initialization or per request:\n\n### At Agent Initialization\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst allTools = await mcpConfig.getTools();\n\nconst agent = new Agent({\n  name: \"MCP Agent\",\n  instructions: \"You can use MCP tools to access external systems\",\n  model: \"openai/gpt-4o\",\n  tools: allTools,\n});\n\nawait agent.generateText(\"List files in Desktop\", { userId: \"user123\" });\nawait mcpConfig.disconnect();\n```\n\n### At Request Time\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: \"You are a helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst allTools = await mcpConfig.getTools();\n\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Error Handling\n\nHandle failures during connection and tool execution:\n\n```ts\n// Connection errors\ntry {\n  const tools = await mcpConfig.getTools();\n} catch (error) {\n  console.error(\"Failed to fetch MCP tools:\", error);\n}\n\n// Client-specific errors via events\nconst clients = await mcpConfig.getClients();\nclients.filesystem?.on(\"error\", (error) => {\n  console.error(\"Filesystem MCP error:\", error);\n});\n\n// Tool execution errors\ntry {\n  const response = await agent.generateText(\"List files\", {\n    userId: \"user123\",\n    tools: await mcpConfig.getTools(),\n  });\n} catch (error) {\n  console.error(\"Agent execution error:\", error);\n}\n```\n\n### HTTP Fallback Behavior\n\nWhen using `type: \"http\"`, the client attempts streamable HTTP first. If it fails, it automatically creates a new SSE transport and retries the connection.\n\n## User Input Handling (Elicitation)\n\nSome MCP servers may request user input during tool execution. For example, a server might ask for confirmation before performing a destructive action. VoltAgent provides a dynamic API to handle these elicitation requests.\n\n### Setting Up an Elicitation Handler\n\nUse the `elicitation` bridge on individual clients to register handlers:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\n// Set a persistent handler\nclients.myServer.elicitation.setHandler(async (request) => {\n  console.log(\"Server asks:\", request.message);\n  console.log(\"Expected schema:\", request.requestedSchema);\n\n  // Collect user input (e.g., via CLI prompt or UI form)\n  const userConfirmed = await askUserForConfirmation(request.message);\n\n  return {\n    action: userConfirmed ? \"accept\" : \"decline\",\n    content: userConfirmed ? { confirmed: true } : undefined,\n  };\n});\n```\n\nThe handler receives a request object with:\n\n- `message`: Human-readable message from the server\n- `requestedSchema`: JSON schema describing the expected response format\n\nThe handler must return an object with:\n\n- `action`: One of `\"accept\"`, `\"decline\"`, or `\"cancel\"`\n- `content`: Optional data matching the requested schema (when accepting)\n\n### One-Time Handler\n\nHandle only the next elicitation request, then auto-remove:\n\n```ts\nclients.myServer.elicitation.once(async (request) => {\n  return { action: \"accept\", content: { approved: true } };\n});\n```\n\n### Removing the Handler\n\n```ts\nclients.myServer.elicitation.removeHandler();\n```\n\nWhen no handler is registered, elicitation requests are automatically cancelled.\n\n### Checking Handler Status\n\n```ts\nif (clients.myServer.elicitation.hasHandler) {\n  console.log(\"Handler is registered\");\n} else {\n  console.log(\"No handler - requests will be cancelled\");\n}\n```\n\n### Method Chaining\n\nAll elicitation methods support chaining:\n\n```ts\nclients.myServer.elicitation.setHandler(myHandler).removeHandler().setHandler(anotherHandler);\n```\n\n### Agent-Level Elicitation\n\nInstead of setting handlers on individual clients, you can pass an elicitation handler directly to `generateText` or `streamText`. This handler applies to all MCP tools during the request:\n\n```ts\nconst response = await agent.generateText(\"Perform action with MCP tool\", {\n  userId: \"user123\",\n  elicitation: async (request) => {\n    console.log(\"Server asks:\", request.message);\n\n    // Collect user input (CLI prompt, UI form, etc.)\n    const confirmed = await askUser(request.message);\n\n    return {\n      action: confirmed ? \"accept\" : \"decline\",\n      content: confirmed ? { confirmed: true } : undefined,\n    };\n  },\n});\n```\n\nThis approach is useful when:\n\n- You want request-scoped handlers that don't persist\n- You need access to request context (userId, conversationId) in your handler\n- You prefer not to manage client-level handlers manually\n\n## Lifecycle\n\n1. Create `MCPConfiguration` with server definitions\n2. Fetch tools with `getTools()` or `getToolsets()`\n3. Pass tools to agent (initialization or per request)\n4. Set up elicitation handlers if needed (optional)\n5. Use agent methods (`generateText`, `streamText`, etc.)\n6. Monitor with `getClients()` and event listeners (optional)\n7. Disconnect with `disconnect()`\n","frontmatter":{"title":"MCP Client","slug":"/agents/mcp"},"isInternal":false,"tokens":2040,"sizeBytes":8851},{"name":"memory.md","path":"website/docs/agents/memory.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory.md","title":"Subagent: memory","category":"subagent-persona","format":"markdown","content":"---\ntitle: Memory\nslug: /agents/memory\n---\n\n# Agent Memory\n\nVoltAgent's `Memory` class stores conversation history and enables agents to maintain context across interactions. Supports persistent storage, semantic search, and working memory.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n- **Conversation Storage** - Messages stored per `userId` and `conversationId`\n- **Semantic Search** - Retrieve past messages by similarity (requires embedding + vector adapters)\n- **Working Memory** - Compact context storage (Markdown template, JSON schema, or free-form)\n- **Workflow State** - Suspendable workflow checkpoint storage\n\n## Quick Start\n\n```typescript\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"my-app-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// First message\nawait agent.generateText(\"My name is Sarah\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n\n// Agent remembers context\nawait agent.generateText(\"What's my name?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n```\n\n## Complete Documentation\n\nFor detailed configuration, provider setup, and advanced features:\n\n- **[Memory Overview](./memory/overview.md)** - Full memory system documentation\n- **[Memory API Endpoints](../api/endpoints/memory.md)** - HTTP endpoints for conversations and messages\n- **[Managed Memory](./memory/managed-memory.md)** - Production-ready hosted storage\n- **[Semantic Search](./memory/semantic-search.md)** - Vector-based message retrieval\n- **[Working Memory](./memory/working-memory.md)** - Compact context management\n- **[Storage Adapters](./memory/in-memory.md)** - Provider-specific guides\n","frontmatter":{"title":"Memory","slug":"/agents/memory"},"isInternal":false,"tokens":601,"sizeBytes":2682},{"name":"cloudflare-d1.md","path":"website/docs/agents/memory/cloudflare-d1.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/cloudflare-d1.md","title":"Subagent: cloudflare-d1","category":"subagent-persona","format":"markdown","content":"---\ntitle: Cloudflare D1 Memory\nslug: /agents/memory/cloudflare-d1\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Cloudflare D1 Memory\n\n`D1MemoryAdapter` stores conversations in Cloudflare D1 for Workers deployments.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n</Tabs>\n\n## Cloudflare D1 binding\n\nAdd a D1 binding to your `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"voltagent\"\ndatabase_id = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n```\n\n## Configuration (Workers)\n\n```ts\nimport { Agent, Memory, VoltAgent } from \"@voltagent/core\";\nimport { D1MemoryAdapter } from \"@voltagent/cloudflare-d1\";\nimport { serverlessHono } from \"@voltagent/serverless-hono\";\nimport type { D1Database } from \"@cloudflare/workers-types\";\n\nimport { weatherTool } from \"./tools\";\n\ntype Env = {\n  DB: D1Database;\n  OPENAI_API_KEY: string;\n};\n\nconst createWorker = (env: Env) => {\n  const memory = new Memory({\n    storage: new D1MemoryAdapter({\n      binding: env.DB,\n      tablePrefix: \"voltagent_memory\",\n    }),\n  });\n\n  const agent = new Agent({\n    name: \"Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    tools: [weatherTool],\n    memory,\n  });\n\n  const voltAgent = new VoltAgent({\n    agents: { agent },\n    serverless: serverlessHono(),\n  });\n\n  return voltAgent.serverless().toCloudflareWorker();\n};\n\nlet cached: ReturnType<typeof createWorker> | undefined;\n\nexport default {\n  fetch: (request: Request, env: Env, ctx: ExecutionContext) => {\n    if (!cached) {\n      cached = createWorker(env);\n    }\n\n    return cached.fetch(request, env, ctx);\n  },\n};\n```\n\n### Configuration Options\n\n| Option         | Type         | Description                                              |\n| -------------- | ------------ | -------------------------------------------------------- |\n| `binding`      | `D1Database` | Worker D1 binding (required)                             |\n| `tablePrefix`  | `string`     | Table name prefix (default: `voltagent_memory`)          |\n| `maxRetries`   | `number`     | Retry count for busy/locked DB operations (default: `3`) |\n| `retryDelayMs` | `number`     | Initial backoff delay in ms (default: `100`)             |\n| `debug`        | `boolean`    | Enable debug logging (default: `false`)                  |\n| `logger`       | `Logger`     | Optional logger for structured logging                   |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n- `${tablePrefix}_steps`\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new D1MemoryAdapter({ binding: env.DB }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Optional)\n\nD1 provides storage only. To enable semantic search, pair it with an embedding model string (for example, `openai/text-embedding-3-small`) and a vector adapter such as `InMemoryVectorAdapter`.\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n## Learn More\n\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","frontmatter":{"title":"Cloudflare D1 Memory","slug":"/agents/memory/cloudflare-d1"},"isInternal":false,"tokens":988,"sizeBytes":3983},{"name":"in-memory.md","path":"website/docs/agents/memory/in-memory.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/in-memory.md","title":"Subagent: in-memory","category":"subagent-persona","format":"markdown","content":"---\ntitle: In-Memory Storage\nslug: /agents/memory/in-memory\n---\n\n# In-Memory Storage\n\n`InMemoryStorageAdapter` stores conversation history in application memory. Data is lost when the application restarts.\n\n## Default Behavior\n\nAgents use in-memory storage by default when no `memory` option is provided:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Uses InMemoryStorageAdapter automatically\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users with questions.\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\nOmitting `memory` does **not** disable memory. To make an agent stateless, set `memory: false`.\n\n## Explicit Configuration\n\nConfigure storage limits explicitly:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n## Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved in memory\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Development)\n\nCombine with `InMemoryVectorAdapter` for semantic search during development:\n\n```ts\nimport { Memory, InMemoryVectorAdapter, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n});\n```\n\nBoth storage and vectors are lost on restart. For persistent vectors, use `LibSQLVectorAdapter`.\n\n## Use Cases\n\n### Development & Testing\n\nTest agent logic without database setup:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst testAgent = new Agent({\n  name: \"Test Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new InMemoryStorageAdapter(),\n  }),\n});\n\n// Test conversations without persistence\nawait testAgent.generateText(\"Test message\", {\n  memory: {\n    userId: \"test-user\",\n    conversationId: \"test-conversation\",\n  },\n});\n```\n\n### Stateless Deployments\n\nServerless functions or ephemeral containers where persistence isn't needed:\n\n```ts\n// Cloud function handler\nexport async function handler(event) {\n  const agent = new Agent({\n    name: \"Serverless Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    // Default in-memory storage\n  });\n\n  return await agent.generateText(event.message, {\n    memory: {\n      userId: event.userId,\n      conversationId: event.sessionId,\n    },\n  });\n}\n```\n\n### Demos & Examples\n\nQuick prototypes without infrastructure dependencies.\n\n## Limitations\n\n- **No persistence** - All data lost on restart\n- **Memory usage** - Large message counts consume application memory\n- **Not for production** - Use persistent adapters for production applications\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n","frontmatter":{"title":"In-Memory Storage","slug":"/agents/memory/in-memory"},"isInternal":false,"tokens":811,"sizeBytes":3502},{"name":"libsql.md","path":"website/docs/agents/memory/libsql.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/libsql.md","title":"Subagent: libsql","category":"subagent-persona","format":"markdown","content":"---\ntitle: LibSQL / SQLite Memory\nslug: /agents/memory/libsql\n---\n\n# LibSQL / SQLite Memory\n\n`LibSQLMemoryAdapter` stores conversations in LibSQL, supporting local SQLite files, remote instances, and self-hosted `sqld`.\n\n## Installation\n\n```bash\nnpm install @voltagent/libsql\n```\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\n// Local SQLite\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"file:./.voltagent/memory.db\", // or \":memory:\" for ephemeral\n  }),\n});\n\n// Remote instance\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"libsql://your-database.example.io\",\n    authToken: process.env.LIBSQL_AUTH_TOKEN,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option        | Type     | Description                                                |\n| ------------- | -------- | ---------------------------------------------------------- |\n| `url`         | `string` | Connection URL (`file:<path>`, `libsql://`, or `:memory:`) |\n| `authToken`   | `string` | Auth token for remote instances (optional for local files) |\n| `tablePrefix` | `string` | Table name prefix (default: `voltagent_memory`)            |\n| `logger`      | `Logger` | Optional logger for debugging                              |\n\n### URL Formats\n\n- **Local SQLite**: `file:./.voltagent/memory.db` or `file:memory.db`\n- **In-memory (testing)**: `:memory:` or `file::memory:`\n- **Remote instance** (e.g., Turso): `libsql://your-database.example.io`\n- **Self-hosted sqld**: `libsql://your-server.com` or `https://your-server.com`\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nSchema migrations run automatically when updating VoltAgent versions.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    template: `# Context\\n- Key facts:\\n`,\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Vector Storage (Optional)\n\nUse `LibSQLVectorAdapter` for persistent vector storage:\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n```\n\n**Vector adapter options:**\n\n- Local file: Same URL as storage adapter\n- In-memory: `:memory:` (vectors lost on restart)\n- Turso: Same URL + auth token as storage adapter\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n**Use cases:**\n\n- Local development with SQLite files\n- Edge deployments with remote instances\n- Serverless environments\n- Simple deployments without separate database servers\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Examples\n\n### Development with Local SQLite\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Dev Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: \"file:./.voltagent/dev-memory.db\",\n    }),\n  }),\n});\n```\n\n### Remote Instance\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: process.env.LIBSQL_DATABASE_URL!,\n      authToken: process.env.LIBSQL_AUTH_TOKEN!,\n    }),\n  }),\n});\n```\n\n### Testing with In-Memory Database\n\n```ts\nconst testMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \":memory:\", // ephemeral\n  }),\n});\n```\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search with LibSQLVectorAdapter\n","frontmatter":{"title":"LibSQL / SQLite Memory","slug":"/agents/memory/libsql"},"isInternal":false,"tokens":1160,"sizeBytes":4717},{"name":"managed-memory.md","path":"website/docs/agents/memory/managed-memory.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/managed-memory.md","title":"Subagent: managed-memory","category":"subagent-persona","format":"markdown","content":"---\ntitle: Managed Memory\nslug: /agents/memory/managed-memory\n---\n\n# Managed Memory\n\nVoltOps Managed Memory is a production-ready hosted memory service for VoltAgent. Create a database through the VoltOps Console and connect using API credentials - no infrastructure provisioning or schema management required.\n\n## Availability\n\n- **US Region**: Virginia (us-east-1)\n- **EU Region**: Germany (eu-central-1)\n\n## Installation\n\n```bash\nnpm install @voltagent/voltagent-memory\n```\n\n## Configuration\n\n### Automatic Setup (Recommended)\n\nGet your credentials from [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory) and set environment variables:\n\n<video controls width=\"100%\">\n  <source src=\"https://cdn.voltagent.dev/docs/voltagent-managed-memory.mp4\" type=\"video/mp4\" />\n  Your browser does not support the video tag.\n</video>\n\n```bash\n# .env\nVOLTAGENT_PUBLIC_KEY=pk_...\nVOLTAGENT_SECRET_KEY=sk_...\n```\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\n// Adapter automatically uses VoltOps credentials from environment\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nThe adapter checks `AgentRegistry` for a global `VoltOpsClient` instance configured from environment variables. This is the simplest setup - no need to instantiate `VoltOpsClient` manually.\n\n### Manual Setup\n\nPass a `VoltOpsClient` instance explicitly:\n\n```ts\nimport { Agent, Memory, VoltOpsClient } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY!,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY!,\n  // baseUrl: \"https://api.voltagent.dev\", // optional\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient, // explicit client\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nUse manual setup when:\n\n- Running multiple adapters with different credentials\n- Dynamically changing credentials at runtime\n- Testing with mock clients\n\n### Configuration Options\n\n| Option          | Type            | Required | Description                                                      |\n| --------------- | --------------- | -------- | ---------------------------------------------------------------- |\n| `databaseName`  | `string`        | Yes\\*    | Database name from VoltOps Console                               |\n| `databaseId`    | `string`        | Yes\\*    | Database ID from VoltOps API (alternative to `databaseName`)     |\n| `voltOpsClient` | `VoltOpsClient` | No       | Explicit VoltOps client (optional if credentials in environment) |\n| `debug`         | `boolean`       | No       | Enable debug logging (default: `false`)                          |\n\n\\*Either `databaseName` or `databaseId` is required.\n\n## Creating a Database\n\n1. Navigate to [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory)\n2. Click **Create Database**\n3. Enter a name and select region (US or EU)\n4. Copy the database credentials\n\nTo get your VoltOps API keys, go to **Settings** in the console to find your public and secret keys.\n\n## Features\n\n### Conversation Storage\n\nAll `StorageAdapter` methods are supported:\n\n- Message persistence (`addMessage`, `addMessages`, `getMessages`, `clearMessages`)\n- Conversation management (`createConversation`, `getConversation`, `updateConversation`, `deleteConversation`)\n- Working memory (`getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`)\n- Workflow state (`getWorkflowState`, `setWorkflowState`, `updateWorkflowState`)\n\n### Vector Storage (Optional)\n\nEnable semantic search with `ManagedMemoryVectorAdapter`:\n\n```ts\nimport { ManagedMemoryAdapter, ManagedMemoryVectorAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new ManagedMemoryVectorAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n```\n\nBoth adapters resolve credentials the same way (environment or explicit client). See [Semantic Search](./semantic-search.md) for usage.\n\n## Migration from Self-Hosted\n\n### Export from LibSQL\n\n```ts\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst localAdapter = new LibSQLMemoryAdapter({\n  url: \"file:./.voltagent/memory.db\",\n});\n\n// Export conversations for a user\nconst conversations = await localAdapter.getConversationsByUserId(\"user-123\");\n\nfor (const conv of conversations) {\n  const messages = await localAdapter.getMessages(\"user-123\", conv.id);\n  console.log(`Conversation ${conv.id}: ${messages.length} messages`);\n}\n```\n\n### Import to Managed Memory\n\n```ts\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst managedMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\n// Import conversation\nawait managedMemory.createConversation({\n  id: conv.id,\n  userId: conv.userId,\n  resourceId: conv.resourceId,\n  title: conv.title,\n  metadata: conv.metadata,\n});\n\n// Import messages\nawait managedMemory.addMessages(messages, conv.userId, conv.id);\n```\n\nBulk import/export APIs are planned for future releases.\n\n## Use Cases\n\n### Development & Prototyping\n\n```ts\n// No database setup required for pilots\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"dev-memory\",\n  }),\n});\n```\n\n### Multi-Region Deployment\n\n```ts\n// US database for North American users\nconst usMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-us\",\n  }),\n});\n\n// EU database for European users\nconst euMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-eu\",\n  }),\n});\n```\n\n### Team Collaboration\n\nMultiple developers connect to the same managed database using shared VoltOps credentials. Create separate databases for staging and production.\n\n## Limitations\n\n- **Regional latency**: Choose the region closest to your application servers\n- **Storage quotas**: Check VoltOps Console for plan-specific limits\n- **Credential rotation**: Update environment variables when rotating credentials\n\n## Comparison with Self-Hosted\n\n| Feature             | Managed Memory    | Self-Hosted               |\n| ------------------- | ----------------- | ------------------------- |\n| Setup time          | < 3 minutes       | Hours                     |\n| Schema management   | Automatic         | Manual migrations         |\n| Regional hosting    | US & EU           | DIY                       |\n| Credential rotation | API-based         | Manual                    |\n| Monitoring          | Console dashboard | DIY                       |\n| Cost                | Usage-based       | Infrastructure + ops time |\n\nProduction-ready for teams without database infrastructure or during rapid deployment.\n\n## Troubleshooting\n\n### Adapter initialization failed\n\n```\nError: Unable to locate managed memory database\n```\n\n**Solution**: Verify `databaseName` matches the name in VoltOps Console, or use `databaseId` instead.\n\n### VoltOps client not available\n\n```\nError: VoltOps client is not available for managed memory initialization\n```\n\n**Solution**: Ensure `VOLTAGENT_PUBLIC_KEY` and `VOLTAGENT_SECRET_KEY` environment variables are set, or pass `voltOpsClient` explicitly.\n\n### Enable debug logging\n\n```ts\nconst adapter = new ManagedMemoryAdapter({\n  databaseName: \"production-memory\",\n  debug: true, // Logs all API calls and metadata\n});\n```\n\n## Learn More\n\n- **[Semantic Search](./semantic-search.md)** - Enable vector search with managed memory\n- **[Working Memory](./working-memory.md)** - Configure working memory with managed storage\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres alternative\n","frontmatter":{"title":"Managed Memory","slug":"/agents/memory/managed-memory"},"isInternal":false,"tokens":1796,"sizeBytes":8212},{"name":"overview.md","path":"website/docs/agents/memory/overview.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/overview.md","title":"Subagent: overview","category":"subagent-persona","format":"markdown","content":"---\ntitle: Overview\nslug: /agents/memory/overview\n---\n\n# Memory Overview\n\nVoltAgent's `Memory` class stores conversation history and optional semantic search vectors. Agents retrieve past messages before generating responses and persist new interactions after completion.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Cloudflare D1**  | `@voltagent/cloudflare-d1`    | Cloudflare D1 (SQLite) | Cloudflare Workers deployments   |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- Auto-creates conversations on first message\n- Configurable message limits (oldest pruned first)\n\n### Conversation Titles (Optional)\n\nWhen enabled, VoltAgent generates a concise title from the first user message. Title generation runs only when the conversation is created and does not overwrite existing titles.\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: true,\n});\n```\n\nCustom configuration:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: {\n    enabled: true,\n    model: \"gpt-4o-mini\", // default agent model\n    systemPrompt: \"Generate a short title (max 6 words).\",\n    temperature: null, // optional; default is 0, use null to omit\n    maxLength: 60,\n    maxOutputTokens: 128, // optional; default is 32\n  },\n});\n```\n\nNotes:\n\n- The agent's main model is used unless `generateTitle.model` is provided.\n- `generateTitle.model` accepts either a provider/model string or an AI SDK model instance.\n- Title generation sends `temperature: 0` by default. Set `generateTitle.temperature` to `null` to\n  omit the parameter for reasoning models such as OpenAI `o`-series and `gpt-5-mini`.\n- Reasoning models may need a higher `maxOutputTokens` value because reasoning tokens count toward\n  the output budget. Configure `generateTitle.maxOutputTokens` if the default `32` is too low.\n- If title generation fails, VoltAgent keeps the default conversation title and logs the cause at\n  `warn` level, including a hint to set `generateTitle.temperature` to `null` when appropriate.\n- Only the first user message is summarized.\n- If you create conversations manually via the Memory API, set `title` explicitly.\n\n### Conversation Steps\n\n- Every LLM/text/tool step can be recorded with metadata (operationId, agent/sub-agent IDs, usage, tool arguments/results).\n- VoltOps Observability consumes these records to render the Memory Explorer “Steps” tab and to correlate traces/logs with memory.\n- Adapters that implement the step APIs persist sub-agent activity alongside primary agent steps, so hierarchies stay visible.\n- If an adapter does not support steps, the console warns with “Conversation steps are not supported by this memory adapter.”\n- By default, agents checkpoint conversation progress during multi-step runs (`conversationPersistence.mode: \"step\"`).\n- Tool completion events (`tool-result`, `tool-error`) trigger immediate flushes by default (`flushOnToolResult: true`), reducing history loss risk on crashes or process interruption.\n- Use `conversationPersistence.mode: \"finish\"` if you want end-of-turn persistence only.\n\n### Semantic Search (Optional)\n\n- Requires `embedding` + `vector` adapters\n- Auto-embeds messages on save\n- Retrieves similar past messages by content, not recency\n- Merges semantic results with recent messages\n\n### Working Memory (Optional)\n\n- Stores compact context across conversation turns\n- Three formats: Markdown template, JSON schema (Zod), or free-form\n- Two scopes: `conversation` (default) or `user`\n- Agent exposes tools: `get_working_memory`, `update_working_memory`, `clear_working_memory`\n- If `memory.options.readOnly: true` is set per call, only `get_working_memory` is exposed and memory writes are skipped\n\n### Workflow State\n\n- Suspendable workflow checkpoint storage\n- Tracks execution state, context, suspension metadata\n\n## Agent Configuration\n\nAgents accept a `memory` option:\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\n\n// Default when omitted: built-in in-memory storage\nconst agent1 = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  // memory omitted (or undefined)\n});\n\n// Disable memory entirely\nconst agent2 = new Agent({\n  name: \"Stateless\",\n  model: \"openai/gpt-4o-mini\",\n  memory: false,\n});\n\n// Persistent storage\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent3 = new Agent({\n  name: \"Persistent\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  }),\n});\n```\n\n**`memory` option behavior**\n\n| Value                 | Behavior                                                                |\n| --------------------- | ----------------------------------------------------------------------- |\n| Omitted / `undefined` | Conversation memory is enabled with a built-in `InMemoryStorageAdapter` |\n| `false`               | Conversation memory is disabled                                         |\n| `new Memory({ ... })` | Conversation memory is enabled with your configured adapter             |\n\nFor stateless sub-agents, set `memory: false` on each sub-agent explicitly.\n\nWhen a supervisor delegates to sub-agents, delegated messages are tagged with sub-agent metadata so supervisor memory reads can filter sub-agent records from parent conversation context.\n\n### Global Defaults (VoltAgent)\n\nSet default memory instances once at the VoltAgent entrypoint. Defaults apply only when an agent or workflow does not specify `memory`. If nothing is configured, VoltAgent still falls back to built-in in-memory storage. An explicit `memory: false` on an agent disables memory and bypasses defaults.\n\n```ts\nimport { Memory, VoltAgent } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agentMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/agent.db\" }),\n});\n\nconst workflowMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/workflows.db\" }),\n});\n\nnew VoltAgent({\n  agentMemory,\n  workflowMemory,\n  // memory: sharedFallbackMemory,\n});\n```\n\n**Precedence**\n\n- Agents: agent `memory` > `agentMemory` > `memory` > built-in in-memory\n- Workflows: workflow `memory` > `workflowMemory` > `memory` > built-in in-memory\n\n## Usage with User and Conversation IDs\n\nProvide `memory.userId` and `memory.conversationId` in generation calls to scope memory:\n\n```ts\nconst response = await agent.generateText(\"What did we discuss yesterday?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n  },\n});\n```\n\n**Behavior:**\n\n- **Both provided**: Retrieves history for that specific thread\n- **Only userId**: New `conversationId` generated per call (fresh context each time)\n- **Neither provided**: Uses default user ID with new conversation ID\n\n## Examples\n\n### Managed Memory (Zero Setup)\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { VoltOpsClient } from \"@voltagent/core\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Semantic Search + Working Memory\n\n```ts\nimport { Agent, Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\",\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n      goals: z.array(z.string()).optional(),\n    }),\n  },\n});\n\nconst agent = new Agent({\n  name: \"Smart Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// Enable semantic search per call\nconst result = await agent.generateText(\"What preferences did I mention?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n    options: {\n      semanticMemory: {\n        enabled: true,\n        semanticLimit: 5,\n        semanticThreshold: 0.7,\n      },\n    },\n  },\n});\n```\n\n## Custom Adapters\n\nImplement the `StorageAdapter` interface to use custom databases:\n\n```ts\nimport type { StorageAdapter, UIMessage, Conversation, OperationContext } from \"@voltagent/core\";\n\nexport class MyStorageAdapter implements StorageAdapter {\n  async addMessage(\n    msg: UIMessage,\n    userId: string,\n    conversationId: string,\n    context?: OperationContext // Optional: access operation context\n  ): Promise<void> {\n    // Access user-provided context values\n    const tenantId = context?.context.get(\"tenantId\");\n\n    // Use logger and tracing\n    context?.logger.info(\"Storing message\", { userId, tenantId });\n\n    // Store message in your database\n  }\n\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: { limit?: number },\n    context?: OperationContext // Optional: access operation context\n  ): Promise<UIMessage[]> {\n    // Retrieve messages in chronological order (oldest first)\n    return [];\n  }\n\n  async createConversation(input: CreateConversationInput): Promise<Conversation> {\n    // Create conversation record\n  }\n\n  // ... implement remaining StorageAdapter methods\n}\n```\n\nRequired methods:\n\n- If you want Memory Explorer’s Steps tab (and Observability APIs) to work, make sure your adapter persists conversation steps by implementing the methods below. Otherwise VoltAgent will surface the “Conversation steps are not supported by this memory adapter.” warning.\n\n- Messages: `addMessage`, `addMessages`, `getMessages`, `clearMessages`\n- Conversations: `createConversation`, `getConversation`, `getConversations`, `getConversationsByUserId`, `queryConversations`, `updateConversation`, `deleteConversation`\n- Conversation steps: `saveConversationSteps`, `getConversationSteps`\n- Working memory: `getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`\n- Workflow state: `getWorkflowState`, `queryWorkflowRuns`, `setWorkflowState`, `updateWorkflowState`, `getSuspendedWorkflowStates`\n\nUse `queryWorkflowRuns({ workflowId?, status?, from?, to?, limit?, offset? })` for filtered/paginated listings (ordered by newest first).\n\n### Advanced: Context-Aware Adapters\n\nCustom adapters can access `OperationContext` for dynamic, per-request behavior:\n\n```ts\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log with tracing\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n    });\n\n    // Use scoped ID for data isolation\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Pass tenant per request\nawait agent.generateText(\"Query\", {\n  memory: {\n    userId: \"user-123\",\n  },\n  context: { tenantId: \"company-abc\" }, // Different tenant = different data\n});\n```\n\n**Use cases for context-aware adapters:**\n\n- **Multi-tenancy**: Isolate data by tenant using composite keys\n- **Audit logging**: Track all memory access with full context\n- **Access control**: Validate permissions before queries\n- **Dynamic behavior**: Adjust queries based on runtime values\n\nSee [Operation Context](../context.md#memory-adapters) for more examples and patterns.\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Semantic Search](./semantic-search.md)** - Retrieve messages by similarity\n- **[Working Memory](./working-memory.md)** - Maintain compact context across turns\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n- **[In-Memory Storage](./in-memory.md)** - Default ephemeral storage\n","frontmatter":{"title":"Overview","slug":"/agents/memory/overview"},"isInternal":false,"tokens":3127,"sizeBytes":13752},{"name":"postgres.md","path":"website/docs/agents/memory/postgres.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/postgres.md","title":"Subagent: postgres","category":"subagent-persona","format":"markdown","content":"---\ntitle: PostgreSQL Memory\nslug: /agents/memory/postgres\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# PostgreSQL Memory\n\n`PostgreSQLMemoryAdapter` stores conversations in PostgreSQL for production applications with existing Postgres infrastructure.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/postgres\n```\n\n  </TabItem>\n</Tabs>\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\n// Using connection string\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n    // or: \"postgresql://user:password@localhost:5432/mydb\"\n  }),\n});\n\n// Using connection object\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST || \"localhost\",\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: process.env.NODE_ENV === \"production\",\n    },\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option           | Type               | Description                                                                            |\n| ---------------- | ------------------ | -------------------------------------------------------------------------------------- |\n| `connection`     | `string \\| object` | Connection string or object with `host`, `port`, `database`, `user`, `password`, `ssl` |\n| `maxConnections` | `number`           | Connection pool size (default: `10`)                                                   |\n| `tablePrefix`    | `string`           | Table name prefix (default: `voltagent_memory`)                                        |\n| `debug`          | `boolean`          | Enable debug logging (default: `false`)                                                |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nPostgreSQL version 12 or higher recommended.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- Supports complex queries with filtering, pagination, and sorting\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // or \"conversation\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nStorage:\n\n- Conversation scope: `conversations.metadata.workingMemory`\n- User scope: `${tablePrefix}_users.metadata.workingMemory`\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### PostgreSQL Vector Storage\n\nStore vector embeddings directly in PostgreSQL for semantic search (no extensions required):\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter, PostgresVectorAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new PostgresVectorAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n});\n```\n\n**How It Works:**\n\n- **BYTEA Storage**: Vectors are stored as binary data using PostgreSQL's native `BYTEA` type\n- **In-Memory Similarity**: Cosine similarity is computed in-memory after loading vectors\n- **No Extensions**: Works with vanilla PostgreSQL 12+ without any extensions\n- **Automatic Setup**: Creates tables and indexes automatically on first use\n\n**Configuration Options:**\n\n```ts\nconst vectorAdapter = new PostgresVectorAdapter({\n  connection: process.env.DATABASE_URL!,\n\n  // Optional: customize table name (default: \"voltagent_vector\")\n  tablePrefix: \"custom_vector\",\n\n  // Optional: LRU cache size for frequently accessed vectors (default: 100)\n  cacheSize: 100,\n\n  // Optional: max vector dimensions (default: 1536)\n  maxVectorDimensions: 1536,\n});\n```\n\n**Benefits:**\n\n- **Unified Storage**: Keep messages and vectors in the same database\n- **No Extensions**: Works with any PostgreSQL instance (managed or self-hosted)\n- **Simple Setup**: No extension installation or special configuration needed\n- **Standard Tools**: Use familiar PostgreSQL monitoring and backup tools\n\n**Performance Considerations:**\n\nThis adapter loads all vectors into memory for similarity computation, which works well for:\n\n- Small to medium datasets (< 10,000 vectors)\n- Development and prototyping\n- Environments where extension installation is restricted\n\nFor large-scale production with millions of vectors, consider specialized vector databases or use `InMemoryVectorAdapter` with periodic persistence.\n\nSee [Semantic Search](./semantic-search.md) for detailed configuration and usage.\n\n## Production Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: true, // Enable SSL for production\n    },\n    maxConnections: 20, // Adjust based on load\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Production Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Advanced SSL Configuration\n\nFor databases requiring custom CA certificates or client certificates:\n\n```ts\nimport fs from \"fs\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: {\n        rejectUnauthorized: true,\n        ca: fs.readFileSync(\"/path/to/ca-certificate.crt\").toString(),\n        // Optional: client certificate authentication\n        key: fs.readFileSync(\"/path/to/client-key.key\").toString(),\n        cert: fs.readFileSync(\"/path/to/client-cert.crt\").toString(),\n      },\n    },\n    maxConnections: 20,\n  }),\n});\n```\n\nThe `ssl` option accepts either:\n\n- `boolean` - Simple SSL enable/disable\n- `ConnectionOptions` - Full TLS configuration object with support for:\n  - `ca` - Custom Certificate Authority\n  - `key` - Client private key\n  - `cert` - Client certificate\n  - `rejectUnauthorized` - Certificate validation control\n  - All other Node.js [TLS options](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)\n\n### Security\n\n- Use SSL connections in production (`ssl: true` or advanced config)\n- Store credentials and certificates in environment variables or secure secret management\n- Implement regular database backups\n- Adjust `maxConnections` based on concurrent usage\n\n**Use cases:**\n\n- Applications with existing Postgres infrastructure\n- High-availability requirements (replication, failover)\n- Complex SQL queries or analytics on conversation data\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","frontmatter":{"title":"PostgreSQL Memory","slug":"/agents/memory/postgres"},"isInternal":false,"tokens":1752,"sizeBytes":8112},{"name":"semantic-search.md","path":"website/docs/agents/memory/semantic-search.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/semantic-search.md","title":"Subagent: semantic-search","category":"subagent-persona","format":"markdown","content":"---\ntitle: Semantic Search\nslug: /agents/memory/semantic-search\n---\n\n# Semantic Search\n\nSemantic search retrieves past messages by similarity rather than recency. It requires an embedding adapter (text to vectors) and a vector adapter (storage and search).\n\n## Configuration\n\n```ts\nimport { Agent, Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }), // or InMemoryVectorAdapter() for dev\n  enableCache: true, // optional embedding cache\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nYou can pass an embedding config object to set adapter options:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: {\n    model: \"openai/text-embedding-3-small\",\n    normalize: true,\n  },\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n```\n\n### Available Adapters\n\n**Embedding:**\n\nMemory accepts an embedding adapter or a provider-qualified model string such as\n`\"openai/text-embedding-3-small\"`.\n\n- `AiSdkEmbeddingAdapter` - Wraps any AI SDK embedding model\n\n**Vector Storage:**\n\n- `InMemoryVectorAdapter` (`@voltagent/core`) - Development only\n- `LibSQLVectorAdapter` (`@voltagent/libsql`) - Persistent vectors in SQLite/LibSQL/Turso\n- `PostgresVectorAdapter` (`@voltagent/postgres`) - Persistent vectors in Postgres with pgvector\n- `ManagedMemoryVectorAdapter` (`@voltagent/voltagent-memory`) - VoltOps-hosted vectors\n\n## Usage\n\nEnable semantic search per generation call:\n\n```ts\nconst result = await agent.generateText(\"What pricing model did we discuss?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n    options: {\n      semanticMemory: {\n        enabled: true, // default: auto-enabled when vector support is present\n        semanticLimit: 5, // number of similar messages to retrieve\n        semanticThreshold: 0.7, // minimum similarity score (0-1)\n        mergeStrategy: \"append\", // \"prepend\" | \"append\" | \"interleave\"\n      },\n    },\n  },\n});\n```\n\n### Default Behavior\n\nWhen `embedding` and `vector` adapters are configured:\n\n- Semantic search auto-enables for calls with `memory.userId` and `memory.conversationId`\n- Default `semanticLimit`: 5 messages\n- Default `semanticThreshold`: 0.7\n- Default `mergeStrategy`: `\"append\"` (recent messages first, then similar messages)\n\n### Merge Strategies\n\n- **`append`** (default): `[recent messages] + [similar messages]` - preserves chronological order\n- **`prepend`**: `[similar messages] + [recent messages]` - emphasizes relevance\n- **`interleave`**: Alternates between similar and recent messages\n\n## How It Works\n\n### On Message Save\n\nWhen saving messages, the `Memory` class:\n\n1. Extracts text content from `UIMessage.parts`\n2. Generates embeddings via the embedding adapter\n3. Stores vectors with metadata:\n   - ID: `msg_${conversationId}_${message.id}`\n   - Metadata: `{ messageId, conversationId, userId, role, createdAt }`\n\n### On Message Retrieval\n\nWhen semantic search is enabled:\n\n1. Embed the current query\n2. Search for similar vectors using the vector adapter\n3. Retrieve matching messages by ID\n4. Merge with recent messages using the configured strategy\n5. Remove duplicates (messages in both sets)\n\n## Programmatic API\n\nDirect search without agent generation:\n\n```ts\n// Check if vectors are configured\nconst hasVectors = memory.hasVectorSupport(); // boolean\n\n// Search similar messages\nconst results = await memory.searchSimilar(\"pricing discussion\", {\n  limit: 10,\n  threshold: 0.8,\n  filter: { userId: \"user-123\", conversationId: \"thread-abc\" },\n});\n\nfor (const result of results) {\n  console.log(result.id, result.score, result.metadata);\n}\n```\n\n## Embedding Cache\n\nEnable caching to avoid re-embedding identical text:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  enableCache: true, // enable cache\n  cacheSize: 1000, // max entries (default: 1000)\n  cacheTTL: 3600000, // TTL in ms (default: 1 hour)\n});\n```\n\nThe cache stores `text → vector` mappings in memory with LRU eviction.\n\n## Vector Adapters\n\n### InMemoryVectorAdapter\n\n```ts\nimport { InMemoryVectorAdapter } from \"@voltagent/core\";\n\nconst vector = new InMemoryVectorAdapter();\n```\n\n- Uses cosine similarity\n- Supports metadata filtering\n- Lost on restart (use for development only)\n\n### LibSQLVectorAdapter\n\n```ts\nimport { LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\n// Local SQLite\nconst vector = new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" });\n\n// In-memory (testing)\nconst vector = new LibSQLVectorAdapter({ url: \":memory:\" });\n\n// Turso\nconst vector = new LibSQLVectorAdapter({\n  url: \"libsql://your-db.turso.io\",\n  authToken: process.env.TURSO_AUTH_TOKEN,\n});\n```\n\n- Stores vectors as BLOBs\n- Supports metadata filtering and thresholds\n- Persistent across restarts\n\n### ManagedMemoryVectorAdapter\n\n```ts\nimport { ManagedMemoryVectorAdapter } from \"@voltagent/voltagent-memory\";\n\nconst vector = new ManagedMemoryVectorAdapter({\n  databaseName: \"production-memory\",\n  // voltOpsClient optional (auto-resolves from environment)\n});\n```\n\n- VoltOps-hosted vectors with zero setup\n- See [Managed Memory](./managed-memory.md) for configuration\n\n## Example: Full Semantic Search Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  enableCache: true,\n});\n\nconst agent = new Agent({\n  name: \"Research Assistant\",\n  instructions: \"Help users recall past discussions and find relevant information.\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// Semantic search automatically enabled\nconst result = await agent.generateText(\n  \"What did we decide about the API authentication approach?\",\n  {\n    memory: {\n      userId: \"user-123\",\n      conversationId: \"project-alpha\",\n      options: {\n        semanticMemory: {\n          semanticLimit: 10,\n          semanticThreshold: 0.75,\n        },\n      },\n    },\n  }\n);\n```\n\n## Learn More\n\n- **[Working Memory](./working-memory.md)** - Maintain compact context across turns\n- **[Managed Memory](./managed-memory.md)** - Zero-setup vector storage\n- **[LibSQL / Turso](./libsql.md)** - Self-hosted vector storage\n","frontmatter":{"title":"Semantic Search","slug":"/agents/memory/semantic-search"},"isInternal":false,"tokens":1714,"sizeBytes":6893},{"name":"supabase.md","path":"website/docs/agents/memory/supabase.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/supabase.md","title":"Subagent: supabase","category":"subagent-persona","format":"markdown","content":"---\ntitle: Supabase Memory\nslug: /agents/memory/supabase\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Supabase Memory\n\n`SupabaseMemoryAdapter` stores conversations in Supabase Postgres for applications already using Supabase.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/supabase @supabase/supabase-js\n```\n\n  </TabItem>\n</Tabs>\n\n## Database Setup\n\nRun this SQL in your Supabase SQL Editor (adjusts table prefix if needed):\n\n<details>\n<summary>Schema SQL (click to expand)</summary>\n\n```sql\n-- Users table (for user-scoped working memory)\nCREATE TABLE IF NOT EXISTS voltagent_memory_users (\n  id TEXT PRIMARY KEY,\n  metadata JSONB,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Conversations table\nCREATE TABLE IF NOT EXISTS voltagent_memory_conversations (\n  id TEXT PRIMARY KEY,\n  resource_id TEXT NOT NULL,\n  user_id TEXT NOT NULL,\n  title TEXT NOT NULL,\n  metadata JSONB NOT NULL,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  updated_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Messages table (UIMessage format)\nCREATE TABLE IF NOT EXISTS voltagent_memory_messages (\n  conversation_id TEXT NOT NULL REFERENCES voltagent_memory_conversations(id) ON DELETE CASCADE,\n  message_id TEXT NOT NULL,\n  user_id TEXT NOT NULL,\n  role TEXT NOT NULL,\n  parts JSONB,\n  metadata JSONB,\n  format_version INTEGER DEFAULT 2,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now()),\n  PRIMARY KEY (conversation_id, message_id)\n);\n\n-- Workflow states (for suspension/resume)\nCREATE TABLE IF NOT EXISTS voltagent_memory_workflow_states (\n  id TEXT PRIMARY KEY,\n  workflow_id TEXT NOT NULL,\n  workflow_name TEXT NOT NULL,\n  status TEXT NOT NULL,\n  suspension JSONB,\n  events JSONB,\n  output JSONB,\n  cancellation JSONB,\n  user_id TEXT,\n  conversation_id TEXT,\n  metadata JSONB,\n  created_at TIMESTAMPTZ NOT NULL,\n  updated_at TIMESTAMPTZ NOT NULL\n);\n\n-- Conversation steps (structured LLM/tool events)\nCREATE TABLE IF NOT EXISTS voltagent_memory_steps (\n  id TEXT PRIMARY KEY,\n  conversation_id TEXT NOT NULL REFERENCES voltagent_memory_conversations(id) ON DELETE CASCADE,\n  user_id TEXT NOT NULL,\n  agent_id TEXT NOT NULL,\n  agent_name TEXT,\n  operation_id TEXT,\n  step_index INTEGER NOT NULL,\n  type TEXT NOT NULL,\n  role TEXT NOT NULL,\n  content TEXT,\n  arguments JSONB,\n  result JSONB,\n  usage JSONB,\n  sub_agent_id TEXT,\n  sub_agent_name TEXT,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT timezone('utc'::text, now())\n);\n\n-- Indexes\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_conversations_user_id\n  ON voltagent_memory_conversations(user_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_conversations_resource_id\n  ON voltagent_memory_conversations(resource_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_messages_conversation_id\n  ON voltagent_memory_messages(conversation_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_messages_created_at\n  ON voltagent_memory_messages(created_at);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_workflow_states_workflow_id\n  ON voltagent_memory_workflow_states(workflow_id);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_workflow_states_status\n  ON voltagent_memory_workflow_states(status);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_steps_conversation\n  ON voltagent_memory_steps(conversation_id, step_index);\n\nCREATE INDEX IF NOT EXISTS idx_voltagent_memory_steps_operation\n  ON voltagent_memory_steps(conversation_id, operation_id);\n```\n\n</details>\n\n## Credentials\n\nGet your Supabase credentials:\n\n1. Go to [Supabase Dashboard](https://app.supabase.com)\n2. Open your project\n3. Go to **Project Settings** → **API**\n4. Copy **Project URL** and **anon key**\n\nStore as environment variables: `SUPABASE_URL` and `SUPABASE_KEY`\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\n\n// Using URL and key\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n});\n\n// Using existing Supabase client\nimport { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseClient = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_KEY!);\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    client: supabaseClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option        | Type             | Description                                           |\n| ------------- | ---------------- | ----------------------------------------------------- |\n| `supabaseUrl` | `string`         | Supabase project URL (required if not using `client`) |\n| `supabaseKey` | `string`         | Supabase anon key (required if not using `client`)    |\n| `client`      | `SupabaseClient` | Existing Supabase client (alternative to URL/key)     |\n| `tableName`   | `string`         | Table name prefix (default: `voltagent_memory`)       |\n| `debug`       | `boolean`        | Enable debug logging (default: `false`)               |\n| `logger`      | `Logger`         | Optional logger for structured logging                |\n\n**Note**: Table prefix must match the SQL schema. If you use a custom `tableName`, update the SQL accordingly.\n\n## Features\n\n- Messages stored per `userId` and `conversationId`\n- Conversation steps persisted for Observability (Memory Explorer “Steps” tab, sub-agent visibility, usage metadata)\n- Supports complex queries with filtering, pagination, and sorting\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\n```ts\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md).\n\n### Semantic Search\n\n```ts\nimport { Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    supabaseUrl: process.env.SUPABASE_URL!,\n    supabaseKey: process.env.SUPABASE_KEY!,\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(), // or pgvector adapter\n});\n```\n\nSee [Semantic Search](./semantic-search.md).\n\n## Production Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { SupabaseMemoryAdapter } from \"@voltagent/supabase\";\nimport { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseClient = createClient(\n  process.env.SUPABASE_URL!,\n  process.env.SUPABASE_KEY! // or service_role key for backend\n);\n\nconst memory = new Memory({\n  storage: new SupabaseMemoryAdapter({\n    client: supabaseClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n**Security:**\n\n- Use `anon` key with Row Level Security (RLS) policies\n- Use `service_role` key only in secure backend environments\n- Store credentials in environment variables\n\n**Use cases:**\n\n- Applications already using Supabase\n- Projects leveraging Supabase Auth, Realtime, or Storage\n- Environments requiring RLS policies\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n","frontmatter":{"title":"Supabase Memory","slug":"/agents/memory/supabase"},"isInternal":false,"tokens":2027,"sizeBytes":8108},{"name":"working-memory.md","path":"website/docs/agents/memory/working-memory.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/memory/working-memory.md","title":"Subagent: working-memory","category":"subagent-persona","format":"markdown","content":"---\ntitle: Working Memory\nslug: /agents/memory/working-memory\n---\n\n# Working Memory\n\nWorking memory stores compact context across conversation turns. Unlike full message history, it tracks key facts, preferences, or goals that persist throughout interactions.\n\n## Configuration\n\nThree formats supported:\n\n1. **Markdown template** - Structured text with sections\n2. **JSON schema** - Validated structured data using Zod\n3. **Free-form** - Unstructured text\n\nScope options:\n\n- **`conversation`** (default) - Context per conversation thread\n- **`user`** - Context shared across all user conversations\n\n### Markdown Template\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // default\n    template: `\n# User Profile\n- Name:\n- Role:\n- Timezone:\n\n# Current Goals\n-\n\n# Preferences\n-\n`,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### JSON Schema\n\n```ts\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // persist across conversations\n    schema: z.object({\n      profile: z\n        .object({\n          name: z.string().optional(),\n          role: z.string().optional(),\n          timezone: z.string().optional(),\n        })\n        .optional(),\n      preferences: z.array(z.string()).optional(),\n      goals: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\n### Free-Form\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true, // no template or schema\n  },\n});\n```\n\n## Agent Integration\n\nWhen working memory is enabled, the agent:\n\n1. **Adds instructions to system prompt** - Includes current working memory content and usage guidelines\n2. **Exposes tools automatically**:\n   - `get_working_memory()` - Retrieve current content\n   - `update_working_memory(content)` - Update content (validated against schema if configured)\n   - `clear_working_memory()` - Clear content\n\nIf you run an operation with `options.memory.options.readOnly: true`, the agent only exposes\n`get_working_memory()` and skips write operations.\n\nThe agent manages working memory proactively based on conversation flow.\n\n## Update Modes\n\nTwo modes available:\n\n### Append Mode (Default)\n\nSafely merges new information with existing content:\n\n```ts\n// Existing working memory:\n// { profile: { name: \"Alice\" }, goals: [\"Learn TypeScript\"] }\n\n// Agent calls update_working_memory with:\n// { goals: [\"Build an API\"] }\n\n// Result (merged):\n// { profile: { name: \"Alice\" }, goals: [\"Learn TypeScript\", \"Build an API\"] }\n```\n\n**For JSON schemas:**\n\n- Objects: Deep merge\n- Arrays: Deduplicated concatenation\n- Primitives: Overwrite\n\n**For Markdown:**\n\n- New content appended with separator\n\n### Replace Mode\n\nOverwrites all existing content:\n\n```ts\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: { profile: { name: \"Bob\" } }, // existing data lost\n  options: { mode: \"replace\" },\n});\n```\n\nUse replace mode only when intentionally resetting all context.\n\n## Programmatic API\n\nDirect memory access without agent tools:\n\n```ts\n// Get current working memory\nconst content = await memory.getWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n});\nconsole.log(content); // string (JSON or Markdown)\n\n// Update (default: append mode)\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: { goals: [\"Complete onboarding\"] }, // object or string\n});\n\n// Update (replace mode)\nawait memory.updateWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n  content: \"Fresh context\",\n  options: { mode: \"replace\" },\n});\n\n// Clear\nawait memory.clearWorkingMemory({\n  conversationId: \"thread-123\",\n  userId: \"user-456\",\n});\n\n// Introspect configuration\nconst format = memory.getWorkingMemoryFormat(); // \"markdown\" | \"json\" | null\nconst template = memory.getWorkingMemoryTemplate(); // string | null\nconst schema = memory.getWorkingMemorySchema(); // ZodObject | null\n```\n\n## Storage Implementation\n\nWorking memory is stored differently per scope:\n\n**Conversation scope:**\n\n- Stored in `conversations.metadata.workingMemory` field\n- Isolated per conversation thread\n\n**User scope:**\n\n- Stored in `${tablePrefix}_users.metadata.workingMemory` field (adapter-specific)\n- Shared across all user conversations\n\nAll official adapters (LibSQL, Postgres, Supabase, Managed Memory) support both scopes.\n\n## Example: User-Scoped Preferences\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\nimport { z } from \"zod\";\n\nconst userPreferencesSchema = z.object({\n  name: z.string().optional(),\n  timezone: z.string().optional(),\n  communicationStyle: z.enum([\"formal\", \"casual\"]).optional(),\n  interests: z.array(z.string()).optional(),\n});\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // persist across all conversations\n    schema: userPreferencesSchema,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Personal Assistant\",\n  instructions: \"Adapt responses based on user preferences stored in working memory.\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// First conversation\nawait agent.generateText(\"I prefer casual communication and I'm into AI and music.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"conv-1\",\n  },\n});\n\n// Different conversation - agent remembers user preferences\nawait agent.generateText(\"What should I learn next?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"conv-2\", // different thread\n  },\n});\n```\n\n## Example: Conversation-Scoped Goals\n\n```ts\nconst projectMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // isolated per project\n    template: `\n# Project Context\n- Name:\n- Deadline:\n- Tech Stack:\n\n# Current Sprint\n- Goals:\n- Blockers:\n`,\n  },\n});\n\nconst agent = new Agent({\n  name: \"Project Manager\",\n  instructions: \"Track project context and help with sprint planning.\",\n  model: \"openai/gpt-4o-mini\",\n  memory: projectMemory,\n});\n\n// Each project gets its own working memory\nawait agent.generateText(\"Let's plan the e-commerce project using Next.js.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"project-ecommerce\",\n  },\n});\n\nawait agent.generateText(\"For the analytics dashboard, we'll use React and D3.\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"project-analytics\",\n  },\n});\n```\n\n## Best Practices\n\n1. **Use JSON schemas for structured data** - Ensures type safety and validation\n2. **Use Markdown templates for narrative context** - Better for summaries, notes, observations\n3. **Default to append mode** - Safer than replace; preserves existing data\n4. **User scope for preferences** - Name, timezone, communication style\n5. **Conversation scope for session data** - Goals, tasks, project details\n6. **Keep it compact** - Working memory supplements message history, not replaces it\n\n## Learn More\n\n- **[Semantic Search](./semantic-search.md)** - Retrieve relevant past messages\n- **[Managed Memory](./managed-memory.md)** - Zero-setup working memory storage\n- **[LibSQL / Turso](./libsql.md)** - Self-hosted working memory storage\n","frontmatter":{"title":"Working Memory","slug":"/agents/memory/working-memory"},"isInternal":false,"tokens":1844,"sizeBytes":7715},{"name":"message-types.md","path":"website/docs/agents/message-types.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/message-types.md","title":"Subagent: message-types","category":"subagent-persona","format":"markdown","content":"---\ntitle: Understanding Message Types\n---\n\n# Understanding Message Types\n\nVoltAgent works with three different message type formats, each serving a specific purpose in the agent lifecycle. Understanding when and how to use each type is essential for building effective AI applications.\n\n## Overview\n\nThe three message types are:\n\n1. **MessageContent** - Raw content format for LLM communication (from AI SDK)\n2. **UIMessage** - Frontend-friendly format with metadata (from AI SDK)\n3. **VoltAgentTextStreamPart** - Streaming format with SubAgent support (VoltAgent extension)\n\nThese types come from the underlying AI SDK architecture and VoltAgent's multi-agent features. Rather than being redundant, they serve different layers of your application.\n\n## The Three Message Types\n\n### 1. MessageContent (AI SDK Content Type)\n\n**MessageContent** represents the raw content that gets sent to language models. It's the `content` property of a `ModelMessage`.\n\n#### Type Definition\n\n```typescript\ntype MessageContent = UserContent | AssistantContent | ToolContent | string;\n\n// Examples:\ntype UserContent = string | Array<TextPart | ImagePart | FilePart>;\ntype AssistantContent = string | Array<TextPart | ToolCallPart>;\ntype ToolContent = Array<ToolResultPart>;\n```\n\n#### Format\n\n```typescript\n// Simple string\nconst content: MessageContent = \"Hello, how can I help you?\";\n\n// Structured array with multiple parts\nconst content: MessageContent = [\n  { type: \"text\", text: \"Check this image:\" },\n  { type: \"image\", image: \"data:image/png;base64,...\" },\n  { type: \"text\", text: \"What do you see?\" },\n];\n```\n\n#### When to Use\n\n- ✅ Creating `ModelMessage` objects for AI SDK\n- ✅ Direct LLM provider calls\n- ✅ Low-level message manipulation\n- ✅ Working with `message.content` property\n\n#### Common Patterns\n\n```typescript\nimport { generateText } from \"ai\";\n\n// Using MessageContent in direct AI SDK calls\nconst result = await generateText({\n  model: \"openai/gpt-4\",\n  messages: [\n    {\n      role: \"user\",\n      content: \"Hello!\", // MessageContent (string)\n    },\n    {\n      role: \"user\",\n      content: [\n        // MessageContent (array)\n        { type: \"text\", text: \"Describe this:\" },\n        { type: \"image\", image: imageData },\n      ],\n    },\n  ],\n});\n```\n\n### 2. UIMessage (AI SDK UI Format)\n\n**UIMessage** is the modern format designed for frontend applications, chat UIs, and memory systems. It includes metadata like message IDs and uses a `parts` array instead of `content`.\n\n#### Type Definition\n\n```typescript\ninterface UIMessage {\n  id: string;\n  role: \"user\" | \"assistant\" | \"system\";\n  parts: Array<TextUIPart | FileUIPart | ToolUIPart>;\n  createdAt?: Date;\n  metadata?: Record<string, unknown>;\n}\n```\n\n#### Format\n\n```typescript\nconst message: UIMessage = {\n  id: \"msg-123\",\n  role: \"user\",\n  parts: [\n    { type: \"text\", text: \"Hello, \" },\n    { type: \"file\", url: \"image.png\", mediaType: \"image/png\" },\n    { type: \"text\", text: \"what is this?\" },\n  ],\n  createdAt: new Date(),\n  metadata: { source: \"mobile\" },\n};\n```\n\n#### When to Use\n\n- ✅ Memory operations (`memory.getMessages()`, `memory.addMessage()`)\n- ✅ Agent hooks (`onPrepareMessages`, `onAfterGeneration`)\n- ✅ Chat UI rendering and state management\n- ✅ Message history and conversation management\n- ✅ Frontend/backend communication\n\n#### Common Patterns\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  memory: myMemory,\n\n  hooks: {\n    // UIMessages in hooks\n    onPrepareMessages: async ({ messages }) => {\n      // messages is UIMessage[]\n      const enhanced = messages.map((msg) => ({\n        ...msg,\n        parts: [...msg.parts, { type: \"text\", text: \" [verified]\" }],\n      }));\n\n      return { messages: enhanced };\n    },\n  },\n});\n\n// Getting messages from memory\nconst conversationHistory = await memory.getMessages(userId, conversationId);\n// Returns UIMessage[]\n\nconversationHistory.forEach((msg) => {\n  console.log(`[${msg.id}] ${msg.role}:`, msg.parts);\n});\n```\n\n#### Feedback metadata\n\nWhen feedback is enabled, VoltAgent attaches feedback metadata to assistant UI messages under `message.metadata.feedback`. This is how UIs can show thumbs up/down and submit feedback later.\n\n```ts\nconst feedback = message.metadata?.feedback as\n  | {\n      traceId?: string;\n      key?: string;\n      url?: string;\n      expiresAt?: string;\n      provided?: boolean;\n      providedAt?: string;\n      feedbackId?: string;\n    }\n  | undefined;\n\nconst alreadyProvided = Boolean(feedback?.provided || feedback?.providedAt || feedback?.feedbackId);\n\nif (feedback?.url && !alreadyProvided) {\n  console.log(\"Submit feedback to:\", feedback.url);\n}\n```\n\nAfter submitting feedback successfully, persist the provided-state in memory with `agent.markFeedbackProvided(...)` so the UI still hides feedback controls after conversation reload.\n\nSee [Feedback](/observability-docs/feedback) for the full flow and API examples.\n\n### 3. VoltAgentTextStreamPart (Streaming Extension)\n\n**VoltAgentTextStreamPart** extends AI SDK's `TextStreamPart` with SubAgent metadata, enabling multi-agent coordination during streaming.\n\n#### Type Definition\n\n```typescript\ntype VoltAgentTextStreamPart<TOOLS extends Record<string, any> = Record<string, any>> =\n  TextStreamPart<TOOLS> & {\n    /**\n     * Optional response message identifier (carried on start/step chunks).\n     */\n    messageId?: string;\n\n    /**\n     * Optional identifier for the subagent that generated this event\n     */\n    subAgentId?: string;\n\n    /**\n     * Optional name of the subagent that generated this event\n     */\n    subAgentName?: string;\n  };\n\n// TextStreamPart types include:\n// - text-start, text-delta, text-end\n// - reasoning-start, reasoning-delta, reasoning-end\n// - tool-input-start, tool-input-delta, tool-input-end\n// - tool-call, tool-result, tool-error\n// - start, start-step, finish-step, finish, abort, error, source, file, raw\n```\n\n#### Format\n\n```typescript\n// Regular streaming part\nconst streamPart: VoltAgentTextStreamPart = {\n  type: \"text-delta\",\n  textDelta: \"Hello\",\n};\n\n// With SubAgent metadata\nconst subAgentStreamPart: VoltAgentTextStreamPart = {\n  type: \"text-delta\",\n  textDelta: \"The answer is 42\",\n  subAgentId: \"agent-calculator\",\n  subAgentName: \"Calculator Agent\",\n};\n```\n\n#### When to Use\n\n- ✅ Streaming responses (`agent.streamText()`)\n- ✅ Real-time UI updates\n- ✅ Multi-agent coordination and event forwarding\n- ✅ Identifying which SubAgent is responding\n- ✅ Building collaborative agent UIs\n\n#### Common Patterns\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  subAgents: [writerAgent, editorAgent],\n});\n\n// Streaming with SubAgent tracking\nconst stream = await agent.streamText(\"Write and edit a blog post\");\n\nfor await (const part of stream.fullStream) {\n  if (part.type === \"text-delta\") {\n    // Check which SubAgent is responding\n    if (part.subAgentId) {\n      console.log(`[${part.subAgentName}]: ${part.textDelta}`);\n    } else {\n      console.log(`[Main Agent]: ${part.textDelta}`);\n    }\n  }\n}\n```\n\n## Message Flow Diagram\n\nHere's how messages flow through a typical VoltAgent application:\n\n```mermaid\ngraph TB\n    User[User Input] -->|string / UIMessage array / ModelMessage array| Agent[Agent]\n\n    Agent -->|normalizes to| UIMsg[UIMessage]\n    UIMsg -->|stored in| Memory[(Memory Storage)]\n    UIMsg -->|passed to| Hooks[Agent Hooks]\n\n    Hooks -->|onPrepareMessages| Transform[Message Transform]\n    Transform -->|UIMessage array| Convert[Convert to ModelMessage]\n\n    Convert -->|ModelMessage with MessageContent| LLM[Language Model]\n\n    LLM -->|streaming response| Stream[Stream Parts]\n    Stream -->|VoltAgentTextStreamPart| UI[Real-time UI]\n\n    Stream -->|on finish| Response[Final Response]\n    Response -->|UIMessage| Memory\n\n    SubAgent[SubAgents] -.->|add metadata| Stream\n\n    style UIMsg fill:#3b82f6,stroke:#1e40af,stroke-width:2px,color:#fff\n    style Stream fill:#f59e0b,stroke:#d97706,stroke-width:2px,color:#fff\n    style Memory fill:#8b5cf6,stroke:#6d28d9,stroke-width:2px,color:#fff\n    style LLM fill:#10b981,stroke:#059669,stroke-width:2px,color:#fff\n```\n\n## Converting Between Types\n\nVoltAgent and AI SDK provide utilities for converting between message formats:\n\n### MessageContent ↔ UIMessage\n\n```typescript\nimport { convertToModelMessages } from \"ai\";\nimport type { UIMessage } from \"ai\";\n\n// UIMessage[] → ModelMessage[] (which contains MessageContent)\nconst uiMessages: UIMessage[] = await memory.getMessages(userId, conversationId);\nconst modelMessages = await convertToModelMessages(uiMessages);\n\n// Access MessageContent from ModelMessage\nconst content = modelMessages[0].content; // MessageContent\n\n// Extract text from either format using helpers\nimport { extractText } from \"@voltagent/core\";\n\nconst text1 = extractText(content); // From MessageContent\nconst text2 = extractText(uiMessages[0]); // From UIMessage (overloaded!)\n```\n\n### Working with Both Formats\n\nVoltAgent's `messageHelpers` utilities support both formats:\n\n```typescript\nimport { extractText, hasImagePart, extractImageParts } from \"@voltagent/core\";\n\n// Works with MessageContent\nconst content: MessageContent = [\n  { type: \"text\", text: \"Hello\" },\n  { type: \"image\", image: \"data:...\" },\n];\n\nconst text = extractText(content); // \"Hello\"\nconst hasImage = hasImagePart(content); // true\n\n// Also works with UIMessage!\nconst message: UIMessage = {\n  id: \"msg-1\",\n  role: \"user\",\n  parts: [\n    { type: \"text\", text: \"Hello\" },\n    { type: \"file\", url: \"image.png\", mediaType: \"image/png\" },\n  ],\n} as UIMessage;\n\nconst text2 = extractText(message); // \"Hello\"\nconst hasImage2 = hasImagePart(message); // true\nconst images = extractImageParts(message); // FileUIPart[]\n```\n\nSee [Message Helpers](/docs/utils/message-helpers) for the complete API.\n\n## Common Patterns and Use Cases\n\n### Pattern 1: Memory + UIMessage\n\n**Use Case**: Store and retrieve conversation history\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  memory: myMemory,\n});\n\n// Generate response (automatically stores UIMessages in memory)\nawait agent.generateText({\n  prompt: \"Hello!\",\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n});\n\n// Retrieve conversation history\nconst history = await myMemory.getMessages(\"user-123\", \"conv-456\");\n// Returns UIMessage[]\n\n// Display in UI\nhistory.forEach((msg) => {\n  const text = extractText(msg);\n  console.log(`${msg.role}: ${text}`);\n});\n```\n\n### Pattern 2: Streaming + VoltAgentTextStreamPart\n\n**Use Case**: Real-time UI updates with SubAgent identification\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst coordinator = new Agent({\n  subAgents: [researchAgent, writerAgent],\n});\n\nconst stream = await coordinator.streamText(\"Research and write about AI\");\n\n// Track which agent is responding\nlet currentAgent = null;\n\nfor await (const part of stream.fullStream) {\n  if (part.type === \"text-delta\") {\n    // Detect agent switches\n    if (part.subAgentId && part.subAgentId !== currentAgent) {\n      currentAgent = part.subAgentId;\n      console.log(`\\n[Switching to: ${part.subAgentName}]`);\n    }\n\n    process.stdout.write(part.textDelta);\n  }\n}\n```\n\n### Pattern 3: Hooks + UIMessage Transformation\n\n**Use Case**: Add context or filter sensitive data\n\n```typescript\nimport { Agent, messageHelpers } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  hooks: {\n    onPrepareMessages: async ({ messages }) => {\n      // Add timestamps to user messages\n      const enhanced = messages.map((msg) => {\n        if (msg.role === \"user\") {\n          return messageHelpers.addTimestampToMessage(msg);\n        }\n        return msg;\n      });\n\n      return { messages: enhanced };\n    },\n\n    onAfterGeneration: async ({ response }) => {\n      // Filter sensitive data from assistant responses\n      const filtered = messageHelpers.mapMessageContent(response, (text) => {\n        // Redact SSN patterns\n        return text.replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, \"[REDACTED]\");\n      });\n\n      return { response: filtered };\n    },\n  },\n});\n```\n\n## Decision Tree: Which Type Should I Use?\n\n```\nAre you working with...\n\n┌─ Memory operations (store/retrieve)?\n│  └─> Use UIMessage\n│\n├─ Agent hooks (transform messages)?\n│  └─> Use UIMessage\n│\n├─ Streaming responses?\n│  └─> Use VoltAgentTextStreamPart\n│     └─ Need SubAgent tracking?\n│        └─> Yes: Check subAgentId/subAgentName properties\n│\n├─ Direct AI SDK calls (generateText/streamText)?\n│  └─> Use MessageContent (in ModelMessage.content)\n│\n├─ Chat UI rendering?\n│  └─> Use UIMessage\n│\n└─ Message content manipulation?\n   └─> Use messageHelpers (supports both MessageContent and UIMessage!)\n```\n\n## FAQ\n\n### Q: Why not just one unified message type?\n\n**A**: These types come from different layers of the AI SDK and serve different purposes:\n\n- **MessageContent** is optimized for LLM communication (minimal structure)\n- **UIMessage** is optimized for UIs and storage (includes metadata, IDs)\n- **VoltAgentTextStreamPart** is optimized for streaming (real-time, event-based)\n\nUnifying them would lose these optimizations and break compatibility with AI SDK.\n\n### Q: Do I need to convert between types manually?\n\n**A**: Usually not! VoltAgent handles conversions automatically:\n\n- Agent automatically converts UIMessages to ModelMessages when calling LLMs\n- Memory automatically stores responses as UIMessages\n- `messageHelpers` utilities work with both MessageContent and UIMessage\n\nYou only need manual conversion for advanced use cases.\n\n### Q: Which type does `memory.getMessages()` return?\n\n**A**: Always **UIMessage[]**. Memory operations use UIMessage format for:\n\n- Message IDs (tracking individual messages)\n- Metadata (timestamps, sources, etc.)\n- Frontend compatibility\n\n### Q: Can I use MessageContent with message helpers?\n\n**A**: Yes! All extractor and checker functions in `messageHelpers` support both:\n\n```typescript\nimport { extractText } from \"@voltagent/core\";\n\n// Both work!\nextractText(messageContent); // MessageContent\nextractText(uiMessage); // UIMessage\n```\n\n### Q: How do I know which SubAgent is responding in a stream?\n\n**A**: Check the `subAgentId` and `subAgentName` properties:\n\n```typescript\nfor await (const part of stream.fullStream) {\n  if (part.subAgentId) {\n    console.log(`SubAgent: ${part.subAgentName}`);\n  } else {\n    console.log(\"Main Agent\");\n  }\n}\n```\n\n### Q: Are these types specific to VoltAgent?\n\n**A**: No! **MessageContent** and **UIMessage** come from Vercel's AI SDK. Only **VoltAgentTextStreamPart** is a VoltAgent extension (adding `subAgentId` and `subAgentName` to AI SDK's `TextStreamPart`).\n\n## Related Documentation\n\n- [Message Helpers](/docs/utils/message-helpers) - Utilities for working with both MessageContent and UIMessage\n- [Memory Guide](/docs/agents/memory) - How memory uses UIMessage format\n- [Agent Hooks](/docs/agents/hooks) - UIMessage transformation in hooks\n- [API Streaming](/docs/api/streaming) - Working with VoltAgentTextStreamPart\n\n## Summary\n\n| Type                        | Source    | Primary Use Case  | Key Features                       |\n| --------------------------- | --------- | ----------------- | ---------------------------------- |\n| **MessageContent**          | AI SDK    | LLM communication | String or array, minimal structure |\n| **UIMessage**               | AI SDK    | UI & Memory       | Has `id`, `parts`, metadata        |\n| **VoltAgentTextStreamPart** | VoltAgent | Streaming         | Has `subAgentId`, `subAgentName`   |\n\n**Golden Rule**: Let VoltAgent handle conversions automatically. Use `messageHelpers` for content manipulation, and check the documentation for each API to know which type it expects.\n","frontmatter":{"title":"Understanding Message Types"},"isInternal":false,"tokens":3828,"sizeBytes":15793},{"name":"middleware.md","path":"website/docs/agents/middleware.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/middleware.md","title":"Subagent: middleware","category":"subagent-persona","format":"markdown","content":"---\ntitle: Middleware\nslug: /agents/middleware\n---\n\n# Agent Middleware\n\nMiddleware runs before guardrails and hooks on each agent call. Use it to read or modify input and output, or to stop a call with a typed error.\n\n## Execution Order\n\n`generateText` and `generateObject`:\n\n1. Input middlewares\n2. Input guardrails\n3. Hooks (`onStart`)\n4. Model call (with retries and fallback)\n5. Output middlewares\n6. Output guardrails\n7. Hooks (`onEnd`)\n\n`streamText` and `streamObject`:\n\n1. Input middlewares\n2. Input guardrails\n3. Stream starts\n\nOutput middlewares do not run in streaming calls.\n\n## Configure Middleware\n\nAgent-level middlewares run before per-call middlewares. Per-call middlewares are appended to the list for that call.\n\n```ts\nimport { Agent, createInputMiddleware, createOutputMiddleware } from \"@voltagent/core\";\n\nconst normalizeInput = createInputMiddleware({\n  name: \"NormalizeInput\",\n  handler: ({ input }) => {\n    if (typeof input !== \"string\") return input;\n    return input.trim();\n  },\n});\n\nconst requireSignature = createOutputMiddleware<string>({\n  name: \"RequireSignature\",\n  handler: ({ output, abort }) => {\n    if (!output.includes(\"-- Support\")) {\n      abort(\"Missing signature\", { retry: true, metadata: { reason: \"signature\" } });\n    }\n    return output;\n  },\n});\n\nconst agent = new Agent({\n  name: \"Support\",\n  instructions: \"Answer support questions with short, direct replies.\",\n  model: \"openai/gpt-4o-mini\",\n  maxMiddlewareRetries: 1,\n  inputMiddlewares: [normalizeInput],\n  outputMiddlewares: [requireSignature],\n});\n```\n\nPer-call middleware:\n\n```ts\nawait agent.generateText(\"hi\", {\n  inputMiddlewares: [({ input }) => (typeof input === \"string\" ? `Customer: ${input}` : input)],\n});\n```\n\n## Middleware API\n\nInput middleware receives:\n\n- `input`: current input value\n- `originalInput`: input from the caller\n- `agent`: agent instance\n- `context`: operation context\n- `operation`: operation name (for example `generateText`)\n- `retryCount`: current middleware retry count\n- `abort(reason?, options?)`: stop the call or request a retry\n\nOutput middleware receives:\n\n- `output`: current output value\n- `originalOutput`: output from the model\n- `usage`, `finishReason`, `warnings`: model metadata when available\n- `agent`, `context`, `operation`, `retryCount`, `abort(...)`\n\nReturn a new value to replace the input or output; return `void` to keep the current value.\n\n## Retry Behavior\n\nMiddleware retries are controlled by `maxMiddlewareRetries`. The default is `0`.\n\n- Call `abort(\"reason\", { retry: true })` to request a retry.\n- Each retry restarts the full attempt: input middleware, guardrails, hooks, model selection, model retries/fallback, output middleware.\n- The retry reason is added as a system message for the next attempt.\n- `retryCount` starts at `0` and increments after each middleware-triggered retry.\n- If `maxMiddlewareRetries` is exceeded, the middleware error is returned to the caller.\n- In streaming calls, retry can only happen before the stream starts, so only input middlewares can trigger it.\n\nMiddleware retries are separate from model retries. Model retries use `maxRetries` and are evaluated per model call. For model fallback configuration, see [Retries and Fallbacks](/docs/agents/retries-fallback).\n","frontmatter":{"title":"Middleware","slug":"/agents/middleware"},"isInternal":false,"tokens":766,"sizeBytes":3267},{"name":"multi-modal.md","path":"website/docs/agents/multi-modal.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/multi-modal.md","title":"Subagent: multi-modal","category":"subagent-persona","format":"markdown","content":"# Multi-Modal Inputs\n\nAgents can process text, images, and files in a single request. This document covers how to send multi-modal inputs to agent methods.\n\n## Basic Usage\n\nAgent methods (`generateText`, `streamText`, `generateObject`, `streamObject`) accept three input formats:\n\n- `string` - Plain text input\n- `UIMessage[]` - AI SDK's UI message format (used in chat interfaces and streaming)\n- `BaseMessage[]` - AI SDK's model message format (alias for `ModelMessage`)\n\nBoth message formats are from the AI SDK and re-exported by VoltAgent. To send images or files, use either message array format with structured content.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport type { BaseMessage } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"vision-assistant\",\n  instructions: \"Analyze images and answer questions about them.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"What's in this image?\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/photo.jpg\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\nconsole.log(result.text);\n```\n\n## Image Formats\n\nImages can be provided as URLs, data URIs, or base64 strings.\n\n### Using URLs\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Describe this chart\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/chart.png\",\n      },\n    ],\n  },\n];\n```\n\n### Using Base64\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst imageBuffer = readFileSync(\"./photo.jpg\");\nconst base64Image = imageBuffer.toString(\"base64\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"What's in this photo?\" },\n      {\n        type: \"image\",\n        image: `data:image/jpeg;base64,${base64Image}`,\n        mediaType: \"image/jpeg\",\n      },\n    ],\n  },\n];\n```\n\n### Using Binary Data\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst imageBuffer = readFileSync(\"./photo.jpg\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Analyze this image\" },\n      {\n        type: \"image\",\n        image: new Uint8Array(imageBuffer),\n        mediaType: \"image/jpeg\",\n      },\n    ],\n  },\n];\n```\n\n## File Attachments\n\nFor non-image files (PDFs, documents), use the file content type.\n\n```ts\nimport { readFileSync } from \"fs\";\n\nconst pdfBuffer = readFileSync(\"./report.pdf\");\nconst base64Pdf = pdfBuffer.toString(\"base64\");\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Summarize this document\" },\n      {\n        type: \"file\",\n        url: `data:application/pdf;base64,${base64Pdf}`,\n        mediaType: \"application/pdf\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\n```\n\n## Multiple Images\n\nYou can include multiple images in a single message.\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Compare these two images\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/before.jpg\",\n      },\n      {\n        type: \"image\",\n        image: \"https://example.com/after.jpg\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateText(messages);\n```\n\n## Streaming with Images\n\nStreaming works the same way with multi-modal inputs.\n\n```ts\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Describe this image in detail\" },\n      {\n        type: \"image\",\n        image: \"https://example.com/landscape.jpg\",\n      },\n    ],\n  },\n];\n\nconst stream = await agent.streamText(messages);\n\nfor await (const chunk of stream.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n## Structured Output with Images\n\nUse `generateObject` to extract structured data from images.\n\n```ts\nimport { z } from \"zod\";\n\nconst receiptSchema = z.object({\n  total: z.number(),\n  items: z.array(\n    z.object({\n      name: z.string(),\n      price: z.number(),\n    })\n  ),\n  date: z.string(),\n});\n\nconst messages: BaseMessage[] = [\n  {\n    role: \"user\",\n    content: [\n      { type: \"text\", text: \"Extract the receipt details\" },\n      {\n        type: \"image\",\n        image: \"data:image/jpeg;base64,/9j/4AAQSkZJRg...\",\n      },\n    ],\n  },\n];\n\nconst result = await agent.generateObject(messages, receiptSchema);\nconsole.log(result.object);\n// { total: 42.50, items: [...], date: \"2024-01-15\" }\n```\n\n## Content Part Types Reference\n\nThese types are from the AI SDK and re-exported by VoltAgent for convenience.\n\nWhen using message arrays, the `content` field can be:\n\n- A `string` for text-only messages\n- An array of content parts for multi-modal messages\n\n### Text Part\n\n```ts\n{\n  type: \"text\",\n  text: \"Your text here\"\n}\n```\n\n### Image Part\n\n```ts\n{\n  type: \"image\",\n  image: string | URL | Uint8Array,  // URL, data URI, base64, or binary\n  mediaType?: string,                 // \"image/jpeg\", \"image/png\", etc.\n  alt?: string                        // Alternative text description\n}\n```\n\n### File Part\n\n```ts\n{\n  type: \"file\",\n  url: string,           // Absolute URL or data URI\n  mediaType: string      // \"application/pdf\", \"text/csv\", etc.\n}\n```\n\nFor complete type definitions, see the [AI SDK documentation](https://sdk.vercel.ai/docs/reference/ai-sdk-core/model-message).\n\n## Model Support\n\nNot all models support vision or file inputs. Check your provider's documentation.\n\nVision-capable models include:\n\n- **OpenAI**: `gpt-4o`, `gpt-4o-mini`, `gpt-4-turbo`\n- **Anthropic**: `claude-3-5-sonnet`, `claude-3-opus`, `claude-3-haiku`\n- **Google**: `gemini-1.5-pro`, `gemini-1.5-flash`\n\nImage format support, size limits, and costs vary by provider. Refer to your model provider's documentation for details.\n\n## Error Handling\n\n```ts\ntry {\n  const messages: BaseMessage[] = [\n    {\n      role: \"user\",\n      content: [\n        { type: \"text\", text: \"What's in this image?\" },\n        {\n          type: \"image\",\n          image: \"https://example.com/image.jpg\",\n        },\n      ],\n    },\n  ];\n\n  const result = await agent.generateText(messages);\n  console.log(result.text);\n} catch (error) {\n  if (error.message.includes(\"vision\")) {\n    console.error(\"This model does not support image inputs\");\n  } else {\n    console.error(\"Error:\", error);\n  }\n}\n```\n\n## VoltOps Console\n\nThe [VoltOps Console](https://console.voltagent.dev/) includes a chat interface with file upload support.\n\nClick the attachment button (📎) to upload images or files. The console converts uploaded files to the appropriate message format (base64 data URIs) and sends them to your agent.\n\nThis allows you to test multi-modal agents without writing code for file handling.\n","isInternal":false,"tokens":1725,"sizeBytes":6781},{"name":"overview.md","path":"website/docs/agents/overview.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/overview.md","title":"Subagent: overview","category":"subagent-persona","format":"markdown","content":"---\ntitle: Overview\nslug: /agents/overview\n---\n\n# Agent Overview\n\nAn agent in VoltAgent wraps a language model with instructions, tools, memory, and other capabilities. You create an agent instance, then call its methods to generate responses or stream output.\n\nThere are two ways to use agents in VoltAgent:\n\n1. **Direct method calls** - Call agent methods (`generateText`, `streamText`) from your application code\n2. **REST API** - Use VoltAgent's HTTP server to expose agents as REST endpoints\n\nThis document covers both approaches, starting with the basics.\n\n## Creating an Agent\n\nAn agent requires three properties: a name, instructions, and a model.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n```\n\nYou can also use a model string instead of importing a provider:\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n```\n\nThe `instructions` property defines the agent's behavior. The `model` can be an ai-sdk `LanguageModel` or a `provider/model` string resolved by VoltAgent. See [Model Router & Registry](/docs/getting-started/model-router) for details.\n\nTo configure fallback lists and per-model retries, see [Retries and Fallbacks](/docs/agents/retries-fallback).\nTo run pre-guardrail input/output handlers, see [Middleware](/docs/agents/middleware).\n\n## Using Agents: Direct Method Calls\n\nAgents have two core methods for generating responses:\n\n### Text Generation\n\nUse these methods when you need text responses.\n\n**generateText** - Returns the complete text response at once.\n\n```ts\nconst result = await agent.generateText(\"What is TypeScript?\");\nconsole.log(result.text);\n```\n\n**streamText** - Streams text chunks as they're generated (for real-time UIs).\n\n```ts\nconst stream = await agent.streamText(\"Explain async/await\");\n\nfor await (const chunk of stream.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n### Streaming Features\n\nWhen using `streamText`, you can access detailed events and final values.\n\n#### fullStream for Detailed Events\n\nUse `fullStream` to receive detailed streaming events including tool calls, reasoning steps, and completion status.\n\n```ts\nconst response = await agent.streamText(\"Write a story\");\n\nfor await (const chunk of response.fullStream) {\n  switch (chunk.type) {\n    case \"reasoning-start\":\n      console.log(\"\\nReasoning started\");\n      break;\n    case \"reasoning-delta\":\n      process.stdout.write(chunk.delta ?? chunk.text ?? \"\");\n      break;\n    case \"reasoning-end\":\n      console.log(\"\\nReasoning completed\");\n      break;\n    case \"text-delta\":\n      process.stdout.write(chunk.delta ?? chunk.text ?? \"\");\n      break;\n    case \"tool-call\":\n      console.log(`\\nUsing tool: ${chunk.toolName}`);\n      break;\n    case \"tool-result\":\n      console.log(`Tool completed: ${chunk.toolName}`);\n      break;\n    case \"finish\":\n      console.log(`\\nDone! Tokens: ${chunk.usage?.totalTokens}`);\n      break;\n  }\n}\n```\n\n#### Promise-based Properties\n\nAccess final values as Promises that resolve when streaming completes.\n\n```ts\nconst response = await agent.streamText(\"Explain async/await\");\n\n// Process stream\n(async () => {\n  for await (const chunk of response.textStream) {\n    process.stdout.write(chunk);\n  }\n})();\n\n// Access final values (resolve when stream completes)\nconst [fullText, finalStepUsage, totalUsage, finishReason] = await Promise.all([\n  response.text, // Promise<string>\n  response.usage, // Promise<LanguageModelUsage> for the final step\n  response.totalUsage, // Promise<LanguageModelUsage> across all steps\n  response.finishReason, // Promise<string>\n]);\n\nconsole.log(`\\nTotal: ${fullText.length} chars, ${totalUsage?.totalTokens} tokens`);\nconsole.log(`Final step tokens: ${finalStepUsage?.totalTokens}`);\n```\n\n### Feedback (optional)\n\nIf you have VoltOps API keys configured, you can enable feedback per agent or per call. VoltAgent creates a short-lived feedback token and attaches it to the assistant message metadata and the result object.\n\n```ts\nconst result = await agent.generateText(\"Summarize this trace\", {\n  feedback: {\n    key: \"satisfaction\",\n    feedbackConfig: {\n      type: \"categorical\",\n      categories: [\n        { value: 1, label: \"Satisfied\" },\n        { value: 0, label: \"Unsatisfied\" },\n      ],\n    },\n  },\n});\n\nconsole.log(result.feedback?.url);\n```\n\n`result.feedback` may also include `provided`, `providedAt`, and `feedbackId` so UI clients can hide feedback controls once submitted.\n\nIf the feedback key is already registered, you can pass only `key` and let the stored config populate the token.\n\n```ts\nconst result = await agent.generateText(\"Quick rating?\", {\n  feedback: { key: \"satisfaction\" },\n});\n```\n\nTo persist \"already submitted\" state across memory reloads, you can use the result helper:\n\n```ts\nconst feedbackId = \"feedback-id-from-ingestion-response\"; // returned by your feedback ingestion API response\n\nif (result.feedback && !result.feedback.isProvided()) {\n  await result.feedback.markFeedbackProvided({\n    feedbackId, // optional\n  });\n}\n```\n\nYou can still call `agent.markFeedbackProvided(...)` directly if you prefer explicit control.\n\nThese helpers require memory-backed conversations (`userId` and `conversationId`) and are typically called from your backend after feedback ingestion succeeds.\n\nFor end-to-end examples (SDK, API, and useChat), see [Feedback](/observability-docs/feedback).\n\n### Structured Data Generation\n\nUse `output` with `generateText`/`streamText` to get structured data while still using tools and all agent capabilities.\n`generateObject` and `streamObject` are deprecated in VoltAgent 2.x.\n\n```ts\nimport { Output } from \"ai\";\nimport { z } from \"zod\";\n\nconst recipeSchema = z.object({\n  name: z.string(),\n  ingredients: z.array(z.string()),\n  steps: z.array(z.string()),\n  prepTime: z.number(),\n});\n\n// With generateText - supports tool calling\nconst result = await agent.generateText(\"Create a pasta recipe\", {\n  output: Output.object({ schema: recipeSchema }),\n});\nconsole.log(result.output); // { name: \"...\", ingredients: [...], ... }\n\n// With streamText - stream partial objects while using tools\nconst stream = await agent.streamText(\"Create a detailed recipe\", {\n  output: Output.object({ schema: recipeSchema }),\n});\n\nfor await (const partial of stream.partialOutputStream ?? []) {\n  console.log(partial); // Incrementally built object\n}\n\n// Constrained text generation\nconst haiku = await agent.generateText(\"Write a haiku about coding\", {\n  output: Output.text(),\n});\nconsole.log(haiku.output);\n```\n\n### Summarization\n\nSummarization inserts a system summary and keeps the last N non-system messages before each model call. Configure it with the `summarization` option on the agent. See [Summarization](./summarization.md) for configuration and storage details.\n\n### Conversation Persistence\n\nVoltAgent persists conversation messages and step records while a run is executing. By default, it uses step-level checkpoints, which is safer for long multi-step tool chains.\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users reliably.\",\n  model: \"openai/gpt-4o-mini\",\n  conversationPersistence: {\n    mode: \"step\", // \"step\" (default) or \"finish\"\n    debounceMs: 200, // default\n    flushOnToolResult: true, // default\n  },\n});\n```\n\nYou can also override this per call:\n\n```ts\nawait agent.generateText(\"Run the workflow\", {\n  memory: {\n    options: {\n      conversationPersistence: {\n        mode: \"finish\",\n      },\n    },\n  },\n});\n```\n\n`mode: \"step\"` schedules persistence during execution and flushes immediately on tool completion by default. `mode: \"finish\"` keeps the legacy behavior of persisting only at operation completion.\n\n### Input Types\n\nAll methods accept either a string or an array of messages:\n\n```ts\n// String input\nawait agent.generateText(\"Hello\");\n\n// Message array (for multimodal or conversation history)\nawait agent.generateText([{ role: \"user\", content: \"What's in this image?\" }]);\n```\n\n## Using Agents: REST API\n\nVoltAgent can expose agents through HTTP endpoints. This lets you call agents from any client (web, mobile, etc.).\n\n### Server Setup\n\nCreate a `VoltAgent` instance with a server provider:\n\n```ts\nimport { VoltAgent, Agent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst agent = new Agent({\n  name: \"assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n});\n\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer(), // Starts on port 3141 by default\n});\n```\n\n### Available Endpoints\n\nThe server exposes the following REST endpoints:\n\n#### Text Generation\n\n- `POST /agents/:id/text` - Generate complete text response (synchronous)\n- `POST /agents/:id/stream` - Stream raw fullStream events (SSE)\n- `POST /agents/:id/chat` - Stream UI messages for useChat hook (SSE)\n\nIf you need clients to reconnect after refresh and continue the same response, enable resumable streaming. See [Resumable Streaming](/docs/agents/resumable-streaming/).\n\n#### Structured Data\n\n- `POST /agents/:id/object` - Generate structured object (synchronous)\n- `POST /agents/:id/stream-object` - Stream object generation (SSE)\n\n**Endpoint comparison:**\n\n| Endpoint         | Method | Response Type | Use Case                                                |\n| ---------------- | ------ | ------------- | ------------------------------------------------------- |\n| `/text`          | POST   | JSON          | Complete text response at once                          |\n| `/stream`        | POST   | SSE           | Raw stream events (text/reasoning/tool/lifecycle parts) |\n| `/chat`          | POST   | SSE           | UI message stream for ai-sdk's useChat hook             |\n| `/object`        | POST   | JSON          | Complete structured object at once                      |\n| `/stream-object` | POST   | SSE           | Streaming partial objects                               |\n\n### Structured Output via HTTP\n\nYou can use `output` with the text endpoints (`/text`, `/stream`, `/chat`) to get structured data while maintaining tool calling capabilities. Add the `output` field to the `options` object in your request:\n\n```typescript\nconst response = await fetch(\"http://localhost:3141/agents/assistant/text\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    input: \"Create a recipe\",\n    options: {\n      output: {\n        type: \"object\",\n        schema: {\n          type: \"object\",\n          properties: {\n            name: { type: \"string\" },\n            ingredients: { type: \"array\", items: { type: \"string\" } },\n          },\n          required: [\"name\"],\n        },\n      },\n    },\n  }),\n});\n\nconst data = await response.json();\nconsole.log(data.data.output); // Structured object\n```\n\nFor detailed API reference and examples, see [Agent Endpoints - output](../api/endpoints/agents.md#using-output-for-structured-generation).\n\n### Calling from Next.js API Route\n\nExample Next.js API route using the SDK (not direct REST calls):\n\n```ts\n// app/api/chat/route.ts\nimport { agent } from \"@/voltagent\";\n\nexport async function POST(req: Request) {\n  const { messages, conversationId, userId } = await req.json();\n\n  const result = await agent.streamText(messages, {\n    memory: {\n      conversationId,\n      userId,\n    },\n  });\n\n  return result.toUIMessageStreamResponse();\n}\n```\n\nTo call the REST API directly from a client:\n\n```ts\n// Client-side code\nconst response = await fetch(\"http://localhost:3141/agents/assistant/chat\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    messages: [{ role: \"user\", content: \"Hello\" }],\n  }),\n});\n\n// Stream response (SSE)\nconst reader = response.body.getReader();\n// ... process stream chunks\n```\n\n## Constructor Options\n\nYou can configure agents with additional options:\n\n```typescript\nconst agent = new Agent({\n  // Required\n  name: \"MyAgent\", // Agent identifier\n  instructions: \"You are a helpful assistant\", // Behavior guidelines\n  model: \"openai/gpt-4o\", // Model string or ai-sdk LanguageModel\n\n  // Optional\n  id: \"custom-id\", // Unique ID (auto-generated if not provided)\n  purpose: \"Customer support agent\", // Agent purpose for supervisor context\n  tools: [weatherTool, searchTool], // Available tools\n  memory: memoryStorage, // Memory instance (omit -> built-in in-memory, false -> disable)\n  context: new Map([\n    // Default context for all operations\n    [\"environment\", \"production\"],\n  ]),\n  maxSteps: 10, // Maximum tool-use iterations\n  temperature: 0.7, // Default creativity (overridable per call)\n  maxOutputTokens: 512, // Default token limit (overridable per call)\n  subAgents: [researchAgent], // Sub-agents for delegation\n  supervisorConfig: {\n    // Supervisor behavior config\n    systemMessage: \"Custom supervisor instructions\",\n    includeAgentsMemory: true,\n  },\n\n  // Additional constructor parameters\n  hooks: createHooks({ onStart, onEnd }), // Lifecycle event handlers\n  retriever: new PineconeRetriever(), // RAG retriever\n  voice: new ElevenLabsVoice(), // Voice configuration\n  markdown: true, // Enable markdown formatting\n  voltOpsClient: new VoltOpsClient({\n    // Observability & prompt management\n    publicKey: \"...\",\n    secretKey: \"...\",\n  }),\n  maxHistoryEntries: 1000, // Max history entries to store\n});\n```\n\n## Advanced Features\n\nAgents support additional capabilities through configuration options. Each feature is briefly explained below with a link to detailed documentation.\n\n### Memory\n\nMemory stores conversation history so agents can reference past messages. If `memory` is omitted, agents use built-in in-memory storage. Use `memory: false` for stateless behavior, or configure a persistent adapter for long-term storage.\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n\nconst agent = new Agent({\n  name: \"Agent with Memory\",\n  model: \"openai/gpt-4o\",\n  memory,\n});\n```\n\n[Memory documentation](./memory/overview.md)\n\n### Tools\n\nTools let agents call functions to fetch data, perform actions, or interact with external systems. The LLM decides when to use tools based on the user's input.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather for a location\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => {\n    // Call weather API\n    return { temperature: 72, conditions: \"sunny\" };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Weather Assistant\",\n  instructions: \"Answer weather questions using the get_weather tool.\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n});\n```\n\n[Tools documentation](./tools.md)\n\n#### Using Agents as Tools\n\nAgents can be converted to tools and used by other agents:\n\n```ts\nconst writerAgent = new Agent({\n  id: \"writer\",\n  purpose: \"Writes blog posts\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst editorAgent = new Agent({\n  id: \"editor\",\n  purpose: \"Edits content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Coordinator uses them as tools\nconst coordinator = new Agent({\n  tools: [writerAgent.toTool(), editorAgent.toTool()],\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\nThis is useful when the LLM should decide which agents to call based on the request.\n\n### Guardrails\n\nGuardrails run before and after the model call to validate inputs or adjust outputs. Input guardrails receive the request text, while output guardrails can rewrite or block the response before it reaches the caller.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Guarded Assistant\",\n  instructions: \"Answer briefly.\",\n  model: \"openai/gpt-4o-mini\",\n  inputGuardrails: [\n    {\n      id: \"reject-empty\",\n      name: \"Reject Empty Prompts\",\n      handler: async ({ inputText }) => {\n        if (inputText.trim().length === 0) {\n          return {\n            pass: false,\n            action: \"block\",\n            message: \"Prompt cannot be empty.\",\n          };\n        }\n        return { pass: true };\n      },\n    },\n  ],\n  outputGuardrails: [\n    {\n      id: \"trim-output\",\n      name: \"Trim Whitespace\",\n      handler: async ({ output }) => ({\n        pass: true,\n        action: \"modify\",\n        modifiedOutput: typeof output === \"string\" ? output.trim() : output,\n      }),\n    },\n  ],\n});\n\nconst response = await agent.generateText(\"  Give me one fact about Mars.  \");\nconsole.log(response.text); // \"Give me one fact about Mars.\" (trimmed)\n```\n\n[Learn more about Guardrails](../guardrails/built-in.md)\n\n### Sub-Agents\n\nSub-agents let you delegate tasks to specialized agents. The parent agent can call sub-agents through an automatically generated `delegate_task` tool.\n\n```ts\nconst researchAgent = new Agent({\n  name: \"Researcher\",\n  instructions: \"Research topics thoroughly.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst writerAgent = new Agent({\n  name: \"Writer\",\n  instructions: \"Write clear, concise content.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst coordinator = new Agent({\n  name: \"Coordinator\",\n  instructions: \"Delegate research to Researcher and writing to Writer.\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researchAgent, writerAgent],\n});\n```\n\n#### Event Filtering\n\nWhen streaming with sub-agents, by default only `tool-call` and `tool-result` events are forwarded from sub-agents to reduce noise.\n\n**Enable all event types:**\n\n```ts\nconst coordinator = new Agent({\n  name: \"Coordinator\",\n  instructions: \"Coordinate between agents.\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researchAgent, writerAgent],\n  supervisorConfig: {\n    fullStreamEventForwarding: {\n      types: [\n        \"tool-call\",\n        \"tool-result\",\n        \"text-start\",\n        \"text-delta\",\n        \"text-end\",\n        \"reasoning-start\",\n        \"reasoning-delta\",\n        \"reasoning-end\",\n        \"source\",\n        \"error\",\n        \"finish\",\n      ],\n    },\n  },\n});\n\n// Now receive all sub-agent events\nconst response = await coordinator.streamText(\"Research and write about AI\");\nfor await (const chunk of response.fullStream) {\n  if (chunk.subAgentId && chunk.subAgentName) {\n    console.log(`[${chunk.agentPath?.join(\" > \") ?? chunk.subAgentName}] ${chunk.type}`);\n  }\n}\n```\n\n[Sub-Agents documentation](./subagents.md)\n\n### Hooks\n\nHooks let you run code at specific points in the agent lifecycle: before/after operations, before/after tool calls, etc. Use hooks for logging, monitoring, or custom logic.\n\n```ts\nimport { createHooks } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onStart: async ({ agent, context }) => {\n    console.log(`${agent.name} started`);\n  },\n  onEnd: async ({ agent, output, error }) => {\n    if (error) {\n      console.error(\"Error:\", error);\n    } else {\n      console.log(\"Completed:\", output);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: \"Answer questions.\",\n  model: \"openai/gpt-4o\",\n  hooks,\n});\n```\n\n[Hooks documentation](./hooks.md)\n\n### Prompt Management\n\nInstructions can be static strings, dynamic functions, or managed remotely via VoltOps. Dynamic instructions let you change agent behavior based on runtime context.\n\n```ts\n// Static instructions\nconst agent1 = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions.\",\n  model: \"openai/gpt-4o\",\n});\n\n// Dynamic instructions\nconst agent2 = new Agent({\n  name: \"Assistant\",\n  instructions: ({ context }) => {\n    const tier = context.get(\"tier\") || \"free\";\n    return tier === \"premium\" ? \"Provide detailed answers.\" : \"Provide concise answers.\";\n  },\n  model: \"openai/gpt-4o\",\n});\n```\n\n[Prompt Management documentation](./prompts.md)\n\n### Dynamic Agents\n\nAgent properties (instructions, model, tools) can be functions that evaluate at runtime based on context. This enables multi-tenant apps, role-based access, or per-user model selection.\n\n```ts\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: ({ context }) => {\n    const role = context.get(\"role\");\n    return role === \"admin\" ? \"Admin instructions.\" : \"User instructions.\";\n  },\n  model: ({ context }) => {\n    const tier = context.get(\"tier\");\n    return tier === \"premium\" ? \"openai/gpt-4o\" : \"openai/gpt-4o-mini\";\n  },\n});\n\nawait agent.generateText(\"Hello\", {\n  context: new Map([\n    [\"role\", \"admin\"],\n    [\"tier\", \"premium\"],\n  ]),\n});\n```\n\n[Dynamic Agents documentation](./dynamic-agents.md)\n\n### Operation Context\n\nPass request-specific data (like request IDs, user info) between hooks and tools using the operation context `Map`. Context is scoped to a single operation.\n\n```ts\nconst hooks = createHooks({\n  onStart: async ({ context }) => {\n    context.context.set(\"requestId\", `req-${Date.now()}`);\n  },\n});\n\nconst tool = createTool({\n  name: \"logger\",\n  parameters: z.object({ message: z.string() }),\n  execute: async (params, oc) => {\n    const requestId = oc?.context?.get(\"requestId\");\n    console.log(`[${requestId}] ${params.message}`);\n  },\n});\n```\n\n[Context documentation](./context.md)\n\n### Retriever (RAG)\n\nRetrievers fetch relevant context from external knowledge bases before calling the LLM. Implement the `BaseRetriever` interface to connect vector databases or document stores.\n\n```ts\nclass SimpleRetriever extends BaseRetriever {\n  async retrieve(input: string | BaseMessage[]): Promise<string> {\n    // Fetch relevant documents from vector DB or knowledge base\n    return \"Retrieved context goes here\";\n  }\n}\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer using retrieved context.\",\n  model: \"openai/gpt-4o\",\n  retriever: new SimpleRetriever(),\n});\n```\n\n[Retriever documentation](../rag/overview.md)\n\n### Models and Providers\n\nVoltAgent can use [ai-sdk](https://sdk.vercel.ai) models directly, but model strings are the default. Switch providers by changing the model string (or import another provider).\n\n```ts\nconst agent1 = new Agent({\n  name: \"OpenAI Agent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"Answer questions.\",\n});\n\nconst agent2 = new Agent({\n  name: \"Anthropic Agent\",\n  model: \"anthropic/claude-3-5-sonnet\",\n  instructions: \"Answer questions.\",\n});\n```\n\n[Providers and Models documentation](../getting-started/providers-models.md)\n\n### Call Settings\n\nConfigure generation behavior per-call with parameters like `temperature`, `maxOutputTokens`, `topP`, etc.\n\n```ts\nawait agent.generateText(\"Write a story\", {\n  temperature: 0.8,\n  maxOutputTokens: 500,\n  topP: 0.9,\n  seed: 12345,\n  // Provider-specific options\n  providerOptions: {\n    someProviderSpecificOption: {\n      foo: \"bar\",\n    },\n  },\n});\n```\n\n### Markdown Formatting\n\nEnable automatic markdown formatting in text responses by setting `markdown: true`. The agent will use markdown syntax (headings, lists, bold text, etc.) in its responses.\n\n```ts\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n  markdown: true,\n});\n\nconst result = await agent.generateText(\"Explain how to make tea.\");\n// Response will be formatted with markdown\n```\n\n### Step Control (maxSteps)\n\n`maxSteps` limits how many LLM calls (steps) an agent can make in a single operation. Each tool call consumes a step. Default is 5.\n\n```ts\n// Set maxSteps at agent level\nconst agent = new Agent({\n  name: \"Agent\",\n  model: \"openai/gpt-4o\",\n  maxSteps: 5, // Default for all operations\n});\n\n// Or override per-call\nawait agent.generateText(\"Question\", { maxSteps: 3 });\n```\n\n### Cancellation (AbortController)\n\nCancel long-running operations using `AbortController`. Pass the signal to agent methods.\n\n```ts\nconst abortController = new AbortController();\n\nsetTimeout(() => abortController.abort(), 5000); // Cancel after 5s\n\ntry {\n  const response = await agent.generateText(\"Long task...\", {\n    abortSignal: abortController.signal,\n  });\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Cancelled\");\n  }\n}\n```\n\n[Cancellation documentation](./cancellation.md)\n\n### MCP (Model Context Protocol)\n\nConnect to external MCP servers to use their tools as if they were local tools.\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    myServer: {\n      type: \"http\",\n      url: \"https://mcp-server.example.com\",\n    },\n  },\n});\n\nconst mcpTools = await mcpConfig.getTools();\n\nconst agent = new Agent({\n  name: \"Agent\",\n  model: \"openai/gpt-4o\",\n  tools: mcpTools,\n});\n```\n\n[MCP documentation](./mcp/mcp.md)\n\n### Voice\n\nAdd speech-to-text and text-to-speech capabilities using voice providers.\n\n```ts\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\nconst voice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n  ttsModel: \"tts-1\",\n  voice: \"alloy\",\n});\n\nconst agent = new Agent({\n  name: \"Voice Assistant\",\n  model: \"openai/gpt-4o\",\n  voice,\n});\n\nconst result = await agent.generateText(\"Tell a story\");\nconst audioStream = await agent.voice.speak(result.text);\n```\n\n[Voice documentation](./voice.md)\n\n## Error Handling\n\nWrap agent calls in try-catch blocks. Errors during streaming are recorded in history and can be observed via the `onError` callback.\n\n## Next Steps\n\nSee the links above for detailed documentation on each feature. For working examples, visit the [examples repository](https://github.com/voltagent/voltagent/tree/main/examples).\n","frontmatter":{"title":"Overview","slug":"/agents/overview"},"isInternal":false,"tokens":6039,"sizeBytes":25586},{"name":"plan-agent.md","path":"website/docs/agents/plan-agent.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/plan-agent.md","title":"Subagent: plan-agent","category":"subagent-persona","format":"markdown","content":"---\ntitle: PlanAgent\nslug: /agents/plan-agent\n---\n\n# PlanAgent\n\nPlanAgent is a higher-level agent that adds built-in planning, task delegation, filesystem tools, and summarization on top of the standard `Agent`. Use it when you want multi-step execution with explicit plans and tighter control over context.\n\n## Default Agent (baseline)\n\nStart with a normal `Agent` if you only need straightforward responses.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly and concisely.\",\n  model: \"openai/gpt-4o\",\n});\n\nconst result = await agent.generateText(\"What is VoltAgent?\");\nconsole.log(result.text);\n```\n\n## PlanAgent quickstart\n\nPlanAgent works like `Agent`, but adds planning, filesystem, and subagent tooling by default.\n\n```ts\nimport { PlanAgent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst internetSearch = createTool({\n  name: \"internet_search\",\n  description: \"Search the web for information\",\n  parameters: z.object({\n    query: z.string().describe(\"Search query\"),\n    maxResults: z.number().optional().default(5),\n  }),\n  execute: async ({ query, maxResults }) => {\n    const response = await fetch(\"https://api.tavily.com/search\", {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        Authorization: `Bearer ${process.env.TAVILY_API_KEY}`,\n      },\n      body: JSON.stringify({ query, maxResults }),\n    });\n    return response.json();\n  },\n});\n\nconst agent = new PlanAgent({\n  name: \"Researcher\",\n  systemPrompt: \"You are an expert researcher. Produce clear, sourced answers.\",\n  model: \"openai/gpt-4o\",\n  tools: [internetSearch],\n});\n\nconst result = await agent.generateText(\"What is VoltAgent?\");\nconsole.log(result.text);\n```\n\n## Dynamic systemPrompt\n\n`systemPrompt` can be a function that resolves per request. It receives `{ context, prompts }` and can return a string or a VoltOps prompt payload.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"DynamicPlanner\",\n  model: openai(\"gpt-4o\"),\n  systemPrompt: async ({ context }) => {\n    const tenant = context.get(\"tenant\") ?? \"default\";\n    return `Tenant: ${tenant}. Keep answers concise.`;\n  },\n});\n\nconst context = new Map<string | symbol, unknown>();\ncontext.set(\"tenant\", \"acme\");\n\nconst result = await agent.generateText(\"Summarize the roadmap\", { context });\nconsole.log(result.text);\n```\n\nIf you return a chat prompt:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"DynamicChatPlanner\",\n  model: openai(\"gpt-4o\"),\n  systemPrompt: async () => ({\n    type: \"chat\",\n    messages: [{ role: \"system\", content: \"You are a precise planner.\" }],\n  }),\n});\n```\n\nPlanAgent still appends its base prompt and any extension prompts.\n\n## Built-in capabilities\n\n### Planning with write_todos\n\nPlanAgent injects a planning prompt and a `write_todos` tool. The agent is expected to create a plan before using tools or producing long answers.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Planner\",\n  systemPrompt: \"Plan carefully before acting.\",\n  model: \"openai/gpt-4o\",\n  planning: {\n    systemPrompt: \"Always plan any multi-step task. Keep 4-8 concise steps and update as you go.\",\n  },\n});\n```\n\nDisable planning if you want a lighter agent:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"NoPlan\",\n  systemPrompt: \"Answer quickly.\",\n  model: \"openai/gpt-4o\",\n  planning: false,\n});\n```\n\n### State and persistence\n\nPlanAgent stores its todo list, filesystem state, and summaries per conversation. When a conversation continues, it injects the latest state back into the system prompt so the model can keep working from the same plan and files.\n\n### Filesystem tools\n\nPlanAgent provides `ls`, `read_file`, `write_file`, `edit_file`, `glob`, and `grep`. The default backend is an in-memory filesystem, but you can use a real filesystem backend.\n\n```ts\nimport { NodeFilesystemBackend } from \"@voltagent/core\";\n\nconst agent = new PlanAgent({\n  name: \"FileAgent\",\n  systemPrompt: \"Use the filesystem to store and retrieve context.\",\n  model: \"openai/gpt-4o\",\n  filesystem: {\n    backend: new NodeFilesystemBackend({\n      rootDir: process.cwd(),\n      virtualMode: true,\n    }),\n  },\n});\n```\n\n### Task tool and subagents\n\nPlanAgent adds a `task` tool that can delegate work to subagents for isolated, multi-step tasks.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate deep research tasks to subagents.\",\n  model: \"openai/gpt-4o\",\n  subagents: [\n    {\n      name: \"research-analyst\",\n      description: \"Deep research and synthesis\",\n      systemPrompt: \"Research thoroughly and return a concise report.\",\n      tools: [internetSearch],\n    },\n  ],\n});\n```\n\nYou can also tune the task tool behavior:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  task: {\n    taskDescription: \"Use subagents for complex, independent work.\",\n    maxSteps: 6,\n  },\n});\n```\n\nTo forward subagent stream events when you call `planAgent.streamText(...)`, configure the task's supervisor config:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  task: {\n    supervisorConfig: {\n      fullStreamEventForwarding: {\n        types: [\"tool-call\", \"tool-result\", \"text-delta\"],\n      },\n    },\n  },\n});\n```\n\nPlanAgent also adds a default `general-purpose` subagent unless you disable it:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Supervisor\",\n  systemPrompt: \"Delegate when helpful.\",\n  model: \"openai/gpt-4o\",\n  generalPurposeAgent: false,\n});\n```\n\n### Summarization\n\nPlanAgent can automatically summarize older conversation history and keep recent messages in context.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Summarizer\",\n  systemPrompt: \"Keep context tight.\",\n  model: \"openai/gpt-4o\",\n  summarization: {\n    triggerTokens: 120_000,\n    keepMessages: 6,\n    maxOutputTokens: 600,\n  },\n});\n```\n\nDisable it when you want full context preserved:\n\n```ts\nconst agent = new PlanAgent({\n  name: \"NoSummary\",\n  systemPrompt: \"Keep full context.\",\n  model: \"openai/gpt-4o\",\n  summarization: false,\n});\n```\n\n### Tool result eviction\n\nLarge tool outputs can be offloaded to the filesystem to keep the model context small.\n\n```ts\nconst agent = new PlanAgent({\n  name: \"Evictor\",\n  systemPrompt: \"Store large tool outputs on the filesystem.\",\n  model: \"openai/gpt-4o\",\n  toolResultEviction: {\n    enabled: true,\n    tokenLimit: 20000,\n  },\n});\n```\n\n### Extensions\n\nUse `extensions` to add new toolkits, prompts, or hooks without forking PlanAgent.\n\n```ts\nimport type { PlanAgentExtension } from \"@voltagent/core\";\n\nconst debugExtension: PlanAgentExtension = {\n  name: \"debug\",\n  apply: () => ({\n    systemPrompt: \"Log key decisions to the user.\",\n  }),\n};\n\nconst agent = new PlanAgent({\n  name: \"Extended\",\n  systemPrompt: \"Be helpful.\",\n  model: \"openai/gpt-4o\",\n  extensions: [debugExtension],\n});\n```\n\n## Configuration reference (high-level)\n\n- `systemPrompt`: static string or dynamic function for per-request prompts\n- `planning`: configure or disable planning (`write_todos`)\n- `filesystem`: configure backend and tool prompts\n- `task`: configure task tool prompts and limits\n- `subagents`: custom subagent definitions\n- `generalPurposeAgent`: add/remove the default general-purpose subagent\n- `summarization`: configure summarization triggers and model\n- `toolResultEviction`: offload large tool results to filesystem\n- `extensions`: attach custom tooling or hooks\n\nUse PlanAgent when your workloads require explicit plans, context offloading, or structured delegation to subagents.\n","frontmatter":{"title":"PlanAgent","slug":"/agents/plan-agent"},"isInternal":false,"tokens":1891,"sizeBytes":7655},{"name":"prompts.md","path":"website/docs/agents/prompts.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/prompts.md","title":"Subagent: prompts","category":"subagent-persona","format":"markdown","content":"# Agent Instructions\n\n## Overview\n\nVoltAgent supports three approaches for defining agent instructions. Each approach addresses different requirements around flexibility, team collaboration, and deployment workflows.\n\n### The Three Approaches\n\n| Approach                 | Definition                    | Context Access | Team Workflow    |\n| ------------------------ | ----------------------------- | -------------- | ---------------- |\n| **Static Instructions**  | Hardcoded string              | No             | Code-based       |\n| **Dynamic Instructions** | Function with runtime context | Yes            | Code-based       |\n| **VoltOps Management**   | Externally managed prompts    | Yes            | Platform-managed |\n\n## Static Instructions\n\nStatic instructions are literal strings assigned to the `instructions` property.\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: \"You are a customer support agent. Help users with their questions.\",\n});\n```\n\n**Type signature:**\n\n```typescript\ninstructions: string;\n```\n\n### When to Use\n\nUse static instructions when:\n\n- Agent behavior is consistent across all interactions\n- No runtime context is needed\n- Instructions rarely change\n- Team members edit prompts through code reviews\n\nAvoid when:\n\n- Different users need different behavior\n- Instructions depend on runtime data (user tier, time, location)\n- Non-technical team members need to edit prompts\n- You need prompt versioning outside of code commits\n\n## Dynamic Instructions\n\nDynamic instructions are functions that receive runtime context and return instructions.\n\n### Returning Strings\n\nFunctions can return a plain string based on context:\n\n```typescript\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ context }) => {\n    const userTier = context.get(\"userTier\") || \"basic\";\n\n    if (userTier === \"premium\") {\n      return \"You are a premium customer support agent. Provide detailed explanations and prioritize this customer's requests.\";\n    }\n    return \"You are a customer support agent. Provide helpful but concise answers.\";\n  },\n});\n```\n\n**Using with context:**\n\n```typescript\nconst premiumContext = new Map();\npremiumContext.set(\"userTier\", \"premium\");\n\nconst response = await agent.generateText(\"I need help\", {\n  context: premiumContext,\n});\n```\n\n### Returning PromptContent Objects\n\nFunctions can also return `PromptContent` objects for text or chat-based instructions.\n\n**Text type:**\n\n```typescript\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ context }) => {\n    return {\n      type: \"text\",\n      text: \"You are a customer support agent.\",\n    };\n  },\n});\n```\n\n**Chat type with multiple messages:**\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"ChatAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async () => {\n    return {\n      type: \"chat\",\n      messages: [\n        {\n          role: \"system\",\n          content: \"You are a helpful assistant.\",\n        },\n        {\n          role: \"user\",\n          content: \"Hello!\",\n        },\n        {\n          role: \"assistant\",\n          content: \"Hi! How can I help you today?\",\n        },\n      ],\n    };\n  },\n});\n```\n\n**Chat type with provider-specific options:**\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"CachedAgent\",\n  model: \"anthropic/claude-3-7-sonnet-20250219\",\n  instructions: async () => {\n    return {\n      type: \"chat\",\n      messages: [\n        {\n          role: \"system\",\n          content: \"Long system prompt that should be cached...\",\n          providerOptions: {\n            anthropic: {\n              cacheControl: { type: \"ephemeral\", ttl: \"5m\" },\n            },\n          },\n        },\n      ],\n    };\n  },\n});\n```\n\n**Type signature:**\n\n```typescript\ninstructions: (options: DynamicValueOptions) => Promise<string | PromptContent>;\n\ninterface DynamicValueOptions {\n  context: Map<string | symbol, unknown>;\n  headers?: Record<string, string>;\n  prompts: PromptHelper;\n}\n\ninterface PromptContent {\n  type: \"text\" | \"chat\";\n  text?: string;\n  messages?: ChatMessage[];\n}\n```\n\n### When to Use\n\nUse dynamic instructions when:\n\n- Agent behavior depends on user properties (tier, role, preferences)\n- Instructions need runtime data (time, location, session state)\n- Different tenants require different behavior\n- Conditional logic determines instruction content\n\nAvoid when:\n\n- Multiple non-technical stakeholders need to edit prompts\n- You need prompt version history outside of code\n- Collaborative prompt editing is required\n- Prompts should update without deploying code\n\n## VoltOps Prompt Management\n\nVoltOps separates prompt content from application code. Prompts are created and versioned in the VoltOps platform, then fetched at runtime.\n\nFor complete documentation on VoltOps Prompt Management, see:\n\n- [Prompt Management Overview](/prompt-engineering-docs/) - Creating, versioning, labels, and managing prompts\n- [Usage Guide](/prompt-engineering-docs/usage/) - Integration with agents, caching, variables, and error handling\n\n### Quick Example\n\n```typescript\nimport { Agent, VoltAgent, VoltOpsClient } from \"@voltagent/core\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst agent = new Agent({\n  name: \"SupportAgent\",\n  model: \"openai/gpt-4o-mini\",\n  instructions: async ({ prompts }) => {\n    return await prompts.getPrompt({\n      promptName: \"customer-support-prompt\",\n      label: \"production\",\n      variables: { companyName: \"VoltAgent Corp\" },\n    });\n  },\n});\n\nnew VoltAgent({\n  agents: { agent },\n  voltOpsClient: voltOpsClient,\n});\n```\n\n### When to Use\n\nUse VoltOps when:\n\n- Non-technical team members edit prompts\n- Audit trails and approval workflows are required\n- Multiple environments need different prompt versions\n- Prompt analytics and monitoring are needed\n\nAvoid when:\n\n- External dependencies are not acceptable\n- Offline operation is required\n\n## Comparison\n\n| Feature                   | Static      | Dynamic     | VoltOps            |\n| ------------------------- | ----------- | ----------- | ------------------ |\n| **Type**                  | String      | Function    | External platform  |\n| **Context Access**        | No          | Yes         | Yes (via function) |\n| **Runtime Flexibility**   | None        | Full        | Full               |\n| **Team Collaboration**    | Code review | Code review | Platform UI        |\n| **Version Control**       | Git         | Git         | VoltOps + Git      |\n| **Non-technical Editing** | No          | No          | Yes                |\n| **Analytics**             | No          | No          | Yes                |\n| **Offline Support**       | Yes         | Yes         | No                 |\n| **External Dependency**   | No          | No          | Yes                |\n\n## Examples\n\n**Static (solo developer, consistent behavior):**\n\n```typescript\nconst agent = new Agent({\n  instructions: \"You are a code reviewer. Focus on security and performance.\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n**Dynamic (user-specific behavior):**\n\n```typescript\nconst agent = new Agent({\n  instructions: async ({ context }) => {\n    const tier = context.get(\"tier\");\n    return tier === \"premium\"\n      ? \"You are a premium support agent with deep technical expertise.\"\n      : \"You are a support agent providing efficient solutions.\";\n  },\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n**VoltOps (team collaboration, versioning):**\n\n```typescript\nconst agent = new Agent({\n  instructions: async ({ prompts }) => {\n    return await prompts.getPrompt({\n      promptName: \"customer-support-agent\",\n      label: process.env.NODE_ENV === \"production\" ? \"production\" : \"development\",\n    });\n  },\n  model: \"openai/gpt-4o-mini\",\n  voltOpsClient: voltOpsClient,\n});\n```\n","isInternal":false,"tokens":1756,"sizeBytes":8053},{"name":"providers.md","path":"website/docs/agents/providers.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/providers.md","title":"Subagent: providers","category":"subagent-persona","format":"markdown","content":"---\ntitle: Using Models\nslug: /agents/providers\n---\n\n# Using Models with Agents\n\nVoltAgent 1.x has no `llm` provider prop. Set `model` to either:\n\n- An ai-sdk `LanguageModel` (imported from a provider package), or\n- A model string like `openai/gpt-4o-mini` resolved by the built-in model router.\n\n## Quick Example\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"my-agent\",\n  instructions: \"Helpful and concise\",\n  model: \"anthropic/claude-3-5-sonnet\",\n});\n\nconst res = await agent.generateText(\"Say hi\", { temperature: 0.2 });\nconsole.log(res.text);\n```\n\nYou can also pass a `LanguageModel` directly:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport { anthropic } from \"@ai-sdk/anthropic\";\n\nconst agent = new Agent({\n  name: \"my-agent\",\n  instructions: \"Helpful and concise\",\n  model: anthropic(\"claude-3-5-sonnet\"),\n});\n```\n\n## Provider Options (IntelliSense)\n\nUse `providerOptions` for provider-specific settings in `generateText`, `streamText`, `generateObject`, and `streamObject` calls.\n\n```ts\nawait agent.generateText(\"Draft a summary\", {\n  temperature: 0.3,\n  maxOutputTokens: 300,\n  providerOptions: {\n    openai: {\n      reasoningEffort: \"medium\",\n      textVerbosity: \"low\",\n    },\n    anthropic: {\n      sendReasoning: true,\n      cacheControl: { type: \"ephemeral\" },\n    },\n    google: {\n      thinkingConfig: {\n        thinkingBudget: 1024,\n      },\n    },\n    xai: {\n      reasoningEffort: \"medium\",\n    },\n  },\n});\n```\n\nTypeScript IntelliSense is available for `openai`, `anthropic`, `google`, and `xai` option objects.\n\n## Provider Selection\n\nUse any ai-sdk provider package you have configured (OpenAI, Anthropic, Google, Groq, Mistral, Vertex, Bedrock, etc.), or use model strings.\nFor installation and an up-to-date model matrix, see:\n\n**[Providers & Models](/docs/getting-started/providers-models)**\n\nFor how model strings are resolved and how environment variables map, see:\n\n**[Model Router & Registry](/docs/getting-started/model-router)**\n\n## Retries and Fallbacks\n\nFor retry counts and fallback lists, see:\n\n**[Retries and Fallbacks](/docs/agents/retries-fallback)**\n","frontmatter":{"title":"Using Models","slug":"/agents/providers"},"isInternal":false,"tokens":568,"sizeBytes":2134},{"name":"resumable-streaming.md","path":"website/docs/agents/resumable-streaming.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/resumable-streaming.md","title":"Subagent: resumable-streaming","category":"subagent-persona","format":"markdown","content":"---\ntitle: Resumable Streaming\nslug: /agents/resumable-streaming\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Resumable Streaming\n\nResumable streaming lets a client reconnect to an in-flight stream (for example after a refresh) and continue receiving the same response. VoltAgent provides this via `@voltagent/resumable-streams`.\n\n<video controls loop muted playsInline style={{width: '100%', height: 'auto'}}>\n\n  <source src=\"https://cdn.voltagent.dev/docs/resumable-stream.mp4\" type=\"video/mp4\" />\n  Your browser does not support the video tag.\n</video>\n\n## Install\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n    ```bash\n    npm install @voltagent/resumable-streams\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/resumable-streams\n    ```\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n    ```bash\n    pnpm add @voltagent/resumable-streams\n    ```\n  </TabItem>\n</Tabs>\n\n## How it works\n\nResumable streaming is split into two storages:\n\n1. **Stream store**: stores stream chunks and pub/sub messages.\n2. **Active stream store**: maps `userId + \"-\" + conversationId` -> `streamId`.\n\nWhen a stream starts, a new `streamId` is created, the stream store persists the output, and the active stream store is updated. When the client reconnects, the active stream store is used to find the `streamId`, and the stream store resumes from the last position. When the stream finishes, the active stream store entry is cleared.\n\n## Basic usage (Hono server)\n\nCreate a Redis-backed store, build the adapter, and pass it to the Hono server.\n\n```ts\nimport { Agent, VoltAgent } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst resumableStream = await createResumableStreamAdapter({ streamStore });\n\nconst agent = new Agent({\n  id: \"assistant\",\n  name: \"Resumable Stream Agent\",\n  instructions: \"You are a helpful assistant.\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer({\n    resumableStream: { adapter: resumableStream },\n  }),\n});\n```\n\nBy default, the adapter uses `streamStore` as the `activeStreamStore`.  \nFor production, use a shared stream store (Redis) so multiple instances can resume.\n\n### Enabling resumable streams\n\nResumable streams are **opt-in**. You must set:\n\n- `options.resumableStream: true`\n- `options.conversationId`\n- `options.userId`\n\n```ts\nawait fetch(`${baseUrl}/agents/${agentId}/chat`, {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: `{\"input\":\"Hello!\",\"options\":{\"conversationId\":\"conv-1\",\"userId\":\"user-1\",\"resumableStream\":true}}`,\n});\n```\n\n### Server-level default (optional)\n\nIf you want resumable streaming enabled by default, set it at the server level and omit the per-request flag. You can still opt out by sending `options.resumableStream: false`.\n\n```ts\nnew VoltAgent({\n  agents: { assistant: agent },\n  server: honoServer({\n    resumableStream: {\n      adapter: resumableStream,\n      defaultEnabled: true,\n    },\n  }),\n});\n```\n\n### Resume endpoint\n\nThe Hono server exposes:\n\n- `GET /agents/:id/chat/:conversationId/stream?userId=...`\n\nIf there is no active stream, it returns `204`. `userId` is required.\n\n## Store options\n\n### VoltOps managed store\n\n`createResumableStreamVoltOpsStore` stores streams in VoltOps. It uses:\n\n- the global `VoltOpsClient` if one is already configured by `VoltAgent`, or\n- your explicit `voltOpsClient`, or\n- `VOLTAGENT_PUBLIC_KEY` + `VOLTAGENT_SECRET_KEY` from env.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamVoltOpsStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamVoltOpsStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\nIf you need a custom base URL, pass `baseUrl` or set `VOLTAGENT_API_BASE_URL`.\n\nCustom VoltOps client:\n\n```ts\nimport { VoltOpsClient } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamVoltOpsStore,\n} from \"@voltagent/resumable-streams\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst streamStore = await createResumableStreamVoltOpsStore({ voltOpsClient });\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### Redis store (recommended)\n\n`createResumableStreamRedisStore` reads `REDIS_URL` or `KV_URL` by default.  \nIf you need a custom connection string, pass your own Redis clients as `publisher` and `subscriber`.\n\n```ts\nimport { createClient } from \"redis\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\n// Option A: use REDIS_URL or KV_URL from env\nconst streamStore = await createResumableStreamRedisStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n\n// Option B: pass your own clients (custom connection string)\nconst publisher = createClient({ url: \"redis://localhost:6379\" });\nconst subscriber = createClient({ url: \"redis://localhost:6379\" });\nconst customStreamStore = await createResumableStreamRedisStore({ publisher, subscriber });\nconst adapter = await createResumableStreamAdapter({ streamStore: customStreamStore });\n```\n\nIf you pass custom clients, you must connect them yourself.\n\n### Memory store (dev/test)\n\n`createResumableStreamMemoryStore` keeps everything in-process.  \nData is lost on restart and not shared across instances.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamMemoryStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamMemoryStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### Generic store (custom backend)\n\n`createResumableStreamGenericStore` accepts your own `publisher`/`subscriber`.  \nUse this for non-Redis backends that still provide pub/sub + KV semantics.\n\n```ts\nimport type {\n  ResumableStreamPublisher,\n  ResumableStreamSubscriber,\n} from \"@voltagent/resumable-streams\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamGenericStore,\n} from \"@voltagent/resumable-streams\";\n\nconst publisher: ResumableStreamPublisher = {\n  async connect() {},\n  async publish(channel, message) {\n    return 1;\n  },\n  async set(key, value, options) {\n    return \"OK\";\n  },\n  async get(key) {\n    return null;\n  },\n  async incr(key) {\n    return 1;\n  },\n};\n\nconst subscriber: ResumableStreamSubscriber = {\n  async connect() {},\n  async subscribe(channel, callback) {\n    return 1;\n  },\n  async unsubscribe(channel) {},\n};\n\nconst streamStore = await createResumableStreamGenericStore({\n  publisher,\n  subscriber,\n  keyPrefix: \"voltagent\",\n  waitUntil: null,\n});\n\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n## Next.js + AI SDK (useChat)\n\nThis section mirrors the `with-nextjs-resumable-stream` example. Full project:  \nhttps://github.com/VoltAgent/voltagent/tree/main/examples/with-nextjs-resumable-stream\n\n### 1) Adapter helper\n\nCreate the adapter once and reuse it across routes.\n\n```ts\n// lib/resumable-stream.ts\nimport type { ResumableStreamAdapter } from \"@voltagent/core\";\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\nimport { after } from \"next/server\";\n\nlet adapterPromise: Promise<ResumableStreamAdapter> | undefined;\n\nexport function getResumableStreamAdapter() {\n  if (!adapterPromise) {\n    adapterPromise = (async () => {\n      const streamStore = await createResumableStreamRedisStore({ waitUntil: after });\n      return createResumableStreamAdapter({ streamStore });\n    })();\n  }\n\n  return adapterPromise;\n}\n```\n\n### 2) POST route (create stream)\n\n```ts\n// app/api/chat/route.ts\nimport { getResumableStreamAdapter } from \"@/lib/resumable-stream\";\nimport { agent } from \"@/voltagent\";\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\nimport { setWaitUntil } from \"@voltagent/core\";\nimport { after } from \"next/server\";\n\nexport async function POST(req: Request) {\n  const body = await req.json();\n  const { message, messages, options } = body;\n  const conversationId = options.conversationId;\n  const userId = options.userId;\n  const input = message ?? messages;\n\n  setWaitUntil(after);\n\n  const adapter = await getResumableStreamAdapter();\n  const session = createResumableChatSession({\n    adapter,\n    conversationId,\n    userId,\n    agentId: agent.id,\n  });\n\n  await session.clearActiveStream();\n\n  const result = await agent.streamText(input, {\n    userId,\n    conversationId,\n  });\n\n  return result.toUIMessageStreamResponse({\n    consumeSseStream: session.consumeSseStream,\n    onFinish: session.onFinish,\n  });\n}\n```\n\n### 3) GET route (resume stream)\n\n```ts\n// app/api/chat/[id]/stream/route.ts\nimport { getResumableStreamAdapter } from \"@/lib/resumable-stream\";\nimport { agent } from \"@/voltagent\";\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\n\nexport async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {\n  const { id } = await params;\n  const userId = new URL(request.url).searchParams.get(\"userId\") as string;\n\n  const adapter = await getResumableStreamAdapter();\n  const session = createResumableChatSession({\n    adapter,\n    conversationId: id,\n    userId,\n    agentId: agent.id,\n  });\n\n  return session.resumeResponse();\n}\n```\n\n### 4) Client-side resume\n\n```tsx\n// components/chat-interface.tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\n\nconst { messages, sendMessage } = useChat({\n  id: chatId,\n  messages: initialMessages,\n  resume: true,\n  transport: new DefaultChatTransport({\n    api: \"/api/chat\",\n    prepareSendMessagesRequest: ({ id, messages }) => ({\n      body: {\n        message: messages[messages.length - 1],\n        options: {\n          conversationId: id,\n          userId,\n        },\n      },\n    }),\n    prepareReconnectToStreamRequest: ({ id }) => ({\n      api: `/api/chat/${id}/stream?userId=${encodeURIComponent(userId)}`,\n    }),\n  }),\n});\n```\n\n## Custom usage (no AI SDK)\n\nUse `createResumableChatSession` when you control the SSE pipeline.\n\n```ts\nimport {\n  createResumableChatSession,\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst adapter = await createResumableStreamAdapter({ streamStore });\n\nconst sseHeaders = {\n  \"Content-Type\": \"text/event-stream\",\n  \"Cache-Control\": \"no-cache\",\n  Connection: \"keep-alive\",\n};\n\nexport async function POST(req: Request) {\n  const { options, agentId, text } = await req.json();\n  const conversationId = options.conversationId;\n  const userId = options.userId;\n\n  const session = createResumableChatSession({\n    adapter,\n    conversationId,\n    userId,\n    agentId,\n    resumeHeaders: sseHeaders,\n  });\n\n  const stream = new ReadableStream<string>({\n    start(controller) {\n      controller.enqueue(`data: ${text}\\n\\n`);\n      controller.close();\n    },\n  });\n\n  const [persistStream, responseStream] = stream.tee();\n  await session.createStream(persistStream);\n\n  return new Response(responseStream.pipeThrough(new TextEncoderStream()), {\n    headers: sseHeaders,\n  });\n}\n```\n\nIf you want JSON payloads in SSE, serialize them with your preferred helper.\n\n## Route helpers (optional)\n\n`createResumableChatHandlers` can generate `POST`/`GET` handlers.  \nDefaults:\n\n- `conversationId`: `body.options.conversationId` (GET uses `params.id`)\n- `userId`: `body.options.userId` (GET uses `?userId=...`)\n\n## Custom stores and adapters\n\nYou have two extension points:\n\n- Implement `ResumableStreamStore` and pass it to the adapter.\n- Implement `ResumableStreamAdapter` directly for full control.\n\n`ResumableStreamStore` handles stream chunk storage.  \nIf it also implements `ResumableStreamActiveStore`, it can be used as `activeStreamStore`.  \nOtherwise, pass a separate `activeStreamStore`.\n\n## Important notes\n\n- Keying rule: `userId + \"-\" + conversationId`. `userId` is required.  \n  If you do not have a user identity, generate a stable id (for example `crypto.randomUUID()`).\n- When resumable streaming is enabled, the abort signal is removed.  \n  If you need abortable streams, do not use `resumableStream`.\n- Resumable streaming does not persist messages. For message persistence, enable memory.  \n  See [Memory](../agents/memory.md).\n\n## VoltOps plan limits (managed store)\n\nWhen you use the VoltOps managed store, concurrent resumable streams are limited per project:\n\n- Free: 1 concurrent stream\n- Core: 100 concurrent streams\n- Pro: 1000 concurrent streams\n\n## API reference (@voltagent/resumable-streams)\n\n### createResumableStreamAdapter\n\nBuilds the adapter used by servers or custom routes.\n\nProps:\n\n- `streamStore` (required): a `ResumableStreamStore` for stream chunks.\n- `activeStreamStore` (optional): overrides the active stream index.  \n  If omitted, the adapter uses `streamStore` when it implements `ResumableStreamActiveStore`.\n\n```ts\nimport {\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore({ keyPrefix: \"voltagent\" });\nconst adapter = await createResumableStreamAdapter({ streamStore });\n```\n\n### createResumableStreamRedisStore\n\nCreates a Redis-backed stream store.\n\nProps:\n\n- `keyPrefix` (optional): prefix for Redis keys.\n- `waitUntil` (optional): keep-alive hook for serverless environments.\n- `publisher` (optional): Redis publisher client.\n- `subscriber` (optional): Redis subscriber client.\n\n```ts\nimport { createClient } from \"redis\";\nimport { createResumableStreamRedisStore } from \"@voltagent/resumable-streams\";\n\n// Option A: use REDIS_URL or KV_URL from env\nconst streamStore = await createResumableStreamRedisStore();\n\n// Option B: custom clients (custom connection string)\nconst publisher = createClient({ url: \"redis://localhost:6379\" });\nconst subscriber = createClient({ url: \"redis://localhost:6379\" });\nconst customStreamStore = await createResumableStreamRedisStore({ publisher, subscriber });\n```\n\nIf you pass custom clients, you must connect them yourself.\n\n### createResumableStreamVoltOpsStore\n\nCreates a managed store backed by VoltOps.\n\nProps:\n\n- `voltOpsClient` (optional)\n- `publicKey` / `secretKey` (optional, used if `voltOpsClient` is not provided)\n- `baseUrl` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport { createResumableStreamVoltOpsStore } from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamVoltOpsStore({\n  baseUrl: \"https://api.voltagent.dev\",\n});\n```\n\n### createResumableStreamMemoryStore\n\nCreates an in-process stream store (dev/test).\n\nProps:\n\n- `keyPrefix` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport { createResumableStreamMemoryStore } from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamMemoryStore({ keyPrefix: \"local\" });\n```\n\n### createResumableStreamGenericStore\n\nCreates a store from a custom pub/sub + KV backend.\n\nProps:\n\n- `publisher` (required)\n- `subscriber` (required)\n- `keyPrefix` (optional)\n- `waitUntil` (optional)\n\n```ts\nimport type {\n  ResumableStreamPublisher,\n  ResumableStreamSubscriber,\n} from \"@voltagent/resumable-streams\";\nimport { createResumableStreamGenericStore } from \"@voltagent/resumable-streams\";\n\nconst publisher: ResumableStreamPublisher = {\n  async connect() {},\n  async publish(channel, message) {\n    return 1;\n  },\n  async set(key, value, options) {\n    return \"OK\";\n  },\n  async get(key) {\n    return null;\n  },\n  async incr(key) {\n    return 1;\n  },\n};\n\nconst subscriber: ResumableStreamSubscriber = {\n  async connect() {},\n  async subscribe(channel, callback) {\n    return 1;\n  },\n  async unsubscribe(channel) {},\n};\n\nconst streamStore = await createResumableStreamGenericStore({\n  publisher,\n  subscriber,\n  keyPrefix: \"voltagent\",\n  waitUntil: null,\n});\n```\n\n### createMemoryResumableStreamActiveStore\n\nCreates an in-memory active stream index. Use this to override `activeStreamStore`.\n\n```ts\nimport {\n  createMemoryResumableStreamActiveStore,\n  createResumableStreamAdapter,\n  createResumableStreamRedisStore,\n} from \"@voltagent/resumable-streams\";\n\nconst streamStore = await createResumableStreamRedisStore();\nconst activeStreamStore = createMemoryResumableStreamActiveStore();\nconst adapter = await createResumableStreamAdapter({ streamStore, activeStreamStore });\n```\n\n### createResumableChatSession\n\nBuilds a session helper for custom route handlers.\n\nProps:\n\n- `adapter` (required)\n- `conversationId` (required)\n- `userId` (required)\n- `agentId` (optional)\n- `logger` (optional)\n- `resumeHeaders` (optional): headers for SSE resume responses\n\n```ts\nimport { createResumableChatSession } from \"@voltagent/resumable-streams\";\n\nconst session = createResumableChatSession({\n  adapter,\n  conversationId,\n  userId,\n  agentId,\n});\n\nconst result = await agent.streamText(input, { conversationId, userId });\nreturn result.toUIMessageStreamResponse({\n  consumeSseStream: session.consumeSseStream,\n  onFinish: session.onFinish,\n});\n```\n\n```ts\nreturn session.resumeResponse();\n```\n\n### createResumableChatHandlers\n\nGenerates `POST` and `GET` handlers.\n\nProps:\n\n- `agent` (required)\n- `adapter` (required)\n- `waitUntil` (optional)\n- `logger` (optional)\n- `agentId` (optional)\n- `sendReasoning` (optional)\n- `sendSources` (optional)\n- `resolveInput` (optional)\n- `resolveConversationId` (optional)\n- `resolveUserId` (optional)\n- `resolveOptions` (optional)\n\n```ts\nimport { createResumableChatHandlers } from \"@voltagent/resumable-streams\";\n\nconst { POST, GET } = createResumableChatHandlers({\n  agent,\n  adapter,\n  resolveConversationId: ({ body }) => body.options.conversationId,\n  resolveUserId: ({ body }) => body.options.userId,\n});\n\nexport { POST, GET };\n```\n\n## Type contracts\n\n### ResumableStreamStore\n\n- `createNewResumableStream(streamId, makeStream, skipCharacters?)`\n- `resumeExistingStream(streamId, skipCharacters?)`\n\n### ResumableStreamActiveStore\n\n- `getActiveStreamId(context)`\n- `setActiveStreamId(context, streamId)`\n- `clearActiveStream(context)`\n\n### ResumableStreamAdapter\n\n- `createStream({ conversationId, userId, stream, agentId? })`\n- `resumeStream(streamId)`\n- `getActiveStreamId(context)`\n- `clearActiveStream(context)`\n\n### ResumableStreamPublisher / ResumableStreamSubscriber\n\nPublisher methods:\n\n- `connect()`\n- `publish(channel, message)`\n- `set(key, value, { EX })` (TTL in seconds)\n- `get(key)`\n- `incr(key)`\n\nSubscriber methods:\n\n- `connect()`\n- `subscribe(channel, callback)`\n- `unsubscribe(channel)`\n","frontmatter":{"title":"Resumable Streaming","slug":"/agents/resumable-streaming"},"isInternal":false,"tokens":4715,"sizeBytes":18894},{"name":"retries-fallback.md","path":"website/docs/agents/retries-fallback.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/retries-fallback.md","title":"Subagent: retries-fallback","category":"subagent-persona","format":"markdown","content":"---\ntitle: Retries and Fallbacks\nslug: /agents/retries-fallback\n---\n\n# Retries and Fallbacks\n\nVoltAgent supports per-call retries and ordered model fallback lists. Retries are handled by VoltAgent with exponential backoff. After retries are exhausted for a model, VoltAgent attempts the next model in the list.\n\n## Configure Retries\n\nYou can set a default retry count at the agent level, then override it per model or per call.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Support\",\n  instructions: \"Resolve tickets quickly.\",\n  model: \"openai/gpt-4o-mini\",\n  maxRetries: 3,\n});\n\n// Per-call override\nawait agent.generateText(\"Summarize this ticket\", { maxRetries: 0 });\n```\n\n- `maxRetries` is the number of retry attempts for a single model call (total attempts = `maxRetries + 1`).\n- Priority order: per-call `maxRetries` > per-model `maxRetries` > agent `maxRetries` > default `3`.\n- `0` disables retries for that call.\n- Retry delay uses exponential backoff: 1s, 2s, 4s, 8s (max 10s).\n- Errors with `isRetryable: false` skip retries; fallback can still run.\n\n## Configure Fallbacks\n\nProvide a list of models. VoltAgent tries them in order until one succeeds.\nEach entry can have its own retry count and can be toggled on/off.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\nimport { anthropic } from \"@ai-sdk/anthropic\";\n\nconst agent = new Agent({\n  name: \"FallbackAgent\",\n  instructions: \"Be concise.\",\n  model: [\n    { id: \"primary\", model: \"openai/gpt-4o-mini\", maxRetries: 2 },\n    { id: \"secondary\", model: anthropic(\"claude-3-5-sonnet\"), maxRetries: 1 },\n    { id: \"tertiary\", model: \"google/gemini-2.0-flash\", enabled: true },\n  ],\n});\n```\n\nNotes:\n\n- The first enabled model is the primary.\n- `maxRetries` falls back to the agent default if omitted.\n- `enabled: false` lets you keep a model configured but temporarily disabled.\n\n## How Fallback Works\n\nVoltAgent tries each enabled model in order:\n\n1. Resolve the model (dynamic or static).\n2. Execute the call with that model, retrying up to `maxRetries` on error.\n3. If retries are exhausted, move to the next model.\n\nFallback **does not** trigger for:\n\n- Abort or bail errors.\n- Guardrail blocks.\n- Tool execution errors.\n\nStreaming behavior:\n\n- Retries and fallback happen only if the stream fails before the first output chunk.\n- If a stream fails after output starts, the error is surfaced to the caller and the stream is not restarted.\n\n## Middleware Retries\n\nMiddleware retries are separate from model retries and fallback. A middleware can call `abort(\"reason\", { retry: true })` to restart the full attempt.\n\n- Controlled by `maxMiddlewareRetries` (agent-level or per-call).\n- Each retry reruns the full pipeline: middleware, guardrails, hooks, model selection, model retries, and fallback.\n- In streaming calls, only input middlewares can trigger a retry before the stream starts.\n\nSee [Middleware](/docs/agents/middleware) for configuration and API details.\n\n## Real-World Scenarios\n\n### 1. Provider Outage During a Traffic Spike\n\nThe primary provider returns 5xx errors during a traffic spike. Configure a fallback provider with its own retry count.\n\n```ts\nconst agent = new Agent({\n  name: \"LaunchAssistant\",\n  instructions: \"Handle onboarding questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n    { model: \"anthropic/claude-3-5-sonnet\", maxRetries: 2 },\n  ],\n});\n```\n\nExpected behavior:\n\n- Primary errors are retried up to `maxRetries`.\n- After retries, the secondary provider is attempted.\n\n### 2. Rate Limits During Peak Hours\n\nThe primary model returns 429 during peak hours. Set `maxRetries: 0` on the primary and allow retries on the fallback.\n\n```ts\nconst agent = new Agent({\n  name: \"PeakTrafficAgent\",\n  instructions: \"Answer pricing questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 0 },\n    { model: \"groq/llama-3.3-70b-versatile\", maxRetries: 2 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The primary fails without retry delay.\n- The fallback is attempted and can retry if needed.\n\n### 3. Timeouts in One Region\n\nRequests in a region start timing out. Configure a regional primary with a global fallback.\n\n```ts\nconst agent = new Agent({\n  name: \"RegionalAgent\",\n  instructions: \"Support users in APAC.\",\n  model: [\n    { model: \"google/gemini-2.0-flash\", maxRetries: 1 },\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n  ],\n});\n```\n\nExpected behavior:\n\n- Timeouts on the primary move the call to the fallback after retries.\n- The fallback handles calls that the primary fails.\n\n### 4. Compliance or Data Residency Constraints\n\nSome users require a specific provider for data residency. Select the primary model dynamically and keep a fallback entry.\n\n```ts\nconst agent = new Agent({\n  name: \"ComplianceAgent\",\n  instructions: \"Handle regulated requests.\",\n  model: [\n    {\n      model: async ({ context }) => {\n        const region = (context.get(\"region\") as string) || \"us\";\n        return region === \"eu\" ? \"mistral/mistral-large-latest\" : \"openai/gpt-4o-mini\";\n      },\n      maxRetries: 1,\n    },\n    { model: \"anthropic/claude-3-5-sonnet\", maxRetries: 1 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The model selection uses `context` values on each call.\n- If the selected model fails, the fallback is attempted.\n\n### 5. Cost Control with Fallback\n\nUse a lower-cost primary model and a second model for error cases.\n\n```ts\nconst agent = new Agent({\n  name: \"CostAwareAgent\",\n  instructions: \"Handle receipts and billing questions.\",\n  model: [\n    { model: \"openai/gpt-4o-mini\", maxRetries: 1 },\n    { model: \"openai/gpt-4o\", maxRetries: 0 },\n  ],\n});\n```\n\nExpected behavior:\n\n- The primary handles successful calls.\n- The fallback is only used when the primary errors.\n\n## Troubleshooting Tips\n\n- If fallbacks never trigger, check for guardrail blocks or tool errors.\n- If retries are skipped, check whether the provider marks the error as `isRetryable: false`.\n- If all configured models are disabled, VoltAgent throws `MODEL_LIST_EMPTY`.\n- For long streaming sessions, consider application-level retry on stream errors.\n- Keep the fallback list short to avoid long end-to-end latency when providers are degraded.\n","frontmatter":{"title":"Retries and Fallbacks","slug":"/agents/retries-fallback"},"isInternal":false,"tokens":1576,"sizeBytes":6189},{"name":"subagents.md","path":"website/docs/agents/subagents.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/subagents.md","title":"Subagent: subagents","category":"subagent-persona","format":"markdown","content":"---\ntitle: Sub-agents\nslug: /agents/sub-agents\n---\n\n# Sub-agents\n\nSub-agents are agents that work under a supervisor agent to handle specific tasks. This architecture allows you to create agent workflows where each sub-agent focuses on a specific domain, coordinated by a supervisor.\n\n## Why Use Sub-agents?\n\n- **Task delegation**: Assign specific tasks to agents configured for particular domains (e.g., coding, translation, data analysis)\n- **Workflow orchestration**: Build multi-step workflows by delegating tasks to appropriate agents\n- **Code organization**: Break down complex problems into smaller components\n- **Modularity**: Add or swap agents without disrupting the entire system\n\n## Creating and Using Sub-agents\n\n### Creating Individual Agents\n\nCreate the agents that will serve as sub-agents:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Create an agent for content creation\nconst contentCreatorAgent = new Agent({\n  name: \"ContentCreator\",\n  instructions: \"Creates short text content on requested topics\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Create an agent for formatting\nconst formatterAgent = new Agent({\n  name: \"Formatter\",\n  instructions: \"Formats and styles text content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Give sub-agents a concise purpose to control what the supervisor sees\nconst summarizerAgent = new Agent({\n  name: \"Summarizer\",\n  purpose: \"Summarize long support tickets\",\n  instructions:\n    \"Read the conversation and produce a concise summary highlighting blockers and owners\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\n### Creating a Supervisor Agent\n\nPass the agents in the `subAgents` array during supervisor initialization:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst supervisorAgent = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinates between content creation and formatting agents\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [contentCreatorAgent, formatterAgent, summarizerAgent],\n});\n```\n\n:::tip Advanced Sub-agent Configuration\nBy default, sub-agents use the `streamText` method. You can specify different methods like `generateText`, `generateObject`, or `streamObject` with custom schemas and options.\n\nSee: [Advanced Configuration](#advanced-configuration)\n:::\n\n## Customizing Supervisor Behavior\n\nSupervisor agents use an automatically generated system message that includes guidelines for managing sub-agents. Customize this behavior using the `supervisorConfig` option.\n\n### Purpose vs. Instructions (what the supervisor sees)\n\n- The supervisor lists sub-agents in a `<specialized_agents>` block using the sub-agent `purpose` when provided; if `purpose` is missing, it falls back to `instructions`, and if both are missing it uses `\"Dynamic instructions\"`.\n- Set a short `purpose` to avoid leaking long/verbose instructions into the supervisor’s prompt and to keep the prompt small. The full `instructions` still run for the sub-agent itself when delegated.\n- If you leave `purpose` empty, expect the supervisor prompt to include the entire `instructions` string for that sub-agent.\n\n:::info Default System Message\nSee the [generateSupervisorSystemMessage implementation](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/subagent/index.ts#L131) on GitHub.\n:::\n\n:::note Type Safety\nThe `supervisorConfig` option is only available when `subAgents` are provided. TypeScript will prevent you from using `supervisorConfig` on agents without sub-agents.\n:::\n\n### Basic Supervisor Configuration\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst supervisorAgent = new Agent({\n  name: \"Content Supervisor\",\n  instructions: \"Coordinate content creation workflow\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    // Add custom guidelines to the default ones\n    customGuidelines: [\n      \"Always thank the user at the end\",\n      \"Keep responses concise and actionable\",\n      \"Prioritize user experience\",\n    ],\n\n    // Control whether prior sub-agent interactions are injected into supervisor prompt\n    includeAgentsMemory: true, // default: true\n  },\n});\n```\n\n### Stream Event Forwarding Configuration\n\nControl which events from sub-agents are forwarded to the parent stream. By default, only `tool-call` and `tool-result` events are forwarded.\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Content Supervisor\",\n  instructions: \"Coordinate content creation workflow\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    // Configure which sub-agent events to forward\n    fullStreamEventForwarding: {\n      // Default: ['tool-call', 'tool-result']\n      types: [\n        \"tool-call\",\n        \"tool-result\",\n        \"text-delta\",\n        \"reasoning-start\",\n        \"reasoning-delta\",\n        \"reasoning-end\",\n        \"source\",\n        \"error\",\n        \"finish\",\n      ],\n    },\n  },\n});\n```\n\n**Common Configurations:**\n\n```ts\n// Minimal - Only tool events (default)\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result'],\n}\n\n// Text + Tools - Include text generation\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result', 'text-delta'],\n}\n\n// Full visibility - All events including reasoning\nfullStreamEventForwarding: {\n  types: [\n    'tool-call',\n    'tool-result',\n    'text-delta',\n    'reasoning-start',\n    'reasoning-delta',\n    'reasoning-end',\n    'source',\n    'error',\n    'finish'\n  ],\n}\n\n// Clean tool names - No agent prefix (add prefix manually when consuming events if desired)\nfullStreamEventForwarding: {\n  types: ['tool-call', 'tool-result'],\n}\n```\n\nThis configuration balances stream performance and information detail for sub-agent interactions.\n\n### Error Handling Configuration\n\nControl how the supervisor handles sub-agent failures.\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate between agents\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [dataProcessor, analyzer],\n\n  supervisorConfig: {\n    // Control whether stream errors throw exceptions\n    throwOnStreamError: false, // default: false\n\n    // Control whether error messages appear in empty responses\n    includeErrorInEmptyResponse: true, // default: true\n  },\n});\n```\n\n#### Configuration Options\n\n**`throwOnStreamError`** (boolean, default: `false`)\n\n- When `false`: Stream errors are caught and returned as error results with `status: \"error\"`\n- When `true`: Stream errors throw exceptions that must be caught with try/catch\n- Set to `true` to handle errors at a higher level or trigger retry logic\n\n**`includeErrorInEmptyResponse`** (boolean, default: `true`)\n\n- When `true`: Error messages are included in the response when no content was generated\n- When `false`: Returns empty string in result, but still marks status as \"error\"\n- Set to `false` to handle error messaging yourself\n\n#### Common Error Handling Patterns\n\n**Default - Graceful Error Handling:**\n\n```ts\n// Errors are returned as results with helpful messages\nsupervisorConfig: {\n  throwOnStreamError: false,\n  includeErrorInEmptyResponse: true,\n}\n\n// Usage:\nconst result = await supervisor.streamText(\"Process data\");\n// If sub-agent fails:\n// result contains error message like \"Error in DataProcessor: Stream failed\"\n```\n\n:::info Retry Behavior\nVoltAgent retries model calls based on `maxRetries`. `throwOnStreamError` controls how stream errors are surfaced; it does not change retry or fallback behavior.\n:::\n\n**Silent Errors - Custom Messaging:**\n\n```ts\n// Errors don't include automatic messages\nsupervisorConfig: {\n  includeErrorInEmptyResponse: false,\n}\n\n// Usage with custom error handling:\nconst result = await supervisor.streamText(\"Process data\");\nfor await (const event of result.fullStream) {\n  if (event.type === \"error\") {\n    // Provide custom user-friendly error message\n    console.log(\"We're having trouble processing your request. Please try again.\");\n  }\n}\n```\n\n**Production Setup - Error Tracking:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: false, // Don't crash the app\n  includeErrorInEmptyResponse: true, // Help with debugging\n\n  // Capture error events for monitoring\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result', 'error'],\n  },\n}\n```\n\n#### Error Handling in Practice\n\nThe supervisor's behavior when a sub-agent encounters an error depends on your configuration:\n\n```ts\n// Example: Sub-agent fails during stream\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [unreliableAgent],\n  supervisorConfig: {\n    throwOnStreamError: false,\n    includeErrorInEmptyResponse: true,\n  },\n});\n\n// The supervisor handles the failure\nconst response = await supervisor.streamText(\"Do something risky\");\n\n// Check the response\nif (response.status === \"error\") {\n  console.log(\"Sub-agent failed:\", response.error);\n  // response.result contains: \"Error in UnreliableAgent: [error details]\"\n} else {\n  // Process successful response\n}\n```\n\n#### Using with fullStream\n\nWhen using `fullStream`, the configuration controls what you receive from sub-agents. VoltAgent\nforwards metadata onto each forwarded chunk so you can attribute every event:\n\n- `subAgentId` / `subAgentName`: the sub-agent that produced the chunk\n- `executingAgentId` / `executingAgentName`: same as above (reserved for nested handoffs)\n- `parentAgentId` / `parentAgentName`: the supervisor that forwarded the chunk\n- `agentPath`: ordered names from supervisor → executing agent\n\nThis metadata is only on the streamed events (fullStream / UI streams); it is **not** sent back to\nthe model provider or injected into the LLM messages.\n\n```ts\n// Stream with full event details\nconst result = await supervisorAgent.streamText(\"Create and edit content\");\n\n// Process different event types\nfor await (const event of result.fullStream) {\n  if (event.type === \"tool-call\") {\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Tool called: ${event.toolName}`);\n    }\n  } else if (event.type === \"tool-result\") {\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Tool result:`, event.output);\n    }\n  } else if (event.type === \"text-delta\") {\n    // Only appears if included in types array\n    if (event.subAgentName) {\n      console.log(`[${event.subAgentName}] Text: ${event.text ?? event.delta ?? \"\"}`);\n    }\n  }\n}\n```\n\n#### Filtering Sub-agent Events\n\nIdentify which events come from sub-agents by checking for `subAgentId` and `subAgentName` properties:\n\n```ts\nconst result = await supervisorAgent.streamText(\"Create and edit content\");\n\nfor await (const event of result.fullStream) {\n  if (event.subAgentId && event.subAgentName) {\n    console.log(\n      `Event from sub-agent ${event.subAgentName} (path: ${event.agentPath?.join(\" > \")})`\n    );\n    console.log(`  Type: ${event.type}`);\n    console.log(`  Payload:`, {\n      toolName: event.toolName,\n      output: event.output,\n      text: (event as { text?: string }).text,\n    });\n    continue;\n  }\n\n  // This is from the supervisor agent itself\n  console.log(`Supervisor event: ${event.type}`);\n}\n```\n\nThis allows you to:\n\n- Distinguish between supervisor and sub-agent events\n- Filter events by specific sub-agent\n- Apply different handling logic based on the event source\n\n#### Filtering Historical Conversations\n\nSub-agent metadata is also persisted in memory, so you can apply the same filtering logic when you replay a conversation later:\n\n```ts\nconst messages = await memory.getMessages(userId, conversationId);\n\nconst writerMessages = messages.filter(\n  (message) => message.metadata?.subAgentName === \"WriterAgent\"\n);\n\nfor (const message of writerMessages) {\n  console.log(`[${message.metadata?.subAgentName}]`, message.parts);\n}\n```\n\nEvery `UIMessage` in memory now includes the `metadata.subAgentId` and `metadata.subAgentName` fields, making it easy to render supervisor history the same way you render live streams.\n\n### Complete System Message Override\n\nProvide a custom `systemMessage` to replace the default template:\n\n```ts\nconst supervisorAgent = new Agent({\n  name: \"Custom Supervisor\",\n  instructions: \"This will be ignored when systemMessage is provided\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writerAgent, editorAgent],\n\n  supervisorConfig: {\n    systemMessage: `\nYou are a content manager named \"ContentBot\".\n\nYour team:\n- Writer: Creates original content\n- Editor: Reviews and improves content\n\nYour workflow:\n1. Analyze user requests\n2. Use delegate_task to assign work to appropriate specialists\n3. Coordinate between specialists as needed\n4. Provide final responses\n5. Maintain a professional tone\n\nRemember: Use the delegate_task tool to assign tasks to your specialists.\n    `.trim(),\n\n    // Control memory inclusion even with custom system message\n    includeAgentsMemory: true,\n  },\n});\n```\n\n### Quick Usage\n\n**Add custom rules:**\n\n```ts\nsupervisorConfig: {\n  customGuidelines: [\"Verify sources\", \"Include confidence levels\"];\n}\n```\n\n**Override entire system message:**\n\n```ts\nsupervisorConfig: {\n  systemMessage: \"You are TaskBot. Use delegate_task(task, [agentNames]) to assign work.\";\n}\n```\n\n**Control memory:**\n\n```ts\nsupervisorConfig: {\n  includeAgentsMemory: false; // Exclude prior sub-agent interactions from supervisor prompt (default: true)\n}\n```\n\n:::note `includeAgentsMemory` vs `memory: false`\n`includeAgentsMemory` only controls prompt construction for the supervisor. It does not disable conversation memory inside each sub-agent. If you need a stateless sub-agent, set `memory: false` on that sub-agent.\n:::\n\n**Stateless sub-agent:**\n\n```ts\nconst writerAgent = new Agent({\n  name: \"Writer\",\n  instructions: \"Write concise drafts.\",\n  model: \"openai/gpt-4o-mini\",\n  memory: false,\n});\n```\n\n**Configure event forwarding:**\n\n```ts\nsupervisorConfig: {\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result', 'text-delta'], // Control which events to forward\n  }\n}\n```\n\n**Handle errors gracefully:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: false, // Return errors as results (default)\n  includeErrorInEmptyResponse: true // Include error details in response (default)\n}\n```\n\n**Throw exceptions for custom error handling:**\n\n```ts\nsupervisorConfig: {\n  throwOnStreamError: true; // Throw exceptions on sub-agent failures\n}\n```\n\n## How Sub-agents Work\n\nThe supervisor agent delegates tasks to its sub-agents using the automatically provided `delegate_task` tool.\n\n1. A user sends a request to the supervisor agent.\n2. The supervisor's LLM analyzes the request and its system prompt (which lists available sub-agents).\n3. Based on the task, the supervisor decides which sub-agent(s) to use.\n4. The supervisor uses the `delegate_task` tool to hand off the task(s).\n\n### The `delegate_task` Tool\n\nThis tool is automatically added to supervisor agents and handles delegation.\n\n- **Name**: `delegate_task`\n- **Description**: \"Delegate a task to one or more specialized agents\"\n- **Parameters**:\n  - `task` (string, required): The task description to be delegated\n  - `targetAgents` (array of strings, required): Sub-agent names to delegate the task to. The supervisor can delegate to multiple agents simultaneously\n  - `context` (object, optional): Additional context needed by the sub-agent(s)\n- **Execution**:\n  - Finds the sub-agent instances based on the provided names\n  - Calls the `handoffTask` (or `handoffToMultiple`) method internally\n  - Tags delegated sub-agent messages with metadata so supervisor memory reads can exclude sub-agent records\n  - Passes the supervisor's agent ID (`parentAgentId`) and history entry ID (`parentHistoryEntryId`) for observability\n- **Returns**:\n  - **Always returns an array** of result objects (even for single agent):\n    ```ts\n    [\n      {\n        agentName: string; // Name of the sub-agent that executed the task\n        response: string; // The text result returned by the sub-agent\n        usage?: UsageInfo; // Token usage information\n        bailed?: boolean; // True if onHandoffComplete called bail()\n      },\n      // ... more results if multiple agents were targeted\n    ]\n    ```\n\n  When `bailed: true`, the supervisor's execution is terminated immediately and the subagent's response is returned to the user. See [Early Termination (Bail)](#early-termination-bail) for details.\n\n5. Sub-agents process their delegated tasks independently. They can use their own tools or delegate further if they are also supervisors.\n6. Each sub-agent returns its result to the `delegate_task` tool execution context.\n7. The supervisor receives the results from the `delegate_task` tool.\n8. The supervisor synthesizes the final response based on its instructions and the received results.\n\n## Complete Example\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Create agents\nconst writer = new Agent({\n  name: \"Writer\",\n  instructions: \"Write creative stories\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst translator = new Agent({\n  name: \"Translator\",\n  instructions: \"Translate text accurately\",\n  model: \"openai/gpt-4o-mini\",\n});\n\n// Create supervisor\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate story writing and translation\",\n  model: \"openai/gpt-4o-mini\",\n  subAgents: [writer, translator],\n});\n\n// Use it\nconst result = await supervisor.streamText(\"Write a story about AI and translate to Spanish\");\n\nfor await (const chunk of result.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n**What happens:**\n\n1. Supervisor analyzes request\n2. Calls `delegate_task` → Writer creates story\n3. Calls `delegate_task` → Translator translates\n4. Combines results and responds\n\n## Using Hooks\n\nVoltAgent provides hooks to monitor and control the supervisor/subagent workflow:\n\n### `onHandoff` Hook\n\nTriggered when delegation begins:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [writer, translator],\n  hooks: {\n    onHandoff: ({ agent, sourceAgent }) => {\n      console.log(`${sourceAgent.name} → ${agent.name}`);\n    },\n  },\n});\n```\n\n### `onHandoffComplete` Hook\n\nTriggered when a subagent completes execution. This hook enables **early termination (bail)** to optimize token usage:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [dataAnalyzer, reportGenerator],\n  hooks: {\n    onHandoffComplete: async ({ agent, sourceAgent, result, messages, usage, context, bail }) => {\n      // Bail if subagent produced final output\n      if (agent.name === \"Report Generator\") {\n        context.logger?.info(\"Final report ready, bailing\");\n        bail(); // Skip supervisor processing\n      }\n    },\n  },\n});\n```\n\nSee [Early Termination (Bail)](#early-termination-bail) below for detailed usage.\n\n## Context Sharing\n\nSub-agents automatically inherit the supervisor's context:\n\n```ts\n// Supervisor passes context\nconst response = await supervisor.streamText(\"Task\", {\n  context: new Map([[\"projectId\", \"123\"]]),\n});\n\n// Sub-agent receives it automatically\nconst subAgent = new Agent({\n  hooks: {\n    onStart: (context) => {\n      const projectId = context.context.get(\"projectId\"); // \"123\"\n    },\n  },\n});\n```\n\n## Early Termination (Bail)\n\n### The Problem\n\nIn supervisor/subagent workflows, subagents **always** return to the supervisor for processing, even when they generate final outputs (like JSON structures or reports) that need no additional handling. This wastes tokens:\n\n```\nCurrent flow:\nSupervisor → SubAgent (generates 2K token JSON) → Supervisor (processes JSON) → User\n                                                    ↑ Wastes ~2K tokens\n```\n\n**Example impact:**\n\n- Without bail: ~2,650 tokens per request\n- With bail: ~560 tokens per request\n- **Savings: 79%** (~$0.020 per request)\n\n### The Solution\n\nThe `onHandoffComplete` hook allows supervisors to intercept subagent results and **bail** (skip supervisor processing) when the subagent produces final output:\n\n```\nNew flow:\nSupervisor → SubAgent → bail() → User ✅\n```\n\n### Basic Usage\n\nCall `bail()` in the `onHandoffComplete` hook to terminate early:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Workout Supervisor\",\n  subAgents: [exerciseAgent, workoutBuilder],\n  hooks: {\n    onHandoffComplete: async ({ agent, result, bail, context }) => {\n      // Workout Builder produces final JSON - no processing needed\n      if (agent.name === \"Workout Builder\") {\n        context.logger?.info(\"Final output received, bailing\");\n        bail(); // Skip supervisor, return directly to user\n      }\n      // Default: continue to supervisor for processing\n    },\n  },\n});\n```\n\n### Conditional Bail Logic\n\nBail based on agent name, result size, or content:\n\n```ts\nhooks: {\n  onHandoffComplete: async ({ agent, result, bail, context }) => {\n    // By agent name\n    if (agent.name === \"Report Generator\") {\n      bail();\n      return;\n    }\n\n    // By result size (save tokens)\n    if (result.length > 2000) {\n      context.logger?.warn(\"Large result, bailing to save tokens\");\n      bail();\n      return;\n    }\n\n    // By result content\n    if (result.includes(\"FINAL_OUTPUT\")) {\n      bail();\n      return;\n    }\n\n    // Default: continue to supervisor\n  },\n}\n```\n\n### Transform Before Bail\n\nOptionally transform the result before bailing:\n\n```ts\nhooks: {\n  onHandoffComplete: async ({ agent, result, bail }) => {\n    if (agent.name === \"Report Generator\") {\n      // Add metadata before returning\n      const transformed = `# Final Report\\n\\n${result}\\n\\n---\\nGenerated at: ${new Date().toISOString()}`;\n      bail(transformed); // Bail with transformed result\n    }\n  },\n}\n```\n\n### Hook Parameters\n\n```ts\ninterface OnHandoffCompleteHookArgs {\n  agent: Agent; // Target agent (subagent)\n  sourceAgent: Agent; // Source agent (supervisor)\n  result: string; // Subagent's output\n  messages: UIMessage[]; // Full conversation messages\n  usage?: UsageInfo; // Token usage info\n  context: OperationContext; // Operation context\n  bail: (transformedResult?: string) => void; // Call to bail\n}\n```\n\n### Accessing Bailed Results\n\nWhen a subagent bails, the **subagent's result** is returned to the user (not the supervisor's):\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [\n    createSubagent({\n      agent: workoutBuilder,\n      method: \"generateObject\",\n      schema: WorkoutSchema,\n    }),\n  ],\n  hooks: {\n    onHandoffComplete: async ({ agent, bail }) => {\n      if (agent.name === \"Workout Builder\") {\n        bail(); // Return workout JSON directly\n      }\n    },\n  },\n});\n\nconst result = await supervisor.generateText(\"Create workout\");\nconsole.log(result.text); // Contains workout JSON, not supervisor's processing\n```\n\n### Supported Methods\n\nBail works with methods that support tools:\n\n- ✅ `generateText` - Aborts execution, returns bailed result\n- ✅ `streamText` - Aborts stream immediately, returns bailed result\n- ❌ `generateObject` - No tool support, bail not applicable\n- ❌ `streamObject` - No tool support, bail not applicable\n\n### Stream Event Visibility with Bail\n\nWhen using bail with `toUIMessageStream()` or consuming `fullStream` events, you need to configure which events are forwarded from subagents.\n\n**Default Behavior:**\n\nBy default, only `tool-call` and `tool-result` events are forwarded from subagents. This means **subagent text chunks are NOT visible** in the stream:\n\n```ts\n// Default configuration (implicit)\nsupervisorConfig: {\n  fullStreamEventForwarding: {\n    types: ['tool-call', 'tool-result'], // ⚠️ text-delta NOT included\n  }\n}\n```\n\n**To See Subagent Text Output:**\n\nWhen a subagent bails and produces text output, you must explicitly include `text-delta` in the forwarded event types:\n\n```ts\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  subAgents: [workoutBuilder],\n  supervisorConfig: {\n    fullStreamEventForwarding: {\n      types: [\"tool-call\", \"tool-result\", \"text-delta\"], // ✅ Include text-delta\n    },\n  },\n  hooks: {\n    onHandoffComplete: ({ agent, bail }) => {\n      if (agent.name === \"Workout Builder\") {\n        bail(); // Subagent text will be visible in stream\n      }\n    },\n  },\n});\n```\n\n**Consuming Bailed Subagent Output:**\n\nWhen using `toUIMessageStream()`, subagent text appears as `data-subagent-stream` events that are automatically grouped and rendered in the UI:\n\n```ts\nconst result = await supervisor.streamText(\"Create workout\");\n\nfor await (const message of result.toUIMessageStream()) {\n  // Message parts include grouped subagent output; each subagent chunk carries:\n  // subAgentId, subAgentName, executingAgent*, parentAgent*, agentPath\n  // These parts are emitted as `data-subagent-stream` entries.\n}\n```\n\n**Event Types Reference:**\n\n| Event Type                                              | Description                              | Default         |\n| ------------------------------------------------------- | ---------------------------------------- | --------------- |\n| `tool-call`                                             | Tool invocations                         | ✅ Included     |\n| `tool-result`                                           | Tool results                             | ✅ Included     |\n| `text-delta`                                            | Text chunk generation                    | ❌ NOT included |\n| `reasoning-start` / `reasoning-delta` / `reasoning-end` | Model reasoning lifecycle (if available) | ❌ NOT included |\n| `source`                                                | Retrieved sources (if available)         | ❌ NOT included |\n| `error`                                                 | Error events                             | ❌ NOT included |\n| `finish`                                                | Stream completion                        | ❌ NOT included |\n\n### Observability\n\nBailed subagents are tracked in observability with visual indicators:\n\n**Logging:**\n\n```\n[INFO] Supervisor bailed after handoff\n  supervisor: Workout Supervisor\n  subagent: Workout Builder\n  transformed: false\n  resultLength: 450\n```\n\n**OpenTelemetry Attributes:**\n\nBoth supervisor and subagent spans get attributes:\n\n```ts\n// Supervisor span attributes\n{\n  \"bailed\": true,\n  \"bail.subagent\": \"Workout Builder\",\n  \"bail.transformed\": false\n}\n\n// Subagent span attributes\n{\n  \"bailed\": true,\n  \"bail.supervisor\": \"Workout Supervisor\",\n  \"bail.transformed\": false\n}\n```\n\n### Use Cases\n\nPerfect for scenarios where specialized subagents generate final outputs:\n\n1. **JSON/Structured data generators** - Workout builders, report generators, data exporters\n2. **Large content producers** - Document creators, extensive data analysis\n3. **Token optimization** - Skip processing for expensive results\n4. **Business logic** - Conditional routing based on result characteristics\n\n### Best Practices\n\n**✅ DO:**\n\n- Bail when subagent produces final, ready-to-use output\n- Use conditional logic to bail selectively\n- Log bail decisions for debugging\n- Transform results before bailing when needed\n\n**❌ DON'T:**\n\n- Bail on every subagent (defeats supervisor purpose)\n- Bail when supervisor needs to process/combine results\n- Forget to handle non-bailed flow (default case)\n\n````\n\n## Step Control\n\nControl workflow steps with `maxSteps`:\n\n```ts\nconst supervisor = new Agent({\n  subAgents: [writer, editor],\n  maxSteps: 20, // Inherited by all sub-agents\n});\n\n// Override per request\nconst result = await supervisor.generateText(\"Task\", { maxSteps: 10 });\n````\n\n**Default:** `10 × number_of_sub-agents` (prevents infinite loops)\n\n## Observability\n\nSub-agent operations are automatically linked to their supervisor for traceability in monitoring tools.\n\n## Advanced Configuration\n\nUse different execution methods for sub-agents:\n\n```ts\nimport { createSubagent } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst AnalysisSchema = z.object({\n  insights: z.array(z.string()),\n  confidence: z.number().min(0).max(1),\n});\n\nconst supervisor = new Agent({\n  subAgents: [\n    writer, // Default streamText\n    createSubagent({\n      agent: analyzer,\n      method: \"generateObject\",\n      schema: AnalysisSchema,\n      options: { temperature: 0.1 },\n    }),\n  ],\n});\n```\n\n**Available methods:**\n\n- `streamText` (default) - Real-time text streaming\n- `generateText` - Text generation\n- `generateObject` - Structured data with Zod schema\n- `streamObject` - Streaming structured data\n\n## Dynamic Sub-agents\n\nAdd sub-agents after initialization:\n\n```ts\nsupervisor.addSubAgent(newAgent);\n```\n\n## Remove Sub-agents\n\n```ts\nsupervisor.removeSubAgent(agentId);\n```\n\n## Troubleshooting\n\n**Sub-agent not being called?**\n\n- Check agent names match exactly\n- Make supervisor instructions explicit about when to delegate\n- Use `onHandoff` hook to debug delegation flow\n","frontmatter":{"title":"Sub-agents","slug":"/agents/sub-agents"},"isInternal":false,"tokens":6539,"sizeBytes":28646},{"name":"summarization.md","path":"website/docs/agents/summarization.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/summarization.md","title":"Subagent: summarization","category":"subagent-persona","format":"markdown","content":"---\ntitle: Summarization\nslug: /agents/summarization\n---\n\n# Summarization\n\nSummarization shortens long conversations by inserting a system summary and keeping the last N non-system messages. It is configured per agent.\n\n## Configuration\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Answer questions clearly.\",\n  model: \"openai/gpt-4o\",\n  summarization: {\n    enabled: true,\n    triggerTokens: 120_000,\n    keepMessages: 6,\n    maxOutputTokens: 800,\n    systemPrompt: \"Summarize the conversation for the next step.\",\n    model: \"openai/gpt-4o-mini\",\n  },\n});\n```\n\nOptions:\n\n- `enabled`: Enable or disable summarization.\n- `triggerTokens`: Token estimate threshold. The estimate uses a simple character-to-token heuristic.\n- `keepMessages`: Number of most recent non-system messages kept after summarization.\n- `maxOutputTokens`: Token budget for the summary generation call.\n- `systemPrompt`: Prompt used for summary generation. If omitted, a default system prompt is used.\n- `model`: Optional model for summarization; defaults to the agent model.\n\n## How Summarization Runs\n\nOn each request, VoltAgent:\n\n1. Removes any previous summary system message (identified by `<agent_summary>` markers).\n2. Calculates a token estimate for non-system messages.\n3. If `nonSystemMessages.length > keepMessages` and the token estimate meets `triggerTokens`, it generates a summary.\n4. Stores the summary state and injects a system message that contains the summary.\n5. Returns `[systemMessages, summaryMessage, tailMessages]` to the model.\n\nIf summary generation fails, the original sanitized messages are used.\n\nThe injected summary system message looks like:\n\n```text\n<agent_summary>\n...summary text...\n</agent_summary>\n```\n\n## Storage Behavior\n\nSummary state is stored under the conversation metadata key `agent` when memory is enabled and `conversationId` is present. The state includes:\n\n- `summary`\n- `summaryMessageCount`\n- `summaryUpdatedAt`\n\nIf memory is disabled or `conversationId` is missing, summary state is kept in process memory and resets on restart.\n\nIf you manage conversation metadata, reserve the `metadata.agent` key for VoltAgent summarization state.\n\n## When To Use It\n\nUse summarization when conversation history grows past model limits or when token usage needs to be capped. Avoid it when you require full message fidelity for audits or deterministic replay.\n\n## Notes for Framework Integrations\n\n- Pass `conversationId` consistently to keep summaries across requests.\n- If you render messages in a UI, filter summary system messages by the `<agent_summary>` marker.\n- If you hook into `onPrepareMessages`, that hook runs after summarization and receives the summary-injected message list.\n","frontmatter":{"title":"Summarization","slug":"/agents/summarization"},"isInternal":false,"tokens":600,"sizeBytes":2771},{"name":"tools.md","path":"website/docs/agents/tools.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/tools.md","title":"Subagent: tools","category":"subagent-persona","format":"markdown","content":"---\ntitle: Tools\nslug: /agents/tools\n---\n\n# Tools\n\nTools enable agents to interact with external systems, APIs, databases, and perform specific actions beyond generating text. An agent uses its model to decide when to call tools based on their descriptions and the current context.\n\n## Creating a Tool\n\nUse `createTool` to define a tool with type-safe parameters and execution logic.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather for a location\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  execute: async ({ location }) => {\n    // Call weather API\n    const response = await fetch(`https://api.weather.com/current?city=${location}`);\n    const data = await response.json();\n\n    return {\n      location,\n      temperature: data.temp,\n      conditions: data.conditions,\n    };\n  },\n});\n```\n\nEach tool has:\n\n- **name**: Unique identifier\n- **description**: Explains what the tool does (the model uses this to decide when to call it)\n- **parameters**: Input schema defined with Zod\n- **execute**: Function that runs when the tool is called\n- **providerOptions** (optional): Provider-specific options for advanced features\n- **tags** (optional): Optional user-defined tags for organizing or labeling tools.\n\nThe `execute` function's parameter types are automatically inferred from the Zod schema, providing full IntelliSense support.\n\n## Tool Hooks\n\nYou can attach tool-specific hooks to observe or post-process a tool result. Tool hooks run before agent-level hooks, and agent `onToolEnd` can still override the output afterward.\n\n**Hook parameters:**\n\n- `onStart`: `{ tool, args, options }`\n- `onEnd`: `{ tool, args, output, error, options }` (return `{ output }` to override the result)\n\n> Overrides are re-validated if the tool has an `outputSchema`. For streaming tools (AsyncIterable), overrides apply only to the final output.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst summarizeTool = createTool({\n  name: \"summarize_text\",\n  description: \"Summarize text with a hard cap\",\n  parameters: z.object({ text: z.string() }),\n  execute: async ({ text }) => text,\n  hooks: {\n    onStart: ({ tool }) => {\n      console.log(`[tool] ${tool.name} starting`);\n    },\n    onEnd: ({ output }) => {\n      if (typeof output === \"string\") {\n        return { output: output.slice(0, 500) };\n      }\n    },\n  },\n});\n```\n\n## Streaming Tool Results (Preliminary)\n\nIf your tool can provide progress or intermediate status, return an `AsyncIterable` from `execute`.\nEach `yield` is emitted as a preliminary tool result, and the **last yielded value** is treated as the final result.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst weatherOutputSchema = z.discriminatedUnion(\"status\", [\n  z.object({\n    status: z.literal(\"loading\"),\n    text: z.string(),\n    weather: z.undefined().optional(),\n  }),\n  z.object({\n    status: z.literal(\"success\"),\n    text: z.string(),\n    weather: z.object({\n      location: z.string(),\n      temperature: z.number(),\n    }),\n  }),\n]);\n\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a location\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  outputSchema: weatherOutputSchema,\n  async *execute({ location }) {\n    yield {\n      status: \"loading\" as const,\n      text: `Getting weather for ${location}`,\n      weather: undefined,\n    };\n\n    await new Promise((resolve) => setTimeout(resolve, 3000));\n\n    const temperature = 72 + Math.floor(Math.random() * 21) - 10;\n\n    yield {\n      status: \"success\" as const,\n      text: `The weather in ${location} is ${temperature}F`,\n      weather: { location, temperature },\n    };\n  },\n});\n```\n\n### Tool Tags\n\nTags are optional string labels that help organize and categorize tools.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Example: Database tools with tags\nconst queryTool = createTool({\n  name: \"query_database\",\n  description: \"Execute a read-only SQL query\",\n  tags: [\"database\", \"read-only\", \"sql\"],\n  parameters: z.object({\n    query: z.string().describe(\"SQL query to execute\"),\n  }),\n  execute: async ({ query }) => {\n    // Execute query\n    return { results: [] };\n  },\n});\n\nconst updateTool = createTool({\n  name: \"update_record\",\n  description: \"Update database records\",\n  tags: [\"database\", \"write\", \"sql\", \"destructive\"],\n  parameters: z.object({\n    table: z.string().describe(\"Table name\"),\n    id: z.string().describe(\"Record ID\"),\n    data: z.record(z.unknown()).describe(\"Data to update\"),\n  }),\n  execute: async ({ table, id, data }) => {\n    // Update record\n    return { success: true };\n  },\n});\n\n// Example: External API tools\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather data\",\n  tags: [\"weather\", \"external-api\", \"read-only\"],\n  parameters: z.object({\n    location: z.string().describe(\"City name\"),\n  }),\n  execute: async ({ location }) => {\n    // Fetch weather\n    return { temperature: 72, conditions: \"sunny\" };\n  },\n});\n```\n\nTags are included in OpenTelemetry spans and visible in the VoltAgent observability dashboard, making it easy to analyze tool usage patterns and debug agent behavior.\n\nTool tags also flow through the REST tool endpoints (`GET /tools` and `POST /tools/:name/execute` responses), so you can build frontend tooling or access control based on tags.\n\n### Provider-Specific Options\n\nYou can pass provider-specific options to enable advanced features like caching. Currently supported providers include Anthropic, OpenAI, Google, and others.\n\n#### Anthropic Cache Control\n\nAnthropic's prompt caching can reduce costs and latency for repeated tool calls:\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst cityAttractionsTool = createTool({\n  name: \"get_city_attractions\",\n  description: \"Get tourist attractions for a city\",\n  parameters: z.object({\n    city: z.string().describe(\"The city name\"),\n  }),\n  providerOptions: {\n    anthropic: {\n      cacheControl: { type: \"ephemeral\" },\n    },\n  },\n  execute: async ({ city }) => {\n    return await fetchAttractions(city);\n  },\n});\n```\n\nThe `cacheControl` option tells Anthropic to cache the tool definition, improving performance for subsequent calls. Learn more about [Anthropic's cache control](https://ai-sdk.dev/providers/ai-sdk-providers/anthropic#cache-control).\n\n## Using Tools with Agents\n\nAdd tools when creating an agent. The model decides when to use them based on the user's input and tool descriptions.\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Weather Assistant\",\n  instructions: \"An assistant that provides weather information\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n});\n\n// The model calls the tool when appropriate\nconst response = await agent.generateText(\"What's the weather in Paris?\");\nconsole.log(response.text);\n// \"The current weather in Paris is 22°C and sunny.\"\n```\n\n### Using Multiple Tools\n\nAgents can use multiple tools together to answer complex queries.\n\n```ts\nconst calculatorTool = createTool({\n  name: \"calculate\",\n  description: \"Perform mathematical calculations\",\n  parameters: z.object({\n    expression: z.string().describe(\"The mathematical expression to evaluate\"),\n  }),\n  execute: async ({ expression }) => {\n    // Use a safe math parser in production\n    const result = eval(expression);\n    return { result };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Multi-Tool Assistant\",\n  instructions: \"An assistant that can check weather and perform calculations\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool, calculatorTool],\n});\n\nconst response = await agent.generateText(\"What's the weather in Paris? Also, what is 24 * 7?\");\n// The model uses both tools and combines the results\n```\n\n### Dynamic Tool Registration\n\nAdd tools to an agent after creation or provide them per request.\n\n```ts\n// Add tools after agent creation\nagent.addTools([calculatorTool]);\n\n// Provide tools for a specific request only\nconst response = await agent.generateText(\"Calculate 123 * 456\", {\n  tools: [calculatorTool],\n});\n```\n\n## Accessing Operation Context\n\nThe `execute` function receives a `ToolExecuteOptions` object as its second parameter, which extends `Partial<OperationContext>` and provides access to all operation metadata and control mechanisms.\n\n```ts\nconst debugTool = createTool({\n  name: \"log_debug_info\",\n  description: \"Logs debugging information\",\n  parameters: z.object({\n    message: z.string().describe(\"Debug message to log\"),\n  }),\n  execute: async (args, options) => {\n    // Access tool context (tool-specific metadata)\n    const { name, callId, messages } = options?.toolContext || {};\n    console.log(\"Tool name:\", name);\n    console.log(\"Tool call ID:\", callId);\n    console.log(\"Message history length:\", messages?.length);\n\n    // Access operation metadata directly from options\n    console.log(\"Operation ID:\", options?.operationId);\n    console.log(\"User ID:\", options?.userId);\n    console.log(\"Conversation ID:\", options?.conversationId);\n\n    // Access the original input\n    console.log(\"Original input:\", options?.input);\n\n    // Access user-defined context Map\n    const customValue = options?.context?.get(\"customKey\");\n\n    // Use the operation-scoped logger with tool name\n    options?.logger?.info(`${name}: Logging ${args.message}`);\n\n    // Check if operation is still active\n    if (!options?.isActive) {\n      throw new Error(\"Operation has been cancelled\");\n    }\n\n    return `Logged: ${args.message}`;\n  },\n});\n```\n\nThe `options` parameter includes all `OperationContext` fields plus an **optional** `toolContext` object:\n\n**Tool execution context (`toolContext?` - optional):**\n\n> **Note:** `toolContext` is always populated when your tool is called from a VoltAgent agent. It may be `undefined` when called from external systems (e.g., MCP servers). Always use optional chaining: `options?.toolContext?.name`.\n\n- `toolContext.name`: Name of the tool being executed\n- `toolContext.callId`: Unique identifier for this specific tool call (from AI SDK)\n- `toolContext.messages`: Message history at the time of tool call (from AI SDK)\n- `toolContext.abortSignal`: Abort signal for detecting cancellation (from AI SDK)\n\n**From OperationContext:**\n\n- `operationId`: Unique identifier for this operation\n- `userId`: Optional user identifier\n- `conversationId`: Optional conversation identifier\n- `workspace`: Workspace instance configured on the agent (if available)\n- `context`: Map for user-provided context values\n- `systemContext`: Map for internal system values\n- `isActive`: Whether the operation is still active\n- `input`: The original input (string, UIMessage[], or BaseMessage[])\n- `abortController`: AbortController for cancelling the operation\n- `logger`: Execution-scoped logger with full context\n- `traceContext`: OpenTelemetry trace context\n- `elicitation`: Optional function for requesting user input\n\n> - Since `ToolExecuteOptions` extends `Partial<OperationContext>`, you can name the second parameter anything you like (`options`, `context`, `ctx`, etc.)\n\nExample: reading workspace content inside a custom tool call:\n\n```ts\nconst readWorkspaceData = createTool({\n  name: \"read_workspace_data\",\n  description: \"Read a workspace file and inspect sandbox output\",\n  parameters: z.object({ path: z.string() }),\n  execute: async ({ path }, options) => {\n    const workspace = options?.workspace;\n    if (!workspace) {\n      return \"No workspace configured\";\n    }\n\n    const content = await workspace.filesystem.read(path);\n    const sandboxResult = await workspace.sandbox?.execute({\n      command: \"cat\",\n      args: [path],\n      operationContext: options as any,\n    });\n\n    return `filesystem=${content}\\nsandbox=${sandboxResult?.stdout ?? \"\"}`;\n  },\n});\n```\n\n## Cancellation with AbortController\n\nTools can respond to cancellation signals and cancel the entire operation when needed.\n\n```ts\nconst searchTool = createTool({\n  name: \"search_web\",\n  description: \"Search the web for information\",\n  parameters: z.object({\n    query: z.string().describe(\"The search query\"),\n  }),\n  execute: async (args, options) => {\n    const abortController = options?.abortController;\n    const signal = abortController?.signal;\n\n    // Check if already aborted\n    if (signal?.aborted) {\n      throw new Error(\"Search was cancelled before it started\");\n    }\n\n    // Tool can trigger abort to cancel the entire operation\n    if (args.query.includes(\"forbidden\")) {\n      abortController?.abort(\"Forbidden query detected\");\n      throw new Error(\"Search query contains forbidden terms\");\n    }\n\n    try {\n      // Pass signal to fetch for cancellation support\n      const response = await fetch(`https://api.search.com?q=${args.query}`, { signal });\n\n      // Abort based on response\n      if (!response.ok && response.status === 429) {\n        abortController?.abort(\"Rate limit exceeded\");\n        throw new Error(\"API rate limit exceeded\");\n      }\n\n      return await response.json();\n    } catch (error) {\n      if (error.name === \"AbortError\") {\n        throw new Error(\"Search was cancelled during execution\");\n      }\n      throw error;\n    }\n  },\n});\n```\n\nWhen calling an agent with an abort signal:\n\n```ts\nimport { isAbortError } from \"@voltagent/core\";\n\nconst abortController = new AbortController();\n\n// Set timeout to abort after 30 seconds\nsetTimeout(() => abortController.abort(\"Operation timeout\"), 30000);\n\ntry {\n  const response = await agent.generateText(\"Search for the latest AI developments\", {\n    abortSignal: abortController.signal,\n  });\n  console.log(response.text);\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Operation was cancelled:\", error.message);\n  } else {\n    console.error(\"Error:\", error);\n  }\n}\n```\n\n## Tool Execution Approval (needsApproval)\n\nSome tools should not run automatically because they trigger sensitive actions (payments, file changes, command execution, etc). Set `needsApproval` to require explicit user approval before the tool executes.\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst runCommandTool = createTool({\n  name: \"runCommand\",\n  description: \"Run a shell command\",\n  parameters: z.object({\n    command: z.string().describe(\"Command to execute\"),\n  }),\n  needsApproval: true,\n  execute: async ({ command }) => {\n    // Execute safely\n    return { ok: true, command };\n  },\n});\n```\n\n`needsApproval` can also be a function for dynamic policies:\n\n```ts\nconst processPaymentTool = createTool({\n  name: \"processPayment\",\n  description: \"Charge a customer\",\n  parameters: z.object({\n    amount: z.number(),\n    customerId: z.string(),\n  }),\n  needsApproval: async ({ amount }) => amount > 1000,\n  execute: async ({ amount, customerId }) => {\n    return { success: true, amount, customerId };\n  },\n});\n```\n\n### How the Approval Flow Works\n\n1. The model calls a tool with `needsApproval`.\n2. VoltAgent returns a tool part with `state: \"approval-requested\"` instead of executing.\n3. Your UI asks the user to approve or deny.\n4. Send a tool approval response back to VoltAgent.\n5. If approved, the tool executes in the next step. If denied, the model sees the denial and can respond accordingly.\n\n### UI Implementation (useChat)\n\nVoltAgent's `/agents/:id/chat` stream is compatible with AI SDK `useChat`, so you can render the approval request and send a response from the UI.\n\n```tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport, lastAssistantMessageIsCompleteWithApprovalResponses } from \"ai\";\n\nfunction Chat() {\n  const { messages, addToolApprovalResponse } = useChat({\n    transport: new DefaultChatTransport({ api: \"/agents/weather/chat\" }),\n    sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,\n  });\n\n  return (\n    <div>\n      {messages.map((message) => (\n        <div key={message.id}>\n          {message.parts.map((part) => {\n            if (part.type === \"tool-runCommand\") {\n              if (part.state === \"approval-requested\") {\n                return (\n                  <div key={part.toolCallId}>\n                    <p>Approve command: {part.input.command}?</p>\n                    <button\n                      onClick={() =>\n                        addToolApprovalResponse({\n                          id: part.approval.id,\n                          approved: true,\n                        })\n                      }\n                    >\n                      Approve\n                    </button>\n                    <button\n                      onClick={() =>\n                        addToolApprovalResponse({\n                          id: part.approval.id,\n                          approved: false,\n                          reason: \"User denied\",\n                        })\n                      }\n                    >\n                      Deny\n                    </button>\n                  </div>\n                );\n              }\n            }\n            return null;\n          })}\n        </div>\n      ))}\n    </div>\n  );\n}\n```\n\nIf you do not use `sendAutomaticallyWhen`, call `sendMessage` manually after approval to continue the flow.\n\n## Client-Side Tools\n\nClient-side tools execute in the browser or client application instead of on the server. They're useful for accessing browser APIs, user permissions, or client-specific features.\n\nFor a complete working setup, see the example: [with-client-side-tools](https://github.com/VoltAgent/voltagent/tree/main/examples/with-client-side-tools).\n\n### What Makes a Tool Client-Side?\n\nA tool without an `execute` function is automatically client-side.\n\n```ts\n// Client-side tool (no execute function)\nconst getLocationTool = createTool({\n  name: \"getLocation\",\n  description: \"Get the user's current location\",\n  parameters: z.object({}),\n  // No execute = client-side\n});\n\n// Another client-side tool\nconst readClipboardTool = createTool({\n  name: \"readClipboard\",\n  description: \"Read content from the user's clipboard\",\n  parameters: z.object({}),\n});\n\n// Server-side tool (has execute)\nconst getWeatherTool = createTool({\n  name: \"getWeather\",\n  description: \"Get current weather for a city\",\n  parameters: z.object({\n    city: z.string().describe(\"City name\"),\n  }),\n  execute: async ({ city }) => {\n    const response = await fetch(`https://api.weather.com/current?city=${city}`);\n    return await response.json();\n  },\n});\n```\n\n### Handling Client-Side Tools\n\nUse the `onToolCall` callback with `useChat` to handle client-side tool execution.\n\n```tsx\nimport { useChat } from \"@ai-sdk/react\";\nimport type { ClientSideToolResult } from \"@voltagent/core\";\nimport { useState, useEffect, useCallback } from \"react\";\n\nfunction Chat() {\n  const [result, setResult] = useState<ClientSideToolResult | null>(null);\n\n  const handleToolCall = useCallback(async ({ toolCall }) => {\n    // Handle automatic execution for getLocation\n    if (toolCall.toolName === \"getLocation\") {\n      navigator.geolocation.getCurrentPosition(\n        (position) => {\n          setResult({\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            output: {\n              latitude: position.coords.latitude,\n              longitude: position.coords.longitude,\n              accuracy: position.coords.accuracy,\n            },\n          });\n        },\n        (error) => {\n          setResult({\n            state: \"output-error\",\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            errorText: error.message,\n          });\n        }\n      );\n    }\n  }, []);\n\n  const { messages, sendMessage, addToolResult, status } = useChat({\n    transport: new DefaultChatTransport({ api: \"/api/chat\" }),\n    onToolCall: handleToolCall,\n  });\n\n  // Send results back to the model\n  useEffect(() => {\n    if (!result) return;\n    addToolResult(result);\n  }, [result, addToolResult]);\n\n  return (\n    <div>\n      {messages.map((message) => (\n        <div key={message.id}>{/* Render message */}</div>\n      ))}\n    </div>\n  );\n}\n```\n\n### Interactive Client-Side Tools\n\nFor tools requiring user interaction, render UI components:\n\n```tsx\nfunction ReadClipboardTool({\n  callId,\n  state,\n  addToolResult,\n}: {\n  callId: string;\n  state?: string;\n  addToolResult: (res: ClientSideToolResult) => void;\n}) {\n  if (state === \"input-streaming\" || state === \"input-available\") {\n    return (\n      <div>\n        <p>Allow access to your clipboard?</p>\n        <button\n          onClick={async () => {\n            try {\n              const text = await navigator.clipboard.readText();\n              addToolResult({\n                tool: \"readClipboard\",\n                toolCallId: callId,\n                output: { content: text },\n              });\n            } catch {\n              addToolResult({\n                state: \"output-error\",\n                tool: \"readClipboard\",\n                toolCallId: callId,\n                errorText: \"Clipboard access denied\",\n              });\n            }\n          }}\n        >\n          Yes\n        </button>\n        <button\n          onClick={() =>\n            addToolResult({\n              state: \"output-error\",\n              tool: \"readClipboard\",\n              toolCallId: callId,\n              errorText: \"Access denied\",\n            })\n          }\n        >\n          No\n        </button>\n      </div>\n    );\n  }\n  return <div>readClipboard: {state}</div>;\n}\n```\n\n**Important**: You must call `addToolResult` to send the tool result back to the model. Without this, the model considers the tool call a failure.\n\n## Agent Tool Hooks\n\nHooks let you respond to tool execution events for logging, UI updates, or additional actions. Use `onToolError` when you need to customize the error payload before it is returned to the model. For tool-level hooks (per tool), see the **Tool Hooks** section above.\n\n```ts\nimport { Agent, createHooks, isAbortError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ agent, tool, context, args }) {\n    console.log(`Tool starting: ${tool.name}`);\n    console.log(`Agent: ${agent.name}`);\n    console.log(`Operation ID: ${context.operationId}`);\n    console.log(`Arguments:`, args);\n  },\n\n  onToolEnd({ agent, tool, output, error, context }) {\n    console.log(`Tool completed: ${tool.name}`);\n\n    if (error) {\n      if (isAbortError(error)) {\n        console.log(`Tool was aborted: ${error.message}`);\n      } else {\n        console.error(`Tool failed: ${error.message}`);\n      }\n    } else {\n      console.log(`Result:`, output);\n    }\n  },\n\n  onToolError({ tool, originalError }) {\n    const maybeAxios = (originalError as any).isAxiosError === true;\n    if (!maybeAxios) {\n      return;\n    }\n\n    return {\n      output: {\n        error: true,\n        message: originalError.message,\n        code: (originalError as any).code,\n        status: (originalError as any).response?.status,\n      },\n    };\n  },\n});\n\nconst agent = new Agent({\n  name: \"Assistant with Tool Hooks\",\n  instructions: \"An assistant that logs tool execution\",\n  model: \"openai/gpt-4o\",\n  tools: [weatherTool],\n  hooks: hooks,\n});\n```\n\n### Using Tags with Hooks\n\nTool tags can be accessed in hooks for implementing custom logic like access control or conditional behavior:\n\n```ts\nimport { createHooks, ToolDeniedError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ tool, context }) {\n    // Check if tool has destructive tag and user has permission\n    if (tool.tags?.includes(\"destructive\")) {\n      const userRole = context.context.get(\"userRole\");\n      if (userRole !== \"admin\") {\n        throw new ToolDeniedError({\n          toolName: tool.name,\n          message: \"Admin permission required for destructive operations\",\n          code: \"TOOL_FORBIDDEN\",\n        });\n      }\n    }\n\n    // Log tools by category\n    if (tool.tags?.includes(\"external-api\")) {\n      console.log(`Calling external API: ${tool.name}`);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Controlled Assistant\",\n  instructions: \"An assistant with tag-based access control\",\n  model: \"openai/gpt-4o\",\n  tools: [queryTool, updateTool, weatherTool],\n  hooks: hooks,\n});\n\n// Usage with context\nconst response = await agent.generateText(\"Update the user record\", {\n  context: { userRole: \"admin\" }, // Admin can use destructive tools\n});\n```\n\n### Policy Enforcement with ToolDeniedError\n\nThrow `ToolDeniedError` from a hook to block a tool and immediately stop the entire agent operation.\n\n```ts\nimport { createHooks, ToolDeniedError } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onToolStart({ tool, context }) {\n    const plan = context.context.get(\"userPlan\");\n\n    // Block expensive tools for non-pro users\n    if (tool.name === \"search_web\" && plan !== \"pro\") {\n      throw new ToolDeniedError({\n        toolName: tool.name,\n        message: \"Pro plan required to use web search.\",\n        code: \"TOOL_PLAN_REQUIRED\",\n        httpStatus: 402,\n      });\n    }\n  },\n});\n```\n\nCatching the denial:\n\n```ts\nimport { isToolDeniedError } from \"@voltagent/core\";\n\ntry {\n  const res = await agent.generateText(\"Please search the web\", { hooks });\n} catch (err) {\n  if (isToolDeniedError(err)) {\n    console.log(\"Tool denied:\", {\n      tool: err.name,\n      status: err.httpStatus,\n      code: err.code,\n      message: err.message,\n    });\n    // Show upgrade UI, redirect, etc.\n  } else {\n    console.error(\"Operation failed:\", err);\n  }\n}\n```\n\nAllowed `code` values:\n\n- `TOOL_ERROR`\n- `TOOL_FORBIDDEN`\n- `TOOL_PLAN_REQUIRED`\n- `TOOL_QUOTA_EXCEEDED`\n- Custom codes (e.g., `\"TOOL_REGION_BLOCKED\"`)\n\n### Multi-modal Tool Results\n\nTools can return images and media content to the LLM using the `toModelOutput` function. This enables visual workflows where tools can provide screenshots, generated images, or other media for the LLM to analyze.\n\n**Supported Providers:** Anthropic, OpenAI\n\n#### Screenshot Tool Example\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\nimport fs from \"fs\";\n\nconst screenshotTool = createTool({\n  name: \"take_screenshot\",\n  description: \"Takes a screenshot of the screen\",\n  parameters: z.object({\n    region: z.string().optional().describe(\"Region to capture\"),\n  }),\n  execute: async ({ region }) => {\n    // Take screenshot and return base64\n    const imageData = fs.readFileSync(\"./screenshot.png\").toString(\"base64\");\n\n    return {\n      type: \"image\",\n      data: imageData,\n      timestamp: new Date().toISOString(),\n    };\n  },\n  // Convert output to multi-modal content for LLM\n  toModelOutput: ({ output }) => ({\n    type: \"content\",\n    value: [\n      {\n        type: \"text\",\n        text: `Screenshot captured at ${output.timestamp}`,\n      },\n      {\n        type: \"media\",\n        data: output.data,\n        mediaType: \"image/png\",\n      },\n    ],\n  }),\n});\n```\n\n#### Return Formats\n\nThe `toModelOutput` function can return different content types:\n\n**Text only:**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"text\",\n  value: \"Operation completed successfully\",\n});\n```\n\n**JSON data:**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"json\",\n  value: { status: \"success\", data: output },\n});\n```\n\n**Multi-modal (text + image):**\n\n```ts\ntoModelOutput: ({ output }) => ({\n  type: \"content\",\n  value: [\n    { type: \"text\", text: \"Here is the image:\" },\n    { type: \"media\", data: output.base64, mediaType: \"image/png\" },\n  ],\n});\n```\n\n**Error handling:**\n\n```ts\ntoModelOutput: ({ output }) => {\n  if (output.error) {\n    return {\n      type: \"error-text\",\n      value: `Failed: ${output.error}`,\n    };\n  }\n  return {\n    type: \"text\",\n    value: output.result,\n  };\n};\n```\n\nLearn more: [AI SDK Multi-modal Tool Results](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling#multi-modal-tool-results)\n\n## Best Practices\n\n### Clear Descriptions\n\nProvide clear descriptions for tools and parameters. The model relies on these to understand when and how to use tools.\n\n**Bad:**\n\n```ts\nconst badTool = createTool({\n  name: \"search\",\n  description: \"Searches things\", // Too vague\n  parameters: z.object({\n    q: z.string(), // No description\n  }),\n  execute: async (args) => {\n    /* ... */\n  },\n});\n```\n\n**Good:**\n\n```ts\nconst goodTool = createTool({\n  name: \"search_web\",\n  description:\n    \"Searches the web for current information. Use when you need recent or factual information not in your training data.\",\n  parameters: z.object({\n    query: z.string().describe(\"The search query. Be specific about what information is needed.\"),\n    results_count: z\n      .number()\n      .min(1)\n      .max(10)\n      .optional()\n      .describe(\"Number of results to return. Defaults to 3.\"),\n  }),\n  execute: async (args) => {\n    /* ... */\n  },\n});\n```\n\n### Error Handling\n\nImplement error handling that provides useful feedback to the model.\n\n```ts\nexecute: async (args) => {\n  try {\n    const result = await performOperation(args);\n    return result;\n  } catch (error) {\n    throw new Error(`Failed to process request: ${error.message}`);\n  }\n};\n```\n\n### Timeout Handling\n\nFor long-running operations, implement timeouts using `AbortController`.\n\n```ts\nexecute: async (args, options) => {\n  const timeoutController = new AbortController();\n  const timeoutId = setTimeout(() => {\n    timeoutController.abort(\"Operation timed out\");\n  }, 5000);\n\n  try {\n    // Listen to parent abort if provided\n    const parentController = options?.abortController;\n    if (parentController?.signal) {\n      parentController.signal.addEventListener(\"abort\", () => {\n        timeoutController.abort(\"Parent operation aborted\");\n        clearTimeout(timeoutId);\n      });\n    }\n\n    const result = await fetch(url, {\n      signal: timeoutController.signal,\n    });\n    clearTimeout(timeoutId);\n    return result;\n  } catch (error) {\n    clearTimeout(timeoutId);\n    throw error;\n  }\n};\n```\n\n## MCP (Model Context Protocol) Support\n\nVoltAgent supports the [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol/mcp), allowing agents to connect with external MCP-compatible servers and tools.\n\n### Using MCP Tools\n\nConnect to MCP servers and use their tools with your agents:\n\n```ts\nimport { Agent, MCPConfiguration } from \"@voltagent/core\";\n\n// Configure MCP servers\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    // HTTP server\n    browserTools: {\n      type: \"http\",\n      url: \"https://your-mcp-server.example.com/browser\",\n    },\n    // Local stdio server\n    localAI: {\n      type: \"stdio\",\n      command: \"python\",\n      args: [\"local_ai_server.py\"],\n    },\n  },\n});\n\n// Get tools grouped by server\nconst toolsets = await mcpConfig.getToolsets();\nconst browserToolsOnly = toolsets.browserTools.getTools();\n\n// Or get all tools combined\nconst allMcpTools = await mcpConfig.getTools();\n\n// Create agent with MCP tools\nconst agent = new Agent({\n  name: \"MCP-Enhanced Assistant\",\n  description: \"Assistant with MCP tools\",\n  model: \"openai/gpt-4o\",\n  tools: allMcpTools,\n});\n```\n\nFor detailed MCP setup and usage, see the [MCP documentation](./mcp/mcp.md).\n","frontmatter":{"title":"Tools","slug":"/agents/tools"},"isInternal":false,"tokens":7148,"sizeBytes":31233},{"name":"voice.md","path":"website/docs/agents/voice.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/voice.md","title":"Subagent: voice","category":"subagent-persona","format":"markdown","content":"---\ntitle: Voice\nslug: /agents/voice\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Voice\n\nThe `@voltagent/voice` package provides text-to-speech (TTS) and speech-to-text (STT) capabilities through various providers. It allows your applications to speak text and transcribe audio with minimal setup.\n\n## Installation\n\nInstall the package using your preferred package manager:\n\n<Tabs>\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/voice\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/voice\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/voice\n```\n\n  </TabItem>\n</Tabs>\n\n## Supported Providers\n\n- **OpenAI**: High-quality voices and transcription.\n- **ElevenLabs**: Realistic, customizable voices.\n- **xsAI**: Lightweight OpenAI-compatible voice API.\n\n## Basic Usage\n\nFirst, initialize a voice provider instance.\n\n### Initialize a Voice Provider\n\n```typescript\n// Initialize with OpenAI\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\nconst openAIVoice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY, // Ensure API key is set in environment variables\n  ttsModel: \"tts-1\",\n  voice: \"alloy\", // Available voices: alloy, echo, fable, onyx, nova, shimmer\n});\n\n// Or initialize with ElevenLabs\nimport { ElevenLabsVoiceProvider } from \"@voltagent/voice\";\n\nconst elevenLabsVoice = new ElevenLabsVoiceProvider({\n  apiKey: process.env.ELEVENLABS_API_KEY, // Ensure API key is set\n  ttsModel: \"eleven_multilingual_v2\",\n  voice: \"Rachel\", // Example voice ID\n});\n\n// Or initialize with xsAI\nimport { XsAIVoiceProvider } from \"@voltagent/voice\";\n\nconst xsAIVoice = new XsAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY!,\n  ttsModel: \"tts-1\",\n  voice: \"alloy\",\n  // If you are not using OpenAI, simply specify the `baseURL`\n});\n```\n\n**Note:** It's recommended to manage API keys securely, for example, using environment variables.\n\n### Text-to-Speech (TTS)\n\nConvert text into an audio stream. You can then process this stream, for example, by saving it to a file.\n\n```typescript\nimport { createWriteStream } from \"node:fs\";\nimport { PassThrough } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\"; // Use pipeline for better error handling\n\n// --- Example 1: Basic Speak and Save to File ---\n\nconsole.log(\"Generating audio...\");\n// Get the audio stream for the text\nconst audioStream = await openAIVoice.speak(\"Hello from VoltAgent!\");\n\nconsole.log(\"Saving audio to output.mp3...\");\n// Create a file stream to write the audio\nconst fileStream = createWriteStream(\"output.mp3\");\n\ntry {\n  // Pipe the audio stream to the file stream and wait for completion\n  await pipeline(audioStream, fileStream);\n  console.log(\"Audio successfully saved to output.mp3\");\n} catch (error) {\n  console.error(\"Failed to save audio:\", error);\n}\n\n// --- Example 2: Speak with Options and Save ---\n\nconsole.log(\"Generating custom audio...\");\nconst customAudioStream = await elevenLabsVoice.speak(\"Speaking faster now.\", {\n  // Provider-specific options can be passed here.\n  // For OpenAI, you might use:\n  // voice: \"nova\", // Override the default voice\n  // speed: 1.5,    // Adjust speaking speed (1.0 is default)\n});\n\nconsole.log(\"Saving custom audio to custom_output.mp3...\");\nconst customFileStream = createWriteStream(\"custom_output.mp3\");\n\ntry {\n  // Pipe the custom audio stream to the file stream\n  await pipeline(customAudioStream, customFileStream);\n  console.log(\"Custom audio successfully saved to custom_output.mp3\");\n} catch (error) {\n  console.error(\"Failed to save custom audio:\", error);\n}\n\n// Note: The audioStream is a standard Node.js Readable stream.\n// You can pipe it to other destinations or process it directly.\n```\n\n### Speech-to-Text (STT)\n\nTranscribe audio from a stream into text.\n\n```typescript\nimport { createReadStream } from \"node:fs\";\n\n// Ensure you have an audio file (e.g., input.mp3)\nconst audioFileStream = createReadStream(\"input.mp3\");\n\n// Basic transcription with OpenAI\nconst transcribedText = await openAIVoice.listen(audioFileStream);\nconsole.log(\"Transcription:\", transcribedText);\n\n// Transcription with options (e.g., language hint)\nconst transcribedSpanish = await openAIVoice.listen(audioFileStream, {\n  language: \"es\", // Specify the language code\n});\nconsole.log(\"Spanish Transcription:\", transcribedSpanish);\n\n// Streaming transcription (Provider-dependent, e.g., OpenAI)\ntry {\n  const streamingTranscription = await openAIVoice.listen(audioFileStream, {\n    stream: true, // Enable streaming\n  });\n\n  for await (const chunk of streamingTranscription) {\n    // Process transcription chunks as they arrive\n    // The structure of 'chunk' depends on the provider\n    console.log(\"Stream Chunk:\", chunk);\n  }\n} catch (error) {\n  console.error(\"Streaming transcription failed:\", error);\n}\n```\n\n### List Available Voices\n\nFetch the list of voices supported by the provider.\n\n```typescript\n// Using the initialized openAIVoice instance\nconst availableVoices = await openAIVoice.getVoices();\nconsole.log(\"Available OpenAI Voices:\", availableVoices);\n/* Example Output:\n[\n  { id: 'alloy', name: 'Alloy', language: 'en', gender: 'neutral' },\n  { id: 'echo', name: 'Echo', language: 'en', gender: 'male' },\n  // ... other voices\n]\n*/\n\nconst elevenLabsVoices = await elevenLabsVoice.getVoices();\nconsole.log(\"Available ElevenLabs Voices:\", elevenLabsVoices); // Structure might differ\n```\n\n## Integrating with Agent\n\nYou can integrate the voice provider directly into an `Agent` instance.\n\n```typescript\nimport { Agent, OpenAIAgentProvider } from \"@voltagent/core\";\nimport { OpenAIVoiceProvider } from \"@voltagent/voice\";\n\n// Initialize the voice provider\nconst voice = new OpenAIVoiceProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n  ttsModel: \"tts-1\",\n  voice: \"nova\",\n});\n\n// Initialize the agent provider\nconst provider = new OpenAIAgentProvider({\n  apiKey: process.env.OPENAI_API_KEY,\n});\n\n// Create the agent, passing the voice instance\nconst voiceEnabledAgent = new Agent({\n  name: \"VoiceBot\",\n  instructions: \"An agent that can speak.\",\n  provider,\n  model: \"openai/gpt-4o\", // Choose an appropriate model\n  voice: voice, // Assign the voice provider here\n});\n\n// Now the agent instance potentially uses the voice capabilities\n// (Specific usage within the agent depends on future VoltAgent features or custom implementations)\n\n// Example: Manually trigger speech from agent's response\n// const response = await voiceEnabledAgent.generateText(\"Tell me a short story.\");\n// if (voiceEnabledAgent.voice) {\n//   const audio = await voiceEnabledAgent.voice.speak(response.text);\n//   // Pipe audio to output\n//   audio.pipe(createWriteStream(\"story.mp3\"));\n// }\n```\n\n**Note:** The direct integration of `voice` within the `Agent`'s core methods (`generateText`, `streamText`, etc.) for automatic speaking/listening is under development. Currently, you can access the `agent.voice` property to manually trigger TTS or STT.\n\n## Event Handling\n\nListen for events emitted by the voice provider instance.\n\n```typescript\n// Using the initialized openAIVoice instance\n\n// Listen for the start of speech synthesis\nopenAIVoice.on(\"speaking\", (data) => {\n  console.log(`[Speaking Started] Text: \"${data.text}\"`);\n});\n\n// Listen for the start of transcription\nopenAIVoice.on(\"listening\", (data) => {\n  // data might contain information about the audio stream\n  console.log(\"[Listening Started]\");\n});\n\n// Handle errors\nopenAIVoice.on(\"error\", (errorData) => {\n  console.error(\n    `[Voice Error] Code: ${errorData.code}, Message: ${errorData.message}`,\n    errorData.details\n  );\n});\n\n// Trigger an action that emits events\ntry {\n  await openAIVoice.speak(\"Testing event listeners.\");\n  // const audioInput = createReadStream(\"input.mp3\");\n  // await openAIVoice.listen(audioInput);\n} catch (error) {\n  // Errors caught here might also be emitted via the 'error' event\n  console.error(\"Direct action failed:\", error);\n}\n```\n\n## Creating a Custom Voice Provider\n\nExtend the `BaseVoiceProvider` to integrate other TTS/STT services.\n\n```typescript\nimport {\n  BaseVoiceProvider,\n  VoiceProviderEvents,\n  VoiceMetadata,\n  VoiceSpeakOptions,\n  VoiceListenOptions,\n} from \"@voltagent/voice\";\nimport { PassThrough, Readable } from \"node:stream\"; // Import Readable\n\n// Define your custom provider options\ntype MyCustomProviderOptions = {\n  apiKey: string;\n  region?: string;\n  // Add other necessary options\n};\n\n// Define potential errors specific to your provider\nenum MyCustomErrorCode {\n  AuthError = \"AUTH_ERROR\",\n  ApiError = \"API_ERROR\",\n  InvalidInput = \"INVALID_INPUT\",\n}\n\nexport class MyCustomVoiceProvider extends BaseVoiceProvider<\n  MyCustomProviderOptions,\n  VoiceProviderEvents\n> {\n  private readonly apiKey: string;\n  private readonly region: string;\n  // Add client instances or other state needed\n\n  constructor(options: MyCustomProviderOptions) {\n    super(options); // Pass options to the base class\n    this.apiKey = options.apiKey;\n    this.region = options.region || \"default-region\";\n    // Initialize your API client here\n  }\n\n  /**\n   * Connect to the service if needed (e.g., WebSockets).\n   */\n  async connect(): Promise<void> {\n    console.log(\"Connecting to Custom Voice Service...\");\n    // Add connection logic if required\n    // Simulating async operation\n    await new Promise((resolve) => setTimeout(resolve, 100));\n    console.log(\"Connected.\");\n  }\n\n  /**\n   * Disconnect from the service.\n   */\n  disconnect(): void {\n    console.log(\"Disconnecting from Custom Voice Service...\");\n    // Add disconnection logic\n  }\n\n  /**\n   * Send audio data (e.g., for streaming STT).\n   */\n  async send(audioData: Readable): Promise<void> {\n    console.log(\"Sending audio data chunk...\");\n    // Implementation for sending streaming audio\n    // Read from audioData stream and send to your API\n    // Example: audioData.pipe(yourApiStream);\n    await new Promise((resolve) => setTimeout(resolve, 50)); // Simulate async send\n  }\n\n  /**\n   * Convert text to speech.\n   */\n  async speak(\n    text: string, // Assuming text is always string for simplicity here\n    options?: VoiceSpeakOptions // Use standardized options type\n  ): Promise<Readable> {\n    // Return standard Readable stream\n    this.emit(\"speaking\", { text }); // Emit standard event\n\n    try {\n      console.log(`Speaking text: \"${text}\" with voice: ${options?.voice}`);\n      // --- Your TTS API Call Logic ---\n      // const apiResponse = await yourTTSClient.synthesize({\n      //   text: text,\n      //   voice: options?.voice || 'default-voice',\n      //   apiKey: this.apiKey,\n      //   // map other options like speed if applicable\n      // });\n      // -----------------------------\n\n      // Simulate receiving audio data from API\n      await new Promise((resolve) => setTimeout(resolve, 500));\n      const audioBuffer = Buffer.from(`Simulated audio for: ${text}`);\n\n      // Return audio data as a Readable stream\n      const passThrough = new PassThrough();\n      passThrough.end(audioBuffer);\n      return passThrough;\n    } catch (error: any) {\n      const errorMessage = error.message || \"Unknown TTS error\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError, // Use your specific error codes\n        details: error,\n      });\n      throw new Error(`Custom TTS failed: ${errorMessage}`); // Re-throw a standard error\n    }\n  }\n\n  /**\n   * Convert speech to text.\n   */\n  async listen(\n    audio: Readable, // Expect a Readable stream\n    options?: VoiceListenOptions // Use standardized options type\n  ): Promise<string | AsyncIterable<any>> {\n    // Return string or stream based on options\n    this.emit(\"listening\", { audio }); // Emit standard event\n\n    try {\n      console.log(`Listening with language: ${options?.language}, stream: ${options?.stream}`);\n\n      // --- Your STT API Call Logic ---\n      // Handle both standard and streaming modes\n      if (options?.stream) {\n        // Implement streaming transcription logic\n        // Example: const transcriptStream = yourSTTClient.startStream(audio, { language: options.language });\n        // return transcriptStream; // Return the async iterable from your client\n\n        // --- Placeholder for streaming ---\n        async function* generateChunks() {\n          yield { delta: \"Simulated \" };\n          await new Promise((resolve) => setTimeout(resolve, 200));\n          yield { delta: \"streamed \" };\n          await new Promise((resolve) => setTimeout(resolve, 200));\n          yield { delta: \"transcript.\" };\n        }\n        return generateChunks();\n        // --- End Placeholder ---\n      } else {\n        // Implement non-streaming transcription logic\n        // Example: const result = await yourSTTClient.transcribe(audio, { language: options?.language });\n        // return result.text;\n\n        // --- Placeholder for non-streaming ---\n        // Simulate reading the stream and getting a result\n        let fullAudio = Buffer.alloc(0);\n        for await (const chunk of audio) {\n          fullAudio = Buffer.concat([fullAudio, chunk]);\n        }\n        await new Promise((resolve) => setTimeout(resolve, 500)); // Simulate API call\n        return \"Simulated full transcript of audio.\";\n        // --- End Placeholder ---\n      }\n      // -----------------------------\n    } catch (error: any) {\n      const errorMessage = error.message || \"Unknown STT error\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError, // Use your specific error codes\n        details: error,\n      });\n      throw new Error(`Custom STT failed: ${errorMessage}`); // Re-throw a standard error\n    }\n  }\n\n  /**\n   * Fetch available voices from your service.\n   */\n  async getVoices(): Promise<VoiceMetadata[]> {\n    try {\n      // --- Your API Call Logic to Get Voices ---\n      // const apiVoices = await yourClient.listVoices({ apiKey: this.apiKey });\n      // return apiVoices.map(v => ({ id: v.id, name: v.name, language: v.lang, gender: v.gender }));\n      // ---------------------------------------\n\n      // --- Placeholder ---\n      await new Promise((resolve) => setTimeout(resolve, 100)); // Simulate API call\n      return [\n        { id: \"custom-voice-1\", name: \"Custom Voice One\", language: \"en\", gender: \"female\" },\n        { id: \"custom-voice-2\", name: \"Custom Voice Two\", language: \"es\", gender: \"male\" },\n      ];\n      // --- End Placeholder ---\n    } catch (error: any) {\n      const errorMessage = error.message || \"Failed to fetch custom voices\";\n      this.emit(\"error\", {\n        message: errorMessage,\n        code: MyCustomErrorCode.ApiError,\n        details: error,\n      });\n      // Return empty array or throw, depending on desired behavior\n      return [];\n    }\n  }\n}\n\n// Example usage of the custom provider\n// const myVoice = new MyCustomVoiceProvider({ apiKey: \"YOUR_CUSTOM_API_KEY\" });\n// const audio = await myVoice.speak(\"Hello from my custom provider!\");\n// audio.pipe(createWriteStream(\"custom_output.mp3\"));\n```\n\nThis updated custom provider example uses standardized types like `VoiceSpeakOptions`, `VoiceListenOptions`, `VoiceMetadata`, and `Readable` stream types for better consistency and type safety. It also includes more detailed placeholders for API logic and error handling.\n","frontmatter":{"title":"Voice","slug":"/agents/voice"},"isInternal":false,"tokens":3571,"sizeBytes":15354},{"name":"voltagent-instance.md","path":"website/docs/agents/voltagent-instance.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/website/docs/agents/voltagent-instance.md","title":"Subagent: voltagent-instance","category":"subagent-persona","format":"markdown","content":"---\ntitle: VoltAgent Instance\nsidebar_label: VoltAgent Instance\n---\n\n# VoltAgent Instance\n\n`VoltAgent` is the application entrypoint. It wires agents and workflows together, applies global defaults, and optionally starts an HTTP or serverless provider.\n\n## Basic Setup\n\n```ts\nimport { Agent, VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst assistant = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users get answers fast.\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nnew VoltAgent({\n  agents: { assistant },\n  server: honoServer({ port: 3141 }),\n});\n```\n\n## What VoltAgent Manages\n\n- Registers agents and workflows for API access and observability.\n- Applies global defaults (logger, observability, memory, conversation persistence).\n- Boots HTTP or serverless providers.\n- Registers triggers, MCP servers, and A2A servers.\n- Coordinates graceful shutdown for servers and telemetry.\n\n## Registering Agents and Workflows\n\nYou can register instances on startup or later at runtime.\n\n```ts\nimport { VoltAgent } from \"@voltagent/core\";\n\nconst app = new VoltAgent({\n  agents: { assistant },\n  workflows: { onboardingWorkflow },\n});\n\napp.registerAgent(anotherAgent);\napp.registerWorkflow(anotherWorkflow);\n```\n\nTo register triggers at runtime:\n\n```ts\napp.registerTriggers(triggers);\n```\n\n## Memory Defaults\n\nSet default memory once at the VoltAgent entrypoint. Defaults apply only when an agent or workflow does not specify `memory`. Omitting `memory` does not disable it; it still resolves to configured defaults (or built-in in-memory). An explicit `memory: false` on an agent disables memory entirely.\n\n```ts\nimport { Memory, VoltAgent } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agentMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/agent.db\" }),\n});\n\nconst workflowMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/workflows.db\" }),\n});\n\nnew VoltAgent({\n  agentMemory,\n  workflowMemory,\n  // memory: sharedFallbackMemory,\n});\n```\n\n**Precedence**\n\n- Agents: agent `memory` > `agentMemory` > `memory` > built-in in-memory\n- Workflows: workflow `memory` > `workflowMemory` > `memory` > built-in in-memory\n\nSee [Memory Overview](/docs/agents/memory/overview) for adapter options.\n\n## Conversation Persistence Defaults\n\nSet a default persistence strategy for agents that do not explicitly define `conversationPersistence`.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  agentConversationPersistence: {\n    mode: \"step\", // default\n    debounceMs: 200, // default\n    flushOnToolResult: true, // default\n  },\n});\n```\n\n**Precedence**\n\n- Per-call `options.memory.options.conversationPersistence` (preferred)\n- Per-call `options.conversationPersistence` (deprecated)\n- Agent `conversationPersistence`\n- VoltAgent `agentConversationPersistence`\n- Built-in defaults (`mode: \"step\"`, `debounceMs: 200`, `flushOnToolResult: true`)\n\n## Observability and Logging\n\nVoltAgent creates an OpenTelemetry-based observability instance by default. You can provide your own instance or logger if you want full control.\n\n```ts\nimport { VoltAgent, VoltAgentObservability } from \"@voltagent/core\";\n\nconst observability = new VoltAgentObservability({\n  serviceName: \"my-voltagent-app\",\n});\n\nnew VoltAgent({\n  agents: { assistant },\n  observability,\n});\n```\n\nSee [Observability Overview](/docs/observability/overview) and [Logging](/docs/observability/logging) for details.\n\nIf you already have a `VoltOpsClient` or a custom logger, pass them once and VoltAgent shares them across agents and workflows.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  voltOpsClient,\n  logger,\n});\n```\n\n## Server and Serverless Providers\n\nUse `server` to expose HTTP endpoints, or `serverless` for fetch-based runtimes.\n\n```ts\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst app = new VoltAgent({\n  agents: { assistant },\n  server: honoServer({ port: 3141, enableSwaggerUI: true }),\n});\n```\n\nSee [API Overview](/docs/api/overview) for the full endpoint list.\n\nFor HTTP servers, use `startServer()` and `stopServer()` if you need manual control:\n\n```ts\nawait app.startServer();\nawait app.stopServer();\n```\n\nYou can read the server instance directly:\n\n```ts\nconst serverInstance = app.getServerInstance();\n```\n\nIf you are using a serverless provider, you can access it with `serverless()`:\n\n```ts\nimport { serverlessHono } from \"@voltagent/serverless-hono\";\n\nconst serverlessApp = new VoltAgent({\n  agents: { assistant },\n  serverless: serverlessHono(),\n});\n\nconst serverlessProvider = serverlessApp.serverless();\n```\n\n## Triggers and Protocol Servers\n\nVoltAgent can register triggers and protocol servers alongside your agents.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  triggers,\n  mcpServers,\n  a2aServers,\n});\n```\n\nSee [Triggers](/docs/triggers) and [MCP](/docs/agents/mcp) for setup details.\nFor A2A servers, see [A2A Server](/docs/agents/a2a/a2a-server).\n\n## Runtime Accessors and Shutdown\n\nVoltAgent exposes accessors for registered components and a graceful shutdown helper.\n\n```ts\nconst agents = app.getAgents();\nconst workflows = app.getWorkflows();\nconst observability = app.getObservability();\n\nawait app.shutdown();\n```\n\n## Dependency Update Checks\n\n`checkDependencies` controls a background dependency update check intended for Node runtimes. It is skipped in serverless or edge environments, and the current core implementation is a no-op placeholder.\n\n```ts\nnew VoltAgent({\n  agents: { assistant },\n  checkDependencies: false,\n});\n```\n","frontmatter":{"title":"VoltAgent Instance","sidebar_label":"VoltAgent Instance"},"isInternal":false,"tokens":1325,"sizeBytes":5588},{"name":"README.md","path":"examples/with-client-side-tools/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-client-side-tools/README.md","title":"with-client-side-tools Documentation","category":"plugin-manifest","format":"markdown","content":"# VoltAgent Client-Side Tools Example\n\nThis Next.js example demonstrates how to use client-side tools with VoltAgent, providing a smooth developer experience similar to Vercel AI SDK.\n\n## Features\n\n- 🎨 **UI Tools**: Change theme, show notifications\n- 📊 **Data Tools**: Get location, read clipboard\n- 🤝 **Interactive Tools**: Ask for user confirmation\n- ⚡ **Smooth DX**: Uses Vercel AI SDK's `useChat` hook\n- 🔄 **Automatic Handling**: Tools without `execute` are automatically client-side\n\n## Running the Example\n\n1. Set your OpenAI API key:\n\n```bash\nexport OPENAI_API_KEY=\"your-api-key\"\n```\n\n2. Install dependencies:\n\n```bash\npnpm install\n```\n\n3. Run the development server:\n\n```bash\npnpm dev\n```\n\n4. Start the VoltAgent built-in server in a separate terminal:\n\n```bash\npnpm voltagent:run\n```\n\n5. Open [http://localhost:3000](http://localhost:3000)\n\nTo inspect runs, open `https://console.voltagent.dev` and connect it to `http://localhost:3141`.\n\n### How It Works\n\n### 1. Define Tools (No Execute = Client-Side)\n\n```typescript\n// Client-side tool (no execute function)\nconst getLocationTool = createTool({\n  name: \"getLocation\",\n  description: \"Get user's current location\",\n  parameters: z.object({}),\n  // No execute = automatically client-side\n});\n```\n\n### 2. Handle Client-Side Tools with useChat\n\n```typescript\nconst [result, setResult] = useState<ClientSideToolResult | null>(null);\n\nconst { messages, input, handleSubmit, addToolResult } = useChat({\n  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,\n  // Automatic client-side tool execution\n  async onToolCall({ toolCall }) {\n    if (toolCall.toolName === \"getLocation\") {\n      navigator.geolocation.getCurrentPosition(\n        (position) => {\n          const payload: ClientSideToolResult = {\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            output: {\n              latitude: position.coords.latitude,\n              longitude: position.coords.longitude,\n              accuracy: position.coords.accuracy,\n            },\n          };\n          setResult(payload);\n        },\n        (error) => {\n          const payload: ClientSideToolResult = {\n            state: \"output-error\",\n            tool: \"getLocation\",\n            toolCallId: toolCall.toolCallId,\n            errorText: error.message,\n          };\n          setResult(payload);\n        }\n      );\n    }\n  },\n});\n```\n\n### 2. Sending back tool results to the model\n\nWhen a client-side tool is executed, you **MUST** call `addToolResult` to provide the tool result to the model. The model will then use the result to update the conversation state.\nOtherwise, the model will consider the tool call as a failure.\n\n```ts\nuseEffect(() => {\n  if (!result) return;\n  addToolResult(result);\n}, [result, addToolResult]);\n```\n\n## Tool Types\n\n1. **Client-side Tools**: Execute client-side, don't return data\n   - Change theme\n   - Show notifications\n   - Get location\n   - Read clipboard\n   - Ask for confirmation\n\n2. **Server Tools**: Traditional server-side execution\n   - Get weather\n   - Database queries\n\n## Developer Experience\n\nThe integration is designed to be as simple as possible:\n\n1. Tools without `execute` are automatically client-side\n2. `useChat` hook handles all the streaming complexity\n3. `onToolCall` callback invoked when a tool call is received\n4. You **must** call `addToolResult` to send back the tool result.\n5. Full TypeScript support\n\n## Try These Examples\n\n- \"What's my current location?\" - Data tool\n- \"What's in my clipboard?\" - Data tool\n- \"What's the weather in San Francisco?\" - Server tool\n\n## Security Considerations\n\nClient-side tools have access to browser APIs, so:\n\n- Always validate tool arguments\n- Request user permission for sensitive operations\n- Use the interactive pattern for destructive actions\n- Consider rate limiting tool executions\n","isInternal":false,"tokens":889,"sizeBytes":3867},{"name":"README.md","path":"examples/with-tools/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-tools/README.md","title":"with-tools Documentation","category":"plugin-manifest","format":"markdown","content":"<div align=\"center\">\n<a href=\"https://voltagent.dev/\">\n<img width=\"1800\" alt=\"435380213-b6253409-8741-462b-a346-834cd18565a9\" src=\"https://github.com/user-attachments/assets/452a03e7-eeda-4394-9ee7-0ffbcf37245c\" />\n</a>\n\n<br/>\n<br/>\n\n<div align=\"center\">\n    <a href=\"https://voltagent.dev\">Home Page</a> |\n    <a href=\"https://voltagent.dev/docs/\">Documentation</a> |\n    <a href=\"https://github.com/voltagent/voltagent/tree/main/examples\">Examples</a> |\n    <a href=\"https://s.voltagent.dev/discord\">Discord</a> |\n    <a href=\"https://voltagent.dev/blog/\">Blog</a>\n</div>\n</div>\n\n<br/>\n\n<div align=\"center\">\n    <strong>VoltAgent is an open source TypeScript framework for building and orchestrating AI agents.</strong><br>\nEscape the limitations of no-code builders and the complexity of starting from scratch.\n    <br />\n    <br />\n</div>\n\n<div align=\"center\">\n    \n[![npm version](https://img.shields.io/npm/v/@voltagent/core.svg)](https://www.npmjs.com/package/@voltagent/core)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CODE_OF_CONDUCT.md)\n[![Discord](https://img.shields.io/discord/1361559153780195478.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://s.voltagent.dev/discord)\n[![Twitter Follow](https://img.shields.io/twitter/follow/voltagent_dev?style=social)](https://twitter.com/voltagent_dev)\n    \n</div>\n\n<br/>\n\n<div align=\"center\">\n<a href=\"https://voltagent.dev/\">\n<img width=\"896\" alt=\"VoltAgent Schema\" src=\"https://github.com/user-attachments/assets/f0627868-6153-4f63-ba7f-bdfcc5dd603d\" />\n</a>\n\n</div>\n\n## VoltAgent: Build AI Agents Fast and Flexibly\n\nVoltAgent is an open-source TypeScript framework for creating and managing AI agents. It provides modular components to build, customize, and scale agents with ease. From connecting to APIs and memory management to supporting multiple LLMs, VoltAgent simplifies the process of creating sophisticated AI systems. It enables fast development, maintains clean code, and offers flexibility to switch between models and tools without vendor lock-in.\n\n## Try Example\n\n```bash\nnpm create voltagent-app@latest -- --example with-tools\n```\n","isInternal":false,"tokens":619,"sizeBytes":2186},{"name":"README.md","path":"examples/with-xquik-tools/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/examples/with-xquik-tools/README.md","title":"with-xquik-tools Documentation","category":"plugin-manifest","format":"markdown","content":"# VoltAgent with Xquik Tools\n\nThis example shows how to add Xquik REST API tools to a VoltAgent research agent for public X/Twitter data.\n\n## Features\n\n- Search public posts with X query operators\n- Look up posts by ID with author, metrics, and media\n- Look up public user profiles by username or ID\n- Fetch recent public posts from a user\n- Retrieve public X/Twitter trends by WOEID region\n\n## Prerequisites\n\n1. **Xquik API Key**: Create an API key from the [Xquik dashboard](https://dashboard.xquik.com)\n2. **OpenAI API Key**: Used by the example agent model\n\n## Setup\n\n1. Install dependencies:\n\n   ```bash\n   pnpm install\n   ```\n\n2. Configure environment variables:\n\n   ```bash\n   cp .env.example .env\n   ```\n\n   Then edit `.env`:\n\n   ```env\n   OPENAI_API_KEY=your_openai_api_key_here\n   XQUIK_API_KEY=your_xquik_api_key_here\n   XQUIK_BASE_URL=https://xquik.com/api/v1\n   ```\n\n3. Run the example:\n\n   ```bash\n   pnpm dev\n   ```\n\nThe agent runs on the default VoltAgent server port and exposes one agent named `xquikResearchAgent`.\n\n## Tools Available\n\n### 1. Search X Posts\n\n- **Purpose**: Search public X/Twitter posts with X query operators\n- **Endpoint**: `GET /x/tweets/search`\n- **Parameters**:\n  - `query`: Search query string\n  - `queryType`: `Latest` or `Top`\n  - `limit`: Maximum posts to return\n  - `cursor`: Optional pagination cursor\n  - `sinceTime`, `untilTime`: Optional ISO 8601 time bounds\n\n### 2. Get X Post\n\n- **Purpose**: Look up a public post by ID\n- **Endpoint**: `GET /x/tweets/{id}`\n\n### 3. Get X User\n\n- **Purpose**: Look up a public user profile by username or ID\n- **Endpoint**: `GET /x/users/{id}`\n\n### 4. Get X User Posts\n\n- **Purpose**: Fetch recent public posts from a user\n- **Endpoint**: `GET /x/users/{id}/tweets`\n- **Parameters**:\n  - `user`: Username without `@`, or numeric user ID\n  - `cursor`: Optional pagination cursor\n  - `includeReplies`: Include replies\n  - `includeParentTweet`: Include parent posts for replies\n\n### 5. Get X Trends\n\n- **Purpose**: Fetch public trends by WOEID region\n- **Endpoint**: `GET /x/trends`\n- **Parameters**:\n  - `woeid`: Region WOEID, with `1` for worldwide\n  - `count`: Number of trends to return\n\n## Example Queries\n\n- \"Search recent posts about AI agent frameworks and summarize the top themes.\"\n- \"Look up @voltagent_dev and summarize recent public posts.\"\n- \"Find worldwide X trends and explain which ones relate to developer tools.\"\n- \"Get this post by ID and extract the author, timestamp, and engagement metrics.\"\n\n## API Integration\n\nThis example uses the Xquik public REST API with the `x-api-key` header and the `xquik-api-contract` header set to `2026-04-29`.\n\nSee the [Xquik API reference](https://docs.xquik.com/api-reference/overview) for endpoint details.\n\n## Troubleshooting\n\n- **Missing API key**: Set `XQUIK_API_KEY` in `.env`.\n- **Authentication errors**: Create a fresh API key from the Xquik dashboard.\n- **Empty results**: Try a broader query, switch between `Latest` and `Top`, or remove time bounds.\n- **Regional trends**: Use WOEID `1` for worldwide, `23424977` for the US, or another supported region.\n","isInternal":false,"tokens":834,"sizeBytes":3103},{"name":"README.md","path":"packages/a2a-server/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/a2a-server/README.md","title":"a2a-server Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fa2a-server%2FREADME.md -->","isInternal":false,"tokens":184,"sizeBytes":719},{"name":"README.md","path":"packages/ag-ui/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/ag-ui/README.md","title":"ag-ui Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fag-ui%2FREADME.md -->","isInternal":false,"tokens":1458,"sizeBytes":6499},{"name":"README.md","path":"packages/cli/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/cli/README.md","title":"cli Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcli%2FREADME.md -->","isInternal":false,"tokens":3255,"sizeBytes":14609},{"name":"README.md","path":"packages/cloudflare-d1/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/cloudflare-d1/README.md","title":"cloudflare-d1 Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcloudflare-d1%2FREADME.md -->","isInternal":false,"tokens":452,"sizeBytes":1743},{"name":"README.md","path":"packages/core/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/core/README.md","title":"core Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcore%2FREADME.md -->","isInternal":false,"tokens":4279,"sizeBytes":17124},{"name":"README.md","path":"packages/create-voltagent-app/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/create-voltagent-app/README.md","title":"create-voltagent-app Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fcreate-voltagent-app%2FREADME.md -->","isInternal":false,"tokens":3282,"sizeBytes":14678},{"name":"README.md","path":"packages/docs-mcp/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/docs-mcp/README.md","title":"docs-mcp Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fdocs-mcp%2FREADME.md -->","isInternal":false,"tokens":1872,"sizeBytes":7479},{"name":"README.md","path":"packages/evals/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/evals/README.md","title":"evals Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fevals%2FREADME.md -->","isInternal":false,"tokens":1365,"sizeBytes":5649},{"name":"README.md","path":"packages/internal/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/internal/README.md","title":"internal Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Finternal%2FREADME.md -->","isInternal":false,"tokens":160,"sizeBytes":640},{"name":"README.md","path":"packages/langfuse-exporter/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/langfuse-exporter/README.md","title":"langfuse-exporter Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flangfuse-exporter%2FREADME.md -->","isInternal":false,"tokens":2883,"sizeBytes":12944},{"name":"README.md","path":"packages/libsql/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/libsql/README.md","title":"libsql Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flibsql%2FREADME.md -->","isInternal":false,"tokens":3255,"sizeBytes":14609},{"name":"README.md","path":"packages/logger/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/logger/README.md","title":"logger Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Flogger%2FREADME.md -->","isInternal":false,"tokens":608,"sizeBytes":2577},{"name":"README.md","path":"packages/mcp-server/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/mcp-server/README.md","title":"mcp-server Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fmcp-server%2FREADME.md -->","isInternal":false,"tokens":415,"sizeBytes":1870},{"name":"README.md","path":"packages/postgres/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/postgres/README.md","title":"postgres Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fpostgres%2FREADME.md -->","isInternal":false,"tokens":1300,"sizeBytes":5905},{"name":"README.md","path":"packages/sandbox-blaxel/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/sandbox-blaxel/README.md","title":"sandbox-blaxel Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-blaxel%2FREADME.md -->","isInternal":false,"tokens":1160,"sizeBytes":4681},{"name":"README.md","path":"packages/sandbox-daytona/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/sandbox-daytona/README.md","title":"sandbox-daytona Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-daytona%2FREADME.md -->","isInternal":false,"tokens":1104,"sizeBytes":4859},{"name":"README.md","path":"packages/sandbox-e2b/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/sandbox-e2b/README.md","title":"sandbox-e2b Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsandbox-e2b%2FREADME.md -->","isInternal":false,"tokens":1156,"sizeBytes":4983},{"name":"README.md","path":"packages/scorers/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/scorers/README.md","title":"scorers Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fscorers%2FREADME.md -->","isInternal":false,"tokens":93,"sizeBytes":369},{"name":"README.md","path":"packages/sdk/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/sdk/README.md","title":"sdk Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsdk%2FREADME.md -->","isInternal":false,"tokens":4277,"sizeBytes":16813},{"name":"README.md","path":"packages/server-core/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/server-core/README.md","title":"server-core Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserver-core%2FREADME.md -->","isInternal":false,"tokens":1782,"sizeBytes":7452},{"name":"README.md","path":"packages/server-elysia/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/server-elysia/README.md","title":"server-elysia Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserver-elysia%2FREADME.md -->","isInternal":false,"tokens":1586,"sizeBytes":6308},{"name":"README.md","path":"packages/serverless-hono/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/serverless-hono/README.md","title":"serverless-hono Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fserverless-hono%2FREADME.md -->","isInternal":false,"tokens":181,"sizeBytes":704},{"name":"README.md","path":"packages/supabase/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/supabase/README.md","title":"supabase Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fsupabase%2FREADME.md -->","isInternal":false,"tokens":4287,"sizeBytes":19133},{"name":"README.md","path":"packages/vercel-ai-exporter/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/vercel-ai-exporter/README.md","title":"vercel-ai-exporter Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvercel-ai-exporter%2FREADME.md -->","isInternal":false,"tokens":1012,"sizeBytes":3984},{"name":"README.md","path":"packages/voice/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/voice/README.md","title":"voice Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvoice%2FREADME.md -->","isInternal":false,"tokens":2586,"sizeBytes":11710},{"name":"README.md","path":"packages/voltagent-memory/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/packages/voltagent-memory/README.md","title":"voltagent-memory Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=packages%2Fvoltagent-memory%2FREADME.md -->","isInternal":false,"tokens":1041,"sizeBytes":4075},{"name":"README.md","path":"tools/core/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/tools/core/README.md","title":"core Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=tools%2Fcore%2FREADME.md -->","isInternal":false,"tokens":1720,"sizeBytes":7937},{"name":"README.md","path":"tools/README.md","rawUrl":"https://raw.githubusercontent.com/VoltAgent/voltagent/HEAD/tools/README.md","title":"tools Documentation","category":"plugin-manifest","format":"markdown","content":"<!-- Skill available on-demand via /api/skills/VoltAgent/voltagent?path=tools%2FREADME.md -->","isInternal":false,"tokens":138,"sizeBytes":547}],"systemPromptSnippet":"<yakaai_skills repo=\"VoltAgent/voltagent\">\n<!-- File: AGENTS.md (Tokens: ~336 | Category: root-instruction) -->\n# VoltAgent\n\nVoltAgent is an open-source TypeScript framework for building and orchestrating AI agents.\n\n## What is VoltAgent?\n\nVoltAgent is a comprehensive framework that enables developers to build sophisticated AI agents with memory, tools, observability, and sub-agent capabilities. It provides direct AI SDK integration with comprehensive OpenTelemetry tracing built-in.\n\n## Overview\n\n- View [`docs/structure.md`](./docs/structure.md) for the repository structure and organization\n- View [`docs/tooling.md`](./docs/tooling.md) for development tools and utilities\n- View [`docs/testing.md`](./docs/testing.md) for testing guidelines and commands\n- View [`docs/linting.md`](./docs/linting.md) for code formatting and linting\n\n## Validating Changes\n\nTo validate your changes you can run the following commands:\n\n```bash\npnpm test:all        # Run all tests\npnpm build:all       # Build all packages\npnpm lint            # Run linting checks\npnpm lint:fix        # Auto-fix linting issues\n```\n\n## Important Notes for AI Agents\n\n1. **Always check existing patterns** before implementing new features\n2. **Use the established registry patterns** for agent and tool management\n3. **Maintain type safety** - this is a TypeScript-first codebase\n4. **Follow the monorepo structure** - changes may impact multiple packages\n5. **Test your changes** - ensure all tests pass before committing\n\n## Gotchas\n\n- **JSON.stringify** SHOULD NEVER BE USED, used the `safeStringify` function instead, imported from `@voltagent/internal`\n\n<!-- File: CLAUDE.md (Tokens: ~3 | Category: claude-rule) -->\nAGENTS.md\n\n<!-- File: .cursor/rules/agents.mdc (Tokens: ~38 | Category: cursor-rule) -->\n---\ndescription: \nglobs: \nalwaysApply: true\n---\nALWAYS read the `AGENTS.md` in the root of the repository and apply the instructions to the current task.\n\n<!-- File: examples/with-workspace/workspace/skills/data-analysis/references/schema.md (Tokens: ~53 | Category: anthropic-skill) -->\n# CSV Schema\n\nExpected columns:\n\n- customer_id: unique identifier\n- segment: smb | mid-market | enterprise\n- mrr: monthly recurring revenue (number)\n- region: NA | EU | APAC\n- plan: Starter | Pro | Enterprise\n\n<!-- File: examples/with-workspace/workspace/skills/data-analysis/SKILL.md (Tokens: ~98 | Category: anthropic-skill) -->\n---\nname: Workspace Data Analyst\ndescription: Analyze CSV files in the workspace and summarize insights.\nversion: \"1.0.0\"\ntags:\n  - data\n  - csv\n  - analysis\nreferences:\n  - references/schema.md\nassets:\n  - assets/sample.csv\n---\n\nWhen analyzing CSV data:\n\n1. Load the file and inspect headers.\n2. Summarize totals, averages, and outliers.\n3. Provide a short insight summary and recommended next steps.\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/references/request-mocking.md (Tokens: ~549 | Category: anthropic-skill) -->\n# Request Mocking\n\nIntercept, mock, modify, and block network requests.\n\n## CLI Route Commands\n\n```bash\n# Mock with custom status\nplaywright-cli route \"**/*.jpg\" --status=404\n\n# Mock with JSON body\nplaywright-cli route \"**/api/users\" --body='[{\"id\":1,\"name\":\"Alice\"}]' --content-type=application/json\n\n# Mock with custom headers\nplaywright-cli route \"**/api/data\" --body='{\"ok\":true}' --header=\"X-Custom: value\"\n\n# Remove headers from requests\nplaywright-cli route \"**/*\" --remove-header=cookie,authorization\n\n# List active routes\nplaywright-cli route-list\n\n# Remove a route or all routes\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n## URL Patterns\n\n```\n**/api/users           - Exact path match\n**/api/*/details       - Wildcard in path\n**/*.{png,jpg,jpeg}    - Match file extensions\n**/search?q=*          - Match query parameters\n```\n\n## Advanced Mocking with run-code\n\nFor conditional responses, request body inspection, response modification, or delays:\n\n### Conditional Response Based on Request\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/login', route => {\n    const body = route.request().postDataJSON();\n    if (body.username === 'admin') {\n      route.fulfill({ body: JSON.stringify({ token: 'mock-token' }) });\n    } else {\n      route.fulfill({ status: 401, body: JSON.stringify({ error: 'Invalid' }) });\n    }\n  });\n}\"\n```\n\n### Modify Real Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/user', async route => {\n    const response = await route.fetch();\n    const json = await response.json();\n    json.isPremium = true;\n    await route.fulfill({ response, json });\n  });\n}\"\n```\n\n### Simulate Network Failures\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/offline', route => route.abort('internetdisconnected'));\n}\"\n# Options: connectionrefused, timedout, connectionreset, internetdisconnected\n```\n\n### Delayed Response\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.route('**/api/slow', async route => {\n    await new Promise(r => setTimeout(r, 3000));\n    route.fulfill({ body: JSON.stringify({ data: 'loaded' }) });\n  });\n}\"\n```\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/references/running-code.md (Tokens: ~1263 | Category: anthropic-skill) -->\n# Running Custom Playwright Code\n\nUse `run-code` to execute arbitrary Playwright code for advanced scenarios not covered by CLI commands.\n\n## Syntax\n\n```bash\nplaywright-cli run-code \"async page => {\n  // Your Playwright code here\n  // Access page.context() for browser context operations\n}\"\n```\n\n## Geolocation\n\n```bash\n# Grant geolocation permission and set location\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194 });\n}\"\n\n# Set location to London\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['geolocation']);\n  await page.context().setGeolocation({ latitude: 51.5074, longitude: -0.1278 });\n}\"\n\n# Clear geolocation override\nplaywright-cli run-code \"async page => {\n  await page.context().clearPermissions();\n}\"\n```\n\n## Permissions\n\n```bash\n# Grant multiple permissions\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions([\n    'geolocation',\n    'notifications',\n    'camera',\n    'microphone'\n  ]);\n}\"\n\n# Grant permissions for specific origin\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read'], {\n    origin: 'https://example.com'\n  });\n}\"\n```\n\n## Media Emulation\n\n```bash\n# Emulate dark color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'dark' });\n}\"\n\n# Emulate light color scheme\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ colorScheme: 'light' });\n}\"\n\n# Emulate reduced motion\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ reducedMotion: 'reduce' });\n}\"\n\n# Emulate print media\nplaywright-cli run-code \"async page => {\n  await page.emulateMedia({ media: 'print' });\n}\"\n```\n\n## Wait Strategies\n\n```bash\n# Wait for network idle\nplaywright-cli run-code \"async page => {\n  await page.waitForLoadState('networkidle');\n}\"\n\n# Wait for specific element\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.loading', { state: 'hidden' });\n}\"\n\n# Wait for function to return true\nplaywright-cli run-code \"async page => {\n  await page.waitForFunction(() => window.appReady === true);\n}\"\n\n# Wait with timeout\nplaywright-cli run-code \"async page => {\n  await page.waitForSelector('.result', { timeout: 10000 });\n}\"\n```\n\n## Frames and Iframes\n\n```bash\n# Work with iframe\nplaywright-cli run-code \"async page => {\n  const frame = page.locator('iframe#my-iframe').contentFrame();\n  await frame.locator('button').click();\n}\"\n\n# Get all frames\nplaywright-cli run-code \"async page => {\n  const frames = page.frames();\n  return frames.map(f => f.url());\n}\"\n```\n\n## File Downloads\n\n```bash\n# Handle file download\nplaywright-cli run-code \"async page => {\n  const [download] = await Promise.all([\n    page.waitForEvent('download'),\n    page.click('a.download-link')\n  ]);\n  await download.saveAs('./downloaded-file.pdf');\n  return download.suggestedFilename();\n}\"\n```\n\n## Clipboard\n\n```bash\n# Read clipboard (requires permission)\nplaywright-cli run-code \"async page => {\n  await page.context().grantPermissions(['clipboard-read']);\n  return await page.evaluate(() => navigator.clipboard.readText());\n}\"\n\n# Write to clipboard\nplaywright-cli run-code \"async page => {\n  await page.evaluate(text => navigator.clipboard.writeText(text), 'Hello clipboard!');\n}\"\n```\n\n## Page Information\n\n```bash\n# Get page title\nplaywright-cli run-code \"async page => {\n  return await page.title();\n}\"\n\n# Get current URL\nplaywright-cli run-code \"async page => {\n  return page.url();\n}\"\n\n# Get page content\nplaywright-cli run-code \"async page => {\n  return await page.content();\n}\"\n\n# Get viewport size\nplaywright-cli run-code \"async page => {\n  return page.viewportSize();\n}\"\n```\n\n## JavaScript Execution\n\n```bash\n# Execute JavaScript and return result\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(() => {\n    return {\n      userAgent: navigator.userAgent,\n      language: navigator.language,\n      cookiesEnabled: navigator.cookieEnabled\n    };\n  });\n}\"\n\n# Pass arguments to evaluate\nplaywright-cli run-code \"async page => {\n  const multiplier = 5;\n  return await page.evaluate(m => document.querySelectorAll('li').length * m, multiplier);\n}\"\n```\n\n## Error Handling\n\n```bash\n# Try-catch in run-code\nplaywright-cli run-code \"async page => {\n  try {\n    await page.click('.maybe-missing', { timeout: 1000 });\n    return 'clicked';\n  } catch (e) {\n    return 'element not found';\n  }\n}\"\n```\n\n## Complex Workflows\n\n```bash\n# Login and save state\nplaywright-cli run-code \"async page => {\n  await page.goto('https://example.com/login');\n  await page.fill('input[name=email]', 'user@example.com');\n  await page.fill('input[name=password]', 'secret');\n  await page.click('button[type=submit]');\n  await page.waitForURL('**/dashboard');\n  await page.context().storageState({ path: 'auth.json' });\n  return 'Login successful';\n}\"\n\n# Scrape data from multiple pages\nplaywright-cli run-code \"async page => {\n  const results = [];\n  for (let i = 1; i <= 3; i++) {\n    await page.goto(\\`https://example.com/page/\\${i}\\`);\n    const items = await page.locator('.item').allTextContents();\n    results.push(...items);\n  }\n  return results;\n}\"\n```\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/references/session-management.md (Tokens: ~939 | Category: anthropic-skill) -->\n# Browser Session Management\n\nRun multiple isolated browser sessions concurrently with state persistence.\n\n## Named Browser Sessions\n\nUse `-s` flag to isolate browser contexts:\n\n```bash\n# Browser 1: Authentication flow\nplaywright-cli -s=auth open https://app.example.com/login\n\n# Browser 2: Public browsing (separate cookies, storage)\nplaywright-cli -s=public open https://example.com\n\n# Commands are isolated by browser session\nplaywright-cli -s=auth fill e1 \"user@example.com\"\nplaywright-cli -s=public snapshot\n```\n\n## Browser Session Isolation Properties\n\nEach browser session has independent:\n\n- Cookies\n- LocalStorage / SessionStorage\n- IndexedDB\n- Cache\n- Browsing history\n- Open tabs\n\n## Browser Session Commands\n\n```bash\n# List all browser sessions\nplaywright-cli list\n\n# Stop a browser session (close the browser)\nplaywright-cli close                # stop the default browser\nplaywright-cli -s=mysession close   # stop a named browser\n\n# Stop all browser sessions\nplaywright-cli close-all\n\n# Forcefully kill all daemon processes (for stale/zombie processes)\nplaywright-cli kill-all\n\n# Delete browser session user data (profile directory)\nplaywright-cli delete-data                # delete default browser data\nplaywright-cli -s=mysession delete-data   # delete named browser data\n```\n\n## Environment Variable\n\nSet a default browser session name via environment variable:\n\n```bash\nexport PLAYWRIGHT_CLI_SESSION=\"mysession\"\nplaywright-cli open example.com  # Uses \"mysession\" automatically\n```\n\n## Common Patterns\n\n### Concurrent Scraping\n\n```bash\n#!/bin/bash\n# Scrape multiple sites concurrently\n\n# Start all browsers\nplaywright-cli -s=site1 open https://site1.com &\nplaywright-cli -s=site2 open https://site2.com &\nplaywright-cli -s=site3 open https://site3.com &\nwait\n\n# Take snapshots from each\nplaywright-cli -s=site1 snapshot\nplaywright-cli -s=site2 snapshot\nplaywright-cli -s=site3 snapshot\n\n# Cleanup\nplaywright-cli close-all\n```\n\n### A/B Testing Sessions\n\n```bash\n# Test different user experiences\nplaywright-cli -s=variant-a open \"https://app.com?variant=a\"\nplaywright-cli -s=variant-b open \"https://app.com?variant=b\"\n\n# Compare\nplaywright-cli -s=variant-a screenshot\nplaywright-cli -s=variant-b screenshot\n```\n\n### Persistent Profile\n\nBy default, browser profile is kept in memory only. Use `--persistent` flag on `open` to persist the browser profile to disk:\n\n```bash\n# Use persistent profile (auto-generated location)\nplaywright-cli open https://example.com --persistent\n\n# Use persistent profile with custom directory\nplaywright-cli open https://example.com --profile=/path/to/profile\n```\n\n## Default Browser Session\n\nWhen `-s` is omitted, commands use the default browser session:\n\n```bash\n# These use the same default browser session\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli close  # Stops default browser\n```\n\n## Browser Session Configuration\n\nConfigure a browser session with specific settings when opening:\n\n```bash\n# Open with config file\nplaywright-cli open https://example.com --config=.playwright/my-cli.json\n\n# Open with specific browser\nplaywright-cli open https://example.com --browser=firefox\n\n# Open in headed mode\nplaywright-cli open https://example.com --headed\n\n# Open with persistent profile\nplaywright-cli open https://example.com --persistent\n```\n\n## Best Practices\n\n### 1. Name Browser Sessions Semantically\n\n```bash\n# GOOD: Clear purpose\nplaywright-cli -s=github-auth open https://github.com\nplaywright-cli -s=docs-scrape open https://docs.example.com\n\n# AVOID: Generic names\nplaywright-cli -s=s1 open https://github.com\n```\n\n### 2. Always Clean Up\n\n```bash\n# Stop browsers when done\nplaywright-cli -s=auth close\nplaywright-cli -s=scrape close\n\n# Or stop all at once\nplaywright-cli close-all\n\n# If browsers become unresponsive or zombie processes remain\nplaywright-cli kill-all\n```\n\n### 3. Delete Stale Browser Data\n\n```bash\n# Remove old browser data to free disk space\nplaywright-cli -s=oldsession delete-data\n```\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/references/storage-state.md (Tokens: ~1249 | Category: anthropic-skill) -->\n# Storage Management\n\nManage cookies, localStorage, sessionStorage, and browser storage state.\n\n## Storage State\n\nSave and restore complete browser state including cookies and storage.\n\n### Save Storage State\n\n```bash\n# Save to auto-generated filename (storage-state-{timestamp}.json)\nplaywright-cli state-save\n\n# Save to specific filename\nplaywright-cli state-save my-auth-state.json\n```\n\n### Restore Storage State\n\n```bash\n# Load storage state from file\nplaywright-cli state-load my-auth-state.json\n\n# Reload page to apply cookies\nplaywright-cli open https://example.com\n```\n\n### Storage State File Format\n\nThe saved file contains:\n\n```json\n{\n  \"cookies\": [\n    {\n      \"name\": \"session_id\",\n      \"value\": \"abc123\",\n      \"domain\": \"example.com\",\n      \"path\": \"/\",\n      \"expires\": 1735689600,\n      \"httpOnly\": true,\n      \"secure\": true,\n      \"sameSite\": \"Lax\"\n    }\n  ],\n  \"origins\": [\n    {\n      \"origin\": \"https://example.com\",\n      \"localStorage\": [\n        { \"name\": \"theme\", \"value\": \"dark\" },\n        { \"name\": \"user_id\", \"value\": \"12345\" }\n      ]\n    }\n  ]\n}\n```\n\n## Cookies\n\n### List All Cookies\n\n```bash\nplaywright-cli cookie-list\n```\n\n### Filter Cookies by Domain\n\n```bash\nplaywright-cli cookie-list --domain=example.com\n```\n\n### Filter Cookies by Path\n\n```bash\nplaywright-cli cookie-list --path=/api\n```\n\n### Get Specific Cookie\n\n```bash\nplaywright-cli cookie-get session_id\n```\n\n### Set a Cookie\n\n```bash\n# Basic cookie\nplaywright-cli cookie-set session abc123\n\n# Cookie with options\nplaywright-cli cookie-set session abc123 --domain=example.com --path=/ --httpOnly --secure --sameSite=Lax\n\n# Cookie with expiration (Unix timestamp)\nplaywright-cli cookie-set remember_me token123 --expires=1735689600\n```\n\n### Delete a Cookie\n\n```bash\nplaywright-cli cookie-delete session_id\n```\n\n### Clear All Cookies\n\n```bash\nplaywright-cli cookie-clear\n```\n\n### Advanced: Multiple Cookies or Custom Options\n\nFor complex scenarios like adding multiple cookies at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.context().addCookies([\n    { name: 'session_id', value: 'sess_abc123', domain: 'example.com', path: '/', httpOnly: true },\n    { name: 'preferences', value: JSON.stringify({ theme: 'dark' }), domain: 'example.com', path: '/' }\n  ]);\n}\"\n```\n\n## Local Storage\n\n### List All localStorage Items\n\n```bash\nplaywright-cli localstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli localstorage-get token\n```\n\n### Set Value\n\n```bash\nplaywright-cli localstorage-set theme dark\n```\n\n### Set JSON Value\n\n```bash\nplaywright-cli localstorage-set user_settings '{\"theme\":\"dark\",\"language\":\"en\"}'\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli localstorage-delete token\n```\n\n### Clear All localStorage\n\n```bash\nplaywright-cli localstorage-clear\n```\n\n### Advanced: Multiple Operations\n\nFor complex scenarios like setting multiple values at once, use `run-code`:\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    localStorage.setItem('token', 'jwt_abc123');\n    localStorage.setItem('user_id', '12345');\n    localStorage.setItem('expires_at', Date.now() + 3600000);\n  });\n}\"\n```\n\n## Session Storage\n\n### List All sessionStorage Items\n\n```bash\nplaywright-cli sessionstorage-list\n```\n\n### Get Single Value\n\n```bash\nplaywright-cli sessionstorage-get form_data\n```\n\n### Set Value\n\n```bash\nplaywright-cli sessionstorage-set step 3\n```\n\n### Delete Single Item\n\n```bash\nplaywright-cli sessionstorage-delete step\n```\n\n### Clear sessionStorage\n\n```bash\nplaywright-cli sessionstorage-clear\n```\n\n## IndexedDB\n\n### List Databases\n\n```bash\nplaywright-cli run-code \"async page => {\n  return await page.evaluate(async () => {\n    const databases = await indexedDB.databases();\n    return databases;\n  });\n}\"\n```\n\n### Delete Database\n\n```bash\nplaywright-cli run-code \"async page => {\n  await page.evaluate(() => {\n    indexedDB.deleteDatabase('myDatabase');\n  });\n}\"\n```\n\n## Common Patterns\n\n### Authentication State Reuse\n\n```bash\n# Step 1: Login and save state\nplaywright-cli open https://app.example.com/login\nplaywright-cli snapshot\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\n\n# Save the authenticated state\nplaywright-cli state-save auth.json\n\n# Step 2: Later, restore state and skip login\nplaywright-cli state-load auth.json\nplaywright-cli open https://app.example.com/dashboard\n# Already logged in!\n```\n\n### Save and Restore Roundtrip\n\n```bash\n# Set up authentication state\nplaywright-cli open https://example.com\nplaywright-cli eval \"() => { document.cookie = 'session=abc123'; localStorage.setItem('user', 'john'); }\"\n\n# Save state to file\nplaywright-cli state-save my-session.json\n\n# ... later, in a new session ...\n\n# Restore state\nplaywright-cli state-load my-session.json\nplaywright-cli open https://example.com\n# Cookies and localStorage are restored!\n```\n\n## Security Notes\n\n- Never commit storage state files containing auth tokens\n- Add `*.auth-state.json` to `.gitignore`\n- Delete state files after automation completes\n- Use environment variables for sensitive data\n- By default, sessions run in-memory mode which is safer for sensitive operations\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/references/test-generation.md (Tokens: ~563 | Category: anthropic-skill) -->\n# Test Generation\n\nGenerate Playwright test code automatically as you interact with the browser.\n\n## How It Works\n\nEvery action you perform with `playwright-cli` generates corresponding Playwright TypeScript code.\nThis code appears in the output and can be copied directly into your test files.\n\n## Example Workflow\n\n```bash\n# Start a session\nplaywright-cli open https://example.com/login\n\n# Take a snapshot to see elements\nplaywright-cli snapshot\n# Output shows: e1 [textbox \"Email\"], e2 [textbox \"Password\"], e3 [button \"Sign In\"]\n\n# Fill form fields - generates code automatically\nplaywright-cli fill e1 \"user@example.com\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');\n\nplaywright-cli fill e2 \"password123\"\n# Ran Playwright code:\n# await page.getByRole('textbox', { name: 'Password' }).fill('password123');\n\nplaywright-cli click e3\n# Ran Playwright code:\n# await page.getByRole('button', { name: 'Sign In' }).click();\n```\n\n## Building a Test File\n\nCollect the generated code into a Playwright test:\n\n```typescript\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"login flow\", async ({ page }) => {\n  // Generated code from playwright-cli session:\n  await page.goto(\"https://example.com/login\");\n  await page.getByRole(\"textbox\", { name: \"Email\" }).fill(\"user@example.com\");\n  await page.getByRole(\"textbox\", { name: \"Password\" }).fill(\"password123\");\n  await page.getByRole(\"button\", { name: \"Sign In\" }).click();\n\n  // Add assertions\n  await expect(page).toHaveURL(/.*dashboard/);\n});\n```\n\n## Best Practices\n\n### 1. Use Semantic Locators\n\nThe generated code uses role-based locators when possible, which are more resilient:\n\n```typescript\n// Generated (good - semantic)\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Avoid (fragile - CSS selectors)\nawait page.locator(\"#submit-btn\").click();\n```\n\n### 2. Explore Before Recording\n\nTake snapshots to understand the page structure before recording actions:\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli snapshot\n# Review the element structure\nplaywright-cli click e5\n```\n\n### 3. Add Assertions Manually\n\nGenerated code captures actions but not assertions. Add expectations in your test:\n\n```typescript\n// Generated action\nawait page.getByRole(\"button\", { name: \"Submit\" }).click();\n\n// Manual assertion\nawait expect(page.getByText(\"Success\")).toBeVisible();\n```\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/references/tracing.md (Tokens: ~859 | Category: anthropic-skill) -->\n# Tracing\n\nCapture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.\n\n## Basic Usage\n\n```bash\n# Start trace recording\nplaywright-cli tracing-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli click e1\nplaywright-cli fill e2 \"test\"\n\n# Stop trace recording\nplaywright-cli tracing-stop\n```\n\n## Trace Output Files\n\nWhen you start tracing, Playwright creates a `traces/` directory with several files:\n\n### `trace-{timestamp}.trace`\n\n**Action log** - The main trace file containing:\n\n- Every action performed (clicks, fills, navigations)\n- DOM snapshots before and after each action\n- Screenshots at each step\n- Timing information\n- Console messages\n- Source locations\n\n### `trace-{timestamp}.network`\n\n**Network log** - Complete network activity:\n\n- All HTTP requests and responses\n- Request headers and bodies\n- Response headers and bodies\n- Timing (DNS, connect, TLS, TTFB, download)\n- Resource sizes\n- Failed requests and errors\n\n### `resources/`\n\n**Resources directory** - Cached resources:\n\n- Images, fonts, stylesheets, scripts\n- Response bodies for replay\n- Assets needed to reconstruct page state\n\n## What Traces Capture\n\n| Category        | Details                                            |\n| --------------- | -------------------------------------------------- |\n| **Actions**     | Clicks, fills, hovers, keyboard input, navigations |\n| **DOM**         | Full DOM snapshot before/after each action         |\n| **Screenshots** | Visual state at each step                          |\n| **Network**     | All requests, responses, headers, bodies, timing   |\n| **Console**     | All console.log, warn, error messages              |\n| **Timing**      | Precise timing for each operation                  |\n\n## Use Cases\n\n### Debugging Failed Actions\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://app.example.com\n\n# This click fails - why?\nplaywright-cli click e5\n\nplaywright-cli tracing-stop\n# Open trace to see DOM state when click was attempted\n```\n\n### Analyzing Performance\n\n```bash\nplaywright-cli tracing-start\nplaywright-cli open https://slow-site.com\nplaywright-cli tracing-stop\n\n# View network waterfall to identify slow resources\n```\n\n### Capturing Evidence\n\n```bash\n# Record a complete user flow for documentation\nplaywright-cli tracing-start\n\nplaywright-cli open https://app.example.com/checkout\nplaywright-cli fill e1 \"4111111111111111\"\nplaywright-cli fill e2 \"12/25\"\nplaywright-cli fill e3 \"123\"\nplaywright-cli click e4\n\nplaywright-cli tracing-stop\n# Trace shows exact sequence of events\n```\n\n## Trace vs Video vs Screenshot\n\n| Feature                 | Trace       | Video       | Screenshot       |\n| ----------------------- | ----------- | ----------- | ---------------- |\n| **Format**              | .trace file | .webm video | .png/.jpeg image |\n| **DOM inspection**      | Yes         | No          | No               |\n| **Network details**     | Yes         | No          | No               |\n| **Step-by-step replay** | Yes         | Continuous  | Single frame     |\n| **File size**           | Medium      | Large       | Small            |\n| **Best for**            | Debugging   | Demos       | Quick capture    |\n\n## Best Practices\n\n### 1. Start Tracing Before the Problem\n\n```bash\n# Trace the entire flow, not just the failing step\nplaywright-cli tracing-start\nplaywright-cli open https://example.com\n# ... all steps leading to the issue ...\nplaywright-cli tracing-stop\n```\n\n### 2. Clean Up Old Traces\n\nTraces can consume significant disk space:\n\n```bash\n# Remove traces older than 7 days\nfind .playwright-cli/traces -mtime +7 -delete\n```\n\n## Limitations\n\n- Traces add overhead to automation\n- Large traces can consume significant disk space\n- Some dynamic content may not replay perfectly\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/references/video-recording.md (Tokens: ~265 | Category: anthropic-skill) -->\n# Video Recording\n\nCapture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).\n\n## Basic Recording\n\n```bash\n# Start recording\nplaywright-cli video-start\n\n# Perform actions\nplaywright-cli open https://example.com\nplaywright-cli snapshot\nplaywright-cli click e1\nplaywright-cli fill e2 \"test input\"\n\n# Stop and save\nplaywright-cli video-stop demo.webm\n```\n\n## Best Practices\n\n### 1. Use Descriptive Filenames\n\n```bash\n# Include context in filename\nplaywright-cli video-stop recordings/login-flow-2024-01-15.webm\nplaywright-cli video-stop recordings/checkout-test-run-42.webm\n```\n\n## Tracing vs Video\n\n| Feature  | Video                | Tracing                                  |\n| -------- | -------------------- | ---------------------------------------- |\n| Output   | WebM file            | Trace file (viewable in Trace Viewer)    |\n| Shows    | Visual recording     | DOM snapshots, network, console, actions |\n| Use case | Demos, documentation | Debugging, analysis                      |\n| Size     | Larger               | Smaller                                  |\n\n## Limitations\n\n- Recording adds slight overhead to automation\n- Large recordings can consume significant disk space\n\n<!-- File: examples/with-workspace/workspace/skills/playwright-cli/SKILL.md (Tokens: ~1555 | Category: anthropic-skill) -->\n---\nname: playwright-cli\ndescription: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\nallowed-tools: Bash(playwright-cli:*)\n---\n\n# Browser Automation with playwright-cli\n\n## Quick start\n\n```bash\n# open new browser\nplaywright-cli open\n# navigate to a page\nplaywright-cli goto https://playwright.dev\n# interact with the page using refs from the snapshot\nplaywright-cli click e15\nplaywright-cli type \"page.click\"\nplaywright-cli press Enter\n# take a screenshot\nplaywright-cli screenshot\n# close the browser\nplaywright-cli close\n```\n\n## Commands\n\n### Core\n\n```bash\nplaywright-cli open\n# open and navigate right away\nplaywright-cli open https://example.com/\nplaywright-cli goto https://playwright.dev\nplaywright-cli type \"search query\"\nplaywright-cli click e3\nplaywright-cli dblclick e7\nplaywright-cli fill e5 \"user@example.com\"\nplaywright-cli drag e2 e8\nplaywright-cli hover e4\nplaywright-cli select e9 \"option-value\"\nplaywright-cli upload ./document.pdf\nplaywright-cli check e12\nplaywright-cli uncheck e12\nplaywright-cli snapshot\nplaywright-cli snapshot --filename=after-click.yaml\nplaywright-cli eval \"document.title\"\nplaywright-cli eval \"el => el.textContent\" e5\nplaywright-cli dialog-accept\nplaywright-cli dialog-accept \"confirmation text\"\nplaywright-cli dialog-dismiss\nplaywright-cli resize 1920 1080\nplaywright-cli close\n```\n\n### Navigation\n\n```bash\nplaywright-cli go-back\nplaywright-cli go-forward\nplaywright-cli reload\n```\n\n### Keyboard\n\n```bash\nplaywright-cli press Enter\nplaywright-cli press ArrowDown\nplaywright-cli keydown Shift\nplaywright-cli keyup Shift\n```\n\n### Mouse\n\n```bash\nplaywright-cli mousemove 150 300\nplaywright-cli mousedown\nplaywright-cli mousedown right\nplaywright-cli mouseup\nplaywright-cli mouseup right\nplaywright-cli mousewheel 0 100\n```\n\n### Save as\n\n```bash\nplaywright-cli screenshot\nplaywright-cli screenshot e5\nplaywright-cli screenshot --filename=page.png\nplaywright-cli pdf --filename=page.pdf\n```\n\n### Tabs\n\n```bash\nplaywright-cli tab-list\nplaywright-cli tab-new\nplaywright-cli tab-new https://example.com/page\nplaywright-cli tab-close\nplaywright-cli tab-close 2\nplaywright-cli tab-select 0\n```\n\n### Storage\n\n```bash\nplaywright-cli state-save\nplaywright-cli state-save auth.json\nplaywright-cli state-load auth.json\n\n# Cookies\nplaywright-cli cookie-list\nplaywright-cli cookie-list --domain=example.com\nplaywright-cli cookie-get session_id\nplaywright-cli cookie-set session_id abc123\nplaywright-cli cookie-set session_id abc123 --domain=example.com --httpOnly --secure\nplaywright-cli cookie-delete session_id\nplaywright-cli cookie-clear\n\n# LocalStorage\nplaywright-cli localstorage-list\nplaywright-cli localstorage-get theme\nplaywright-cli localstorage-set theme dark\nplaywright-cli localstorage-delete theme\nplaywright-cli localstorage-clear\n\n# SessionStorage\nplaywright-cli sessionstorage-list\nplaywright-cli sessionstorage-get step\nplaywright-cli sessionstorage-set step 3\nplaywright-cli sessionstorage-delete step\nplaywright-cli sessionstorage-clear\n```\n\n### Network\n\n```bash\nplaywright-cli route \"**/*.jpg\" --status=404\nplaywright-cli route \"https://api.example.com/**\" --body='{\"mock\": true}'\nplaywright-cli route-list\nplaywright-cli unroute \"**/*.jpg\"\nplaywright-cli unroute\n```\n\n### DevTools\n\n```bash\nplaywright-cli console\nplaywright-cli console warning\nplaywright-cli network\nplaywright-cli run-code \"async page => await page.context().grantPermissions(['geolocation'])\"\nplaywright-cli tracing-start\nplaywright-cli tracing-stop\nplaywright-cli video-start\nplaywright-cli video-stop video.webm\n```\n\n### Install\n\n```bash\nplaywright-cli install --skills\nplaywright-cli install-browser\n```\n\n### Configuration\n\n```bash\n# Use specific browser when creating session\nplaywright-cli open --browser=chrome\nplaywright-cli open --browser=firefox\nplaywright-cli open --browser=webkit\nplaywright-cli open --browser=msedge\n# Connect to browser via extension\nplaywright-cli open --extension\n\n# Use persistent profile (by default profile is in-memory)\nplaywright-cli open --persistent\n# Use persistent profile with custom directory\nplaywright-cli open --profile=/path/to/profile\n\n# Start with config file\nplaywright-cli open --config=my-config.json\n\n# Close the browser\nplaywright-cli close\n# Delete user data for the default session\nplaywright-cli delete-data\n```\n\n### Browser Sessions\n\n```bash\n# create new browser session named \"mysession\" with persistent profile\nplaywright-cli -s=mysession open example.com --persistent\n# same with manually specified profile directory (use when requested explicitly)\nplaywright-cli -s=mysession open example.com --profile=/path/to/profile\nplaywright-cli -s=mysession click e6\nplaywright-cli -s=mysession close  # stop a named browser\nplaywright-cli -s=mysession delete-data  # delete user data for persistent session\n\nplaywright-cli list\n# Close all browsers\nplaywright-cli close-all\n# Forcefully kill all browser processes\nplaywright-cli kill-all\n```\n\n## Example: Form submission\n\n```bash\nplaywright-cli open https://example.com/form\nplaywright-cli snapshot\n\nplaywright-cli fill e1 \"user@example.com\"\nplaywright-cli fill e2 \"password123\"\nplaywright-cli click e3\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Multi-tab workflow\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tab-new https://example.com/other\nplaywright-cli tab-list\nplaywright-cli tab-select 0\nplaywright-cli snapshot\nplaywright-cli close\n```\n\n## Example: Debugging with DevTools\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli console\nplaywright-cli network\nplaywright-cli close\n```\n\n```bash\nplaywright-cli open https://example.com\nplaywright-cli tracing-start\nplaywright-cli click e4\nplaywright-cli fill e7 \"test\"\nplaywright-cli tracing-stop\nplaywright-cli close\n```\n\n## Specific tasks\n\n- **Request mocking** [references/request-mocking.md](references/request-mocking.md)\n- **Running Playwright code** [references/running-code.md](references/running-code.md)\n- **Browser session management** [references/session-management.md](references/session-management.md)\n- **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)\n- **Test generation** [references/test-generation.md](references/test-generation.md)\n- **Tracing** [references/tracing.md](references/tracing.md)\n- **Video recording** [references/video-recording.md](references/video-recording.md)\n\n<!-- File: website/docs/tools/overview.md (Tokens: ~2200 | Category: anthropic-skill) -->\n---\ntitle: Overview\n---\n\n# Tools & Toolkits\n\nVoltAgent allows you to extend the capabilities of your AI agents by providing them with **Tools**. Tools enable agents to interact with external APIs, perform calculations, access databases, or execute virtually any custom code. This guide covers how to define and use individual tools and the new `Toolkit` concept for managing related tools.\n\n## Defining a Single Tool\n\nThe most basic way to define a tool is using the `createTool` helper function (or instantiating the `Tool` class directly).\n\nA tool requires:\n\n- `name`: A unique name for the tool (used by the LLM to call it).\n- `description`: A clear description of what the tool does (used by the LLM to decide when to use it).\n- `parameters`: A Zod schema defining the input arguments the tool expects.\n- `execute`: An asynchronous function that contains the tool's logic, taking the validated arguments as input.\n- `outputSchema` (optional): A Zod schema defining the expected output format. When provided, the tool's output will be validated against this schema.\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define a simple weather tool\nconst getWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches the current weather for a given location.\",\n  parameters: z.object({\n    location: z.string().describe(\"The city and state, e.g., San Francisco, CA\"),\n  }),\n  execute: async ({ location }) => {\n    // In a real scenario, you would call a weather API here\n    console.log(`Fetching weather for ${location}...`);\n    if (location.toLowerCase().includes(\"tokyo\")) {\n      return { temperature: \"15°C\", condition: \"Cloudy\" };\n    }\n    return { temperature: \"22°C\", condition: \"Sunny\" };\n  },\n});\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that can fetch weather information.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeatherTool], // Add the tool to the agent\n});\n\n// Now the agent can use the 'get_weather' tool when asked about weather.\n```\n\n## Accessing Operation Context in Tools\n\nTools can access operation metadata, user context, and control mechanisms through the second parameter:\n\n```typescript\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Tool that uses operation context\nconst contextAwareWeatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Fetches weather with user preferences\",\n  parameters: z.object({\n    location: z.string().describe(\"The city name\"),\n  }),\n  execute: async ({ location }, options) => {\n    // Access user-defined context\n    const units = options?.context?.get(\"preferredUnits\") || \"celsius\";\n    const userId = options?.userId;\n\n    // Use operation-scoped logger\n    options?.logger?.info(`Fetching weather for ${location} in ${units}`);\n\n    // Check abort signal\n    if (options?.abortSignal?.aborted) {\n      throw new Error(\"Request was cancelled\");\n    }\n\n    // Call weather API with user preferences\n    const response = await fetch(\n      `https://api.weather.com/current?city=${location}&units=${units}`,\n      { signal: options?.abortSignal }\n    );\n\n    return await response.json();\n  },\n});\n\n// Use the tool with context\nconst context = new Map();\ncontext.set(\"preferredUnits\", \"fahrenheit\");\n\nconst agent = new Agent({\n  name: \"WeatherAgent\",\n  instructions: \"An agent that respects user preferences\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [contextAwareWeatherTool],\n});\n\nconst response = await agent.generateText(\"What's the weather in Paris?\", {\n  userId: \"user123\",\n  context,\n});\n```\n\nThe `options` parameter includes:\n\n- **Operation metadata**: `operationId`, `userId`, `conversationId`\n- **User context**: `context` (Map for custom data)\n- **Control mechanisms**: `abortController`, `abortSignal`\n- **Logging**: `logger` (operation-scoped logger)\n- **AI SDK data**: `toolCallId`, `messages`\n\nFor more details, see the [Tools documentation](/docs/agents/tools#accessing-operation-context).\n\n## Tool Output Schema Validation\n\nVoltAgent supports optional output schema validation for tools. This feature ensures that tool outputs conform to a predefined structure, providing several benefits:\n\n- **Type Safety**: Tool outputs are typed based on the schema\n- **Runtime Validation**: Invalid outputs are caught immediately\n- **Error Recovery**: When validation fails, the LLM receives an error message and can retry with corrected output\n- **Consistency**: All tool responses follow the same structure\n- **Documentation**: Output schemas serve as API contracts\n\n### Example with Output Schema\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Define the output schema\nconst weatherOutputSchema = z.object({\n  location: z.string(),\n  temperature: z.number(),\n  condition: z.enum([\"sunny\", \"cloudy\", \"rainy\", \"snowy\"]),\n  humidity: z.number().min(0).max(100),\n  forecast: z.object({\n    high: z.number(),\n    low: z.number(),\n    description: z.string(),\n  }),\n});\n\n// Create a tool with output validation\nconst weatherTool = createTool({\n  name: \"get_weather\",\n  description: \"Get current weather with forecast\",\n  parameters: z.object({\n    location: z.string().describe(\"City name\"),\n  }),\n  outputSchema: weatherOutputSchema, // Optional output schema\n  execute: async ({ location }) => {\n    // This output will be validated against weatherOutputSchema\n    return {\n      location,\n      temperature: 22,\n      condition: \"sunny\",\n      humidity: 65,\n      forecast: {\n        high: 25,\n        low: 18,\n        description: \"Clear skies throughout the day\",\n      },\n    };\n  },\n});\n```\n\n### How Output Validation Works\n\n1. When a tool is executed, its output is validated against the `outputSchema` if provided\n2. If validation succeeds, the validated output is returned\n3. If validation fails, an error object is returned to the LLM:\n   ```json\n   {\n     \"error\": true,\n     \"message\": \"Output validation failed: Expected number, received string\",\n     \"validationErrors\": [...],\n     \"actualOutput\": {...}\n   }\n   ```\n4. The LLM can see the validation error and potentially fix the issue by calling the tool again\n\n### Best Practices\n\n- Use output schemas for tools that need consistent response formats\n- Keep schemas focused and avoid overly complex nested structures\n- Use descriptive error messages in your schemas with `.describe()`\n- Consider making some fields optional with `.optional()` for flexibility\n- Output schemas are completely optional - tools without them work as before\n\n## Grouping Tools with Toolkits\n\nOften, several tools work together logically. For instance, tools for step-by-step reasoning (`think`, `analyze`) or a set of tools interacting with the same API. For these scenarios, VoltAgent provides **Toolkits**.\n\nA `Toolkit` allows you to:\n\n1.  **Group related tools:** Keep your tool management organized.\n2.  **Define shared instructions:** Provide common guidance to the LLM on how to use all tools within the toolkit.\n3.  **Control instruction injection:** Decide if the toolkit's shared instructions should be automatically added to the agent's system prompt.\n\n### Defining a Toolkit\n\nA `Toolkit` is an object with the following structure:\n\n```typescript\nimport { createTool, createToolkit, type Tool, type Toolkit } from \"@voltagent/core\";\n\nconst myCalculatorToolkit = createToolkit({\n  name: \"calculator_toolkit\",\n  description: \"Tools for performing basic arithmetic operations.\",\n  // Optional instructions for the LLM\n  instructions: `Use these tools for calculations. Always use 'add' for addition, 'subtract' for subtraction.`,\n  // Set to true to add the above instructions to the system prompt\n  addInstructions: true,\n  tools: [\n    createTool({\n      /* ... definition for 'add' tool ... */\n    }),\n    createTool({\n      /* ... definition for 'subtract' tool ... */\n    }),\n    // ... other calculator tools\n  ],\n});\n```\n\n**Important:** With the introduction of Toolkits, individual `Tool` instances no longer have their own `instructions` or `addInstructions` properties. Instructions are managed at the Toolkit level.\n\n### Adding Tools and Toolkits to an Agent\n\nThe `tools` option in the `Agent` constructor now accepts an array containing both individual `Tool` objects and `Toolkit` objects. The `ToolManager` handles both seamlessly.\n\n```typescript\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool, createToolkit, type Toolkit } from \"@voltagent/core\";\n// ... import other tools and toolkits ...\n\nconst agent = new Agent({\n  name: \"MultiToolAgent\",\n  instructions: \"An agent with various tools and toolkits.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [\n    getWeatherTool, // Add an individual tool\n    myCalculatorToolkit, // Add a toolkit\n    openai.tools.webSearch(), // Add a provider-defined tool\n    // ... other tools or toolkits\n  ],\n});\n```\n\n### Automatic Instructions\n\nWhen an agent is initialized, its `getSystemMessage` method checks all the `Toolkit`s provided in the `tools` array. If a `Toolkit` has `addInstructions: true` and defines an `instructions` string, those instructions will be automatically appended to the agent's base description, forming part of the final system prompt sent to the LLM.\n\n## Provider-defined Tools\n\nSome providers expose their own tools (via the Vercel AI SDK).\n\n- They can be standalone tools or live inside Toolkits and are subject to name-conflict checks when adding toolkits.\n- They are not executable on your server via the usual `Tool.execute` handler — the provider manages them.\n\n## Next Steps\n\n- See the [Reasoning Tools](/docs/tools/reasoning-tool/) documentation for a practical example of a pre-built toolkit.\n- Explore creating your own tools to connect agents to your specific data sources and APIs.\n\n<!-- File: website/docs/tools/tool-routing.md (Tokens: ~1420 | Category: anthropic-skill) -->\n---\ntitle: Tool Routing\n---\n\n# Tool Routing\n\nTool routing keeps the full tool pool hidden and exposes two system tools to the model:\n\n- `searchTools` returns tool metadata and schemas for a query.\n- `callTool` executes a tool by name with validated arguments.\n\nOnly `searchTools`, `callTool`, and any tools listed in `toolRouting.expose` are visible to the model.\n\n## How Tool Routing Works\n\n- Tool routing builds a hidden pool from `toolRouting.pool` or all registered tools (by default).\n- The model calls `searchTools` with the user request and receives tool details.\n- The model calls `callTool` with the exact tool name and schema-compliant arguments.\n- Pool tools stay hidden unless explicitly exposed.\n\n## Quick Setup With Embedding Search\n\nThis configuration enables embedding-based tool search and exposes only `searchTools` and `callTool` to the model.\n\n```ts\nimport { openai } from \"@ai-sdk/openai\";\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    temperatureC: 22,\n    condition: \"sunny\",\n  }),\n});\n\nconst getTimeZone = createTool({\n  name: \"get_time_zone\",\n  description: \"Get the time zone offset for a city\",\n  parameters: z.object({\n    location: z.string(),\n  }),\n  execute: async ({ location }) => ({\n    location,\n    timeZone: \"UTC+1\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Tool Routing Agent\",\n  instructions:\n    \"When you need a tool, call searchTools with the user request, then call callTool with the exact tool name and schema-compliant arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [getWeather, getTimeZone],\n  toolRouting: {\n    embedding: \"openai/text-embedding-3-small\",\n    topK: 2,\n  },\n});\n```\n\nNotes:\n\n- If `toolRouting.pool` is not set, the pool defaults to all tools registered in the agent.\n- If `toolRouting.expose` is not set, only `searchTools` and `callTool` are visible to the model.\n\n## Tool Pool and Exposed Tools\n\nUse `pool` to define the hidden tool set and `expose` to keep a subset visible.\n\n```ts\nimport { Agent, createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst getWeather = createTool({\n  name: \"get_weather\",\n  description: \"Get the current weather for a city\",\n  parameters: z.object({ location: z.string() }),\n  execute: async ({ location }) => ({ location, temperatureC: 22 }),\n});\n\nconst getStatus = createTool({\n  name: \"get_status\",\n  description: \"Return the service status\",\n  parameters: z.object({}),\n  execute: async () => ({ status: \"ok\" }),\n});\n\nconst agent = new Agent({\n  name: \"Support Agent\",\n  instructions:\n    \"Use searchTools to find tool schemas, then call callTool with validated arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    pool: [getWeather],\n    expose: [getStatus],\n  },\n});\n```\n\n## Enforcing Search Before Call\n\nBy default, `callTool` requires the tool to appear in a prior `searchTools` result.\nYou can disable this behavior:\n\n```ts\nconst agent = new Agent({\n  name: \"Relaxed Agent\",\n  instructions: \"Use searchTools before callTool when possible.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    pool: [\n      /* tools and toolkits */\n    ],\n    enforceSearchBeforeCall: false,\n  },\n});\n```\n\n## Embedding Configuration\n\n`toolRouting.embedding` accepts either a model string or an object with extra options.\n\n- `\"openai/text-embedding-3-small\"`\n- `\"text-embedding-3-small\"`\n\nProvider-qualified strings use the same registry and type list as agent model strings.\n\n```ts\nconst agent = new Agent({\n  name: \"Custom Embedding Agent\",\n  instructions:\n    \"Use searchTools to discover tool schemas, then call callTool with the right arguments.\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: {\n      model: \"openai/text-embedding-3-small\",\n      topK: 3,\n      toolText: (tool) => {\n        const tags = tool.tags?.join(\", \") ?? \"\";\n        return [tool.name, tool.description, tags].filter(Boolean).join(\"\\n\");\n      },\n    },\n  },\n});\n```\n\nThe embedding search caches vectors in memory. The cache is reset when the process restarts.\nA tool text change for a given tool name triggers a new embedding for that tool.\n\n## Per-Call Overrides\n\nYou can disable tool routing for a single call or provide a temporary routing config.\n\n```ts\nconst result = await agent.generateText(\"List tools\", {\n  toolRouting: false,\n});\n\nconst routed = await agent.generateText(\"What time is it in Berlin?\", {\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n    topK: 1,\n  },\n});\n```\n\n## Hooks, Approval, and Error Handling\n\n- `needsApproval`, input/output guardrails, and tool hooks still run when tools are executed via `callTool`.\n- If a tool is not in the pool, `callTool` returns a tool error.\n- `callTool` validates arguments against the target schema and surfaces validation errors for retries.\n- Provider tools and MCP tools can be pooled; execution still runs through approvals and hooks.\n\n## Observability\n\nTool routing adds spans for selection and embedding steps:\n\n- `tool.search.selection:*` spans include `tool.search.candidates`, `tool.search.selection.count`, and selected names.\n- `tool.search.embedding:*` spans include `embedding.model`, `embedding.dimensions`, and cache hit/miss counts.\n\nThese spans appear under the `searchTools` tool span in the execution trace.\n\n## PlanAgent\n\n`PlanAgent` accepts `toolRouting` in its options. It passes routing config to its internal agent.\n\n```ts\nimport { PlanAgent } from \"@voltagent/core\";\n\nconst agent = new PlanAgent({\n  name: \"Planning Agent\",\n  model: \"openai/gpt-4o-mini\",\n  toolRouting: {\n    embedding: \"text-embedding-3-small\",\n  },\n});\n```\n\n<!-- File: website/docs/agents/a2a/a2a-server.md (Tokens: ~2102 | Category: subagent-persona) -->\n---\ntitle: A2A Server\ndescription: Step-by-step guide for exposing VoltAgent over the Agent-to-Agent protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent A2A Server Quick Start\n\nFollow the steps below to let other agents talk to your VoltAgent application over the Agent-to-Agent (A2A) protocol.\n\n## Install Packages\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/a2a-server @voltagent/server-hono\n    ```\n  </TabItem>\n</Tabs>\n\n`@voltagent/a2a-server` exposes VoltAgent agents via JSON-RPC. `@voltagent/server-hono` wires the routes into the default Hono server (feel free to swap it out for another framework later).\n\n## Create An A2A Server Instance\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  description: \"VoltAgent A2A example\",\n  provider: {\n    organization: \"Acme\",\n    url: \"https://acme.example\",\n  },\n});\n```\n\nThe server metadata feeds the discovery card served from `/.well-known/{serverId}/agent-card.json`. The card's `url` field points to `/a2a/{serverId}`, and when the card is fetched over HTTP VoltAgent returns that URL as an absolute address.\n\n## Register The Server With VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { a2aServer } from \"./a2a/server\";\nimport { assistant } from \"./agents/assistant\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  a2aServers: {\n    a2aServer,\n  },\n  server: honoServer({ port: 3141 }),\n});\n```\n\nWith this in place, VoltAgent automatically exposes:\n\n- `GET /.well-known/{serverId}/agent-card.json`\n- `POST /a2a/{serverId}`\n\nThe JSON-RPC handler accepts `message/send`, `message/stream`, `tasks/get`, and `tasks/cancel` requests.\n\n## Available Endpoints\n\n| Method | Path                                      | Description                                                                                                      |\n| ------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |\n| `GET`  | `/.well-known/{serverId}/agent-card.json` | Returns the discovery card for the specified A2A server. The card's `url` points to `/a2a/{serverId}`.           |\n| `POST` | `/a2a/{serverId}`                         | Accepts JSON-RPC 2.0 requests. Supported methods: `message/send`, `message/stream`, `tasks/get`, `tasks/cancel`. |\n\nExample JSON-RPC payload for `message/send`:\n\n```json\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    \"message\": {\n      \"kind\": \"message\",\n      \"role\": \"user\",\n      \"messageId\": \"msg-1\",\n      \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }]\n    }\n  }\n}\n```\n\nYou can include an optional `context` object alongside the JSON-RPC payload to forward metadata such as `userId`, `sessionId`, or arbitrary `metadata` fields:\n\n```json\n{\n  \"context\": {\n    \"userId\": \"user-42\",\n    \"sessionId\": \"chat-1\",\n    \"metadata\": { \"tenant\": \"acme\" }\n  },\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": {\n    /* ... */\n  }\n}\n```\n\nThe context values surface inside the agent call so you can track callers or reuse an existing conversation identifier. `context.userId` is forwarded directly as the VoltAgent `userId` option, while `sessionId` and any nested `metadata` keys are merged into the agent's `options.context` map together with the request-level `params.metadata` and per-message `message.metadata` fields.\n\nIf you cannot modify the JSON body, send the same object as a JSON-encoded query parameter. VoltAgent reads `context` (or the legacy `runtimeContext`) from the query string and merges it with the body-provided values, letting the body override duplicated keys while metadata objects are combined.\n\n```bash\ncurl \\\n  -X POST \"http://localhost:3141/a2a/support-agent?context=%7B%22userId%22%3A%22user-42%22%7D\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n  \"jsonrpc\": \"2.0\",\n  \"id\": \"req-1\",\n  \"method\": \"message/send\",\n  \"params\": { \"message\": { \"kind\": \"message\", \"role\": \"user\", \"messageId\": \"msg-1\", \"parts\": [{ \"kind\": \"text\", \"text\": \"Hello\" }] } }\n}'\n```\n\n## Configure Which Agents Are Exposed (Optional)\n\nYou can publish agents that are not part of the primary VoltAgent registry or filter the combined list before answering A2A requests:\n\n```ts title=\"src/a2a/server.ts\"\nimport { A2AServer } from \"@voltagent/a2a-server\";\nimport { assistant } from \"./agents/assistant\";\nimport { shadowAgent } from \"./agents/shadow\";\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n  agents: {\n    \"shadow-agent\": shadowAgent,\n  },\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n});\n```\n\nConfigured agents appear alongside registry entries. The filter receives the merged list and can drop or reorder entries before discovery and JSON-RPC calls run.\n\nVoltAgent already formats Server-Sent Events when you use `@voltagent/server-hono`, so no extra work is needed for streaming clients. If you forward chunks manually, serialize each payload with `safeStringify` and prepend `\\x1E` before the JSON string.\n\n## Provide A Different Task Store (Optional)\n\nBy default, `A2AServer` uses an in-memory store. Provide your own implementation (Redis, Postgres, KV…) by implementing the `TaskStore` interface:\n\n```ts title=\"src/a2a/redis-store.ts\"\nimport { createClient } from \"redis\";\nimport type { TaskRecord, TaskStore } from \"@voltagent/a2a-server\";\n\nexport class RedisTaskStore implements TaskStore {\n  private client = createClient({ url: process.env.REDIS_URL });\n\n  async load({ agentId, taskId }: { agentId: string; taskId: string }): Promise<TaskRecord | null> {\n    const raw = await this.client.get(`${agentId}::${taskId}`);\n    return raw ? (JSON.parse(raw) as TaskRecord) : null;\n  }\n\n  async save({ agentId, data }: { agentId: string; data: TaskRecord }): Promise<void> {\n    await this.client.set(`${agentId}::${data.id}`, JSON.stringify(data));\n  }\n}\n```\n\nPass the store during initialisation:\n\n```ts title=\"src/a2a/server.ts\"\nimport { RedisTaskStore } from \"./redis-store\";\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { assistant } from \"../agents/assistant\";\n\nconst redisTaskStore = new RedisTaskStore();\n\nexport const a2aServer = new A2AServer({\n  name: \"support-agent\",\n  version: \"0.1.0\",\n});\n\nexport const voltAgent = new VoltAgent({\n  agents: { assistant },\n  a2aServers: { a2aServer },\n  server: honoServer({ port: 3141 }),\n});\n\na2aServer.initialize({\n  agentRegistry: voltAgent.agentRegistry,\n  taskStore: redisTaskStore,\n});\n```\n\nRemember to implement TTL or pruning inside your custom store if you need to cap history growth.\n\n## Run The Example Smoke Test\n\nCreate the example project and run its smoke test to ensure everything is wired correctly:\n\n```bash\nnpm create voltagent-app@latest -- --example with-a2a-server\ncd with-a2a-server\npnpm test:smoke\n```\n\nThe test sends a `message/send`, streams a `message/stream`, and exercises `tasks/cancel` to verify streaming cancellation propagation.\n\n## Troubleshooting checklist\n\n- **404 for discovery card**: ensure the `serverId` in `/.well-known/{serverId}/agent-card.json` matches `A2AServer({ id })`, or the normalized `name` when `id` is omitted. It does not come from `VoltAgent({ agents: { ... } })` or the `a2aServers` map key.\n- **Unexpected JSON in SSE**: confirm you are stripping the `\\x1E` prefix before parsing the JSON payload.\n- **Cancellation not propagating**: verify you call `tasks/cancel` with the task ID from the stream and that your TaskStore preserves the `activeCancellations` set.\n\n<!-- File: website/docs/agents/cancellation.md (Tokens: ~2208 | Category: subagent-persona) -->\n---\ntitle: Cancellation\nslug: /agents/cancellation\n---\n\n# Cancellation\n\nVoltAgent implements cancellation through the standard `AbortController` API. Pass an `AbortController` instance to agent methods to stop LLM generation, tool execution, and multi-agent workflows.\n\n## Basic Cancellation\n\nCreate an `AbortController` and pass it to agent methods:\n\n```typescript\nimport { Agent, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst abortController = new AbortController();\n\n// Cancel after 5 seconds\nsetTimeout(() => {\n  abortController.abort(\"Timeout: Operation took too long\");\n}, 5000);\n\ntry {\n  const response = await agent.generateText(\"Write a detailed analysis...\", {\n    abortController,\n  });\n  console.log(response.text);\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"Operation cancelled:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## How Cancellation Works\n\nWhen you provide an `AbortController` to an agent method:\n\n1. The signal is passed to the LLM provider\n2. Tools receive the `OperationContext` containing the `abortController`\n3. Subagents inherit the parent's `abortController`\n4. All operations check the signal state before proceeding\n\nCancellation propagates through the entire operation chain for clean shutdown at every level.\n\n:::note Parameter Deprecation\nThe `signal` parameter is deprecated. Use `abortController` instead. The `signal` parameter will be removed in a future version.\n:::\n\n## Streaming Cancellation\n\nCancellation works with streaming responses:\n\n```typescript\nconst abortController = new AbortController();\n\n// User can cancel anytime\ndocument.getElementById(\"stop-btn\")?.addEventListener(\"click\", () => {\n  abortController.abort(\"User requested stop\");\n});\n\nconst response = await agent.streamText(\"Generate a long report...\", {\n  abortController,\n});\n\ntry {\n  for await (const chunk of response.textStream) {\n    process.stdout.write(chunk);\n  }\n} catch (error) {\n  if (isAbortError(error)) {\n    console.log(\"\\nStream cancelled\");\n  }\n}\n```\n\nWith `fullStream`, you receive detailed cancellation feedback:\n\n```typescript\nconst response = await agent.streamText(\"Complex task...\", {\n  abortController,\n});\n\nfor await (const event of response.fullStream) {\n  if (event.type === \"error\" && isAbortError(event.error)) {\n    console.log(\"Cancelled during:\", event.context);\n    break;\n  }\n  // Process other events\n}\n```\n\n## Tool Cancellation\n\nTools receive the `OperationContext` as the second parameter. Access `abortController` through the context to respond to or trigger cancellation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst dataProcessingTool = createTool({\n  name: \"process_data\",\n  description: \"Process large datasets\",\n  parameters: z.object({\n    dataset: z.string(),\n    operation: z.string(),\n  }),\n  execute: async (args, context) => {\n    const abortController = context?.abortController;\n    const signal = abortController?.signal;\n\n    // Check if already aborted\n    if (signal?.aborted) {\n      return { error: \"Operation was already cancelled\" };\n    }\n\n    // Tool can trigger abort based on conditions\n    if (args.dataset === \"restricted\") {\n      abortController?.abort(\"Access to restricted dataset denied\");\n      return { error: \"Dataset access denied\" };\n    }\n\n    // Pass signal to async operations\n    try {\n      const response = await fetch(`/api/process/${args.dataset}`, {\n        method: \"POST\",\n        body: JSON.stringify({ operation: args.operation }),\n        signal,\n      });\n\n      return await response.json();\n    } catch (error) {\n      if (error.name === \"AbortError\") {\n        return { error: \"Processing cancelled\" };\n      }\n      throw error;\n    }\n  },\n});\n```\n\n## Multi-Agent Cancellation\n\nIn supervisor-subagent architectures, the abort signal propagates to all subagents:\n\n```typescript\nconst researcher = new Agent({\n  name: \"Researcher\",\n  instructions: \"Research topics thoroughly\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst writer = new Agent({\n  name: \"Writer\",\n  instructions: \"Write detailed content\",\n  model: \"openai/gpt-4o-mini\",\n});\n\nconst supervisor = new Agent({\n  name: \"Supervisor\",\n  instructions: \"Coordinate research and writing\",\n  model: \"openai/gpt-4o\",\n  subAgents: [researcher, writer],\n});\n\nconst abortController = new AbortController();\n\n// Cancel supervisor and any active subagent operations\nsetTimeout(() => {\n  abortController.abort(\"Deadline reached\");\n}, 10000);\n\nconst response = await supervisor.streamText(\"Research and write about quantum computing\", {\n  abortController,\n});\n```\n\nWhen the supervisor is cancelled:\n\n- Any active `delegate_task` operations stop\n- Subagents inherit the parent's `abortController`\n- All tool executions within subagents are cancelled\n- The entire workflow stops\n\n## Timeout Implementation\n\nImplement timeouts for operations:\n\n```typescript\nconst abortController = new AbortController();\n\n// Set a timeout to abort after 30 seconds\nconst timeoutId = setTimeout(() => {\n  abortController.abort(\"Operation timeout - exceeded 30 seconds\");\n}, 30000);\n\ntry {\n  const response = await agent.generateText(\"Complex analysis...\", {\n    abortController,\n  });\n\n  // Clear timeout if operation completes\n  clearTimeout(timeoutId);\n  console.log(response.text);\n} catch (error) {\n  clearTimeout(timeoutId); // Always clear the timeout\n\n  if (isAbortError(error)) {\n    console.log(\"Operation timed out:\", error.message);\n  } else {\n    throw error;\n  }\n}\n```\n\n## Error Detection with Hooks\n\nAgent hooks can detect and handle abort errors:\n\n```typescript\nimport { createHooks, isAbortError } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"A helpful assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ error, context }) => {\n      if (isAbortError(error)) {\n        // Handle abort - error.name === \"AbortError\"\n        console.log(\"Operation aborted:\", error.message);\n\n        // Cleanup resources\n        await cleanup(context);\n\n        // Log metrics\n        await logAbortMetrics({\n          operationId: context.operationId,\n          reason: error.message,\n          duration: Date.now() - context.startTime,\n        });\n      } else if (error) {\n        // Handle other errors\n        console.error(\"Operation failed:\", error);\n      }\n    },\n  }),\n});\n```\n\n## REST API Cancellation\n\nClients can cancel REST API requests by closing the connection:\n\n```typescript\n// Client-side cancellation\nconst controller = new AbortController();\n\nconst response = await fetch(\"http://localhost:3141/agents/my-agent/stream\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({\n    input: \"Generate a comprehensive report\",\n  }),\n  signal: controller.signal,\n});\n\n// Cancel button\ndocument.getElementById(\"cancel\")?.addEventListener(\"click\", () => {\n  controller.abort();\n});\n\n// Process stream\nconst reader = response.body?.getReader();\nif (reader) {\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      // Process chunk\n    }\n  } catch (error) {\n    if (error.name === \"AbortError\") {\n      console.log(\"Stream cancelled\");\n    }\n  }\n}\n```\n\nThe server detects client disconnection and aborts the agent operation.\n\n## Cancellation States\n\nOperations can be in different states regarding cancellation:\n\n```typescript\nconst abortController = new AbortController();\n\n// Check if aborted\nif (abortController.signal.aborted) {\n  console.log(\"Already aborted\");\n}\n\n// Listen for abort\nabortController.signal.addEventListener(\"abort\", () => {\n  console.log(\"Just aborted:\", abortController.signal.reason);\n});\n\n// Trigger abort with reason\nabortController.abort(\"User cancelled\");\n```\n\n## Implementation Patterns\n\n### Graceful Shutdown\n\nFor individual agent operations, manually handle shutdown signals:\n\n```typescript\nasync function processWithGracefulShutdown(agent: Agent, input: string) {\n  const abortController = new AbortController();\n\n  // Handle shutdown signals\n  const shutdownHandler = () => {\n    console.log(\"Shutting down...\");\n    abortController.abort(\"System shutdown\");\n  };\n\n  process.on(\"SIGINT\", shutdownHandler);\n  process.on(\"SIGTERM\", shutdownHandler);\n\n  try {\n    const response = await agent.generateText(input, {\n      abortController,\n    });\n    return response.text;\n  } finally {\n    process.off(\"SIGINT\", shutdownHandler);\n    process.off(\"SIGTERM\", shutdownHandler);\n  }\n}\n```\n\n### VoltAgent-Level Shutdown\n\nWhen using the `VoltAgent` class with a server, shutdown is handled automatically:\n\n```typescript\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst voltAgent = new VoltAgent({\n  agents: { myAgent },\n  server: honoServer({ port: 3141 }),\n});\n\n// VoltAgent automatically registers SIGINT/SIGTERM handlers\n// For programmatic shutdown:\nawait voltAgent.shutdown(); // Stops server, workflows, and telemetry\n```\n\nVoltAgent automatically registers SIGINT/SIGTERM handlers that clean up the server, suspend workflows, and shutdown telemetry. See [Server Architecture - Graceful Shutdown](../api/server-architecture.md#graceful-shutdown) for details.\n\n### Concurrent Operations with Cancellation\n\n```typescript\nasync function processMultiple(agent: Agent, inputs: string[]) {\n  const abortController = new AbortController();\n\n  // Cancel all if any fails\n  const promises = inputs.map(async (input) => {\n    try {\n      return await agent.generateText(input, { abortController });\n    } catch (error) {\n      abortController.abort(\"One operation failed\");\n      throw error;\n    }\n  });\n\n  return Promise.all(promises);\n}\n```\n\n## Next Steps\n\n- Learn about [Tools](./tools.md) and how they handle cancellation\n- Explore [Sub-Agents](./subagents.md) for multi-agent cancellation patterns\n- See [Hooks](./hooks.md) for cancellation detection and handling\n\n<!-- File: website/docs/agents/context.md (Tokens: ~6503 | Category: subagent-persona) -->\n---\ntitle: Operation Context (context)\nslug: /agents/context\ndescription: Pass custom data through agent operations using context.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Operation Context (`context`)\n\nThe `context` field in `OperationContext` is a Map that passes custom data throughout a single agent operation. All components (hooks, tools, retrievers, sub-agents) can read and write to this shared data structure during execution.\n\n## Basic Concept\n\nHere's how `context` flows through an agent operation:\n\n```\nYou → Agent → Hooks → Tools → Retrievers → Memory → Sub-Agents\n     ↑                                                        ↓\n     ← ← ← ← ← ← context flows everywhere ← ← ← ← ← ← ← ← ←\n```\n\n## Initialize context\n\nYou can provide initial data in two ways:\n\n### Method 1: Set Default Context in Constructor\n\nSet default `context` when creating the agent. This context is used for all operations unless overridden at execution time:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\n// Set default context at agent creation\nconst defaultContext = new Map();\ndefaultContext.set(\"environment\", \"production\");\ndefaultContext.set(\"projectId\", \"my-project\");\n\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  context: defaultContext, // Default context for all operations\n});\n\n// Uses default context automatically\nconst response1 = await agent.generateText(\"Hello!\");\nconsole.log(\"Environment:\", response1.context?.get(\"environment\")); // \"production\"\n\n// Override with execution context (replaces default completely)\nconst response2 = await agent.generateText(\"Debug this\", {\n  context: new Map([\n    [\"environment\", \"development\"],\n    [\"projectId\", \"my-project\"],\n  ]),\n});\nconsole.log(\"Environment:\", response2.context?.get(\"environment\")); // \"development\"\nconsole.log(\"Project ID:\", response2.context?.get(\"projectId\")); // \"my-project\"\n```\n\n### Method 2: Pass Context During Execution\n\nPass `context` when calling the agent to provide operation-specific data:\n\n```typescript\nconst agent = new Agent({\n  name: \"SimpleAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Pass context when calling the agent\nconst executionContext = new Map();\nexecutionContext.set(\"language\", \"English\");\n\nconst response = await agent.generateText(\"Hello!\", {\n  context: executionContext,\n});\n// Now you can access the data from the response\nconsole.log(\"Language:\", response.context?.get(\"language\"));\n```\n\n## Sending User Information\n\nYou can pass user information to the agent using the `context`. This information is automatically picked up by the tracing system and associated with the trace.\n\nSupported fields:\n\n- `user.name`: User's full name\n- `user.email`: User's email address\n- `user.avatar`: User's avatar URL (optional, falls back to Gravatar using email if not provided)\n- `user.metadata`: Additional metadata object\n\n<Tabs>\n  <TabItem value=\"sdk\" label=\"SDK\" default>\n\nWhen using the SDK, pass user information in the `context` Map:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({ ... });\n\nconst context = new Map();\ncontext.set(\"user.name\", \"John Doe\");\ncontext.set(\"user.email\", \"john@example.com\");\ncontext.set(\"user.avatar\", \"https://example.com/avatar.jpg\");\ncontext.set(\"user.metadata\", { plan: \"pro\" });\n\nawait agent.generateText(\"Hello\", { userId: \"user_123\", context });\n```\n\n  </TabItem>\n  <TabItem value=\"api\" label=\"REST API\">\n\nWhen using the REST API, you can pass user information in the `context` object. You can use either a nested `user` object or flat keys.\n\n**Option 1: Nested User Object**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user\": {\n      \"name\": \"John Doe\",\n      \"email\": \"john@example.com\",\n      \"avatar\": \"https://example.com/avatar.jpg\",\n      \"metadata\": { \"plan\": \"pro\" }\n    }\n  }\n}\n```\n\n**Option 2: Flat Keys**\n\n```json\n{\n  \"prompt\": \"Hello\",\n  \"userId\": \"user_123\",\n  \"context\": {\n    \"user.name\": \"John Doe\",\n    \"user.email\": \"john@example.com\",\n    \"user.avatar\": \"https://example.com/avatar.jpg\",\n    \"user.metadata\": { \"plan\": \"pro\" }\n  }\n}\n```\n\n  </TabItem>\n</Tabs>\n\n## Hooks Access context\n\nHooks can read and write to `context`:\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"HookAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Read data that was passed in\n      const language = context.context.get(\"language\");\n      console.log(`Starting operation for language: ${language}`);\n\n      // Add new data\n      context.context.set(\"requestId\", `req-${Date.now()}`);\n      context.context.set(\"startTime\", new Date().toISOString());\n    },\n    onEnd: (context) => {\n      // Read data from context\n      const requestId = context.context.get(\"requestId\");\n      const startTime = context.context.get(\"startTime\");\n      console.log(`Request ${requestId} completed (started at ${startTime})`);\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\n\nawait agent.generateText(\"Hello!\", { context });\n```\n\n## Tools Access Context\n\nTools receive the full operation context through the second parameter (`options`), which includes all `OperationContext` fields directly accessible:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst loggerTool = createTool({\n  name: \"log_message\",\n  description: \"Logs a message with user context\",\n  parameters: z.object({\n    message: z.string(),\n  }),\n  execute: async ({ message }, options) => {\n    // Access user-defined context directly\n    const language = options?.context?.get(\"language\");\n    const requestId = options?.context?.get(\"requestId\");\n\n    // Access operation metadata\n    const userId = options?.userId;\n    const operationId = options?.operationId;\n\n    // Use the operation-scoped logger\n    options?.logger?.info(`[${requestId}] User ${userId}: ${message}`);\n\n    console.log(`[${requestId}] Language ${language}: ${message}`);\n\n    // Write to context\n    const ctx = options?.context;\n    if (ctx) {\n      const logs = ctx.get(\"logs\") || [];\n      logs.push({\n        message,\n        timestamp: new Date().toISOString(),\n        userId,\n        operationId,\n      });\n      ctx.set(\"logs\", logs);\n    }\n\n    return `Message logged for language ${language}`;\n  },\n});\n\nconst agentWithTool = new Agent({\n  name: \"ToolAgent\",\n  model: \"openai/gpt-4o\",\n  tools: [loggerTool],\n  instructions: \"Use the log_message tool to log what the user says.\",\n});\n\n// Usage\nconst context = new Map();\ncontext.set(\"language\", \"English\");\ncontext.set(\"requestId\", \"req-456\");\n\nconst response = await agentWithTool.generateText(\"Log this: Hello world!\", {\n  context,\n  userId: \"user123\",\n});\n\n// Check what was logged\nconst logs = response.context?.get(\"logs\");\nconsole.log(\"All logs:\", logs);\n```\n\n### What's Available in Tool Options?\n\nThe `options` parameter in tool `execute` functions combines `ToolExecuteOptions` and `Partial<OperationContext>`:\n\n**From OperationContext:**\n\n- `operationId` - Unique operation identifier\n- `userId` - User identifier (if provided)\n- `conversationId` - Conversation identifier (if provided)\n- `workspace` - Workspace instance configured on the agent (if available)\n- `context` - User-defined context Map (read/write)\n- `systemContext` - Internal system context Map\n- `logger` - Operation-scoped logger with full context\n- `abortController` - For operation cancellation\n- `traceContext` - OpenTelemetry trace context\n- `input` - Original input to the agent\n- `isActive` - Whether operation is still active\n\n**Tool-specific context (from `toolContext?` - optional):**\n\n> **Note:** `toolContext` is always populated when your tool is called from a VoltAgent agent. It may be `undefined` when called externally.\n\n- `toolContext?.name` - Name of the tool being executed\n- `toolContext?.callId` - Unique identifier for this tool call (from AI SDK)\n- `toolContext?.messages` - Message history at tool call time (from AI SDK)\n- `toolContext?.abortSignal` - Abort signal for cancellation (from AI SDK)\n\n```typescript\n// Example: Authorization check using context\nconst sensitiveDataTool = createTool({\n  name: \"get_sensitive_data\",\n  description: \"Retrieves sensitive user data\",\n  parameters: z.object({\n    dataType: z.string(),\n  }),\n  execute: async ({ dataType }, options) => {\n    // Check user role from context\n    const userRole = options?.context?.get(\"userRole\");\n\n    if (userRole !== \"admin\") {\n      throw new Error(\"Unauthorized: Admin role required\");\n    }\n\n    // Log access with full context including tool name\n    const toolName = options?.toolContext?.name;\n    options?.logger?.warn(`${toolName}: Sensitive data accessed: ${dataType}`, {\n      userId: options?.userId,\n      operationId: options?.operationId,\n    });\n\n    return await fetchSensitiveData(dataType);\n  },\n});\n```\n\n## Access Input and Output in Context\n\nThe `OperationContext` provides access to both the input and output of the current operation, making debugging and observability much easier.\n\n### Accessing Input\n\nThe `input` field contains the original input provided to the agent operation:\n\n```typescript\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst logTool = createTool({\n  name: \"log_input\",\n  description: \"Logs the original user input\",\n  parameters: z.object({}),\n  execute: async (args, options) => {\n    const input = options?.operationContext?.input;\n    console.log(\"Original input:\", input);\n    // input can be: string, UIMessage[], or BaseMessage[]\n\n    return \"Input logged successfully\";\n  },\n});\n```\n\n### Accessing Output\n\nThe `output` field contains the generated response (only available after generation completes, mainly useful in hooks):\n\n```typescript\nimport { createHooks } from \"@voltagent/core\";\n\nconst hooks = createHooks({\n  onEnd: async ({ context }) => {\n    // Access both input and output\n    console.log(\"User asked:\", context.input);\n    console.log(\"Agent responded:\", context.output);\n\n    // Output type depends on the method used:\n    // - generateText/streamText: string\n    // - generateObject/streamObject: object\n\n    // Log complete interaction\n    const interaction = {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    };\n\n    console.log(\"Complete interaction:\", interaction);\n  },\n});\n```\n\n### Use Cases\n\nThe `input` and `output` fields are particularly useful for:\n\n- **Audit Logging**: Track what users asked and what the agent responded\n- **Analytics**: Analyze input/output patterns across conversations\n- **Debugging**: Trace issues by comparing input to output\n- **Quality Assurance**: Monitor agent responses for quality control\n- **Custom Metrics**: Calculate response time, token efficiency, etc.\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst auditHooks = createHooks({\n  onEnd: async ({ context, output }) => {\n    // Log complete audit trail\n    await auditLog.save({\n      timestamp: context.startTime,\n      operationId: context.operationId,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      input: context.input,\n      output: context.output,\n      finalStepUsage: output?.usage,\n      totalUsage: output?.totalUsage,\n    });\n  },\n});\n\nconst auditedAgent = new Agent({\n  name: \"Audited Assistant\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n  hooks: auditHooks,\n});\n```\n\n## OperationContext Properties\n\nThe `OperationContext` type (defined in [types.ts](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/types.ts)) contains multiple properties beyond the user-managed `context` Map. Understanding these properties helps with debugging, observability, and advanced use cases.\n\n### User-Managed Fields\n\nThese fields are set by you and used to identify and track operations:\n\n```typescript\ninterface OperationContext {\n  // User data storage - read and write custom values\n  readonly context: Map<string | symbol, unknown>;\n\n  // Optional user identifier\n  userId?: string;\n\n  // Optional conversation identifier\n  conversationId?: string;\n\n  // Workspace configured on the agent (if any)\n  workspace?: Workspace;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst response = await agent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n  context: new Map([[\"language\", \"en\"]]),\n});\n\n// Access in hooks or tools\nconsole.log(operationContext.userId); // \"user-123\"\nconsole.log(operationContext.conversationId); // \"conv-456\"\nconsole.log(operationContext.context.get(\"language\")); // \"en\"\nconsole.log(operationContext.workspace?.id); // e.g. \"demo-workspace\"\n```\n\n### Operation Metadata (Read-Only)\n\nThese fields are automatically set by the framework and provide metadata about the current operation:\n\n```typescript\ninterface OperationContext {\n  // Unique identifier for this operation\n  readonly operationId: string;\n\n  // When the operation started\n  startTime: Date;\n\n  // Whether the operation is still active\n  isActive: boolean;\n\n  // Parent agent ID if this is a sub-agent execution\n  parentAgentId?: string;\n}\n```\n\n**Usage example:**\n\n```typescript\nconst agent = new Agent({\n  name: \"TrackedAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onEnd: async ({ context }) => {\n      const duration = Date.now() - context.startTime.getTime();\n      console.log(`Operation ${context.operationId} took ${duration}ms`);\n\n      if (context.parentAgentId) {\n        console.log(`Executed as sub-agent of ${context.parentAgentId}`);\n      }\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n```\n\n### System Fields (Advanced)\n\nThese fields are used internally by the framework. Most users don't need to interact with them directly:\n\n```typescript\ninterface OperationContext {\n  // Internal state management (used by framework)\n  readonly systemContext: Map<string | symbol, unknown>;\n\n  // Scoped logger with operation context\n  logger: Logger;\n\n  // OpenTelemetry span management\n  traceContext: AgentTraceContext;\n\n  // Full conversation history including tool calls\n  conversationSteps?: StepWithContent[];\n\n  // Cancellation control\n  abortController: AbortController;\n\n  // Error to throw when operation is aborted\n  cancellationError?: CancellationError;\n\n  // Bridge for requesting user input (human-in-the-loop)\n  elicitation?: (request: unknown) => Promise<unknown>;\n}\n```\n\n**System Context vs User Context:**\n\n- **`context`**: Your data - use this for sharing information between components\n- **`systemContext`**: Framework internals - stores stream writers, internal state, etc.\n\nThe framework uses `systemContext` to pass internal state between components. For example, when a sub-agent streams data, the parent agent's stream writer is stored in `systemContext` (see [subagent/index.ts:145-148](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/subagent/index.ts#L145-L148)).\n\n### Input/Output (Automatically Set)\n\nThese fields capture the operation's input and output:\n\n```typescript\ninterface OperationContext {\n  // Original input provided to the agent\n  input?: string | UIMessage[] | BaseMessage[];\n\n  // Generated response (text or object)\n  output?: string | object;\n}\n```\n\nThese fields are documented in detail in the \"Access Input and Output in Context\" section below.\n\n### Operation Lifecycle Example\n\nHere's how to use metadata fields for observability:\n\n```typescript\nimport { Agent, createHooks } from \"@voltagent/core\";\n\nconst observableAgent = new Agent({\n  name: \"ObservableAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: ({ context }) => {\n      // Use the scoped logger\n      context.logger.info(\"Operation started\", {\n        operationId: context.operationId,\n        userId: context.userId,\n        conversationId: context.conversationId,\n      });\n\n      // Store start metrics\n      context.context.set(\"metrics\", {\n        startTime: context.startTime,\n        operationId: context.operationId,\n      });\n    },\n    onEnd: ({ context, output }) => {\n      const metrics = context.context.get(\"metrics\") as any;\n      const duration = Date.now() - context.startTime.getTime();\n\n      context.logger.info(\"Operation completed\", {\n        operationId: context.operationId,\n        duration,\n        inputType: typeof context.input,\n        outputType: typeof context.output,\n        isActive: context.isActive,\n      });\n    },\n  }),\n  instructions: \"You are a helpful assistant.\",\n});\n\n// The logger automatically includes userId, conversationId, and operationId\nconst response = await observableAgent.generateText(\"Hello\", {\n  userId: \"user-123\",\n  conversationId: \"conv-456\",\n});\n```\n\n### Cancellation with AbortController\n\nThe `abortController` field allows operation cancellation:\n\n```typescript\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"CancellableAgent\",\n  model: \"openai/gpt-4o\",\n  instructions: \"You are a helpful assistant.\",\n});\n\n// Create an abort controller\nconst abortController = new AbortController();\n\n// Pass the signal to the agent\nconst responsePromise = agent.generateText(\"Write a long story\", {\n  abortSignal: abortController.signal,\n});\n\n// Cancel after 1 second\nsetTimeout(() => {\n  abortController.abort(\"User cancelled\");\n}, 1000);\n\ntry {\n  const response = await responsePromise;\n} catch (error) {\n  if (error.name === \"AbortError\") {\n    console.log(\"Operation was cancelled\");\n  }\n}\n```\n\nThe agent creates an internal `AbortController` and cascades the external signal to it (see [agent.ts:3385-3395](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3385-L3395)). Sub-agents automatically inherit the parent's abort controller.\n\n## Retrievers Store References\n\nRetrievers can store source information in `context`:\n\n```typescript\nimport { BaseRetriever } from \"@voltagent/core\";\n\nclass SimpleRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    // Simulate finding documents\n    const foundDocs = [\n      { title: \"VoltAgent Guide\", url: \"https://docs.example.com\" },\n      { title: \"Agent Tutorial\", url: \"https://tutorial.example.com\" },\n    ];\n\n    // Store references in context\n    if (options?.context) {\n      options.context.set(\"references\", foundDocs);\n      options.context.set(\"searchQuery\", input);\n    }\n\n    // Return content for LLM\n    return foundDocs.map((doc) => `${doc.title}: Some helpful content...`).join(\"\\n\");\n  }\n}\n\nconst agentWithRetriever = new Agent({\n  name: \"RetrievalAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new SimpleRetriever(),\n  instructions: \"Answer using retrieved information.\",\n});\n\n// Usage\nconst response = await agentWithRetriever.generateText(\"How do I use VoltAgent?\");\n\nconsole.log(\"Answer:\", response.text);\nconsole.log(\"Search query:\", response.context?.get(\"searchQuery\"));\nconsole.log(\"References:\", response.context?.get(\"references\"));\n```\n\n## Sub-Agents Automatically Inherit Context\n\nWhen a supervisor delegates to sub-agents, the parent's `OperationContext` is passed down. The sub-agent receives the same `context` Map reference (not a copy), so modifications are visible to both parent and sub-agent.\n\n```typescript\n// Worker agent - automatically receives supervisor's context\nconst workerAgent = new Agent({\n  name: \"WorkerAgent\",\n  model: \"openai/gpt-4o\",\n  hooks: createHooks({\n    onStart: (context) => {\n      // Automatically gets context from supervisor\n      const projectId = context.context.get(\"projectId\");\n      const language = context.context.get(\"language\");\n      console.log(`Worker starting for project ${projectId}, language ${language}`);\n\n      // Can add its own data too\n      context.context.set(\"workerStartTime\", new Date().toISOString());\n    },\n  }),\n  instructions: \"You are a worker that processes tasks.\",\n});\n\n// Supervisor agent\nconst supervisorAgent = new Agent({\n  name: \"SupervisorAgent\",\n  model: \"openai/gpt-4o\",\n  subAgents: [workerAgent],\n  hooks: createHooks({\n    onStart: (context) => {\n      // Set up project context\n      context.context.set(\"projectId\", `project-${Date.now()}`);\n      context.context.set(\"supervisorId\", \"supervisor-001\");\n    },\n  }),\n  instructions: \"You supervise tasks. Delegate work to WorkerAgent when needed.\",\n});\n\n// Usage\nconst initialContext = new Map();\ninitialContext.set(\"language\", \"English\");\ninitialContext.set(\"priority\", \"high\");\n\nconst response = await supervisorAgent.generateText(\"Please delegate this task to the worker\", {\n  context: initialContext,\n});\n\n// Final context includes data from both supervisor and worker\nconsole.log(\"Project ID:\", response.context?.get(\"projectId\"));\nconsole.log(\"Worker start time:\", response.context?.get(\"workerStartTime\"));\n```\n\n### Context Inheritance Priority\n\nWhen creating an `OperationContext`, the framework follows this priority (see [agent.ts:3343-3371](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L3343-L3371)):\n\n1. **Parent context** (if sub-agent): Use parent's context Map reference\n2. **Runtime context** (if provided at execution): Use the provided Map\n3. **Agent-level context** (if set in constructor): Use the agent's default Map\n4. **New Map**: Create an empty Map if none exists\n\nThe framework then fills in missing keys from lower-priority sources without overriding existing values. This means:\n\n- Sub-agents see all parent context data\n- Parent's values take precedence over runtime and agent-level defaults\n- Runtime values take precedence over agent-level defaults\n\n**Important**: The context Map reference is preserved, not cloned. This enables bidirectional updates - changes made by sub-agents are visible to the parent.\n\nFor more details on sub-agent architecture, see the [Sub-Agents guide](./subagents.md).\n\n## Complete Flow Example\n\nHere's how all pieces work together:\n\n```typescript\nimport { Agent, createHooks, createTool, BaseRetriever } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Simple retriever\nclass BasicRetriever extends BaseRetriever {\n  async retrieve(input, options) {\n    if (options.context) {\n      options.context.set(\"references\", [{ title: \"Document 1\", source: \"knowledge-base\" }]);\n    }\n    return \"Retrieved content about the topic\";\n  }\n}\n\n// Simple tool\nconst counterTool = createTool({\n  name: \"increment_counter\",\n  description: \"Increments a counter\",\n  parameters: z.object({}),\n  execute: async (_, options) => {\n    const ctx = options?.context;\n    if (ctx) {\n      const count = (ctx.get(\"counter\") || 0) + 1;\n      ctx.set(\"counter\", count);\n      return `Counter is now: ${count}`;\n    }\n    return \"Counter incremented\";\n  },\n});\n\n// Agent with everything\nconst fullAgent = new Agent({\n  name: \"FullAgent\",\n  model: \"openai/gpt-4o\",\n  retriever: new BasicRetriever(),\n  tools: [counterTool],\n  hooks: createHooks({\n    onStart: (context) => {\n      console.log(\"🚀 Operation started\");\n      context.context.set(\"operationId\", `op-${Date.now()}`);\n    },\n    onEnd: (context) => {\n      const opId = context.context.get(\"operationId\");\n      const counter = context.context.get(\"counter\");\n      const references = context.context.get(\"references\");\n\n      console.log(\"✅ Operation completed\");\n      console.log(`Operation ID: ${opId}`);\n      console.log(`Counter final value: ${counter}`);\n      console.log(`References found: ${references?.length || 0}`);\n    },\n  }),\n  instructions: \"Use tools and retrieval to help users. Always increment the counter.\",\n});\n\n// Usage showing the complete flow\nasync function demonstrateFlow() {\n  const initialContext = new Map();\n  initialContext.set(\"language\", \"English\");\n\n  const response = await fullAgent.generateText(\n    \"Use the increment tool and search for information\",\n    { context: initialContext }\n  );\n\n  console.log(\"Text:\", response.text);\n  const finalContext = response.context;\n  for (const [key, value] of finalContext.entries()) {\n    console.log(`${String(key)}: ${JSON.stringify(value)}`);\n  }\n}\n```\n\n## Key Points\n\n### Context Access by Component\n\n1. **Hooks**: Access via `context.context` in `onStart`/`onEnd` callbacks\n2. **Tools**: Access via `options.operationContext.context` in `execute` function\n3. **Retrievers**: Access via `options.context` in `retrieve` method\n4. **Sub-Agents**: Automatically receive parent's `context` Map reference\n5. **Response**: Access final state via `response.context`\n6. **Dynamic Values**: Constructor context is available in dynamic instructions, model, and tools functions\n\n### OperationContext Fields\n\nThe `OperationContext` contains:\n\n- **User-managed**: `context` (Map), `userId`, `conversationId`, `workspace`\n- **Metadata**: `operationId`, `startTime`, `isActive`, `parentAgentId`\n- **System**: `systemContext`, `logger`, `traceContext`, `conversationSteps`, `abortController`, `cancellationError`, `elicitation`\n- **Input/Output**: `input`, `output` (automatically set)\n\n### Context Initialization Priority\n\n1. Parent context (for sub-agents) - highest priority\n2. Runtime context (execution parameter)\n3. Agent-level context (constructor)\n4. New empty Map - lowest priority\n\nLower-priority sources only fill in missing keys; they don't override existing values. The Map reference is preserved (not cloned) to enable bidirectional updates between parent and sub-agents.\n\n### Tips\n\n**Extend rather than replace default context:**\n\n```typescript\nconst agent = new Agent({\n  context: new Map([[\"env\", \"production\"]]),\n  // ...\n});\n\n// Extend the default context\nconst extendedContext = new Map(agent.context);\nextendedContext.set(\"requestId\", \"req-123\");\n\nawait agent.generateText(\"Hello\", { context: extendedContext });\n```\n\n**Use metadata fields for observability:**\n\n```typescript\nhooks: createHooks({\n  onEnd: ({ context }) => {\n    console.log(\n      `Operation ${context.operationId} completed in ${Date.now() - context.startTime.getTime()}ms`\n    );\n  },\n});\n```\n\n## Memory Adapters\n\nCustom memory adapters can access `OperationContext` to implement cross-cutting concerns like multi-tenancy, logging, and access control without modifying core adapter logic.\n\n### Example: Multi-Tenant Data Isolation\n\nCustom memory adapters can read user-provided context to implement tenant-scoped data access:\n\n```typescript\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext, GetMessagesOptions, UIMessage } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant ID from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID is required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log access for audit trail\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n      traceId: context.traceContext.getTraceId(),\n    });\n\n    // Use scoped user ID to isolate tenant data\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Tenant A: user-123's data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-abc\"]]), // Stores as \"company-abc:user-123\"\n});\n\n// Tenant B: same user ID, but different tenant = different data\nawait agent.generateText(\"Show my history\", {\n  userId: \"user-123\",\n  context: new Map([[\"tenantId\", \"company-xyz\"]]), // Stores as \"company-xyz:user-123\"\n});\n```\n\n**What this enables:**\n\n- **Data isolation** between tenants using composite keys\n- **Per-request tenant context** without adapter reconfiguration\n- **Audit logging** with full operation context\n- **Flexible access control** based on runtime values\n\nAll memory adapters (InMemory, Postgres, LibSQL, Supabase) support `OperationContext` as an optional parameter.\n\n<!-- File: website/docs/agents/dynamic-agents.md (Tokens: ~3105 | Category: subagent-persona) -->\n---\ntitle: Dynamic Agents\nslug: /agents/dynamic-agents\n---\n\n# Dynamic Agents\n\nDynamic agents use functions instead of static values for `instructions`, `model`, and `tools`. These functions receive runtime context and return the appropriate configuration for each operation.\n\nWhen you call `generateText()` or `streamText()`, the agent resolves dynamic values using the `context` map you provide. This lets you configure agent behavior per-request based on user roles, subscription tiers, language preferences, or any custom logic.\n\n## Use Cases\n\nDynamic agents work well for:\n\n- Multi-tenant applications where different users need different capabilities\n- Role-based access control where admins get different tools than regular users\n- Subscription tiers where premium users get access to different models\n- Internationalization where responses adapt to user's language\n- A/B testing where different users get different configurations\n\n## How Dynamic Resolution Works\n\nWhen you provide a function instead of a static value, the agent calls it during each operation:\n\n1. You call `agent.generateText(input, { context })`\n2. Agent invokes your function with `DynamicValueOptions`\n3. Function receives `{ context, prompts }` and returns the value\n4. Agent uses the resolved value for that operation\n\nThe resolution happens in [agent.ts:2583-2606](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L2583-L2606) via the `resolveValue()` method. Dynamic functions are called on every operation, so keep them synchronous or fast.\n\n### Type System\n\nDynamic values use the `DynamicValue<T>` type:\n\n```typescript\ntype DynamicValue<T> = (options: DynamicValueOptions) => Promise<T> | T;\n\ninterface DynamicValueOptions {\n  context: Map<string | symbol, unknown>; // Runtime context you provide\n  headers?: Record<string, string>; // HTTP request headers, when called through a server adapter\n  prompts: PromptHelper; // VoltOps prompt management\n}\n```\n\nThe agent defines three specific dynamic types:\n\n- `InstructionsDynamicValue`: `string | DynamicValue<string | PromptContent>`\n- `ModelDynamicValue<T>`: `T | DynamicValue<T>`\n- `ToolsDynamicValue`: `(Tool | Toolkit)[] | DynamicValue<(Tool | Toolkit)[]>`\n\n## Basic Example\n\nHere's a dynamic agent that changes instructions based on user role:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst dynamicAgent = new Agent({\n  name: \"Adaptive Assistant\",\n\n  // Dynamic instructions based on user context\n  instructions: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n    const language = (context.get(\"language\") as string) || \"English\";\n\n    if (role === \"admin\") {\n      return `You are an admin assistant with special privileges. Respond in ${language}.`;\n    } else {\n      return `You are a helpful assistant. Respond in ${language}.`;\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Dynamic Properties\n\nYou can make `instructions`, `model`, and `tools` dynamic. Each is resolved independently during operation execution.\n\n### Dynamic Instructions\n\nInstructions can be a string or a function that returns a string (or `PromptContent` when using VoltOps).\n\n```ts\nconst agent = new Agent({\n  name: \"Multilingual Support Agent\",\n\n  instructions: ({ context }) => {\n    const language = (context.get(\"language\") as string) || \"English\";\n    const supportTier = (context.get(\"supportTier\") as string) || \"basic\";\n\n    let baseInstructions = `You are a customer support agent. Respond in ${language}.`;\n\n    if (supportTier === \"premium\") {\n      baseInstructions += \" You can offer advanced troubleshooting and priority support.\";\n    } else {\n      baseInstructions += \" Provide standard support within basic guidelines.\";\n    }\n\n    return baseInstructions;\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n### Request Headers\n\nWhen an agent is called through the built-in HTTP endpoints, VoltAgent exposes the request headers to dynamic `instructions`, `model`, and `tools` functions as `headers`. Header names are normalized to lowercase.\n\n```ts\nconst agent = new Agent({\n  name: \"Tenant Agent\",\n  instructions: \"You are a tenant-aware assistant.\",\n  model: ({ headers }) => {\n    const tenantId = headers?.[\"x-tenant-id\"];\n\n    return tenantId === \"enterprise\" ? \"openai/gpt-4o\" : \"openai/gpt-4o-mini\";\n  },\n  tools: ({ headers }) => {\n    const token = headers?.authorization;\n\n    return token ? [createTenantTool(token)] : [];\n  },\n});\n```\n\nFor direct in-process calls, pass `requestHeaders`:\n\n```ts\nawait agent.generateText(\"Hello\", {\n  requestHeaders: {\n    authorization: \"Bearer token\",\n    \"x-tenant-id\": \"tenant-1\",\n  },\n});\n```\n\n### Dynamic Models\n\nThe model can be a static `LanguageModel`, a `provider/model` string, or a function returning either. The agent resolves the model at [agent.ts:1672](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1672) before each generation call.\n\n```ts\nconst agent = new Agent({\n  name: \"Tier-Based Assistant\",\n  instructions: \"You are a helpful assistant.\",\n\n  // Different models for different subscription tiers\n  model: ({ context }) => {\n    const tier = (context.get(\"tier\") as string) || \"free\";\n\n    switch (tier) {\n      case \"premium\":\n        return \"openai/gpt-4o\";\n      case \"pro\":\n        return \"openai/gpt-4o-mini\";\n      default:\n        return \"openai/gpt-3.5-turbo\";\n    }\n  },\n});\n```\n\n### Dynamic Tools\n\nTools can be a static array or a function returning an array. The agent stores dynamic tool functions separately in the `dynamicTools` property ([agent.ts:337](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L337)) and resolves them at [agent.ts:1673](https://github.com/VoltAgent/voltagent/blob/main/packages/core/src/agent/agent.ts#L1673).\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\n// Regular user tool\nconst basicTool = createTool({\n  name: \"get_help\",\n  description: \"Get basic help information\",\n  parameters: z.object({\n    topic: z.string().describe(\"Help topic\"),\n  }),\n  execute: async ({ topic }) => {\n    return `Here's basic help about ${topic}`;\n  },\n});\n\n// Admin-only tool\nconst adminTool = createTool({\n  name: \"admin_action\",\n  description: \"Perform administrative actions\",\n  parameters: z.object({\n    action: z.string().describe(\"Admin action to perform\"),\n  }),\n  execute: async ({ action }) => {\n    return `Admin action performed: ${action}`;\n  },\n});\n\nconst agent = new Agent({\n  name: \"Role-Based Agent\",\n  instructions: \"You are an assistant with role-based capabilities.\",\n\n  // Different tools based on user role\n  tools: ({ context }) => {\n    const role = (context.get(\"role\") as string) || \"user\";\n\n    if (role === \"admin\") {\n      return [basicTool, adminTool]; // Admins get both tools\n    } else {\n      return [basicTool]; // Regular users get basic tools only\n    }\n  },\n\n  model: \"openai/gpt-4o\",\n});\n```\n\n## Execution\n\nPass `context` as an option to any generation method. The agent uses this context to resolve dynamic values:\n\n```ts\n// Create context with relevant information\nconst context = new Map<string, unknown>();\ncontext.set(\"role\", \"admin\");\ncontext.set(\"language\", \"Spanish\");\ncontext.set(\"tier\", \"premium\");\ncontext.set(\"company\", \"TechCorp\");\n\n// Generate response with context\nconst response = await dynamicAgent.generateText(\"Help me manage the system settings\", {\n  context,\n});\n\nconsole.log(response.text);\n// The agent will respond in Spanish, with admin capabilities,\n// using the premium model, and have access to admin tools\n```\n\nYou can also use it with streaming:\n\n```ts\nconst streamResponse = await dynamicAgent.streamText(\"What products are available?\", {\n  context: new Map([\n    [\"role\", \"customer\"],\n    [\"language\", \"French\"],\n    [\"tier\", \"basic\"],\n  ]),\n});\n\nfor await (const chunk of streamResponse.textStream) {\n  process.stdout.write(chunk);\n}\n```\n\n## REST API Usage\n\nYou can pass `context` via the REST API to trigger dynamic resolution over HTTP.\n\n### API Request Format\n\nPass the `context` object within the `options` field of your API request:\n\n```json\n{\n  \"input\": \"Your message to the agent\",\n  \"options\": {\n    \"context\": {\n      \"role\": \"admin\",\n      \"language\": \"Spanish\",\n      \"tier\": \"premium\"\n    },\n    \"temperature\": 0.7,\n    \"maxTokens\": 500\n  }\n}\n```\n\n### REST API Examples\n\n**Basic Text Generation:**\n\n```bash\n# Admin user requesting help in Spanish\ncurl -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/text \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"I need to update system settings\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"admin\",\n           \"language\": \"Spanish\",\n           \"tier\": \"enterprise\"\n         }\n       }\n     }'\n```\n\n**Streaming with Real-time Context:**\n\n```bash\n# Stream response for different user roles\ncurl -N -X POST http://localhost:3141/agents/YOUR_AGENT_NAME/stream \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"input\": \"Write a summary of our quarterly results\",\n       \"options\": {\n         \"context\": {\n           \"role\": \"manager\",\n           \"department\": \"finance\",\n           \"accessLevel\": \"confidential\",\n           \"language\": \"English\"\n         }\n       }\n     }'\n```\n\n## VoltOps Integration\n\nThe VoltOps dashboard lets you test dynamic agents by setting context values through a visual interface.\n\n<!-- GIF PLACEHOLDER: VoltOps Dynamic Agents Demo -->\n\n![VoltOps Dynamic Agents Demo](https://cdn.voltagent.dev/docs/user-context-demo.gif)\n\nYou can:\n\n- Set context values through a form\n- Test different user roles and configurations\n- Monitor how dynamic properties change based on context\n- Debug dynamic agent logic\n\n## Best Practices\n\n### Context Validation\n\nValidate context values and provide defaults:\n\n```ts\ninstructions: ({ context }) => {\n  // Always provide defaults and validate types\n  const role = (context.get(\"role\") as string) || \"user\";\n  const language = (context.get(\"language\") as string) || \"English\";\n\n  // Validate known values\n  const validRoles = [\"admin\", \"support\", \"customer\"];\n  const actualRole = validRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${actualRole} assistant. Respond in ${language}.`;\n};\n```\n\n### Performance\n\nDynamic functions run on every operation. Avoid async operations or heavy computation:\n\n```ts\n// ✅ Good - Fast synchronous logic\nmodel: ({ context }) => {\n  const tier = context.get('tier') as string;\n  return tier === 'premium' ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n},\n\n// ❌ Avoid - API calls or database queries\nmodel: async ({ context }) => {\n  const userProfile = await fetchUserFromDatabase(context.get('userId'));\n  return determineModelFromProfile(userProfile);\n}\n```\n\n### Security\n\nUse allowlists for security-sensitive values:\n\n```ts\ninstructions: ({ context }) => {\n  const role = context.get(\"role\") as string;\n\n  // ✅ Use allowlists for security-sensitive operations\n  const allowedRoles = [\"admin\", \"support\", \"customer\"];\n  const safeRole = allowedRoles.includes(role) ? role : \"customer\";\n\n  return `You are a ${safeRole} assistant.`;\n};\n```\n\n### Context Structure\n\nUse consistent context key naming across your application:\n\n```ts\n// ✅ Consistent naming convention\nconst createUserContext = (user: User) => {\n  const context = new Map<string, unknown>();\n  context.set(\"user.id\", user.id);\n  context.set(\"user.role\", user.role);\n  context.set(\"user.tier\", user.subscriptionTier);\n  context.set(\"user.language\", user.preferredLanguage);\n  context.set(\"request.timestamp\", Date.now());\n  return context;\n};\n```\n\n## Advanced Patterns\n\n### Conditional Resolution\n\nMake properties dynamic only when context requires it:\n\n```ts\nconst agent = new Agent({\n  name: \"Conditionally Dynamic Agent\",\n\n  // Static instructions most of the time\n  instructions: \"You are a helpful assistant.\",\n\n  // But dynamic model when user context is provided\n  model: ({ context }) => {\n    // If no tier specified, use default static model\n    if (!context.has(\"tier\")) {\n      return \"openai/gpt-4o-mini\"; // Default model\n    }\n\n    // Otherwise, choose based on tier\n    const tier = context.get(\"tier\") as string;\n    return tier === \"premium\" ? \"openai/gpt-4o\" : \"openai/gpt-3.5-turbo\";\n  },\n\n  // model is selected dynamically above\n});\n```\n\n### Context Between Operations\n\nPass the same context to related operations for consistency:\n\n```ts\n// Main agent with context\nconst parentResponse = await dynamicAgent.generateText(\"Create a summary\", {\n  context: myContext,\n});\n\n// Sub-agent inherits the same context\nconst detailResponse = await detailAgent.generateText(\n  \"Provide more details\",\n  { context: myContext } // Same context for consistency\n);\n```\n\n## Error Handling\n\nWrap dynamic logic in try-catch and provide fallbacks:\n\n```ts\nmodel: ({ context }) => {\n  try {\n    const tier = context.get(\"tier\") as string;\n\n    if (tier === \"premium\") {\n      return \"openai/gpt-4o\";\n    }\n    return \"openai/gpt-3.5-turbo\";\n  } catch (error) {\n    console.warn(\"Error in dynamic model selection:\", error);\n    // Fallback to safe default\n    return \"openai/gpt-3.5-turbo\";\n  }\n};\n```\n\n<!-- File: website/docs/agents/hooks.md (Tokens: ~5730 | Category: subagent-persona) -->\n---\ntitle: Hooks\nslug: /agents/hooks\n---\n\n# Agent Hooks\n\nHooks intercept specific points in the agent execution pipeline. Use them to observe or modify behavior during agent operations: before a request starts, when preparing messages for the LLM, before and after tool execution, or after completion.\n\n## Defining Hooks\n\nDefine hooks using the `createHooks` helper function. Pass the resulting object to the Agent constructor, or to individual method calls like `generateText` and `streamText`.\n\n```ts\nimport {\n  Agent,\n  createHooks,\n  messageHelpers,\n  type AgentTool,\n  type AgentOperationOutput,\n  type VoltAgentError,\n  type OnStartHookArgs,\n  type OnEndHookArgs,\n  type OnPrepareMessagesHookArgs,\n  type OnPrepareModelMessagesHookArgs,\n  type OnToolStartHookArgs,\n  type OnToolEndHookArgs,\n  type OnToolErrorHookArgs,\n  type OnHandoffHookArgs,\n} from \"@voltagent/core\";\n\n// Define a collection of hooks using the helper\nconst myAgentHooks = createHooks({\n  /**\n   * Called before the agent starts processing a request.\n   */\n  onStart: async (args: OnStartHookArgs) => {\n    const { agent, context } = args;\n    console.log(`[Hook] Agent ${agent.name} starting interaction at ${new Date().toISOString()}`);\n    console.log(`[Hook] Operation ID: ${context.operationId}`);\n  },\n\n  /**\n   * Called after VoltAgent sanitizes UI messages but before the LLM receives them.\n   * `rawMessages` contains the unsanitized list for inspection or metadata recovery.\n   */\n  onPrepareMessages: async (args: OnPrepareMessagesHookArgs) => {\n    const { messages, rawMessages, context } = args;\n    console.log(`Preparing ${messages.length} sanitized messages for LLM`);\n\n    // Add timestamp to each message\n    const timestamp = new Date().toLocaleTimeString();\n    const enhanced = messages.map((msg) => messageHelpers.addTimestampToMessage(msg, timestamp));\n\n    if (rawMessages) {\n      // Access raw message structure for audit logging\n      console.debug(`First raw message parts:`, rawMessages[0]?.parts);\n    }\n\n    return { messages: enhanced };\n  },\n\n  /**\n   * Called after UI messages are converted into provider-specific ModelMessage objects.\n   */\n  onPrepareModelMessages: async (args: OnPrepareModelMessagesHookArgs) => {\n    const { modelMessages, uiMessages } = args;\n    console.log(`Model payload contains ${modelMessages.length} messages`);\n\n    // Inject a system message if none exists\n    if (!modelMessages.some((msg) => msg.role === \"system\")) {\n      return {\n        modelMessages: [\n          {\n            role: \"system\",\n            content: [{ type: \"text\", text: \"Operate within safety budget\" }],\n          },\n          ...modelMessages,\n        ],\n      };\n    }\n\n    return {};\n  },\n\n  /**\n   * Called after the agent completes a request (success or failure).\n   */\n  onEnd: async (args: OnEndHookArgs) => {\n    const { agent, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Agent ${agent.name} finished with error:`, error.message);\n      console.error(`[Hook] Error Details:`, JSON.stringify(error, null, 2));\n    } else if (output) {\n      console.log(`[Hook] Agent ${agent.name} finished successfully.`);\n      // Log aggregate usage or inspect output type\n      if (\"totalUsage\" in output && output.totalUsage) {\n        console.log(`[Hook] Total Token Usage: ${output.totalUsage.totalTokens}`);\n      }\n      if (\"text\" in output && output.text) {\n        console.log(`[Hook] Final text length: ${output.text.length}`);\n      }\n      if (\"object\" in output && output.object) {\n        console.log(`[Hook] Final object keys: ${Object.keys(output.object).join(\", \")}`);\n      }\n    }\n  },\n\n  /**\n   * Called before a tool executes.\n   */\n  onToolStart: async (args: OnToolStartHookArgs) => {\n    const { agent, tool, context, args: toolArgs } = args;\n    console.log(`[Hook] Agent ${agent.name} starting tool: ${tool.name}`);\n    console.log(`[Hook] Tool arguments:`, toolArgs);\n  },\n\n  /**\n   * Called after a tool completes or throws an error.\n   */\n  onToolEnd: async (args: OnToolEndHookArgs) => {\n    const { agent, tool, output, error, context } = args;\n    if (error) {\n      console.error(`[Hook] Tool ${tool.name} failed:`, error.message);\n      console.error(`[Hook] Tool Error Details:`, JSON.stringify(error, null, 2));\n    } else {\n      console.log(`[Hook] Tool ${tool.name} completed with result:`, output);\n    }\n  },\n\n  /**\n   * Called only when a tool throws, before VoltAgent serializes the error payload.\n   */\n  onToolError: async (args: OnToolErrorHookArgs) => {\n    const { tool, originalError } = args;\n    console.error(`[Hook] Raw tool error from ${tool.name}:`, originalError.message);\n\n    // Return { output } to customize what gets sent back to the model.\n    return {\n      output: {\n        error: true,\n        message: \"External API request failed\",\n      },\n    };\n  },\n\n  /**\n   * Called when a task is handed off from a source agent to this agent.\n   */\n  onHandoff: async (args: OnHandoffHookArgs) => {\n    const { agent, sourceAgent } = args;\n    console.log(`[Hook] Task handed off from ${sourceAgent.name} to ${agent.name}`);\n  },\n});\n\n// Pass hooks to the Agent constructor\nconst agentWithHooks = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\n// Or define hooks inline\nconst agentWithInlineHooks = new Agent({\n  name: \"Inline Hooks Agent\",\n  instructions: \"Another assistant\",\n  model: \"openai/gpt-4o\",\n  hooks: {\n    onStart: async ({ agent, context }) => {\n      /* ... */\n    },\n    onEnd: async ({ agent, output, error, context }) => {\n      /* ... */\n    },\n  },\n});\n```\n\n## Passing Hooks to Methods\n\nPass hooks to `generateText`, `streamText`, `generateObject`, or `streamObject` to run hooks for that specific invocation only.\n\n:::warning\nMethod-level hooks do not override agent-level hooks. Both will execute. For most hooks, the method-level hook runs first, then the agent-level hook. For `onPrepareMessages` and `onPrepareModelMessages`, the method-level hook replaces the agent-level hook entirely.\n:::\n\n```ts\nconst agent = new Agent({\n  name: \"My Agent with Hooks\",\n  instructions: \"An assistant demonstrating hooks\",\n  model: \"openai/gpt-4o\",\n  hooks: myAgentHooks,\n});\n\nawait agent.generateText(\"Hello, how are you?\", {\n  hooks: {\n    onEnd: async ({ context }) => {\n      console.log(\"End of generation for this invocation!\");\n    },\n  },\n});\n```\n\nFor example, store conversation history only for specific endpoints:\n\n```ts\nconst agent = new Agent({\n  name: \"Translation Agent\",\n  instructions: \"A translation agent that translates text from English to French\",\n  model: \"openai/gpt-4o\",\n});\n\n// for the translate endpoint, we don't want to store the conversation history\napp.post(\"/api/translate\", async (req, res) => {\n  const result = await agent.generateText(req.body.text);\n  return result;\n});\n\n// for the chat endpoint, we want to store the conversation history\napp.post(\"/api/translate/chat\", async (req, res) => {\n  const result = await agent.streamText(req.body.text, {\n    hooks: {\n      onEnd: async ({ context }) => {\n        await chatStore.save({\n          conversationId: context.conversationId,\n          messages: context.steps,\n        });\n      },\n    },\n  });\n\n  return result.textStream;\n});\n```\n\n## Available Hooks\n\nAll hooks receive a single argument object containing relevant information.\n\n### Choosing the right message hook\n\n| Hook                     | Stage                                                              | When to use                                                                                           |\n| ------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |\n| `onPrepareMessages`      | Operates on sanitized `UIMessage[]` built from memory + user input | Transform messages using helper functions, redact content, or access `rawMessages` for logging.       |\n| `onPrepareModelMessages` | Runs after `convertToModelMessages` on `ModelMessage[]`            | Apply provider-specific adjustments, inject system directives, or modify the final payload structure. |\n\nUse `onPrepareMessages` for general transformations and `onPrepareModelMessages` for provider-specific adjustments. Both hooks can be used together; VoltAgent applies them in that order.\n\n### `onStart`\n\n- **Triggered:** Before the agent begins processing a request (`generateText`, `streamText`, etc.).\n- **Argument Object (`OnStartHookArgs`):** `{ agent: Agent, context: OperationContext }`\n- **Use Cases:** Initialization logic, request logging, setting up request-scoped resources.\n\n```ts\n// Example: Log the start of an operation\nonStart: async ({ agent, context }) => {\n  console.log(`Agent ${agent.name} starting operation ${context.operationId}`);\n};\n```\n\n### `onPrepareMessages`\n\n- **Triggered:** After VoltAgent assembles conversation history and sanitizes UI messages, before conversion to provider-specific payloads.\n- **Argument Object (`OnPrepareMessagesHookArgs`):** `{ messages: UIMessage[], rawMessages?: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Transform sanitized messages (add timestamps, redact content), or access `rawMessages` for logging or metadata recovery.\n- **Return:** `{ messages: UIMessage[] }` to replace the sanitized list, or an empty object to keep the original.\n- **Notes:**\n  - `messages` contains sanitized UI messages safe for the LLM.\n  - `rawMessages` contains the full structure before sanitization, useful for logging or metadata reconstruction.\n\n```ts\nonPrepareMessages: async ({ messages, rawMessages }) => {\n  const tagged = messages.map((msg) =>\n    messageHelpers.addTimestampToMessage(msg, new Date().toISOString())\n  );\n\n  if (rawMessages) {\n    auditTrail.write(rawMessages); // your own analytics sink\n  }\n\n  return { messages: tagged };\n};\n```\n\n### `onPrepareModelMessages`\n\n- **Triggered:** After UI messages are converted via `convertToModelMessages`, immediately before the provider receives them.\n- **Argument Object (`OnPrepareModelMessagesHookArgs`):** `{ modelMessages: ModelMessage[], uiMessages: UIMessage[], context: OperationContext, agent: Agent }`\n- **Use Cases:** Apply provider-specific transformations, inject final system messages, compress conversation length, or add structured metadata.\n- **Return:** `{ modelMessages: ModelMessage[] }` with a replacement list, or an empty object to keep the original.\n- **Tip:** Use `onPrepareMessages` for general transformations, and this hook for provider-specific adjustments.\n\n```ts\nonPrepareModelMessages: async ({ modelMessages }) => {\n  // Force the last message to include a \"speak clearly\" reminder for voice models\n  const last = modelMessages.at(-1);\n  if (last && last.role === \"user\" && Array.isArray(last.content)) {\n    last.content.push({ type: \"text\", text: \"Please answer succinctly.\" });\n  }\n\n  return { modelMessages };\n};\n```\n\n### `onEnd`\n\n- **Triggered:** After the agent completes processing a request (success or failure).\n- **Argument Object (`OnEndHookArgs`):** `{ agent: Agent, output: AgentOperationOutput | undefined, error: VoltAgentError | undefined, conversationId: string, context: OperationContext }`\n- **Use Cases:** Cleanup, logging, analyzing output or errors, recording usage statistics, storing conversation history.\n- **Note:** The `output` structure depends on the method called. Check for `text` or `object` fields. `error` contains the structured `VoltAgentError` on failure.\n\n```ts\n// Example: Log the outcome of an operation and store conversation history\nonEnd: async ({ agent, output, error, conversationId, context }) => {\n  if (error) {\n    console.error(`Agent ${agent.name} operation ${context.operationId} failed: ${error.message}`);\n    console.log(`User input: \"${context.historyEntry.input}\"`);\n    // Only user input available on error (no assistant response)\n  } else {\n    // Check output type if needed\n    if (output && \"text\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with text output.`\n      );\n    } else if (output && \"object\" in output) {\n      console.log(\n        `Agent ${agent.name} operation ${context.operationId} succeeded with object output.`\n      );\n    } else {\n      console.log(`Agent ${agent.name} operation ${context.operationId} succeeded.`);\n    }\n\n    // Access input and output directly from context\n    console.log(\"Request input:\", context.input);\n    console.log(\"Generated output:\", context.output);\n\n    // Log the complete conversation flow\n    console.log(`Conversation flow:`, {\n      user: context.historyEntry.input,\n      assistant: context.steps, // the assistant steps\n      totalMessages: context.steps.length,\n      toolInteractions: context.steps.flatMap((s) => s.toolInvocations || []).length,\n      toolsUsed: context.steps.flatMap((s) => s.toolInvocations || []).map((t) => t.toolName),\n    });\n\n    // Log complete interaction using input/output\n    console.log(\"Full interaction:\", {\n      input: context.input,\n      output: context.output,\n      userId: context.userId,\n      conversationId: context.conversationId,\n      operationId: context.operationId,\n    });\n\n    // Log usage if available\n    if (output?.totalUsage) {\n      console.log(`  Total usage: ${output.totalUsage.totalTokens} tokens`);\n    }\n  }\n};\n```\n\n### `onRetry`\n\n- **Triggered:** When VoltAgent schedules a retry for an LLM attempt or a middleware abort.\n- **Argument Object (`OnRetryHookArgs`):** Includes `source`, `operation`, `context`, and retry details.\n- **Use Cases:** Retry metrics, alerts, tracking retry sources.\n\n```ts\nonRetry: async (args) => {\n  if (args.source === \"llm\") {\n    console.log(\n      `LLM retry ${args.nextAttempt}/${args.maxRetries + 1} for ${args.modelName} (${args.operation})`\n    );\n  } else {\n    console.log(\n      `Middleware retry ${args.retryCount + 1}/${args.maxRetries + 1} for ${args.middlewareId ?? \"unknown\"}`\n    );\n  }\n};\n```\n\n### `onFallback`\n\n- **Triggered:** When VoltAgent selects the next model candidate.\n- **Argument Object (`OnFallbackHookArgs`):** Includes `stage`, `fromModel`, `fromModelIndex`, `nextModel`, and `error`.\n- **Use Cases:** Tracking model failover, audit logs, fallback diagnostics.\n\n```ts\nonFallback: async ({ stage, fromModel, nextModel, operation }) => {\n  console.warn(\n    `Fallback (${stage}) from ${fromModel} to ${nextModel ?? \"next\"} during ${operation}`\n  );\n};\n```\n\n### `onToolStart`\n\n- **Triggered:** Before an agent executes a tool.\n- **Argument Object (`OnToolStartHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool being executed\n  - `args`: Tool input arguments\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Use Cases:** Logging tool usage, inspecting tool arguments, or validating inputs before execution.\n\n```ts\n// Example: Log tool invocation with arguments\nonToolStart: async ({ agent, tool, args, context }) => {\n  console.log(`Agent ${agent.name} invoking tool '${tool.name}'`);\n  console.log(`Tool arguments:`, args);\n};\n```\n\n### `onToolEnd`\n\n- **Triggered:** After a tool execution completes or throws an error.\n- **Argument Object (`OnToolEndHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that completed\n  - `output`: Tool output (undefined on error)\n  - `error`: VoltAgentError when tool throws (undefined on success)\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** `{ output }` to replace the tool result. The replacement is validated again if the tool has an `outputSchema`.\n- **Use Cases:** Logging tool results or errors, post-processing output, triggering actions based on success or failure.\n\n```ts\n// Example: Log the result or error of a tool execution\nonToolEnd: async ({ agent, tool, output, error, context }) => {\n  if (error) {\n    console.error(\n      `Tool '${tool.name}' failed in operation ${context.operationId}: ${error.message}`\n    );\n  } else {\n    console.log(\n      `Tool '${tool.name}' succeeded in operation ${context.operationId}. Result:`,\n      output\n    );\n  }\n};\n```\n\n### `onToolError`\n\n- **Triggered:** Only when a tool throws, before VoltAgent builds its default serialized error result.\n- **Argument Object (`OnToolErrorHookArgs`):**\n  - `agent`: Agent instance running the tool\n  - `tool`: The tool that failed\n  - `args`: Tool input arguments\n  - `error`: Structured `VoltAgentError` for the tool failure\n  - `originalError`: Original thrown error normalized to an `Error` instance\n  - `context`: Operation context for the current call\n  - `options`: ToolExecuteOptions (includes `toolContext`, `abortController`, etc.)\n- **Return:** Optional `{ output }` replacement payload. If omitted, VoltAgent returns its default serialized error payload.\n- **Use Cases:** Centralized error normalization (for example Axios), redacting sensitive fields, shrinking error payload size before it is sent to the model.\n\n```ts\nonToolError: async ({ tool, error, originalError }) => {\n  const maybeAxios = (originalError as any).isAxiosError === true;\n  if (!maybeAxios) {\n    return;\n  }\n\n  return {\n    output: {\n      error: true,\n      name: error.name,\n      message: originalError.message,\n      code: (originalError as any).code,\n      status: (originalError as any).response?.status,\n    },\n  };\n};\n```\n\n### Tool-level hooks (per tool)\n\nTool hooks run for a specific tool instance and are called before/after execution. Tool-level `onEnd` runs before agent-level `onToolEnd`. If both return `{ output }`, the agent hook wins. Any override is re-validated when `outputSchema` is present.\n\n**Tool hook parameters:**\n\n- `onStart`: `{ tool, args, options }`\n- `onEnd`: `{ tool, args, output, error, options }` (return `{ output }` to override)\n\n```ts\nimport { createTool } from \"@voltagent/core\";\nimport { z } from \"zod\";\n\nconst normalizeTool = createTool({\n  name: \"normalize_text\",\n  description: \"Normalize and trim text\",\n  parameters: z.object({ text: z.string() }),\n  execute: async ({ text }) => text,\n  hooks: {\n    onStart: ({ tool }) => {\n      console.log(`[tool] ${tool.name} starting`);\n    },\n    onEnd: ({ output }) => {\n      if (typeof output === \"string\") {\n        return { output: output.trim() };\n      }\n    },\n  },\n});\n```\n\n### `onHandoff`\n\n- **Triggered:** When one agent delegates a task to another agent via the `delegate_task` tool.\n- **Argument Object (`OnHandoffHookArgs`):** `{ agent: Agent, sourceAgent: Agent }`\n- **Use Cases:** Tracking workflow in multi-agent systems, logging agent collaboration.\n\n```ts\n// Example: Log agent handoffs\nonHandoff: async ({ agent, sourceAgent }) => {\n  console.log(`Task handed off from agent '${sourceAgent.name}' to agent '${agent.name}'`);\n};\n```\n\n## Hook Execution and Error Handling\n\n- **Async Execution:** Hooks can be `async` functions. VoltAgent awaits completion before proceeding. Long-running operations in hooks add latency to agent response time.\n- **Error Handling:** Errors thrown inside hooks may interrupt agent execution. Use `try...catch` within hooks or design them to be reliable.\n- **Hook Merging:** When hooks are passed to both the Agent constructor and a method call:\n  - Most hooks (`onStart`, `onEnd`, `onError`, `onHandoff`, `onToolStart`, `onToolEnd`, `onToolError`, `onStepFinish`, `onRetry`, `onFallback`) execute both: method-level first, then agent-level.\n  - Message hooks (`onPrepareMessages`, `onPrepareModelMessages`) do not merge: method-level replaces agent-level entirely.\n\n## Additional Hooks\n\n- **`onError`**: Called when an error occurs during agent execution. Receives `{ agent: Agent, error: Error, context: OperationContext }`.\n- **`onToolError`**: Called when a tool throws, before default tool error serialization. Receives `{ agent, tool, args, error, originalError, context }` and can return `{ output }`.\n- **`onStepFinish`**: Called after each step in multi-step agent execution. Receives `{ agent: Agent, step: any, context: OperationContext }`.\n- **`onRetry`**: Called when VoltAgent schedules a retry. Receives `{ source, operation, ... }` with retry metadata.\n- **`onFallback`**: Called when VoltAgent selects the next model candidate. Receives `{ stage, fromModel, nextModel, error, ... }`.\n\n## Common Use Cases\n\n1.  **Logging & Observability**: Track execution steps, timings, inputs, outputs, and errors.\n2.  **Analytics**: Collect usage data (token counts, tool usage frequency, success/error rates).\n3.  **Message Transformation**: Modify messages before they reach the LLM or provider.\n4.  **State Management**: Initialize or clean up request-specific resources.\n5.  **Workflow Orchestration**: Trigger external actions based on agent events.\n6.  **UI Integration**: Convert `OperationContext` to messages for the Vercel AI SDK using `@voltagent/vercel-ui`.\n\n## Examples\n\n### Message Transformation with onPrepareMessages\n\nTransform messages before they reach the LLM using `onPrepareMessages` and message helper functions:\n\n```ts\nimport { Agent, createHooks, messageHelpers } from \"@voltagent/core\";\n\nconst enhancedHooks = createHooks({\n  onPrepareMessages: async ({ messages, context }) => {\n    const enhanced = messages.map((msg) => {\n      // Add timestamps to user messages\n      if (msg.role === \"user\") {\n        const timestamp = new Date().toLocaleTimeString();\n        msg = messageHelpers.addTimestampToMessage(msg, timestamp);\n      }\n\n      // Redact sensitive data\n      msg = messageHelpers.mapMessageContent(msg, (text) => {\n        text = text.replace(/\\b\\d{3}-\\d{2}-\\d{4}\\b/g, \"[SSN-REDACTED]\");\n        text = text.replace(/\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/g, \"[CC-REDACTED]\");\n        return text;\n      });\n\n      return msg;\n    });\n\n    // Add context based on user\n    if (context.context?.get && context.context.get(\"userId\")) {\n      const systemContext = {\n        role: \"system\" as const,\n        content: `User ID: ${context.context.get(\"userId\")}. Provide personalized responses.`,\n      };\n      enhanced.unshift(systemContext);\n    }\n\n    return { messages: enhanced };\n  },\n\n  onEnd: async ({ output, context }) => {\n    console.log(`Messages processed for operation ${context.operationId}`);\n    if (output?.totalUsage) {\n      console.log(`Tokens used: ${output.totalUsage.totalTokens}`);\n    }\n  },\n});\n\nconst agent = new Agent({\n  name: \"Privacy-Aware Assistant\",\n  instructions: \"A helpful assistant that protects user privacy\",\n  model: \"openai/gpt-4o-mini\",\n  hooks: enhancedHooks,\n});\n\n// User message: \"My SSN is 123-45-6789\"\n// LLM receives: \"[10:30:45] My SSN is [SSN-REDACTED]\"\n```\n\n### Inspecting Output in onEnd\n\nThe `output` parameter structure depends on the agent method called. Check for `text` or `object` fields:\n\nFor token accounting, VoltAgent exposes the AI SDK usage contract directly:\n\n- `output.usage` is the final step usage, matching AI SDK `result.usage`.\n- `output.totalUsage` is the aggregate usage across all steps, matching AI SDK `result.totalUsage`.\n- `output.steps?.[index].usage` is the per-step usage, matching AI SDK `result.steps[].usage`.\n\nIn a single-step run, `usage` and `totalUsage` are usually the same. In a multi-step tool run, use `totalUsage` for total cost or billing, `usage` for the final model step, and `steps[].usage` for step-level analysis.\n\n```ts\nconst hooks = createHooks({\n  onEnd: async ({ output }) => {\n    if (!output) return; // operation failed or was aborted\n\n    const finalStepUsage = output.usage;\n    const aggregateUsage = output.totalUsage;\n    const perStepUsage = output.steps?.map((step) => step.usage);\n\n    console.log({\n      finalStepTokens: finalStepUsage?.totalTokens,\n      aggregateTokens: aggregateUsage?.totalTokens,\n      stepTokens: perStepUsage?.map((usage) => usage.totalTokens),\n    });\n\n    // Handle text results\n    if (\"text\" in output && output.text) {\n      console.log(\"Final text:\", output.text);\n      return;\n    }\n\n    // Handle object results\n    if (\"object\" in output && output.object) {\n      console.log(\"Final object keys:\", Object.keys(output.object));\n    }\n  },\n});\n```\n\n<!-- File: website/docs/agents/mcp/authorization.md (Tokens: ~3577 | Category: subagent-persona) -->\n---\ntitle: MCP Authorization\ndescription: Add access control to MCP tools with VoltAgent's authorization layer\n---\n\n# MCP Authorization\n\nVoltAgent provides an authorization layer for MCP (Model Context Protocol) tools. This allows you to control which tools users can see and execute based on roles, permissions, or any custom logic.\n\n## Overview\n\nThe MCP authorization layer supports two modes:\n\n1. **Tool Discovery Filtering** (`filterOnDiscovery`) - Hide tools from users who don't have permission to use them\n2. **Execution-Time Checks** (`checkOnExecution`) - Verify permissions before each tool call\n\nYou can enable either or both modes depending on your security requirements.\n\n## Quick Start\n\nAdd authorization with the `can` function:\n\n```typescript\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    myServer: {\n      type: \"http\",\n      url: \"http://localhost:3000/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, action, userId, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      // Admin-only tools\n      if (toolName === \"delete_item\" && !roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete items\" };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n\n// Get tools with authorization context\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"], department: \"engineering\" },\n});\n```\n\n## The `can` Function\n\nThe `can` function receives authorization parameters and returns whether access is allowed.\n\n### Parameters\n\n```typescript\ninterface MCPCanParams {\n  /** Tool name (without server prefix) */\n  toolName: string;\n  /** Server/resource identifier */\n  serverName: string;\n  /** The action being authorized: \"discovery\" or \"execution\" */\n  action: \"discovery\" | \"execution\";\n  /** Tool arguments (only available for \"execution\" action) */\n  arguments?: Record<string, unknown>;\n  /** User identifier */\n  userId?: string;\n  /** User-defined context Map */\n  context?: Map<string | symbol, unknown>;\n}\n```\n\n### Return Value\n\nReturn a boolean or an object with `allowed` and optional `reason`:\n\n```typescript\n// Simple boolean\nreturn true;\nreturn false;\n\n// Object with reason (shown in error message when denied)\nreturn { allowed: true };\nreturn { allowed: false, reason: \"Insufficient permissions\" };\n```\n\n## When Actions Are Called\n\nThe `action` parameter tells you whether the check is for listing tools or executing a tool.\n\n### Discovery Action\n\nWhen you call `getTools()` with `filterOnDiscovery: true`, the `can` function is called for each tool with `action: \"discovery\"`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    filterOnDiscovery: true,\n  },\n});\n\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n```\n\n**What `can` receives (for each tool on the server):**\n\n```json\n{\n  \"toolName\": \"list_expenses\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n```json\n{\n  \"toolName\": \"delete_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"discovery\",\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\nNote: `arguments` is `undefined` during discovery since no tool is being executed yet.\n\n### Execution Action\n\nWhen a tool is executed (via agent interaction or direct call) with `checkOnExecution: true`, the `can` function is called with `action: \"execution\"` and the tool's `arguments`.\n\n**Your code:**\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: { expenses: { type: \"http\", url: \"http://localhost:8080/mcp\" } },\n  authorization: {\n    can: async (params) => {\n      console.log(\"can() called with:\", JSON.stringify(params, null, 2));\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n\nconst tools = await mcp.getTools();\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o\",\n  tools,\n});\n\n// When user says \"add an expense for $50 coffee\"\n// and the agent decides to call the add_expense tool:\nawait agent.generateText(\"Add an expense for $50 coffee\", {\n  userId: \"user-123\",\n  context: new Map([[\"roles\", [\"manager\"]]]),\n});\n```\n\n**What `can` receives when the agent calls `add_expense`:**\n\n```json\n{\n  \"toolName\": \"add_expense\",\n  \"serverName\": \"expenses\",\n  \"action\": \"execution\",\n  \"arguments\": {\n    \"amount\": 50,\n    \"description\": \"coffee\"\n  },\n  \"userId\": \"user-123\",\n  \"context\": { \"roles\": [\"manager\"] }\n}\n```\n\n### Different Logic per Action\n\nYou can use different authorization logic depending on the action:\n\n```typescript\ncan: async ({ toolName, action, arguments: args, userId, context }) => {\n  const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n  if (action === \"discovery\") {\n    // During getTools(): hide admin tools from non-admins\n    if (toolName.startsWith(\"admin_\") && !roles.includes(\"admin\")) {\n      return false; // Tool won't appear in the list\n    }\n  }\n\n  if (action === \"execution\") {\n    // During tool execution: check specific permissions\n    if (toolName === \"delete_expense\") {\n      if (!roles.includes(\"admin\")) {\n        return { allowed: false, reason: \"Only admins can delete expenses\" };\n      }\n    }\n\n    // Check argument-based permissions (only available during execution)\n    if (toolName === \"transfer_funds\" && args?.amount > 10000) {\n      if (!roles.includes(\"senior_manager\")) {\n        return { allowed: false, reason: \"Transfers over $10,000 require senior manager approval\" };\n      }\n    }\n  }\n\n  return true;\n};\n```\n\n## Authorization Context\n\nWhen calling `getTools()` or during tool execution, provide authorization context:\n\n```typescript\ninterface MCPAuthorizationContext {\n  userId?: string;\n  context?: Map<string | symbol, unknown> | Record<string, unknown>;\n}\n\n// Example: pass context to getTools()\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: {\n    roles: [\"admin\"],\n    tenantId: \"acme-corp\",\n    permissions: [\"read\", \"write\"],\n  },\n});\n```\n\nThe context is converted to a `Map` and passed to your `can` function.\n\n## Examples\n\n### Role-Based Access\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, context }) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [];\n\n      const toolPermissions: Record<string, string[]> = {\n        list_items: [\"user\", \"manager\", \"admin\"],\n        create_item: [\"user\", \"manager\", \"admin\"],\n        delete_item: [\"admin\"],\n        admin_panel: [\"admin\"],\n      };\n\n      const allowedRoles = toolPermissions[toolName] ?? [];\n      const hasPermission = roles.some((role) => allowedRoles.includes(role));\n\n      if (!hasPermission) {\n        return { allowed: false, reason: `Requires one of: ${allowedRoles.join(\", \")}` };\n      }\n\n      return true;\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Attribute-Based Access Control\n\nUse tool arguments for attribute-based access control (only available during execution):\n\n```typescript\nconst mcp = new MCPConfiguration({\n  servers: {\n    /* ... */\n  },\n  authorization: {\n    can: async ({ toolName, action, arguments: args, userId }) => {\n      // arguments is only available during execution\n      if (action === \"execution\" && toolName === \"get_user_data\") {\n        const requestedUserId = args?.userId as string;\n        if (requestedUserId !== userId) {\n          return { allowed: false, reason: \"Can only access your own data\" };\n        }\n      }\n\n      return true;\n    },\n    checkOnExecution: true,\n  },\n});\n```\n\n## Policy-Based Authorization with Cerbos\n\nFor production applications, use a dedicated authorization service like [Cerbos](https://cerbos.dev/). Cerbos is an open-source authorization layer that lets you define access policies in YAML files.\n\nSee the full working example: [examples/with-cerbos](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos)\n\n### Project Structure\n\n```\nwith-cerbos/\n├── docker-compose.yml    # Cerbos PDP container\n├── policies/\n│   └── mcp_tools.yaml    # Authorization policies\n├── src/\n│   ├── index.ts          # Agent with MCP authorization\n│   └── mcp-server.ts     # MCP server with tools\n└── package.json\n```\n\n### Step 1: Start Cerbos PDP\n\nCreate `docker-compose.yml`:\n\n```yaml\nservices:\n  cerbos:\n    image: ghcr.io/cerbos/cerbos:latest\n    ports:\n      - \"3593:3593\" # gRPC\n      - \"3592:3592\" # HTTP\n    volumes:\n      - ./policies:/policies\n    command: [\"server\", \"--config=/conf.yaml\"]\n    configs:\n      - source: cerbos-config\n        target: /conf.yaml\n\nconfigs:\n  cerbos-config:\n    content: |\n      server:\n        httpListenAddr: \":3592\"\n        grpcListenAddr: \":3593\"\n      storage:\n        driver: disk\n        disk:\n          directory: /policies\n```\n\nStart with `docker compose up -d`.\n\n### Step 2: Define Policies\n\nCreate `policies/mcp_tools.yaml`:\n\n```yaml\napiVersion: api.cerbos.dev/v1\nresourcePolicy:\n  version: \"default\"\n  resource: \"mcp::expenses\"\n  rules:\n    # All users can list expenses\n    - actions: [\"list_expenses\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\", \"user\"]\n\n    # Only users can add expenses\n    - actions: [\"add_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"user\"]\n\n    # Managers and admins can approve/reject\n    - actions: [\"approve_expense\", \"reject_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\", \"manager\"]\n\n    # Only admins can delete\n    - actions: [\"delete_expense\"]\n      effect: EFFECT_ALLOW\n      roles: [\"admin\"]\n```\n\nThe policy structure:\n\n- `resource`: `mcp::expenses` matches the `serverName` in your MCP configuration\n- `actions`: Tool names (e.g., `list_expenses`, `add_expense`)\n- `roles`: Which roles can perform the action\n- `effect`: `EFFECT_ALLOW` or `EFFECT_DENY`\n\n### Step 3: Connect to Cerbos\n\n```typescript\nimport { GRPC } from \"@cerbos/grpc\";\nimport { Agent, MCPConfiguration, VoltAgent } from \"@voltagent/core\";\nimport type { MCPCanParams } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\n\nconst cerbos = new GRPC(\"localhost:3593\", { tls: false });\n\nconst mcp = new MCPConfiguration({\n  servers: {\n    expenses: {\n      type: \"http\",\n      url: \"http://localhost:3142/mcp\",\n    },\n  },\n  authorization: {\n    can: async ({ toolName, serverName, action, userId, context }: MCPCanParams) => {\n      const roles = (context?.get(\"roles\") as string[]) ?? [\"user\"];\n\n      const result = await cerbos.checkResource({\n        principal: {\n          id: userId ?? \"anonymous\",\n          roles,\n        },\n        resource: {\n          kind: `mcp::${serverName}`,\n          id: serverName,\n        },\n        actions: [toolName],\n      });\n\n      const allowed = result.isAllowed(toolName) ?? false;\n\n      return {\n        allowed,\n        reason: allowed ? undefined : `Access denied for ${toolName}`,\n      };\n    },\n    filterOnDiscovery: true,\n    checkOnExecution: true,\n  },\n});\n```\n\n### Step 4: Create the Agent\n\n```typescript\nconst tools = await mcp.getTools({\n  userId: \"user-123\",\n  context: { roles: [\"manager\"] },\n});\n\nconst agent = new Agent({\n  name: \"Finance Assistant\",\n  instructions: `You are a finance assistant that helps users manage expenses.\nAvailable actions depend on the user's role.`,\n  model: \"openai/gpt-4o-mini\",\n  tools,\n});\n\nnew VoltAgent({\n  agents: { agent },\n  server: honoServer(),\n});\n```\n\n### How It Works\n\n1. **Tool Discovery**: When `getTools()` is called, each tool is checked against Cerbos. Tools the user can't access are filtered out.\n\n2. **Tool Execution**: When the agent calls a tool, Cerbos verifies the user has permission before execution.\n\n3. **Policy Evaluation**: Cerbos matches the request against policies:\n   - `principal.roles` → matches policy `roles`\n   - `resource.kind` → matches policy `resource`\n   - `actions[0]` → matches policy `actions`\n\n### Example Flow\n\nUser with role `[\"manager\"]` calls `getTools()`:\n\n| Tool              | Cerbos Check                      | Result      |\n| ----------------- | --------------------------------- | ----------- |\n| `list_expenses`   | manager in [admin, manager, user] | ✅ Allowed  |\n| `add_expense`     | manager in [user]                 | ❌ Filtered |\n| `approve_expense` | manager in [admin, manager]       | ✅ Allowed  |\n| `delete_expense`  | manager in [admin]                | ❌ Filtered |\n\nThe manager sees only `list_expenses` and `approve_expense` in their tool list.\n\n## Error Handling\n\nWhen authorization fails at execution time, an `MCPAuthorizationError` is thrown:\n\n```typescript\nimport { MCPAuthorizationError } from \"@voltagent/core\";\n\ntry {\n  const result = await agent.generateText(\"Delete all expenses\", {\n    userId: \"user-123\",\n    context: new Map([[\"roles\", [\"user\"]]]),\n  });\n} catch (error) {\n  if (error instanceof MCPAuthorizationError) {\n    console.log(`Access denied for tool: ${error.toolName}`);\n    console.log(`Server: ${error.serverName}`);\n    console.log(`Reason: ${error.reason}`);\n  }\n}\n```\n\n## Configuration Reference\n\n### MCPAuthorizationConfig\n\n```typescript\ninterface MCPAuthorizationConfig {\n  /**\n   * Authorization function to check tool access.\n   */\n  can: MCPCanFunction;\n\n  /**\n   * Filter tools on discovery (getTools/getToolsets).\n   * When true, unauthorized tools are hidden from the tool list.\n   * @default false\n   */\n  filterOnDiscovery?: boolean;\n\n  /**\n   * Check authorization on execution (callTool).\n   * When true, each tool call is verified before execution.\n   * @default true\n   */\n  checkOnExecution?: boolean;\n}\n```\n\n## See Also\n\n- [MCP Configuration](./mcp.md) - Basic MCP setup\n- [MCP Server](./mcp-server.md) - Building MCP servers\n- [Cerbos Example](https://github.com/VoltAgent/voltagent/tree/main/examples/with-cerbos) - Full Cerbos integration example\n\n<!-- File: website/docs/agents/mcp/mcp-server.md (Tokens: ~2682 | Category: subagent-persona) -->\n---\ntitle: MCP Server\ndescription: Quick start guide for exposing VoltAgent over the Model Context Protocol.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# VoltAgent MCP Server\n\nFollow these steps to expose your VoltAgent project over MCP so you can browse tools, prompts, and resources from VoltOps Console (`https://console.voltagent.dev/mcp`) or an MCP-compatible IDE.\n\n## 1. Install the MCP server package\n\n<Tabs>\n  <TabItem value=\"pnpm\" label=\"pnpm\" default>\n    ```bash\n    pnpm add @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"npm\">\n    ```bash\n    npm install @voltagent/mcp-server\n    ```\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n    ```bash\n    yarn add @voltagent/mcp-server\n    ```\n  </TabItem>\n</Tabs>\n\n## 2. Create an MCP server instance\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP example\",\n});\n```\n\n> 📘 **Tip:** When you expose agents through MCP, their `purpose` field becomes the tool description shown in clients. Keep it short and user-facing (fallback is the agent instructions if `purpose` is empty).\n\nThis minimal configuration:\n\n- Names the server `voltagent-example` (used in URLs and IDE listings).\n- Registers server metadata so clients can discover it even before you add tools/agents.\n- Enables all transports (`stdio`, `http`, `sse`) by default. Override `protocols` to disable transports you do not need.\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  protocols: {\n    stdio: true,\n    http: false,\n    sse: false,\n  },\n});\n```\n\nThe snippet above shows how to run in stdio-only mode (ideal for IDE integrations or command-line tooling).\n\n## 3. Register the server with VoltAgent\n\n```ts title=\"src/voltagent.ts\"\nimport { VoltAgent } from \"@voltagent/core\";\nimport { honoServer } from \"@voltagent/server-hono\";\nimport { mcpServer } from \"./mcp/server\";\nimport { assistant } from \"./agents/assistant\";\nimport { expenseApprovalWorkflow } from \"./workflows/expense\";\nimport { logger } from \"./logger\";\n\nexport const voltAgent = new VoltAgent({\n  agents: {\n    assistant,\n  },\n  workflows: {\n    expenseApprovalWorkflow,\n  },\n  mcpServers: {\n    mcpServer,\n  },\n  server: honoServer({ port: 3141 }),\n  logger,\n});\n```\n\n- `mcpServers` registers your MCP server alongside the agent/workflow registries.\n- Passing `server: honoServer(...)` makes VoltAgent expose MCP HTTP and SSE routes automatically. With the server running, visit [`https://console.voltagent.dev/mcp`](https://console.voltagent.dev/mcp) to browse, invoke, and debug tools.\n\n## 4. Find the MCP endpoint (HTTP + SSE)\n\nWhen you start the VoltAgent server, MCP transport endpoints are exposed per server ID:\n\n- **Streamable HTTP**: `POST /mcp/{serverId}/mcp`\n- **SSE** (if enabled): `GET /mcp/{serverId}/sse` and `POST /mcp/{serverId}/messages`\n\nThe `{serverId}` is a normalized version of the `name` (lowercased, spaces replaced) and may get a numeric suffix if there are duplicates. You can always discover it via the registry endpoint:\n\n```bash\ncurl http://localhost:3141/mcp/servers\n```\n\nExample response (trimmed):\n\n```json\n{\n  \"servers\": [\n    {\n      \"id\": \"voltagent-example\",\n      \"name\": \"voltagent-example\",\n      \"version\": \"0.1.0\"\n    }\n  ]\n}\n```\n\nOnce you have the `id`, the HTTP MCP URL becomes:\n\n```\nhttp://localhost:3141/mcp/voltagent-example/mcp\n```\n\n## 5. Connect via stdio (local process)\n\nSTDIO does not use an HTTP URL. The MCP client spawns your server process and communicates over stdin/stdout. In the startup log you should see a line like:\n\n```\nSTDIO  uses stdin/stdout. Example client: { type: \"stdio\", command: \"node\", args: [\"dist/index.js\"] }\n```\n\nClient example (MCPClient):\n\n```ts\nimport { MCPClient } from \"@voltagent/core\";\n\nconst client = new MCPClient({\n  clientInfo: { name: \"local-client\", version: \"0.1.0\" },\n  server: {\n    type: \"stdio\",\n    command: \"node\",\n    args: [\"dist/index.js\"],\n    cwd: process.cwd(),\n    env: { ...process.env },\n  },\n});\n\nawait client.connect();\nconst tools = await client.listTools();\n```\n\nReplace `command`/`args` with the entry that starts your VoltAgent + MCP server (for example `pnpm start -- --stdio` or `node dist/index.js` after build).\n\n## 6. Connect via HTTP\n\nUse the MCP URL in `MCPConfiguration`:\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    local: {\n      type: \"http\",\n      url: \"http://localhost:3141/mcp/voltagent-example/mcp\",\n    },\n  },\n});\n\nconst tools = await mcpConfig.getTools();\n```\n\n## 7. Optional: add MCP-only entries\n\nSometimes you want MCP clients to see helpers that are not (yet) registered with VoltAgent. Provide them as keyed objects (just like the main `VoltAgent` config) via the `agents`, `workflows`, or `tools` fields to append entries that live only on the MCP side:\n\n```ts title=\"src/mcp/server.ts\"\nimport { Agent, createTool, createWorkflowChain } from \"@voltagent/core\";\nimport { MCPServer } from \"@voltagent/mcp-server\";\nimport { z } from \"zod\";\n\nconst statusTool = createTool({\n  name: \"status\",\n  description: \"Return the current time\",\n  parameters: z.object({}),\n  async execute() {\n    return { status: \"ok\", time: new Date().toISOString() };\n  },\n});\n\nconst supportAgent = new Agent({\n  name: \"Support Agent\",\n  purpose: \"Route customer tickets to the correct queue.\",\n  instructions:\n    \"Use internal knowledge to triage customer tickets and respond with routing guidance.\",\n  model: \"openai/gpt-4o-mini\",\n  tools: [statusTool],\n});\n\nconst incidentWorkflow = createWorkflowChain({\n  id: \"incident-triage\",\n  name: \"Incident Triage\",\n  purpose: \"Placeholder entry for an external workflow.\",\n  input: z.object({ ticketId: z.string() }),\n}).andThen({\n  id: \"acknowledge\",\n  execute: async ({ data }) => ({ ...data, acknowledged: true }),\n});\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  tools: {\n    statusTool,\n  },\n  agents: {\n    supportAgent,\n  },\n  workflows: {\n    incidentWorkflow,\n  },\n});\n```\n\nThese configured entries behave like regular VoltAgent components for MCP clients, they appear in listings and can be invoked-yet they are not registered with the main `VoltAgent` instance.\n\n## 8. Optional: filter exposed agents/workflows/tools\n\nBy default, every agent, workflow, and tool registered with `VoltAgent` is visible to MCP clients. Provide filter functions when you need to hide or reorder the registry output for a specific transport:\n\n```ts title=\"src/mcp/server.ts\"\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP stdio example\",\n  filterAgents: ({ items }) => items.filter((agent) => agent.id !== \"internal\"),\n  filterWorkflows: ({ items }) => items,\n  filterTools: ({ items }) => items,\n});\n```\n\nFilters receive the list of components sourced from the VoltAgent registries (plus any configured additions) and must return the array you want to expose. They are intended for pruning or sorting, use the `agents`/`workflows`/`tools` fields when you need to introduce brand-new entries.\n\n## 9. Optional: stream prompts and resources\n\nMCP clients can ask a server for structured prompt templates (`prompts/list`, `prompts/get`) and arbitrary resources (`resources/list`, `resources/read`). VoltAgent lets you seed static content and/or forward to a dynamic source via the new `adapters` field:\n\n```ts title=\"src/mcp/server.ts\"\nimport { MCPServer } from \"@voltagent/mcp-server\";\n\n// Replace these placeholders with your own services or data sources\nconst voltOps = {\n  prompts: {\n    list: async () => [\n      {\n        name: \"triage\",\n        description: \"Short ticket triage message\",\n        arguments: [],\n      },\n    ],\n    get: async (name: string, version?: string) => ({\n      description: `Prompt ${name}`,\n      messages: [{ role: \"user\", content: { type: \"text\", text: \"Summarise ticket {{id}}\" } }],\n      version,\n    }),\n  },\n};\n\nconst knowledgeBase = {\n  list: async () => [\n    {\n      uri: \"volt://docs/runbook\",\n      name: \"On-call runbook\",\n      description: \"Operational checklist\",\n      mimeType: \"text/markdown\",\n    },\n  ],\n  read: async (uri: string) => ({\n    uri,\n    mimeType: \"text/markdown\",\n    text: \"# On-call runbook\\n...\",\n  }),\n};\n\nexport const mcpServer = new MCPServer({\n  name: \"voltagent-example\",\n  version: \"0.1.0\",\n  description: \"VoltAgent MCP with prompts/resources\",\n  adapters: {\n    prompts: {\n      listPrompts: async () => voltOps.prompts.list(),\n      getPrompt: async (params) => voltOps.prompts.get(params.name, params.version),\n    },\n    resources: {\n      listResources: async () => knowledgeBase.list(),\n      readResource: async (uri) => knowledgeBase.read(uri),\n    },\n  },\n});\n```\n\n- The `adapters` block forwards MCP requests to any backend (VoltOps Prompt Manager, a documentation service, your own REST API).\n- When you change external data, call `await mcpServer.notifyPromptListChanged()` or `await mcpServer.notifyResourceListChanged()` so connected IDEs receive the standard `list_changed` notifications. If you update a specific resource at runtime, invoke `await mcpServer.notifyResourceUpdated(\"volt://docs/runbook\")` to push an incremental update only to subscribers.\n- If an adapter provides a `sendRequest` method, MCP clients can make `elicitation/create` calls. VoltAgent forwards these requests to the adapter so you can collect data from the user and return an `ElicitResult`. Tools can consume the bridge via `operationContext.elicitation`:\n\n```ts\nconst confirmAction = createTool({\n  name: \"confirm-action\",\n  description: \"Ask the operator to approve a risky step\",\n  parameters: z.object({ description: z.string() }),\n  async execute(args, operationContext) {\n    const handler = operationContext?.elicitation;\n\n    if (!handler) {\n      throw new Error(\"Elicitation bridge unavailable\");\n    }\n\n    return handler({\n      schema: {\n        type: \"object\",\n        properties: {\n          confirmed: { type: \"boolean\" },\n        },\n        required: [\"confirmed\"],\n      },\n      message: `Approve the following action: ${args.description}`,\n    });\n  },\n});\n```\n\nThat’s it! Your VoltAgent stack now speaks MCP. Start the agent, open VoltOps Console (or your preferred MCP client), and you’ll see the server listed with its tools, prompts, resources, and workflows ready to debug.\n\n<!-- File: website/docs/agents/mcp/mcp.md (Tokens: ~2040 | Category: subagent-persona) -->\n---\ntitle: MCP Client\nslug: /agents/mcp\n---\n\n# Model Context Protocol (MCP)\n\nThe [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP) is a standardized protocol for AI agents to interact with external tools and services. VoltAgent implements MCP client capabilities, enabling agents to access functionalities like filesystem operations, browser automation, database interactions, and external AI models.\n\n## Transport Types\n\nVoltAgent supports four transport types for MCP connections:\n\n- **`http`**: Attempts streamable HTTP first, automatically falls back to SSE if not supported\n- **`streamable-http`**: Direct streamable HTTP transport (no fallback)\n- **`sse`**: Server-Sent Events transport\n- **`stdio`**: Standard input/output for local processes\n\n## Getting Started with MCPConfiguration\n\n`MCPConfiguration` manages connections to MCP servers and provides their tools to your agents.\n\n```ts\nimport { MCPConfiguration } from \"@voltagent/core\";\nimport path from \"node:path\";\n\nconst mcpConfig = new MCPConfiguration({\n  servers: {\n    // HTTP with automatic fallback\n    github: {\n      type: \"http\",\n      url: \"https://api.githubcopilot.com/mcp\",\n      timeout: 15000, // Optional, default: 30000ms\n    },\n\n    // Streamable HTTP (no fallback)\n    reddit: {\n      type: \"streamable-http\",\n      url: \"https://mcp.composio.dev/reddit/your-api-key-here\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      sessionId: \"optional-session-id\", // Optional\n      timeout: 20000,\n    },\n\n    // SSE transport\n    linear: {\n      type: \"sse\",\n      url: \"https://mcp.linear.app/sse\",\n      requestInit: {\n        headers: { Authorization: \"Bearer token\" },\n      },\n      eventSourceInit: { withCredentials: true },\n      timeout: 25000,\n    },\n\n    // stdio for local processes\n    filesystem: {\n      type: \"stdio\",\n      command: \"npx\",\n      args: [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        path.join(process.env.HOME || \"\", \"Desktop\"),\n      ],\n      cwd: process.env.HOME,\n      env: { NODE_ENV: \"production\" },\n      timeout: 10000,\n    },\n  },\n});\n```\n\n## Working with MCP Tools\n\nRetrieve tools from configured MCP servers as standard `Tool` objects compatible with VoltAgent agents.\n\n### Get All Tools as Flat Array\n\n`getTools()` returns all tools from all servers in a single array:\n\n```ts\nconst allTools = await mcpConfig.getTools();\n\n// allTools is Tool<any>[]\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n### Get Tools Organized by Server\n\n`getToolsets()` returns tools grouped by server name:\n\n```ts\nconst toolsets = await mcpConfig.getToolsets();\n\n// toolsets.filesystem is a ToolsetWithTools object\nconst filesystemTools = toolsets.filesystem.getTools();\n\nconst response = await agent.generateText(\"List all files in my Desktop folder\", {\n  userId: \"user123\",\n  tools: filesystemTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Event Handling\n\nAccess individual clients to listen for events:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\nif (clients.reddit) {\n  clients.reddit.on(\"connect\", () => {\n    console.log(\"Connected to Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"disconnect\", () => {\n    console.log(\"Disconnected from Reddit MCP server\");\n  });\n\n  clients.reddit.on(\"error\", (error) => {\n    console.error(\"Reddit MCP error:\", error.message);\n  });\n\n  clients.reddit.on(\"toolCall\", (name, args, result) => {\n    console.log(`Tool ${name} called with:`, args);\n  });\n}\n```\n\nAvailable events: `connect`, `disconnect`, `error`, `toolCall`\n\n## Cleanup\n\nDisconnect clients when done to terminate processes and free resources:\n\n```ts\nawait mcpConfig.disconnect();\n```\n\nThis is especially important for `stdio` servers, which spawn child processes.\n\n## Adding MCP Tools to an Agent\n\nAdd MCP tools at initialization or per request:\n\n### At Agent Initialization\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst allTools = await mcpConfig.getTools();\n\nconst agent = new Agent({\n  name: \"MCP Agent\",\n  instructions: \"You can use MCP tools to access external systems\",\n  model: \"openai/gpt-4o\",\n  tools: allTools,\n});\n\nawait agent.generateText(\"List files in Desktop\", { userId: \"user123\" });\nawait mcpConfig.disconnect();\n```\n\n### At Request Time\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\nconst agent = new Agent({\n  name: \"Agent\",\n  instructions: \"You are a helpful assistant\",\n  model: \"openai/gpt-4o\",\n});\n\nconst allTools = await mcpConfig.getTools();\n\nconst response = await agent.generateText(\"What are the top posts on r/programming?\", {\n  userId: \"user123\",\n  tools: allTools,\n});\n\nawait mcpConfig.disconnect();\n```\n\n## Error Handling\n\nHandle failures during connection and tool execution:\n\n```ts\n// Connection errors\ntry {\n  const tools = await mcpConfig.getTools();\n} catch (error) {\n  console.error(\"Failed to fetch MCP tools:\", error);\n}\n\n// Client-specific errors via events\nconst clients = await mcpConfig.getClients();\nclients.filesystem?.on(\"error\", (error) => {\n  console.error(\"Filesystem MCP error:\", error);\n});\n\n// Tool execution errors\ntry {\n  const response = await agent.generateText(\"List files\", {\n    userId: \"user123\",\n    tools: await mcpConfig.getTools(),\n  });\n} catch (error) {\n  console.error(\"Agent execution error:\", error);\n}\n```\n\n### HTTP Fallback Behavior\n\nWhen using `type: \"http\"`, the client attempts streamable HTTP first. If it fails, it automatically creates a new SSE transport and retries the connection.\n\n## User Input Handling (Elicitation)\n\nSome MCP servers may request user input during tool execution. For example, a server might ask for confirmation before performing a destructive action. VoltAgent provides a dynamic API to handle these elicitation requests.\n\n### Setting Up an Elicitation Handler\n\nUse the `elicitation` bridge on individual clients to register handlers:\n\n```ts\nconst clients = await mcpConfig.getClients();\n\n// Set a persistent handler\nclients.myServer.elicitation.setHandler(async (request) => {\n  console.log(\"Server asks:\", request.message);\n  console.log(\"Expected schema:\", request.requestedSchema);\n\n  // Collect user input (e.g., via CLI prompt or UI form)\n  const userConfirmed = await askUserForConfirmation(request.message);\n\n  return {\n    action: userConfirmed ? \"accept\" : \"decline\",\n    content: userConfirmed ? { confirmed: true } : undefined,\n  };\n});\n```\n\nThe handler receives a request object with:\n\n- `message`: Human-readable message from the server\n- `requestedSchema`: JSON schema describing the expected response format\n\nThe handler must return an object with:\n\n- `action`: One of `\"accept\"`, `\"decline\"`, or `\"cancel\"`\n- `content`: Optional data matching the requested schema (when accepting)\n\n### One-Time Handler\n\nHandle only the next elicitation request, then auto-remove:\n\n```ts\nclients.myServer.elicitation.once(async (request) => {\n  return { action: \"accept\", content: { approved: true } };\n});\n```\n\n### Removing the Handler\n\n```ts\nclients.myServer.elicitation.removeHandler();\n```\n\nWhen no handler is registered, elicitation requests are automatically cancelled.\n\n### Checking Handler Status\n\n```ts\nif (clients.myServer.elicitation.hasHandler) {\n  console.log(\"Handler is registered\");\n} else {\n  console.log(\"No handler - requests will be cancelled\");\n}\n```\n\n### Method Chaining\n\nAll elicitation methods support chaining:\n\n```ts\nclients.myServer.elicitation.setHandler(myHandler).removeHandler().setHandler(anotherHandler);\n```\n\n### Agent-Level Elicitation\n\nInstead of setting handlers on individual clients, you can pass an elicitation handler directly to `generateText` or `streamText`. This handler applies to all MCP tools during the request:\n\n```ts\nconst response = await agent.generateText(\"Perform action with MCP tool\", {\n  userId: \"user123\",\n  elicitation: async (request) => {\n    console.log(\"Server asks:\", request.message);\n\n    // Collect user input (CLI prompt, UI form, etc.)\n    const confirmed = await askUser(request.message);\n\n    return {\n      action: confirmed ? \"accept\" : \"decline\",\n      content: confirmed ? { confirmed: true } : undefined,\n    };\n  },\n});\n```\n\nThis approach is useful when:\n\n- You want request-scoped handlers that don't persist\n- You need access to request context (userId, conversationId) in your handler\n- You prefer not to manage client-level handlers manually\n\n## Lifecycle\n\n1. Create `MCPConfiguration` with server definitions\n2. Fetch tools with `getTools()` or `getToolsets()`\n3. Pass tools to agent (initialization or per request)\n4. Set up elicitation handlers if needed (optional)\n5. Use agent methods (`generateText`, `streamText`, etc.)\n6. Monitor with `getClients()` and event listeners (optional)\n7. Disconnect with `disconnect()`\n\n<!-- File: website/docs/agents/memory.md (Tokens: ~601 | Category: subagent-persona) -->\n---\ntitle: Memory\nslug: /agents/memory\n---\n\n# Agent Memory\n\nVoltAgent's `Memory` class stores conversation history and enables agents to maintain context across interactions. Supports persistent storage, semantic search, and working memory.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n- **Conversation Storage** - Messages stored per `userId` and `conversationId`\n- **Semantic Search** - Retrieve past messages by similarity (requires embedding + vector adapters)\n- **Working Memory** - Compact context storage (Markdown template, JSON schema, or free-form)\n- **Workflow State** - Suspendable workflow checkpoint storage\n\n## Quick Start\n\n```typescript\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"my-app-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// First message\nawait agent.generateText(\"My name is Sarah\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n\n// Agent remembers context\nawait agent.generateText(\"What's my name?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"chat-001\",\n  },\n});\n```\n\n## Complete Documentation\n\nFor detailed configuration, provider setup, and advanced features:\n\n- **[Memory Overview](./memory/overview.md)** - Full memory system documentation\n- **[Memory API Endpoints](../api/endpoints/memory.md)** - HTTP endpoints for conversations and messages\n- **[Managed Memory](./memory/managed-memory.md)** - Production-ready hosted storage\n- **[Semantic Search](./memory/semantic-search.md)** - Vector-based message retrieval\n- **[Working Memory](./memory/working-memory.md)** - Compact context management\n- **[Storage Adapters](./memory/in-memory.md)** - Provider-specific guides\n\n<!-- File: website/docs/agents/memory/cloudflare-d1.md (Tokens: ~988 | Category: subagent-persona) -->\n---\ntitle: Cloudflare D1 Memory\nslug: /agents/memory/cloudflare-d1\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Cloudflare D1 Memory\n\n`D1MemoryAdapter` stores conversations in Cloudflare D1 for Workers deployments.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/cloudflare-d1\n```\n\n  </TabItem>\n</Tabs>\n\n## Cloudflare D1 binding\n\nAdd a D1 binding to your `wrangler.toml`:\n\n```toml\n[[d1_databases]]\nbinding = \"DB\"\ndatabase_name = \"voltagent\"\ndatabase_id = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n```\n\n## Configuration (Workers)\n\n```ts\nimport { Agent, Memory, VoltAgent } from \"@voltagent/core\";\nimport { D1MemoryAdapter } from \"@voltagent/cloudflare-d1\";\nimport { serverlessHono } from \"@voltagent/serverless-hono\";\nimport type { D1Database } from \"@cloudflare/workers-types\";\n\nimport { weatherTool } from \"./tools\";\n\ntype Env = {\n  DB: D1Database;\n  OPENAI_API_KEY: string;\n};\n\nconst createWorker = (env: Env) => {\n  const memory = new Memory({\n    storage: new D1MemoryAdapter({\n      binding: env.DB,\n      tablePrefix: \"voltagent_memory\",\n    }),\n  });\n\n  const agent = new Agent({\n    name: \"Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    tools: [weatherTool],\n    memory,\n  });\n\n  const voltAgent = new VoltAgent({\n    agents: { agent },\n    serverless: serverlessHono(),\n  });\n\n  return voltAgent.serverless().toCloudflareWorker();\n};\n\nlet cached: ReturnType<typeof createWorker> | undefined;\n\nexport default {\n  fetch: (request: Request, env: Env, ctx: ExecutionContext) => {\n    if (!cached) {\n      cached = createWorker(env);\n    }\n\n    return cached.fetch(request, env, ctx);\n  },\n};\n```\n\n### Configuration Options\n\n| Option         | Type         | Description                                              |\n| -------------- | ------------ | -------------------------------------------------------- |\n| `binding`      | `D1Database` | Worker D1 binding (required)                             |\n| `tablePrefix`  | `string`     | Table name prefix (default: `voltagent_memory`)          |\n| `maxRetries`   | `number`     | Retry count for busy/locked DB operations (default: `3`) |\n| `retryDelayMs` | `number`     | Initial backoff delay in ms (default: `100`)             |\n| `debug`        | `boolean`    | Enable debug logging (default: `false`)                  |\n| `logger`       | `Logger`     | Optional logger for structured logging                   |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n- `${tablePrefix}_steps`\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new D1MemoryAdapter({ binding: env.DB }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Optional)\n\nD1 provides storage only. To enable semantic search, pair it with an embedding model string (for example, `openai/text-embedding-3-small`) and a vector adapter such as `InMemoryVectorAdapter`.\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n## Learn More\n\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n\n<!-- File: website/docs/agents/memory/in-memory.md (Tokens: ~811 | Category: subagent-persona) -->\n---\ntitle: In-Memory Storage\nslug: /agents/memory/in-memory\n---\n\n# In-Memory Storage\n\n`InMemoryStorageAdapter` stores conversation history in application memory. Data is lost when the application restarts.\n\n## Default Behavior\n\nAgents use in-memory storage by default when no `memory` option is provided:\n\n```ts\nimport { Agent } from \"@voltagent/core\";\n\n// Uses InMemoryStorageAdapter automatically\nconst agent = new Agent({\n  name: \"Assistant\",\n  instructions: \"Help users with questions.\",\n  model: \"openai/gpt-4o-mini\",\n});\n```\n\nOmitting `memory` does **not** disable memory. To make an agent stateless, set `memory: false`.\n\n## Explicit Configuration\n\nConfigure storage limits explicitly:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n## Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved in memory\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Semantic Search (Development)\n\nCombine with `InMemoryVectorAdapter` for semantic search during development:\n\n```ts\nimport { Memory, InMemoryVectorAdapter, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new InMemoryStorageAdapter(),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n});\n```\n\nBoth storage and vectors are lost on restart. For persistent vectors, use `LibSQLVectorAdapter`.\n\n## Use Cases\n\n### Development & Testing\n\nTest agent logic without database setup:\n\n```ts\nimport { Agent, Memory, InMemoryStorageAdapter } from \"@voltagent/core\";\n\nconst testAgent = new Agent({\n  name: \"Test Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new InMemoryStorageAdapter(),\n  }),\n});\n\n// Test conversations without persistence\nawait testAgent.generateText(\"Test message\", {\n  memory: {\n    userId: \"test-user\",\n    conversationId: \"test-conversation\",\n  },\n});\n```\n\n### Stateless Deployments\n\nServerless functions or ephemeral containers where persistence isn't needed:\n\n```ts\n// Cloud function handler\nexport async function handler(event) {\n  const agent = new Agent({\n    name: \"Serverless Assistant\",\n    model: \"openai/gpt-4o-mini\",\n    // Default in-memory storage\n  });\n\n  return await agent.generateText(event.message, {\n    memory: {\n      userId: event.userId,\n      conversationId: event.sessionId,\n    },\n  });\n}\n```\n\n### Demos & Examples\n\nQuick prototypes without infrastructure dependencies.\n\n## Limitations\n\n- **No persistence** - All data lost on restart\n- **Memory usage** - Large message counts consume application memory\n- **Not for production** - Use persistent adapters for production applications\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n\n<!-- File: website/docs/agents/memory/libsql.md (Tokens: ~1160 | Category: subagent-persona) -->\n---\ntitle: LibSQL / SQLite Memory\nslug: /agents/memory/libsql\n---\n\n# LibSQL / SQLite Memory\n\n`LibSQLMemoryAdapter` stores conversations in LibSQL, supporting local SQLite files, remote instances, and self-hosted `sqld`.\n\n## Installation\n\n```bash\nnpm install @voltagent/libsql\n```\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\n// Local SQLite\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"file:./.voltagent/memory.db\", // or \":memory:\" for ephemeral\n  }),\n});\n\n// Remote instance\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \"libsql://your-database.example.io\",\n    authToken: process.env.LIBSQL_AUTH_TOKEN,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option        | Type     | Description                                                |\n| ------------- | -------- | ---------------------------------------------------------- |\n| `url`         | `string` | Connection URL (`file:<path>`, `libsql://`, or `:memory:`) |\n| `authToken`   | `string` | Auth token for remote instances (optional for local files) |\n| `tablePrefix` | `string` | Table name prefix (default: `voltagent_memory`)            |\n| `logger`      | `Logger` | Optional logger for debugging                              |\n\n### URL Formats\n\n- **Local SQLite**: `file:./.voltagent/memory.db` or `file:memory.db`\n- **In-memory (testing)**: `:memory:` or `file::memory:`\n- **Remote instance** (e.g., Turso): `libsql://your-database.example.io`\n- **Self-hosted sqld**: `libsql://your-server.com` or `https://your-server.com`\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nSchema migrations run automatically when updating VoltAgent versions.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\", // or \"user\"\n    template: `# Context\\n- Key facts:\\n`,\n  },\n});\n```\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### Vector Storage (Optional)\n\nUse `LibSQLVectorAdapter` for persistent vector storage:\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter, LibSQLVectorAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new LibSQLVectorAdapter({ url: \"file:./.voltagent/memory.db\" }),\n});\n```\n\n**Vector adapter options:**\n\n- Local file: Same URL as storage adapter\n- In-memory: `:memory:` (vectors lost on restart)\n- Turso: Same URL + auth token as storage adapter\n\nSee [Semantic Search](./semantic-search.md) for usage.\n\n**Use cases:**\n\n- Local development with SQLite files\n- Edge deployments with remote instances\n- Serverless environments\n- Simple deployments without separate database servers\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Examples\n\n### Development with Local SQLite\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Dev Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: \"file:./.voltagent/dev-memory.db\",\n    }),\n  }),\n});\n```\n\n### Remote Instance\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({\n      url: process.env.LIBSQL_DATABASE_URL!,\n      authToken: process.env.LIBSQL_AUTH_TOKEN!,\n    }),\n  }),\n});\n```\n\n### Testing with In-Memory Database\n\n```ts\nconst testMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({\n    url: \":memory:\", // ephemeral\n  }),\n});\n```\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search with LibSQLVectorAdapter\n\n<!-- File: website/docs/agents/memory/managed-memory.md (Tokens: ~1796 | Category: subagent-persona) -->\n---\ntitle: Managed Memory\nslug: /agents/memory/managed-memory\n---\n\n# Managed Memory\n\nVoltOps Managed Memory is a production-ready hosted memory service for VoltAgent. Create a database through the VoltOps Console and connect using API credentials - no infrastructure provisioning or schema management required.\n\n## Availability\n\n- **US Region**: Virginia (us-east-1)\n- **EU Region**: Germany (eu-central-1)\n\n## Installation\n\n```bash\nnpm install @voltagent/voltagent-memory\n```\n\n## Configuration\n\n### Automatic Setup (Recommended)\n\nGet your credentials from [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory) and set environment variables:\n\n<video controls width=\"100%\">\n  <source src=\"https://cdn.voltagent.dev/docs/voltagent-managed-memory.mp4\" type=\"video/mp4\" />\n  Your browser does not support the video tag.\n</video>\n\n```bash\n# .env\nVOLTAGENT_PUBLIC_KEY=pk_...\nVOLTAGENT_SECRET_KEY=sk_...\n```\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\n// Adapter automatically uses VoltOps credentials from environment\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nThe adapter checks `AgentRegistry` for a global `VoltOpsClient` instance configured from environment variables. This is the simplest setup - no need to instantiate `VoltOpsClient` manually.\n\n### Manual Setup\n\nPass a `VoltOpsClient` instance explicitly:\n\n```ts\nimport { Agent, Memory, VoltOpsClient } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY!,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY!,\n  // baseUrl: \"https://api.voltagent.dev\", // optional\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient, // explicit client\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\nUse manual setup when:\n\n- Running multiple adapters with different credentials\n- Dynamically changing credentials at runtime\n- Testing with mock clients\n\n### Configuration Options\n\n| Option          | Type            | Required | Description                                                      |\n| --------------- | --------------- | -------- | ---------------------------------------------------------------- |\n| `databaseName`  | `string`        | Yes\\*    | Database name from VoltOps Console                               |\n| `databaseId`    | `string`        | Yes\\*    | Database ID from VoltOps API (alternative to `databaseName`)     |\n| `voltOpsClient` | `VoltOpsClient` | No       | Explicit VoltOps client (optional if credentials in environment) |\n| `debug`         | `boolean`       | No       | Enable debug logging (default: `false`)                          |\n\n\\*Either `databaseName` or `databaseId` is required.\n\n## Creating a Database\n\n1. Navigate to [console.voltagent.dev/memory/managed-memory](https://console.voltagent.dev/memory/managed-memory)\n2. Click **Create Database**\n3. Enter a name and select region (US or EU)\n4. Copy the database credentials\n\nTo get your VoltOps API keys, go to **Settings** in the console to find your public and secret keys.\n\n## Features\n\n### Conversation Storage\n\nAll `StorageAdapter` methods are supported:\n\n- Message persistence (`addMessage`, `addMessages`, `getMessages`, `clearMessages`)\n- Conversation management (`createConversation`, `getConversation`, `updateConversation`, `deleteConversation`)\n- Working memory (`getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`)\n- Workflow state (`getWorkflowState`, `setWorkflowState`, `updateWorkflowState`)\n\n### Vector Storage (Optional)\n\nEnable semantic search with `ManagedMemoryVectorAdapter`:\n\n```ts\nimport { ManagedMemoryAdapter, ManagedMemoryVectorAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new ManagedMemoryVectorAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n```\n\nBoth adapters resolve credentials the same way (environment or explicit client). See [Semantic Search](./semantic-search.md) for usage.\n\n## Migration from Self-Hosted\n\n### Export from LibSQL\n\n```ts\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst localAdapter = new LibSQLMemoryAdapter({\n  url: \"file:./.voltagent/memory.db\",\n});\n\n// Export conversations for a user\nconst conversations = await localAdapter.getConversationsByUserId(\"user-123\");\n\nfor (const conv of conversations) {\n  const messages = await localAdapter.getMessages(\"user-123\", conv.id);\n  console.log(`Conversation ${conv.id}: ${messages.length} messages`);\n}\n```\n\n### Import to Managed Memory\n\n```ts\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { Memory } from \"@voltagent/core\";\n\nconst managedMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n  }),\n});\n\n// Import conversation\nawait managedMemory.createConversation({\n  id: conv.id,\n  userId: conv.userId,\n  resourceId: conv.resourceId,\n  title: conv.title,\n  metadata: conv.metadata,\n});\n\n// Import messages\nawait managedMemory.addMessages(messages, conv.userId, conv.id);\n```\n\nBulk import/export APIs are planned for future releases.\n\n## Use Cases\n\n### Development & Prototyping\n\n```ts\n// No database setup required for pilots\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"dev-memory\",\n  }),\n});\n```\n\n### Multi-Region Deployment\n\n```ts\n// US database for North American users\nconst usMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-us\",\n  }),\n});\n\n// EU database for European users\nconst euMemory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"memory-eu\",\n  }),\n});\n```\n\n### Team Collaboration\n\nMultiple developers connect to the same managed database using shared VoltOps credentials. Create separate databases for staging and production.\n\n## Limitations\n\n- **Regional latency**: Choose the region closest to your application servers\n- **Storage quotas**: Check VoltOps Console for plan-specific limits\n- **Credential rotation**: Update environment variables when rotating credentials\n\n## Comparison with Self-Hosted\n\n| Feature             | Managed Memory    | Self-Hosted               |\n| ------------------- | ----------------- | ------------------------- |\n| Setup time          | < 3 minutes       | Hours                     |\n| Schema management   | Automatic         | Manual migrations         |\n| Regional hosting    | US & EU           | DIY                       |\n| Credential rotation | API-based         | Manual                    |\n| Monitoring          | Console dashboard | DIY                       |\n| Cost                | Usage-based       | Infrastructure + ops time |\n\nProduction-ready for teams without database infrastructure or during rapid deployment.\n\n## Troubleshooting\n\n### Adapter initialization failed\n\n```\nError: Unable to locate managed memory database\n```\n\n**Solution**: Verify `databaseName` matches the name in VoltOps Console, or use `databaseId` instead.\n\n### VoltOps client not available\n\n```\nError: VoltOps client is not available for managed memory initialization\n```\n\n**Solution**: Ensure `VOLTAGENT_PUBLIC_KEY` and `VOLTAGENT_SECRET_KEY` environment variables are set, or pass `voltOpsClient` explicitly.\n\n### Enable debug logging\n\n```ts\nconst adapter = new ManagedMemoryAdapter({\n  databaseName: \"production-memory\",\n  debug: true, // Logs all API calls and metadata\n});\n```\n\n## Learn More\n\n- **[Semantic Search](./semantic-search.md)** - Enable vector search with managed memory\n- **[Working Memory](./working-memory.md)** - Configure working memory with managed storage\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres alternative\n\n<!-- File: website/docs/agents/memory/overview.md (Tokens: ~3127 | Category: subagent-persona) -->\n---\ntitle: Overview\nslug: /agents/memory/overview\n---\n\n# Memory Overview\n\nVoltAgent's `Memory` class stores conversation history and optional semantic search vectors. Agents retrieve past messages before generating responses and persist new interactions after completion.\n\n## Storage Providers\n\n| Provider           | Package                       | Persistence            | Use Case                         |\n| ------------------ | ----------------------------- | ---------------------- | -------------------------------- |\n| **InMemory**       | `@voltagent/core`             | None (RAM only)        | Development, testing             |\n| **Managed Memory** | `@voltagent/voltagent-memory` | VoltOps-hosted         | Production-ready, zero-setup     |\n| **LibSQL**         | `@voltagent/libsql`           | Local SQLite or remote | Self-hosted, edge deployments    |\n| **Cloudflare D1**  | `@voltagent/cloudflare-d1`    | Cloudflare D1 (SQLite) | Cloudflare Workers deployments   |\n| **Postgres**       | `@voltagent/postgres`         | Self-hosted Postgres   | Existing Postgres infrastructure |\n| **Supabase**       | `@voltagent/supabase`         | Supabase               | Supabase-based applications      |\n\n## Core Features\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- Auto-creates conversations on first message\n- Configurable message limits (oldest pruned first)\n\n### Conversation Titles (Optional)\n\nWhen enabled, VoltAgent generates a concise title from the first user message. Title generation runs only when the conversation is created and does not overwrite existing titles.\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: true,\n});\n```\n\nCustom configuration:\n\n```ts\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  generateTitle: {\n    enabled: true,\n    model: \"gpt-4o-mini\", // default agent model\n    systemPrompt: \"Generate a short title (max 6 words).\",\n    temperature: null, // optional; default is 0, use null to omit\n    maxLength: 60,\n    maxOutputTokens: 128, // optional; default is 32\n  },\n});\n```\n\nNotes:\n\n- The agent's main model is used unless `generateTitle.model` is provided.\n- `generateTitle.model` accepts either a provider/model string or an AI SDK model instance.\n- Title generation sends `temperature: 0` by default. Set `generateTitle.temperature` to `null` to\n  omit the parameter for reasoning models such as OpenAI `o`-series and `gpt-5-mini`.\n- Reasoning models may need a higher `maxOutputTokens` value because reasoning tokens count toward\n  the output budget. Configure `generateTitle.maxOutputTokens` if the default `32` is too low.\n- If title generation fails, VoltAgent keeps the default conversation title and logs the cause at\n  `warn` level, including a hint to set `generateTitle.temperature` to `null` when appropriate.\n- Only the first user message is summarized.\n- If you create conversations manually via the Memory API, set `title` explicitly.\n\n### Conversation Steps\n\n- Every LLM/text/tool step can be recorded with metadata (operationId, agent/sub-agent IDs, usage, tool arguments/results).\n- VoltOps Observability consumes these records to render the Memory Explorer “Steps” tab and to correlate traces/logs with memory.\n- Adapters that implement the step APIs persist sub-agent activity alongside primary agent steps, so hierarchies stay visible.\n- If an adapter does not support steps, the console warns with “Conversation steps are not supported by this memory adapter.”\n- By default, agents checkpoint conversation progress during multi-step runs (`conversationPersistence.mode: \"step\"`).\n- Tool completion events (`tool-result`, `tool-error`) trigger immediate flushes by default (`flushOnToolResult: true`), reducing history loss risk on crashes or process interruption.\n- Use `conversationPersistence.mode: \"finish\"` if you want end-of-turn persistence only.\n\n### Semantic Search (Optional)\n\n- Requires `embedding` + `vector` adapters\n- Auto-embeds messages on save\n- Retrieves similar past messages by content, not recency\n- Merges semantic results with recent messages\n\n### Working Memory (Optional)\n\n- Stores compact context across conversation turns\n- Three formats: Markdown template, JSON schema (Zod), or free-form\n- Two scopes: `conversation` (default) or `user`\n- Agent exposes tools: `get_working_memory`, `update_working_memory`, `clear_working_memory`\n- If `memory.options.readOnly: true` is set per call, only `get_working_memory` is exposed and memory writes are skipped\n\n### Workflow State\n\n- Suspendable workflow checkpoint storage\n- Tracks execution state, context, suspension metadata\n\n## Agent Configuration\n\nAgents accept a `memory` option:\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\n\n// Default when omitted: built-in in-memory storage\nconst agent1 = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  // memory omitted (or undefined)\n});\n\n// Disable memory entirely\nconst agent2 = new Agent({\n  name: \"Stateless\",\n  model: \"openai/gpt-4o-mini\",\n  memory: false,\n});\n\n// Persistent storage\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agent3 = new Agent({\n  name: \"Persistent\",\n  model: \"openai/gpt-4o-mini\",\n  memory: new Memory({\n    storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  }),\n});\n```\n\n**`memory` option behavior**\n\n| Value                 | Behavior                                                                |\n| --------------------- | ----------------------------------------------------------------------- |\n| Omitted / `undefined` | Conversation memory is enabled with a built-in `InMemoryStorageAdapter` |\n| `false`               | Conversation memory is disabled                                         |\n| `new Memory({ ... })` | Conversation memory is enabled with your configured adapter             |\n\nFor stateless sub-agents, set `memory: false` on each sub-agent explicitly.\n\nWhen a supervisor delegates to sub-agents, delegated messages are tagged with sub-agent metadata so supervisor memory reads can filter sub-agent records from parent conversation context.\n\n### Global Defaults (VoltAgent)\n\nSet default memory instances once at the VoltAgent entrypoint. Defaults apply only when an agent or workflow does not specify `memory`. If nothing is configured, VoltAgent still falls back to built-in in-memory storage. An explicit `memory: false` on an agent disables memory and bypasses defaults.\n\n```ts\nimport { Memory, VoltAgent } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\n\nconst agentMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/agent.db\" }),\n});\n\nconst workflowMemory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/workflows.db\" }),\n});\n\nnew VoltAgent({\n  agentMemory,\n  workflowMemory,\n  // memory: sharedFallbackMemory,\n});\n```\n\n**Precedence**\n\n- Agents: agent `memory` > `agentMemory` > `memory` > built-in in-memory\n- Workflows: workflow `memory` > `workflowMemory` > `memory` > built-in in-memory\n\n## Usage with User and Conversation IDs\n\nProvide `memory.userId` and `memory.conversationId` in generation calls to scope memory:\n\n```ts\nconst response = await agent.generateText(\"What did we discuss yesterday?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n  },\n});\n```\n\n**Behavior:**\n\n- **Both provided**: Retrieves history for that specific thread\n- **Only userId**: New `conversationId` generated per call (fresh context each time)\n- **Neither provided**: Uses default user ID with new conversation ID\n\n## Examples\n\n### Managed Memory (Zero Setup)\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { ManagedMemoryAdapter } from \"@voltagent/voltagent-memory\";\nimport { VoltOpsClient } from \"@voltagent/core\";\n\nconst voltOpsClient = new VoltOpsClient({\n  publicKey: process.env.VOLTAGENT_PUBLIC_KEY,\n  secretKey: process.env.VOLTAGENT_SECRET_KEY,\n});\n\nconst memory = new Memory({\n  storage: new ManagedMemoryAdapter({\n    databaseName: \"production-memory\",\n    voltOpsClient,\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Semantic Search + Working Memory\n\n```ts\nimport { Agent, Memory, InMemoryVectorAdapter } from \"@voltagent/core\";\nimport { LibSQLMemoryAdapter } from \"@voltagent/libsql\";\nimport { z } from \"zod\";\n\nconst memory = new Memory({\n  storage: new LibSQLMemoryAdapter({ url: \"file:./.voltagent/memory.db\" }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new InMemoryVectorAdapter(),\n  workingMemory: {\n    enabled: true,\n    scope: \"conversation\",\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n      goals: z.array(z.string()).optional(),\n    }),\n  },\n});\n\nconst agent = new Agent({\n  name: \"Smart Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n\n// Enable semantic search per call\nconst result = await agent.generateText(\"What preferences did I mention?\", {\n  memory: {\n    userId: \"user-123\",\n    conversationId: \"thread-abc\",\n    options: {\n      semanticMemory: {\n        enabled: true,\n        semanticLimit: 5,\n        semanticThreshold: 0.7,\n      },\n    },\n  },\n});\n```\n\n## Custom Adapters\n\nImplement the `StorageAdapter` interface to use custom databases:\n\n```ts\nimport type { StorageAdapter, UIMessage, Conversation, OperationContext } from \"@voltagent/core\";\n\nexport class MyStorageAdapter implements StorageAdapter {\n  async addMessage(\n    msg: UIMessage,\n    userId: string,\n    conversationId: string,\n    context?: OperationContext // Optional: access operation context\n  ): Promise<void> {\n    // Access user-provided context values\n    const tenantId = context?.context.get(\"tenantId\");\n\n    // Use logger and tracing\n    context?.logger.info(\"Storing message\", { userId, tenantId });\n\n    // Store message in your database\n  }\n\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: { limit?: number },\n    context?: OperationContext // Optional: access operation context\n  ): Promise<UIMessage[]> {\n    // Retrieve messages in chronological order (oldest first)\n    return [];\n  }\n\n  async createConversation(input: CreateConversationInput): Promise<Conversation> {\n    // Create conversation record\n  }\n\n  // ... implement remaining StorageAdapter methods\n}\n```\n\nRequired methods:\n\n- If you want Memory Explorer’s Steps tab (and Observability APIs) to work, make sure your adapter persists conversation steps by implementing the methods below. Otherwise VoltAgent will surface the “Conversation steps are not supported by this memory adapter.” warning.\n\n- Messages: `addMessage`, `addMessages`, `getMessages`, `clearMessages`\n- Conversations: `createConversation`, `getConversation`, `getConversations`, `getConversationsByUserId`, `queryConversations`, `updateConversation`, `deleteConversation`\n- Conversation steps: `saveConversationSteps`, `getConversationSteps`\n- Working memory: `getWorkingMemory`, `setWorkingMemory`, `deleteWorkingMemory`\n- Workflow state: `getWorkflowState`, `queryWorkflowRuns`, `setWorkflowState`, `updateWorkflowState`, `getSuspendedWorkflowStates`\n\nUse `queryWorkflowRuns({ workflowId?, status?, from?, to?, limit?, offset? })` for filtered/paginated listings (ordered by newest first).\n\n### Advanced: Context-Aware Adapters\n\nCustom adapters can access `OperationContext` for dynamic, per-request behavior:\n\n```ts\nimport { InMemoryStorageAdapter } from \"@voltagent/core\";\nimport type { OperationContext } from \"@voltagent/core/agent\";\n\nclass TenantMemoryAdapter extends InMemoryStorageAdapter {\n  async getMessages(\n    userId: string,\n    conversationId: string,\n    options?: GetMessagesOptions,\n    context?: OperationContext\n  ): Promise<UIMessage[]> {\n    // Read tenant from user-provided context\n    const tenantId = context?.context.get(\"tenantId\") as string;\n\n    if (!tenantId) {\n      throw new Error(\"Tenant ID required\");\n    }\n\n    // Create tenant-scoped user ID\n    const scopedUserId = `${tenantId}:${userId}`;\n\n    // Log with tracing\n    context?.logger.info(\"Tenant memory access\", {\n      tenantId,\n      userId,\n      scopedUserId,\n    });\n\n    // Use scoped ID for data isolation\n    return super.getMessages(scopedUserId, conversationId, options, context);\n  }\n}\n\n// Use the adapter\nconst agent = new Agent({\n  memory: new Memory({ storage: new TenantMemoryAdapter() }),\n});\n\n// Pass tenant per request\nawait agent.generateText(\"Query\", {\n  memory: {\n    userId: \"user-123\",\n  },\n  context: { tenantId: \"company-abc\" }, // Different tenant = different data\n});\n```\n\n**Use cases for context-aware adapters:**\n\n- **Multi-tenancy**: Isolate data by tenant using composite keys\n- **Audit logging**: Track all memory access with full context\n- **Access control**: Validate permissions before queries\n- **Dynamic behavior**: Adjust queries based on runtime values\n\nSee [Operation Context](../context.md#memory-adapters) for more examples and patterns.\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Semantic Search](./semantic-search.md)** - Retrieve messages by similarity\n- **[Working Memory](./working-memory.md)** - Maintain compact context across turns\n- **[LibSQL / SQLite](./libsql.md)** - Self-hosted SQLite or edge deployments\n- **[PostgreSQL](./postgres.md)** - Self-hosted Postgres adapter\n- **[Supabase](./supabase.md)** - Supabase integration\n- **[In-Memory Storage](./in-memory.md)** - Default ephemeral storage\n\n<!-- File: website/docs/agents/memory/postgres.md (Tokens: ~1752 | Category: subagent-persona) -->\n---\ntitle: PostgreSQL Memory\nslug: /agents/memory/postgres\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# PostgreSQL Memory\n\n`PostgreSQLMemoryAdapter` stores conversations in PostgreSQL for production applications with existing Postgres infrastructure.\n\n## Installation\n\n<Tabs groupId=\"package-manager\">\n  <TabItem value=\"npm\" label=\"npm\" default>\n\n```bash\nnpm install @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"yarn\" label=\"yarn\">\n\n```bash\nyarn add @voltagent/postgres\n```\n\n  </TabItem>\n  <TabItem value=\"pnpm\" label=\"pnpm\">\n\n```bash\npnpm add @voltagent/postgres\n```\n\n  </TabItem>\n</Tabs>\n\n## Configuration\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\n// Using connection string\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n    // or: \"postgresql://user:password@localhost:5432/mydb\"\n  }),\n});\n\n// Using connection object\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST || \"localhost\",\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: process.env.NODE_ENV === \"production\",\n    },\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Configuration Options\n\n| Option           | Type               | Description                                                                            |\n| ---------------- | ------------------ | -------------------------------------------------------------------------------------- |\n| `connection`     | `string \\| object` | Connection string or object with `host`, `port`, `database`, `user`, `password`, `ssl` |\n| `maxConnections` | `number`           | Connection pool size (default: `10`)                                                   |\n| `tablePrefix`    | `string`           | Table name prefix (default: `voltagent_memory`)                                        |\n| `debug`          | `boolean`          | Enable debug logging (default: `false`)                                                |\n\n## Features\n\n### Automatic Schema Creation\n\nTables are created automatically on first use:\n\n- `${tablePrefix}_users`\n- `${tablePrefix}_conversations`\n- `${tablePrefix}_messages`\n- `${tablePrefix}_workflow_states`\n\nPostgreSQL version 12 or higher recommended.\n\n### Conversation Storage\n\n- Messages stored per `userId` and `conversationId`\n- All `StorageAdapter` methods supported\n- Supports complex queries with filtering, pagination, and sorting\n- No automatic message pruning - all messages are preserved\n\n### Working Memory\n\nSupports both conversation and user-scoped working memory:\n\n```ts\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  workingMemory: {\n    enabled: true,\n    scope: \"user\", // or \"conversation\"\n    schema: z.object({\n      preferences: z.array(z.string()).optional(),\n    }),\n  },\n});\n```\n\nStorage:\n\n- Conversation scope: `conversations.metadata.workingMemory`\n- User scope: `${tablePrefix}_users.metadata.workingMemory`\n\nSee [Working Memory](./working-memory.md) for configuration details.\n\n### PostgreSQL Vector Storage\n\nStore vector embeddings directly in PostgreSQL for semantic search (no extensions required):\n\n```ts\nimport { Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter, PostgresVectorAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n  embedding: \"openai/text-embedding-3-small\",\n  vector: new PostgresVectorAdapter({\n    connection: process.env.DATABASE_URL!,\n  }),\n});\n```\n\n**How It Works:**\n\n- **BYTEA Storage**: Vectors are stored as binary data using PostgreSQL's native `BYTEA` type\n- **In-Memory Similarity**: Cosine similarity is computed in-memory after loading vectors\n- **No Extensions**: Works with vanilla PostgreSQL 12+ without any extensions\n- **Automatic Setup**: Creates tables and indexes automatically on first use\n\n**Configuration Options:**\n\n```ts\nconst vectorAdapter = new PostgresVectorAdapter({\n  connection: process.env.DATABASE_URL!,\n\n  // Optional: customize table name (default: \"voltagent_vector\")\n  tablePrefix: \"custom_vector\",\n\n  // Optional: LRU cache size for frequently accessed vectors (default: 100)\n  cacheSize: 100,\n\n  // Optional: max vector dimensions (default: 1536)\n  maxVectorDimensions: 1536,\n});\n```\n\n**Benefits:**\n\n- **Unified Storage**: Keep messages and vectors in the same database\n- **No Extensions**: Works with any PostgreSQL instance (managed or self-hosted)\n- **Simple Setup**: No extension installation or special configuration needed\n- **Standard Tools**: Use familiar PostgreSQL monitoring and backup tools\n\n**Performance Considerations:**\n\nThis adapter loads all vectors into memory for similarity computation, which works well for:\n\n- Small to medium datasets (< 10,000 vectors)\n- Development and prototyping\n- Environments where extension installation is restricted\n\nFor large-scale production with millions of vectors, consider specialized vector databases or use `InMemoryVectorAdapter` with periodic persistence.\n\nSee [Semantic Search](./semantic-search.md) for detailed configuration and usage.\n\n## Production Setup\n\n```ts\nimport { Agent, Memory } from \"@voltagent/core\";\nimport { PostgreSQLMemoryAdapter } from \"@voltagent/postgres\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: true, // Enable SSL for production\n    },\n    maxConnections: 20, // Adjust based on load\n  }),\n});\n\nconst agent = new Agent({\n  name: \"Production Assistant\",\n  model: \"openai/gpt-4o-mini\",\n  memory,\n});\n```\n\n### Advanced SSL Configuration\n\nFor databases requiring custom CA certificates or client certificates:\n\n```ts\nimport fs from \"fs\";\n\nconst memory = new Memory({\n  storage: new PostgreSQLMemoryAdapter({\n    connection: {\n      host: process.env.DB_HOST!,\n      port: parseInt(process.env.DB_PORT || \"5432\"),\n      database: process.env.DB_NAME!,\n      user: process.env.DB_USER!,\n      password: process.env.DB_PASSWORD!,\n      ssl: {\n        rejectUnauthorized: true,\n        ca: fs.readFileSync(\"/path/to/ca-certificate.crt\").toString(),\n        // Optional: client certificate authentication\n        key: fs.readFileSync(\"/path/to/client-key.key\").toString(),\n        cert: fs.readFileSync(\"/path/to/client-cert.crt\").toString(),\n      },\n    },\n    maxConnections: 20,\n  }),\n});\n```\n\nThe `ssl` option accepts either:\n\n- `boolean` - Simple SSL enable/disable\n- `ConnectionOptions` - Full TLS configuration object with support for:\n  - `ca` - Custom Certificate Authority\n  - `key` - Client private key\n  - `cert` - Client certificate\n  - `rejectUnauthorized` - Certificate validation control\n  - All other Node.js [TLS options](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)\n\n### Security\n\n- Use SSL connections in production (`ssl: true` or advanced config)\n- Store credentials and certificates in environment variables or secure secret management\n- Implement regular database backups\n- Adjust `maxConnections` based on concurrent usage\n\n**Use cases:**\n\n- Applications with existing Postgres infrastructure\n- High-availability requirements (replication, failover)\n- Complex SQL queries or analytics on conversation data\n\nFor production-ready zero-setup hosting, see [Managed Memory](./managed-memory.md).\n\n## Learn More\n\n- **[Managed Memory](./managed-memory.md)** - Production-ready hosted memory with zero setup\n- **[Working Memory](./working-memory.md)** - Maintain compact context\n- **[Semantic Search](./semantic-search.md)** - Vector search configuration\n\n<!-- ... +48 more modular skills available via /api/skills/VoltAgent/voltagent?path={path} -->\n</yakaai_skills>"}