TypeScript AI agent orchestration framework
npm install @ladybugs/budA TypeScript framework for building AI agent orchestration systems with type-safe tools, agents, and state-machine workflows.
- Tools - Validated, reusable functions with Zod schemas and automatic retry logic
- Agents - AI-powered entities with tool-calling loops, memory, output mapping, and nested agent support
- Workflows - State machine orchestration with suspend/resume capability
- Panel - Web UI for monitoring, debugging, and executing components in real-time
- Type Safety - Full TypeScript support with Zod validation throughout
- Observable - Event-driven architecture with execution tracing
- Flexible Storage - In-memory (development) or MongoDB (production)
- Pluggable Model Clients - Built-in support for OpenAI-compatible APIs, or bring your own (AWS Bedrock, etc.)
``bash`
npm install @ladybugs/bud
`typescript
import { createBud, MemoryStorage, ChatCompletionsModelClient } from '@ladybugs/bud';
import { z } from 'zod';
// Create a Bud instance
const bud = createBud({
name: 'my-app',
storage: new MemoryStorage(),
model: new ChatCompletionsModelClient({
baseURL: 'https://openrouter.ai/api/v1',
apiKey: process.env.OPENROUTER_API_KEY!,
model: 'openai/gpt-4o-mini',
}),
dependencies: {},
panel: { port: 3001 }, // Optional: enables web UI
});
// Define a tool
const calculator = bud.defineTool({
name: 'calculator',
description: 'Performs arithmetic operations',
input: z.object({
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
a: z.number(),
b: z.number(),
}),
output: z.object({ result: z.number() }),
execute: async ({ input }) => {
const ops = {
add: (a: number, b: number) => a + b,
subtract: (a: number, b: number) => a - b,
multiply: (a: number, b: number) => a * b,
divide: (a: number, b: number) => a / b,
};
return { result: opsinput.operation };
},
});
// Define an agent that uses the tool
const mathAgent = bud.defineAgent({
name: 'math-assistant',
description: 'Helps with math problems',
variables: z.object({ question: z.string() }),
systemPrompt: async () => 'You are a helpful math assistant. Use the calculator tool to solve problems.',
conversation: async ({ variables }) => [
{ role: 'user', content: variables.question },
],
tools: [calculator],
output: z.object({
answer: z.number(),
explanation: z.string(),
}),
maxSteps: 5,
});
// Run the agent
const result = await mathAgent.run({
variables: { question: 'What is 15 * 7 + 23?' },
source: 'code',
});
console.log(result.output);
// { answer: 128, explanation: "15 * 7 = 105, then 105 + 23 = 128" }
// Start the panel (if configured)
await bud.getPanel()?.start();
`
Tools are atomic units of work with validated inputs and outputs.
`typescript
const fetchUser = bud.defineTool({
name: 'fetch-user',
description: 'Fetches user data by ID',
input: z.object({ userId: z.string() }),
output: z.object({
id: z.string(),
name: z.string(),
email: z.string(),
}),
// Optional: tool-specific context
context: z.object({
cache: z.record(z.any()).default({}),
}),
execute: async ({ input, dependencies, context, log }) => {
log.info('Fetching user', { userId: input.userId });
const user = await dependencies.db.findUser(input.userId);
return user;
},
// Optional: retry configuration
retry: {
maxAttempts: 3,
delayMs: 1000,
backoffMultiplier: 2,
},
});
// Execute standalone
const user = await fetchUser.execute({ userId: 'user-123' });
`
Agents are AI-powered entities that execute a tool-calling loop until they produce structured output.
`typescript
const researchAgent = bud.defineAgent({
name: 'researcher',
description: 'Researches topics using available tools',
variables: z.object({
topic: z.string(),
depth: z.enum(['brief', 'detailed']).default('brief'),
}),
systemPrompt: async ({ dependencies }) =>
You are a research assistant.
Current date: ${new Date().toISOString()}
,Research: ${variables.topic} (${variables.depth})
conversation: async ({ variables }) => [
{ role: 'user', content: },
],
tools: [searchTool, fetchTool, summarizeTool],
output: z.object({
summary: z.string(),
sources: z.array(z.string()),
confidence: z.number().min(0).max(1),
}),
maxSteps: 10,
// Optional: persistent memory
memory: {
key: 'researcher-memory',
maxEntries: 100,
},
});
const result = await researchAgent.run({
variables: { topic: 'quantum computing', depth: 'detailed' },
source: 'code',
});
console.log(Answer: ${result.output.summary});Steps: ${result.steps}
console.log();Tokens: ${result.tokenUsage.totalTokens}
console.log();
// Resume the conversation
const followUp = await result.resume({
message: 'Can you elaborate on quantum entanglement?',
});
`
Output Mapping: Transform agent output to a different structure:
`typescript
const dataAgent = bud.defineAgent({
name: 'data-fetcher',
description: 'Fetches and processes data',
variables: z.object({ query: z.string() }),
systemPrompt: async () => 'You fetch data and return it as JSON.',
conversation: async ({ variables }) => [
{ role: 'user', content: variables.query },
],
tools: [fetchTool],
// Schema for what the AI model returns
output: z.object({
rawData: z.string(),
metadata: z.object({ source: z.string() }),
}),
// Transform the output after validation
map: async (output, { variables, context, dependencies }) => {
// Access to variables, context, and dependencies
const processed = await dependencies.processor.transform(output.rawData);
return {
data: processed,
query: variables.query,
fetchedAt: new Date().toISOString(),
};
},
maxSteps: 5,
});
const result = await dataAgent.run({ variables: { query: 'latest news' } });
// result.result has the mapped type: { data: ..., query: ..., fetchedAt: ... }
`
The map function:
- Receives the validated output and { variables, context, dependencies }run()
- Returns a Promise with the transformed output
- Is applied on both and resume() callstoTool()
- Returns the mapped output when the agent is used as a tool via
Nested Agents: Agents can use other agents as tools:
`typescript`
const orchestrator = bud.defineAgent({
name: 'orchestrator',
tools: [researchAgent, writerAgent, reviewerAgent], // Agents as tools
// ...
});
Workflows are state machines that orchestrate tools and agents with suspend/resume capability.
`typescript
const approvalWorkflow = bud.defineWorkflow({
name: 'approval-process',
description: 'Document approval workflow',
input: z.object({
documentId: z.string(),
requesterId: z.string(),
}),
context: z.object({
status: z.string().default('pending'),
approvals: z.array(z.string()).default([]),
rejectionReason: z.string().optional(),
}),
suspendReasons: {
awaitingApproval: {
data: z.object({ documentId: z.string(), approver: z.string() }),
resume: z.object({ approved: z.boolean(), comment: z.string().optional() }),
},
},
output: z.object({
approved: z.boolean(),
finalStatus: z.string(),
}),
states: {
start: {
description: 'Initialize workflow',
handler: async ({ input, context, workflow }) => {
context.update((ctx) => {
ctx.status = 'in-review';
});
return workflow.step('review');
},
},
review: {
description: 'Wait for approval',
handler: async ({ input, workflow }) => {
return workflow.suspend('awaitingApproval', {
resumeAt: 'processDecision',
data: { documentId: input.documentId, approver: 'manager@company.com' },
});
},
},
processDecision: {
description: 'Process approval decision',
handler: async ({ context, workflow, resume }) => {
const { approved, comment } = resume!.data;
if (approved) {
context.update((ctx) => {
ctx.status = 'approved';
ctx.approvals.push('manager');
});
return workflow.finish({ approved: true, finalStatus: 'approved' });
} else {
context.update((ctx) => {
ctx.status = 'rejected';
ctx.rejectionReason = comment;
});
return workflow.finish({ approved: false, finalStatus: 'rejected' });
}
},
},
},
});
// Start the workflow
const result = await approvalWorkflow.start({
input: { documentId: 'doc-123', requesterId: 'user-456' },
});
if (result.type === 'suspended') {
console.log(Workflow suspended: ${result.suspendReason});Waiting for: ${result.data.approver}
console.log();
// Later, resume with approval decision
const finalResult = await result.resume({
reason: 'awaitingApproval',
data: { approved: true, comment: 'Looks good!' },
});
if (finalResult.type === 'completed') {
console.log(Final status: ${finalResult.result.finalStatus});`
}
}
Running agents within workflows:
`typescript`
states: {
analyze: {
handler: async ({ input, workflow }) => {
const analysis = await workflow.run(analyzerAgent, {
variables: { documentId: input.documentId },
});
return workflow.step('nextState');
},
},
},
The Panel provides a web-based interface for monitoring and interacting with your Bud components.
`typescript
const bud = createBud({
// ... other config
panel: {
port: 3001,
host: 'localhost',
title: 'My App Dashboard',
theme: 'dark', // or 'light'
auth: {
username: 'admin',
password: 'secret',
},
},
});
// Components are auto-registered when defined
const tool = bud.defineTool({ / ... / });
const agent = bud.defineAgent({ / ... / });
const workflow = bud.defineWorkflow({ / ... / });
// Start the server
await bud.getPanel()?.start();
// Access at http://localhost:3001
`
Panel Features:
- View all registered tools, agents, and workflows
- Execute tools and agents directly from the UI
- Monitor workflow instances and their states
- View execution traces and timelines
- Real-time updates via Server-Sent Events
- Agent run history with nested run tracking
`typescript
import { MemoryStorage } from '@ladybugs/bud';
const storage = new MemoryStorage();
// Clear for tests
storage.clear();
`
`typescript
import { MongoStorage } from '@ladybugs/bud';
const storage = new MongoStorage({
uri: 'mongodb://localhost:27017',
database: 'my-app',
collections: {
events: 'bud_events',
logs: 'bud_logs',
memory: 'bud_memory',
},
});
await storage.connect();
// Later...
await storage.disconnect();
`
`typescript`
interface BudConfig
name: string; // Instance name
storage: BudStorage; // Storage adapter
model: IModelClient; // AI model client
dependencies: TDependencies; // Injected dependencies
additionalLoggers?: BudLogger[]; // Extra loggers
retry?: RetryConfig; // Default retry config
panel?: PanelOptions; // Panel settings
}
Bud uses the IModelClient interface for AI model communication. Use the built-in ChatCompletionsModelClient for OpenAI-compatible APIs, or implement your own (e.g., for AWS Bedrock).
`typescript
import { ChatCompletionsModelClient } from '@ladybugs/bud';
// OpenAI-compatible APIs (OpenRouter, OpenAI, Ollama, etc.)
const model = new ChatCompletionsModelClient({
baseURL: string; // API base URL
apiKey: string; // API key
model: string; // Model identifier
temperature?: number; // 0-2, default 0.7
maxTokens?: number; // Maximum response tokens, default 4096
contextWindow?: number; // Context window size (required for compact feature)
});
// Custom model client (e.g., AWS Bedrock)
const model: IModelClient = {
chat: async (params) => { / ... / },
getContextWindow: async () => 200000,
};
`
`typescript`
interface RetryConfig {
maxAttempts: number; // Max retry attempts
delayMs: number; // Initial delay
backoffMultiplier: number; // Multiplier for each retry
}
Bud provides specific error classes for different failure modes:
`typescript
import {
// Tool errors
ToolInputValidationError,
ToolOutputValidationError,
ToolExecutionError,
// Agent errors
AgentMaxStepsError,
AgentOutputValidationError,
AgentToolNotFoundError,
// Workflow errors
WorkflowStateError,
WorkflowNotFoundError,
WorkflowCancelledError,
// Model errors
ModelApiError,
} from '@ladybugs/bud';
try {
await agent.run({ variables: { query: 'test' }, source: 'code' });
} catch (error) {
if (error instanceof AgentMaxStepsError) {
console.log('Agent exceeded step limit');
} else if (error instanceof ToolExecutionError) {
console.log('Tool failed:', error.message);
}
}
`
`typescript
import { ConsoleLogger } from '@ladybugs/bud';
const bud = createBud({
// ... config
additionalLoggers: [
new ConsoleLogger({ level: 'debug' }),
],
});
// In tool/agent handlers
execute: async ({ log }) => {
log.debug('Debug message', { data: 'value' });
log.info('Info message');
log.warn('Warning message');
log.error('Error message', new Error('Something failed'));
};
`
`bashInstall dependencies
npm install
$3
Create
.env.development for integration tests:`env
AI_PROVIDER_API_KEY=your-api-key
AI_PROVIDER_BASE_URL=https://openrouter.ai/api/v1
AI_PROVIDER_MODEL=openai/gpt-4o-mini
``MIT