brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
voltagentVoltAgent is an open-source TypeScript framework for building AI agents with typed tools, persistent memory, supervisor/sub-agent teams, MCP tools, guardrails and suspendable workflows, served over a local HTTP API. Use when someone asks to "build an AI agent in TypeScript", "add tools and memory to an agent", "set up a supervisor with sub-agents", "pause a…
Independent GitHub snapshot · Automated classification · Original documentation governs
VoltAgent (@voltagent/core) lets a Node.js project define agents in code: a model, instructions, Zod-typed tools, a memory adapter, optional sub-agents and guardrails. A VoltAgent instance registers agents and workflows and, with @voltagent/server-hono, serves them over a REST API on port 3141 with Swagger UI at /ui. Workflows are declarative step chains that can suspend for a human decision and resume later. The companion VoltOps Console (console.voltagent.dev, cloud or self-hosted) connects to the local server for traces, chat testing and workflow runs; the framework itself is MIT-licensed and works without it.
Scaffold a project (asks for provider, package manager and server; writes .env):
npm create voltagent-app@latest order-desk
cd order-desk
npm run dev
The generated project has src/index.ts, src/tools/, src/workflows/, and scripts dev (tsx watch --env-file=.env ./src), build (tsdown), start (node dist/index.js) and typecheck. --example <name> starts from a folder of the repo's examples/ directory, e.g. npm create voltagent-app@latest -- --example with-research-assistant.
Adding VoltAgent to an existing project instead:
npm install @voltagent/core @voltagent/server-hono @voltagent/libsql @voltagent/logger zod
Put the provider key in .env: OPENAI_API_KEY (platform.openai.com/api-keys), ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, GROQ_API_KEY or MISTRAL_API_KEY.
Models can be given as "provider/model" strings (resolved by VoltAgent's built-in provider registry) or as AI SDK model objects.
import { VoltAgent, Agent, Memory, createTool } from "@voltagent/core";
import { LibSQLMemoryAdapter } from "@voltagent/libsql";
import { honoServer } from "@voltagent/server-hono";
import { z } from "zod";
const lookupOrder = createTool({
name: "lookupOrder",
description: "Look up an order by ID and return its status and carrier",
parameters: z.object({ orderId: z.string().describe("Order ID such as ORD-48213") }),
execute: async ({ orderId }) => {
const res = await fetch(`${process.env.ORDERS_API_URL}/orders/${orderId}`);
return res.json();
},
});
const memory = new Memory({
storage: new LibSQLMemoryAdapter({ url: "file:./.voltagent/memory.db" }),
});
const support = new Agent({
name: "order-support",
instructions: "Answer order questions. Always call lookupOrder before answering.",
model: "openai/gpt-4o-mini",
tools: [lookupOrder],
memory,
});
new VoltAgent({ agents: { support }, server: honoServer({ hostname: "127.0.0.1" }) });
Without memory, an agent keeps history in process memory only; memory: false disables it. LibSQLMemoryAdapter also accepts a Turso url plus authToken.
import { Output } from "ai";
const reply = await support.generateText("Where is order ORD-48213?", {
memory: { userId: "cust-5521", conversationId: "ticket-9912" },
});
console.log(reply.text);
const stream = await support.streamText("Summarize my last three orders", {
memory: { userId: "cust-5521", conversationId: "ticket-9912" },
});
for await (const chunk of stream.textStream) process.stdout.write(chunk);
const triage = await support.generateText("Classify: my parcel arrived damaged", {
output: Output.object({ schema: z.object({ category: z.enum(["delivery", "damage", "billing"]) }) }),
});
console.log(triage.output.category);
Top-level userId/conversationId options still work but are deprecated in core 2.11; use the memory envelope. Structured output is generateText/streamText with an output setting; generateObject/streamObject are deprecated. The same calls are exposed by the server:
curl -s -X POST http://localhost:3141/agents/order-support/text \
-H "Content-Type: application/json" \
-d '{"input":"Where is order ORD-48213?","options":{"memory":{"userId":"cust-5521","conversationId":"ticket-9912"}}}'
Other routes: GET /agents, POST /agents/:id/stream, POST /agents/:id/object, GET /workflows.
Passing agents in subAgents gives the supervisor an automatic delegate_task tool; it picks which specialist gets each part of the task.
const billing = new Agent({
name: "billing",
instructions: "Handle invoices, refunds and payment failures.",
model: "openai/gpt-4o-mini",
});
const lead = new Agent({
name: "support-lead",
instructions: "Route each question to order-support or billing, then write one reply.",
model: "anthropic/claude-sonnet-4-5",
subAgents: [support, billing],
supervisorConfig: { customGuidelines: ["Never promise a refund amount"] },
});
import { MCPConfiguration } from "@voltagent/core";
const mcp = new MCPConfiguration({
servers: {
filesystem: {
type: "stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "./policies"],
},
},
});
const policyTools = await mcp.getTools(); // names are prefixed: filesystem_read_file, ...
Pass policyTools into an agent's tools. Remote servers use type: "http" (or "sse", "streamable-http") with a url. Call mcp.disconnect() on shutdown.
import { createInputGuardrail, createInputLengthGuardrail } from "@voltagent/core";
const noCardNumbers = createInputGuardrail({
name: "block-card-numbers",
handler: async ({ inputText }) =>
/\b\d{16}\b/.test(inputText ?? "")
? { pass: false, action: "block", message: "Please do not paste card numbers." }
: { pass: true },
});
// new Agent({ ..., inputGuardrails: [createInputLengthGuardrail({ maxCharacters: 2000 }), noCardNumbers] })
A blocked input makes generateText throw with code GUARDRAIL_INPUT_BLOCKED. outputGuardrails work the same way on responses; ready-made ones include createPIIInputGuardrail, createEmailRedactorGuardrail and createSensitiveNumberGuardrail.
import { createWorkflowChain } from "@voltagent/core";
import { z } from "zod";
export const refundApproval = createWorkflowChain({
id: "refund-approval",
name: "Refund Approval",
purpose: "Auto-approve small refunds, pause large ones for a reviewer",
input: z.object({ orderId: z.string(), amount: z.number() }),
result: z.object({ status: z.enum(["approved", "rejected"]), approvedBy: z.string() }),
})
.andThen({
id: "check-amount",
resumeSchema: z.object({ approved: z.boolean(), reviewer: z.string() }),
execute: async ({ data, suspend, resumeData }) => {
if (resumeData) return { ...data, approved: resumeData.approved, approvedBy: resumeData.reviewer };
if (data.amount > 200) await suspend("Refund over $200 needs review", { orderId: data.orderId });
return { ...data, approved: true, approvedBy: "auto" };
},
})
.andThen({
id: "finalize",
execute: async ({ data }) => ({
status: data.approved ? ("approved" as const) : ("rejected" as const),
approvedBy: data.approvedBy,
}),
});
Register it with new VoltAgent({ workflows: { refundApproval } }) to run it from the Console or REST. Other step builders: andAgent (call an agent inside a step), andAll and andRace (parallel), andWhen and andBranch (conditions), andForEach, andSleep, andTap.
Request: "Build me a TypeScript agent that answers 'where is my order' questions using our orders API and remembers each customer's ticket."
npm create voltagent-app@latest order-desk, pick OpenAI, then replace src/index.ts with the agent from "Define an agent with a tool and memory" and set ORDERS_API_URL=https://orders.internal.shopnorth.io in .env.npm run dev prints VOLTAGENT SERVER STARTED SUCCESSFULLY, HTTP Server: http://localhost:3141 and Swagger UI: http://localhost:3141/ui./agents/order-support/text. The response looks like:{"success":true,"data":{"text":"Order ORD-48213 shipped via UPS, arriving 2026-10-03.","finishReason":"stop","toolCalls":[{"toolName":"lookupOrder","input":{"orderId":"ORD-48213"}}]}}
Follow-up messages with the same conversationId reuse the history stored in .voltagent/memory.db.
Request: "Refunds under $200 should go through automatically; bigger ones must wait until Maria approves them in our admin panel."
With the refundApproval workflow registered and the server running:
curl -s -X POST http://localhost:3141/workflows/refund-approval/execute \
-H "Content-Type: application/json" \
-d '{"input":{"orderId":"ORD-48377","amount":640}}'
{"success":true,"data":{"executionId":"40be49cb-17d4-49ea-ae6b-5f278a92e993","status":"suspended","result":null}}
The admin panel stores the executionId; when Maria decides, it resumes:
curl -s -X POST http://localhost:3141/workflows/refund-approval/executions/40be49cb-17d4-49ea-ae6b-5f278a92e993/resume \
-H "Content-Type: application/json" \
-d '{"resumeData":{"approved":true,"reviewer":"maria.lopez"}}'
{"success":true,"data":{"status":"completed","result":{"status":"approved","approvedBy":"maria.lopez"}}}
In code the same flow is const wf = refundApproval.toWorkflow(), register wf with new VoltAgent({ workflows: { refundApproval: wf } }), then const run = await wf.run(input) and await run.resume({ approved: true, reviewer: "maria.lopez" }). A $45 refund returns status: "completed" immediately with approvedBy: "auto".
@voltagent/* packages. Core 2.x needs ai 6.x; the npm latest tag of ai and @ai-sdk/openai has moved to the next major, so if you pass AI SDK model objects install the ai-v6 tagged versions (npm install ai@ai-v6 @ai-sdk/openai@ai-v6) or use "provider/model" strings, which need no extra provider package.VoltAgent instance and run through the same object: const wf = chain.toWorkflow(), register wf, call wf.run(). Resuming a chain that was never registered fails with "Workflow not found"; running the chain while VoltAgent holds its own copy fails with "Workflow state not found". Over REST this is handled for you.Memory (for example the LibSQL one above) as memory in createWorkflowChain({...}) so approvals that wait for days survive a restart.POST /tools/:name/execute (runs a tool directly) and /api/memory/* (reads stored conversations), and honoServer() listens on 0.0.0.0, not localhost. For local-only use pass honoServer({ hostname: "127.0.0.1" }). Before exposing it, add authNext: { provider: jwtAuth({ secret: process.env.JWT_SECRET! }) } (jwtAuth is exported by @voltagent/server-hono) and run with NODE_ENV=production: in any other environment a request with the header x-voltagent-dev: true or ?dev=true skips authNext.execute, keep write actions narrow, and use needsApproval on tools that change data..env (git-ignored). VoltOps keys (VOLTAGENT_PUBLIC_KEY, VOLTAGENT_SECRET_KEY, from console.voltagent.dev) are only needed to send traces to VoltOps.maxSteps caps tool-call loops per request; set it on agents whose tools can fail repeatedly.npx -y @voltagent/docs-mcp) gives a coding agent current VoltAgent docs; use it when the API in this skill looks out of date. The repo README still shows the old name @voltagent/mcp-docs-server, which is not on npm.