npm package: @ladybugs/bud
Description: TypeScript AI agent orchestration framework
Author: Ladybugs
Latest version: 0.4.3

# 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

```bash
npm install @ladybugs/bud
```

## Quick Start

```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: 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.

```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

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()}
  `,
  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:

```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 }`
- Returns a Promise with the transformed output
- Is applied on both `run()` and `resume()` calls
- Returns the mapped output when the agent is used as a tool via `toTool()`

**Nested Agents**: Agents can use other agents as tools:

```typescript
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.

```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}`);
  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**:

```typescript
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.

```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

## Storage

### Memory Storage (Development)

```typescript
import { MemoryStorage } from '@ladybugs/bud';

const storage = new MemoryStorage();

// Clear for tests
storage.clear();
```

### MongoDB Storage (Production)

```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();
```

## Configuration

### BudConfig

```typescript
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).

```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,
};
```

### RetryConfig

```typescript
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:

```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);
  }
}
```

## Logging

```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'));
};
```

## Development

```bash
# 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:dev
```

### Environment Variables

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
```

## License

MIT