| name | agent-squad-typescript |
|---|---|
| description | Use when building or modifying a Node.js / TypeScript app that uses the agent-squad npm package — multi-agent orchestration: orchestrator, agents (all built-in types + GroundedAgent), classifier routing (Bedrock / Anthropic / OpenAI), storage (in-memory / DynamoDB / SQL), retrievers (Amazon KB / Dakera), and tools (AgentTools + MCPToolProvider). |
Node.js / TypeScript multi-agent orchestration framework (npm package agent-squad). All public
symbols are exported from a single barrel typescript/src/index.ts. This file is guidance and a
map — not an API reference. Read exact signatures from
typescript/src/ and worked recipes from docs/src/content/docs/; this file tells you what to
use, when, and what to watch out for.
- One assistant → a single
Agentsubclass +AgentSquadwith no routing. Or skip the orchestrator entirely and callagent.processRequest(...)directly. - Several specialists → multiple agents registered with
orchestrator.addAgent(agent), a classifier routes each turn. - Answers must not drift from data (prices, balances, live lookups) →
GroundedAgent: a gatherer LLM calls tools, an isolated presenter LLM speaks only from the curated results. - Fixed pipeline →
ChainAgent: each agent's output is the next agent's input. - One lead LLM coordinating a team →
SupervisorAgent: the lead calls sub-agents as tools. - External tools via MCP →
MCPToolProvider(async factory pattern, optional peer dep). - RAG context → attach a
Retrieverto any agent that supportsretriever?in its options.
npm install agent-squadOptional peer dependencies — install only what you use:
| Package | Used by |
|---|---|
@aws-sdk/client-bedrock-runtime |
BedrockLLMAgent, BedrockClassifier (already a hard dep in current releases) |
@anthropic-ai/sdk |
AnthropicAgent, AnthropicClassifier (already a hard dep) |
openai |
OpenAIAgent, OpenAIClassifier (already a hard dep) |
@modelcontextprotocol/sdk |
MCPToolProvider — lazy await import() at connect time |
@dakera-ai/dakera |
DakeraRetriever — lazy require() at construction time |
@modelcontextprotocol/sdk and @dakera-ai/dakera are the only two true optional peer deps;
everything else ships as a hard dependency at the moment.
routeRequest is the single entry point. It classifies the input, dispatches to the selected
agent, saves the exchange, and returns an AgentResponse. The response is either a plain string or
a Node.js Transform stream:
import { AgentSquad, BedrockLLMAgent, BedrockClassifier } from 'agent-squad';
const orchestrator = new AgentSquad({
classifier: new BedrockClassifier(), // default when omitted
// storage: new DynamoDbChatStorage(...),
// config: { LOG_AGENT_CHAT: true, MAX_MESSAGE_PAIRS_PER_AGENT: 50 },
});
orchestrator.addAgent(new BedrockLLMAgent({
name: 'Tech Support',
description: 'Handles technical questions about software and hardware',
streaming: true,
}));
const response = await orchestrator.routeRequest(
userInput,
userId,
sessionId,
additionalParams // optional Record<string, any>
);
if (response.streaming) {
// response.output is an AccumulatorTransform (Node.js Transform)
for await (const chunk of response.output) {
process.stdout.write(chunk);
}
} else {
// response.output is a string
console.log(response.output);
// response.thinking? is set when the agent used extended thinking
}
// response.metadata: { agentId, agentName, userId, sessionId, userInput, additionalParams }routeRequest never throws — it catches all errors and returns them as a non-streaming
AgentResponse with the error string in output (configurable via GENERAL_ROUTING_ERROR_MSG_MESSAGE).
new AgentSquad(options?: OrchestratorOptions)Key OrchestratorOptions fields:
| Field | Default | Notes |
|---|---|---|
classifier |
new BedrockClassifier() |
Any Classifier subclass |
storage |
new InMemoryChatStorage() |
Any ChatStorage subclass |
defaultAgent |
undefined |
Used when classifier returns no match and USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED is true |
config.USE_DEFAULT_AGENT_IF_NONE_IDENTIFIED |
true |
Fall back to defaultAgent or return NO_SELECTED_AGENT_MESSAGE |
config.MAX_MESSAGE_PAIRS_PER_AGENT |
100 |
Per-agent history cap (pairs = user+assistant) |
config.MAX_RETRIES |
3 |
Classifier retries on bad XML response |
config.LOG_AGENT_CHAT |
false |
Useful methods: addAgent(agent), setDefaultAgent(agent), getDefaultAgent(),
getAllAgents(), analyzeAgentOverlap(), classifyRequest(...), agentProcessRequest(...).
The classifier is exposed as a public field (orchestrator.classifier) so its system prompt can
be overridden after construction.
All agents extend Agent and require at minimum { name, description } in their options.
agent.id is derived automatically from name: non-alphanumeric stripped, spaces → hyphens,
lowercased. "Tech Support" → "tech-support". This is the key used for storage and classifier
matching — it must be stable across restarts.
| Class | Options type | Notes |
|---|---|---|
BedrockLLMAgent |
BedrockLLMAgentOptions |
Bedrock Converse API; supports streaming, modelId, inferenceConfig, guardrailConfig, reasoningConfig, retriever, toolConfig, customSystemPrompt, client, callbacks |
AnthropicAgent |
AnthropicAgentOptions |
Direct Anthropic SDK; similar options shape |
OpenAIAgent |
OpenAIAgentOptions |
OpenAI Chat Completions |
AmazonBedrockAgent |
AmazonBedrockAgentOptions |
Amazon Bedrock Agents (pre-built agents, not Converse) |
BedrockInlineAgent |
BedrockInlineAgentOptions |
Bedrock inline agents |
BedrockFlowsAgent |
BedrockFlowsAgentOptions |
Bedrock Flows |
LambdaAgent |
LambdaAgentOptions |
Invokes a Lambda function as an agent |
LexBotAgent |
LexBotAgentOptions |
Amazon Lex V2 bot |
ChainAgent |
ChainAgentOptions |
Fixed pipeline; agents: Agent[], defaultOutput? |
SupervisorAgent |
SupervisorAgentOptions |
Lead + team; leadAgent must be BedrockLLMAgent or AnthropicAgent; lead must have no toolConfig (SupervisorAgent manages tools) |
GroundedAgent |
GroundedAgentOptions |
2-LLM anti-hallucination; gatherer, presenter, tools, curator?, presenterPrompt? |
AgentOptions base fields: name (required), description (required), saveChat? (default
true), logger?, LOG_AGENT_DEBUG_TRACE?.
BedrockLLMAgent toolConfig shape:
toolConfig: {
tool: AgentTools | Tool[], // AgentTools instance or raw Bedrock Tool array
useToolHandler: (response: any, conversation: ConversationMessage[]) => any,
toolMaxRecursions?: number,
}When using MCPToolProvider, pass it as toolConfig.tool and omit useToolHandler — the
provider overrides toolHandler internally.
Two-LLM anti-hallucination pattern. The gatherer calls tools; the presenter receives only the curated facts (never raw tool output, never chat history from the gatherer):
import {
GroundedAgent, DataBlockCurator, PerToolCurator, PresenterPrompt,
BedrockLLMAgent, AgentTools, AgentTool,
} from 'agent-squad';
const tools = new AgentTools([
new AgentTool({ name: 'get_price', description: '...', func: async ({ sku }) => fetchPrice(sku) }),
]);
const gatherer = new BedrockLLMAgent({ name: 'Gatherer', description: '...', toolConfig: { tool: tools, useToolHandler: ... } });
const presenter = new BedrockLLMAgent({ name: 'Presenter', description: '...' });
const agent = new GroundedAgent({
name: 'Price Agent',
description: 'Answers pricing questions grounded in live data',
gatherer,
presenter,
tools,
curator: new DataBlockCurator(), // default; or PerToolCurator for per-tool formatting
presenterPrompt: PresenterPrompt.default(), // generic grounding prompt; or per-tool map
});A no-tool turn (chit-chat) is answered by the gatherer directly, skipping the presenter.
| Class | Options type | Notes |
|---|---|---|
BedrockClassifier |
BedrockClassifierOptions |
Default when no classifier is passed to AgentSquad |
AnthropicClassifier |
AnthropicClassifierOptions |
|
OpenAIClassifier |
OpenAIClassifierOptions |
All classifiers support setSystemPrompt(template?, variables?) to override the routing prompt.
Template variables use {{VAR_NAME}} syntax; AGENT_DESCRIPTIONS and HISTORY are always
injected automatically.
| Class | Notes |
|---|---|
InMemoryChatStorage |
Default; non-persistent; fine for dev and tests |
DynamoDbChatStorage |
Requires @aws-sdk/client-dynamodb and @aws-sdk/lib-dynamodb (hard deps) |
SqlChatStorage |
Requires @libsql/client (hard dep); works with Turso or local libsql |
SummarizingChatStorage |
Wraps any storage; compresses history via a user-supplied ChatSummarizer callable when fetchChat returns more than triggerAt * 2 messages; cache-based save-back |
Storage is keyed by (userId, sessionId, agentId). fetchAllChats(userId, sessionId) is used by
the classifier to get cross-agent history for context.
| Class | Options type | Notes |
|---|---|---|
AmazonKnowledgeBasesRetriever |
AmazonKnowledgeBasesRetrieverOptions |
Amazon Bedrock Knowledge Bases |
DakeraRetriever |
DakeraRetrieverOptions |
Dakera memory server; optional peer dep @dakera-ai/dakera |
DakeraRetrieverOptions: namespace (required), apiKey? (falls back to DAKERA_API_KEY env),
url? (falls back to DAKERA_URL then http://localhost:3000), topK? (default 10), filter?.
Attach to a BedrockLLMAgent via retriever: option. The agent calls retriever.retrieveAndCombineResults(inputText) to augment its system prompt.
DakeraRetriever.retrieveAndGenerate() always throws — Dakera is retrieval-only.
import { AgentTools, AgentTool } from 'agent-squad';
const myTools = new AgentTools([
new AgentTool({
name: 'search_web',
description: 'Search the web for current information',
properties: {
query: { type: 'string', description: 'The search query' },
},
required: ['query'],
func: async ({ query }) => webSearch(query),
}),
]);AgentTool constructor will auto-extract parameter names from func if properties is omitted —
but this is fragile with minification. Always pass explicit properties and required.
MCPToolProvider extends AgentTools. Always use the async factory — never new MCPToolProvider(...) directly — so that tool definitions are fetched before the agent makes its first API call:
import { MCPToolProvider } from 'agent-squad';
const provider = await MCPToolProvider.create([
{ type: 'stdio', command: 'uvx', args: ['my-mcp-server'] },
{ type: 'sse', url: 'http://localhost:3000/sse', headers: { Authorization: 'Bearer tok' } },
]);
const agent = new BedrockLLMAgent({
name: 'MCP Agent',
description: 'Agent with MCP tools',
toolConfig: { tool: provider },
});
// Clean up when done (closes stdio processes and SSE connections)
await provider.disconnect();MCPServerConfig.type is "stdio" or "sse". For stdio: command is required, args? and
env? are optional. For sse: url is required, headers? is optional.
MCPToolProvider overrides toBedrockFormat(), toAnthropicFormat(), and toOpenAIFormat() to
pass MCP inputSchema through unchanged rather than re-serializing it.
Requires npm install @modelcontextprotocol/sdk. The SDK is imported lazily via await import()
inside ensureConnected() — installing agent-squad without the SDK is safe as long as you don't
instantiate MCPToolProvider.
Extend the abstract base class and pass your type where the built-in goes.
| Seam | Base class | Method to implement | Source |
|---|---|---|---|
| Agent | Agent |
processRequest(inputText, userId, sessionId, chatHistory, additionalParams?) returns Promise<ConversationMessage | AsyncIterable<any>> |
typescript/src/agents/agent.ts |
| Classifier | Classifier |
processRequest(inputText, chatHistory) returns Promise<ClassifierResult> |
typescript/src/classifiers/classifier.ts |
| Storage | ChatStorage |
saveChatMessage, fetchChat, fetchAllChats |
typescript/src/storage/chatStorage.ts |
| Retriever | Retriever |
retrieve, retrieveAndCombineResults, retrieveAndGenerate |
typescript/src/retrievers/retriever.ts |
ClassifierResult shape: { selectedAgent: Agent | null, confidence: number }.
Classifier base class provides setAgents, setHistory, setSystemPrompt, and
getAgentById(agentId) — use getAgentById in your processRequest to look up the selected agent
from the classifier's registered map.
-
agentIdis derived fromnameat construction time: non-alphanumeric stripped, spaces replaced with-, lowercased. Changing an agent'snamechanges itsid, which breaks chat history lookups in storage. Keep names stable across deployments. -
Streaming response is a Node.js Transform stream, not an async generator. Check
response.streamingbefore iterating. The transform accumulates the full response internally;for await (const chunk of response.output)works becauseTransformimplementsAsyncIterable. Do not callresponse.output.read()manually. -
routeRequestnever throws. Errors are returned as non-streamingAgentResponsewith the error string inoutput. If you need to distinguish errors from real responses, checkresponse.metadata.errorType === 'classification_failed'or inspectmetadata.agentId. -
MCPToolProvider.create(...)must be awaited before the agent is used. The constructor alone does not connect; callingprocessRequestbeforecreateresolves means tool definitions are empty and the agent will behave as if it has no tools. -
BedrockClassifieris the default. If boto3/AWS credentials are not configured and you don't pass an explicitclassifier,AgentSquadwill construct aBedrockClassifierthat will fail at runtime. Passclassifier: new AnthropicClassifier(...)ornew OpenAIClassifier(...)if you're not on AWS. -
Optional peer deps use lazy import/require.
MCPToolProviderusesawait import(...)insideensureConnected();DakeraRetrieverusesrequire(...)inside the constructor. Neither adds a top-level import, so a missing peer dep is only discovered at instantiation time — not at module load. Throw the missing-dep error early, before user input arrives. -
SupervisorAgentrestrictions:leadAgentmust beBedrockLLMAgentorAnthropicAgent; the lead agent must have notoolConfigset (SupervisorAgent wires its own tool loop). Pass additional native tools viaextraTools. -
saveChatdefaults totrue. Every agent persists both sides of each exchange after the turn completes. SetsaveChat: falseon agents that should not write to storage (e.g. a presenter inside aGroundedAgentthat is managed externally). -
additionalParamsflows throughrouteRequest→dispatchToAgent→agent.processRequest. Use it to pass per-request context (tenant ID, request ID, feature flags) without touching agent options. The values end up inresponse.metadata.additionalParams. -
AgentToolsauto-extracts parameter names fromfuncvia.toString(). This breaks with minification and TypeScript arrow functions with destructured arguments. Always supply explicitpropertiesandrequiredarrays toAgentTool. -
ThinkingResponse: when aBedrockLLMAgentis configured withreasoningConfig, the non-streaming path may returnresponse.thinking(a string) alongsideresponse.output. The streaming path does not surface thinking tokens separately.
- Prose & recipes —
docs/src/content/docs/(run the site fromdocs/withnpm run dev):orchestrator/overview,agents/built-in/bedrock-llm-agent,agents/built-in/grounded-agent,classifiers/overview,storage/overview,retrievers/overview,tools/mcp. - Exact signatures —
typescript/src/(orchestrator.ts,agents/,classifiers/,storage/,retrievers/,tools/mcpToolProvider.ts,utils/tool.ts,types/index.ts). - Tests —
typescript/tests/for usage patterns and mock strategies (virtual mocks for optional peer deps viajest.mock(..., { virtual: true })). - Barrel —
typescript/src/index.tsis the definitive list of every public symbol.