Skip to main content

Your First Agent

Build a real agent step by step — each section adds one concept.

1. The simplest agent

An agent needs a name and a goal. That's it.

import { Baleybot } from '@baleybots/core';

const bot = Baleybot.create({
name: 'assistant',
goal: 'Help users with their questions',
});

const response = await bot.process('What is the capital of France?');
console.log(response);
// "The capital of France is Paris."

process() sends your input to the LLM and returns the response as a string. The goal becomes the system prompt.

2. Add structured output

Raw text is fine for chat, but most applications need typed data. Add an outputSchema with Zod:

import { Baleybot } from '@baleybots/core';
import { z } from 'zod';

// Define the output schema first
const SentimentResult = z.object({
sentiment: z.enum(['positive', 'negative', 'neutral']),
confidence: z.number().min(0).max(1),
keywords: z.array(z.string()),
});

const analyzer = Baleybot.create({
name: 'sentiment-analyzer',
goal: 'Analyze the sentiment of text',
outputSchema: SentimentResult,
});

const result = await analyzer.process(
'I absolutely love this product!'
);

console.log(result.sentiment); // 'positive'
console.log(result.confidence); // 0.95
console.log(result.keywords); // ['love', 'product']

The return type is fully inferred from the schema — TypeScript knows result.sentiment is 'positive' | 'negative' | 'neutral'. If the LLM returns data that doesn't match the schema, Baleybots catches it and retries automatically.

3. Add tools

Agents become powerful when they can do things. Define a tool with tool():

import { Baleybot, tool } from '@baleybots/core';
import { z } from 'zod';

// 1. Define the schema
const WeatherInput = z.object({
city: z.string().describe('City name'),
});

// 2. Define the tool
const weatherTool = tool(
'get_weather',
'Get current weather for a city',
WeatherInput,
async ({ city }) => {
// In a real app, call a weather API here
return { temp: 72, condition: 'sunny', city };
},
);

// 3. Create the agent with the tool
const bot = Baleybot.create({
name: 'weather-assistant',
goal: 'Help users with weather information',
tools: { weather: weatherTool },
});

const answer = await bot.process(
"What's the weather in Tokyo?"
);
console.log(answer);
// "The weather in Tokyo is 72°F and sunny."

Here's what happens under the hood:

  1. The LLM sees the tool definition and decides to call get_weather
  2. Baleybots executes the tool function with the LLM's arguments
  3. The tool result is sent back to the LLM
  4. The LLM formulates a natural language response

This tool loop runs automatically — you don't write the loop yourself. By default it runs up to 20 steps, configurable with maxSteps or custom stop conditions.

4. Compose agents

Here's the key idea: every agent is a Processable — an object with a .process() method. Pipelines, parallel compositions, and even other agents' tools all accept any Processable. This means agents compose like functions.

Chain with pipelines

import { Baleybot, pipeline } from '@baleybots/core';

const researcher = Baleybot.create({
name: 'researcher',
goal: 'Research a topic and gather key facts',
});

const writer = Baleybot.create({
name: 'writer',
goal: 'Write a clear, engaging article from research notes',
});

const workflow = pipeline()
.step(researcher)
.step(writer)
.build();

const article = await workflow.process('AI agents in 2025');
// researcher runs first, its output feeds into writer

Run in parallel

import { Baleybot, parallel } from '@baleybots/core';

const sentimentBot = Baleybot.create({
name: 'sentiment',
goal: 'Classify sentiment as positive, negative, or neutral',
});

const summaryBot = Baleybot.create({
name: 'summary',
goal: 'Write a one-sentence summary',
});

const analysis = parallel({
sentiment: sentimentBot,
summary: summaryBot,
});

const results = await analysis.process('Your text here...');
// { sentiment: '...', summary: '...' }
// Both agents run simultaneously

Agents as tools

Any Processable can be passed as a tool to another agent:

const expertBot = Baleybot.create({
name: 'expert',
goal: 'Answer technical questions about TypeScript',
});

const managerBot = Baleybot.create({
name: 'manager',
goal: 'Help users by delegating to specialists when needed',
tools: {
ask_expert: expertBot, // An agent used as a tool
},
});

await managerBot.process('How do generics work in TypeScript?');
// managerBot decides to delegate to expertBot via the tool

What's next?

Now that you have the mental model — agents, structured output, tools, composition — go deeper:

  • Building a chat app? React Hooks guideuseChat handles the full lifecycle
  • Building an autonomous agent? Tools guide + Streaming guide — approval gates, stop conditions, real-time output
  • Need a specific provider? Provider Setup — OpenAI, Anthropic, Ollama, or custom endpoints
  • Designing a multi-agent system? Composition guide — pipelines, parallel, routing, and loops