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:
- The LLM sees the tool definition and decides to call
get_weather - Baleybots executes the tool function with the LLM's arguments
- The tool result is sent back to the LLM
- 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 guide —
useChathandles 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