@ladybugs/bud
v0.4.0
Published
TypeScript AI agent orchestration framework
Maintainers
Readme
Bud
A TypeScript framework for building AI agent orchestration systems with type-safe tools, agents, and state-machine workflows.
Features
- 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.)
Installation
npm install @ladybugs/budQuick Start
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: ops[input.operation](input.a, input.b) };
},
});
// 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();Core Concepts
Tools
Tools are atomic units of work with validated inputs and outputs.
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
Agents are AI-powered entities that execute a tool-calling loop until they produce structured output.
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()}
`,
conversation: async ({ variables }) => [
{ role: 'user', content: `Research: ${variables.topic} (${variables.depth})` },
],
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}`);
console.log(`Steps: ${result.steps}`);
console.log(`Tokens: ${result.tokenUsage.totalTokens}`);
// Resume the conversation
const followUp = await result.resume({
message: 'Can you elaborate on quantum entanglement?',
});Output Mapping: Transform agent output to a different structure:
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 } - Returns a Promise with the transformed output
- Is applied on both
run()andresume()calls - Returns the mapped output when the agent is used as a tool via
toTool()
Nested Agents: Agents can use other agents as tools:
const orchestrator = bud.defineAgent({
name: 'orchestrator',
tools: [researchAgent, writerAgent, reviewerAgent], // Agents as tools
// ...
});Workflows
Workflows are state machines that orchestrate tools and agents with suspend/resume capability.
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: '[email protected]' },
});
},
},
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}`);
console.log(`Waiting for: ${result.data.approver}`);
// 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:
states: {
analyze: {
handler: async ({ input, workflow }) => {
const analysis = await workflow.run(analyzerAgent, {
variables: { documentId: input.documentId },
});
return workflow.step('nextState');
},
},
},Panel UI
The Panel provides a web-based interface for monitoring and interacting with your Bud components.
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:3001Panel 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
Storage
Memory Storage (Development)
import { MemoryStorage } from '@ladybugs/bud';
const storage = new MemoryStorage();
// Clear for tests
storage.clear();MongoDB Storage (Production)
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();Configuration
BudConfig
interface BudConfig<TDependencies> {
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
}Model Client
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).
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,
};RetryConfig
interface RetryConfig {
maxAttempts: number; // Max retry attempts
delayMs: number; // Initial delay
backoffMultiplier: number; // Multiplier for each retry
}Error Handling
Bud provides specific error classes for different failure modes:
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);
}
}Logging
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'));
};Development
# Install dependencies
npm install
# Build TypeScript
npm run build
# Run unit tests
npm test
# Run integration tests (requires .env.development)
npm run test:integration
# Build panel UI
npm run build:panel
# Start panel dev server
npm run panel:devEnvironment Variables
Create .env.development for integration tests:
AI_PROVIDER_API_KEY=your-api-key
AI_PROVIDER_BASE_URL=https://openrouter.ai/api/v1
AI_PROVIDER_MODEL=openai/gpt-4o-miniLicense
MIT
