Building Agents
This guide covers building AI agents with @robota-sdk/agent-core — the foundation layer of the Robota SDK.
Robota Class
Robota is the main agent class. It wraps an AI provider with conversation history, tool execution, and plugin support.
import { Robota } from '@robota-sdk/agent-core';
const agent = new Robota({
name: 'MyAgent',
aiProviders: [provider],
defaultModel: {
provider: 'anthropic',
model: 'claude-sonnet-4-6',
systemMessage: 'You are a helpful assistant.',
},
});
const response = await agent.run('Hello!');IAgentConfig
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Agent name (for logging and identification) |
aiProviders | IAIProvider[] | yes | One or more provider instances |
defaultModel | object | yes | Default provider, model, and system message |
defaultModel.provider | string | yes | Provider name (must match an aiProviders entry) |
defaultModel.model | string | yes | Model identifier (e.g., claude-sonnet-4-6) |
defaultModel.systemMessage | string | no | System prompt for the agent |
tools | IToolWithEventService[] | no | Tools the agent can call |
plugins | IPluginContract[] | no | Plugins for lifecycle hooks |
systemMessage | string | no | Top-level system message (alternative to defaultModel.systemMessage) |
Key Methods
| Method | Description |
|---|---|
run(input) | Send a message and get a response. Executes tool calls automatically. |
getHistory() | Get the full conversation history as TUniversalMessage[]. |
clearHistory() | Clear the conversation history. |
setModel({ provider, model }) | Switch provider and model mid-conversation. |
AI Providers
Providers implement the IAIProvider interface from agent-core. Each provider translates between the universal message format and the provider-specific API.
Anthropic (Claude)
import { AnthropicProvider } from '@robota-sdk/agent-provider/anthropic';
const provider = new AnthropicProvider({
apiKey: process.env.ANTHROPIC_API_KEY,
});Supported models: claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5
OpenAI
import { OpenAIProvider } from '@robota-sdk/agent-provider/openai';
const provider = new OpenAIProvider({
apiKey: process.env.OPENAI_API_KEY,
});Gemini
import { GeminiProvider } from '@robota-sdk/agent-provider/gemini';
const provider = new GeminiProvider({
apiKey: process.env.GEMINI_API_KEY,
defaultModel: 'gemini-3-flash-preview',
});Gemini system prompts are sent as Gemini systemInstruction. The provider also supports structured output through responseSchema or responseJsonSchema, provider-level safetySettings, and thinkingConfig.
Gemma
import { GemmaProvider } from '@robota-sdk/agent-provider/gemma';
const provider = new GemmaProvider({
apiKey: 'lm-studio',
baseURL: 'http://localhost:1234/v1',
defaultModel: 'gemma-local-model',
});Use the Gemma provider for Gemma-family local models served through LM Studio or another OpenAI-compatible endpoint. It owns Gemma-specific reasoning marker filtering and native tool-call text projection.
LM Studio and other OpenAI-compatible Chat Completions endpoints support Robota local tools through normal function calling. They are not treated as provider-native web search/fetch providers, so configure web access with local WebSearch/WebFetch tools unless a concrete provider package documents hosted web support.
Qwen
import { QwenProvider } from '@robota-sdk/agent-provider/qwen';
const provider = new QwenProvider({
apiKey: process.env.DASHSCOPE_API_KEY,
defaultModel: 'qwen-plus',
});Qwen can also enable provider-side hosted web search and extraction through builtInWebTools; those tools are separate from Robota local tools and do not bypass local permission checks.
DeepSeek
import { DeepSeekProvider } from '@robota-sdk/agent-provider/deepseek';
const provider = new DeepSeekProvider({
apiKey: process.env.DEEPSEEK_API_KEY,
defaultModel: 'deepseek-v4-flash',
});DeepSeek uses the documented OpenAI-compatible API at https://api.deepseek.com. Provider-owned profile options can enable thinking controls such as thinking: 'enabled' and reasoningEffort: 'high'.
Switching Providers
const agent = new Robota({
name: 'FlexAgent',
aiProviders: [
anthropicProvider,
openaiProvider,
geminiProvider,
gemmaProvider,
qwenProvider,
deepSeekProvider,
],
defaultModel: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
});
// Switch at any time
agent.setModel({ provider: 'openai', model: 'gpt-4o' });
agent.setModel({ provider: 'gemini', model: 'gemini-2.5-pro' });
agent.setModel({ provider: 'gemma', model: 'gemma-local-model' });
agent.setModel({ provider: 'qwen', model: 'qwen-plus' });Tools
Tools let agents call functions during a conversation. The agent decides when to use a tool based on the conversation context.
Creating Tools with Zod
createZodFunctionTool takes positional arguments: name, description, Zod schema, and handler function. The handler receives validated parameters and returns a value (string or JSON-serializable).
import { createZodFunctionTool } from '@robota-sdk/agent-tools';
import { z } from 'zod';
const searchTool = createZodFunctionTool(
'search_files',
'Search for files by name pattern',
z.object({
pattern: z.string().describe('Glob pattern to match'),
directory: z.string().optional().describe('Directory to search in'),
}),
async ({ pattern, directory }) => {
const files = await glob(pattern, { cwd: directory ?? '.' });
return JSON.stringify(files);
},
);Creating Tools with FunctionTool
FunctionTool takes a schema object and a handler function as separate arguments.
import { FunctionTool } from '@robota-sdk/agent-tools';
const timeTool = new FunctionTool(
{
name: 'current_time',
description: 'Get the current date and time',
parameters: {
type: 'object',
properties: {
timezone: { type: 'string', description: 'IANA timezone' },
},
},
},
async (params) => {
return new Date().toLocaleString('en-US', { timeZone: (params.timezone as string) ?? 'UTC' });
},
);Registering Tools with an Agent
const agent = new Robota({
name: 'ToolAgent',
aiProviders: [provider],
defaultModel: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
tools: [searchTool, timeTool],
});
// The agent will call tools automatically when appropriate
const response = await agent.run(
'Find all .ts files in src/ and tell me the current time in Seoul',
);Built-in CLI Tools
@robota-sdk/agent-tools ships 8 ready-to-use tools for file system operations and web access:
| Tool | Description |
|---|---|
bashTool | Execute shell commands |
readTool | Read file contents with line numbers |
writeTool | Write content to a file |
editTool | Replace a string in a file |
globTool | Find files by glob pattern |
grepTool | Search file contents with regex |
webFetchTool | Fetch URL content (HTML-to-text) |
webSearchTool | Web search via Brave Search API |
These are used by agent-framework to assemble the CLI agent, but can also be used independently.
Plugins
Plugins hook into the agent lifecycle to add cross-cutting concerns.
Using Plugins
import { Robota, EventEmitterPlugin } from '@robota-sdk/agent-core';
const agent = new Robota({
name: 'PluginAgent',
aiProviders: [provider],
defaultModel: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
plugins: [new EventEmitterPlugin({ enabled: true })],
});Plugin Lifecycle Hooks
| Hook | Timing | Purpose |
|---|---|---|
beforeRun | Before LLM call | Input transformation, validation |
afterRun | After LLM response | Output processing, recording |
onError | On execution error | Error handling, recovery |
onStreamChunk | During streaming | Chunk processing |
beforeToolExecution | Before tool call | Tool input validation |
afterToolExecution | After tool result | Tool output processing |
Available Plugins
EventEmitterPlugin is built into agent-core. 8 plugins are also available via @robota-sdk/agent-plugin:
| Plugin Export | Purpose |
|---|---|
LoggingPlugin | Multi-backend logging |
UsagePlugin | Token usage and cost tracking |
PerformancePlugin | Metrics collection |
ExecutionAnalyticsPlugin | Execution analytics |
ErrorHandlingPlugin | Error recovery strategies |
LimitsPlugin | Rate limiting |
ConversationHistoryPlugin | Persistent history |
WebhookPlugin | HTTP notifications |
Creating Custom Plugins
import { AbstractPlugin } from '@robota-sdk/agent-core';
class MyPlugin extends AbstractPlugin {
name = 'my-plugin';
version = '1.0.0';
async afterRun(context) {
console.log(`Agent responded with ${context.response.length} characters`);
}
}Streaming
Text Delta Streaming
const agent = new Robota({
name: 'StreamAgent',
aiProviders: [provider],
defaultModel: { provider: 'anthropic', model: 'claude-sonnet-4-6' },
});
// The provider's onTextDelta callback streams text as it arrives
provider.onTextDelta = (delta) => process.stdout.write(delta);
const response = await agent.run('Write a poem about coding');
// Text appears in real-time via onTextDelta, response is the complete textConversation History
Robota maintains conversation history across run() calls. Every message has a unique id and a state ('complete' or 'interrupted').
const agent = new Robota({
/* config */
});
await agent.run('My name is Alice.');
const response = await agent.run('What is my name?');
// response: "Your name is Alice."
// Access the full history
const history = agent.getHistory(); // TUniversalMessage[]
// Each message has: id, timestamp, state, role, content, metadata
// Clear and start fresh
agent.clearHistory();Message State
Messages have a state field that tracks completion:
| State | Meaning |
|---|---|
'complete' | Normal response — fully received |
'interrupted' | User pressed ESC during streaming — partial content |
When the model receives history for the next turn, interrupted messages are annotated with [This response was interrupted by the user] so the model is aware of the interruption.
Streaming Buffer
During streaming, ConversationStore manages a streaming buffer internally:
beginAssistant()— opens a new streaming buffer for the assistant turnappendStreaming(delta)— accumulates text fromonTextDeltacallbackscommitAssistant(state, metadata)— commits the buffer to history as a confirmed message
This is a single path — both normal completion ('complete') and abort ('interrupted') use the same commitAssistant call with different state values. History is append-only and read-only; text content is always preserved. The CLI's onTextDelta callback is preserved as a passthrough for real-time display.
Error Handling
All errors extend RobotaError with code, category, and recoverable properties:
import { ProviderError, RateLimitError } from '@robota-sdk/agent-core';
try {
const response = await agent.run('Hello');
} catch (error) {
if (error instanceof RateLimitError) {
// Wait and retry
} else if (error instanceof ProviderError) {
// Provider-specific error
}
}Changes from v2.0.0
| v2.0.0 | v3.0.0 |
|---|---|
Plugins built into agent-core | 8 plugins available in @robota-sdk/agent-plugin |
FunctionTool in agent-core | Moved to @robota-sdk/agent-tools |
ToolRegistry in agent-core | Moved to @robota-sdk/agent-tools |
MCPTool / RelayMcpTool in agent-core | Moved to @robota-sdk/agent-tool-mcp |
| No permission/hook system | Permission evaluation + shell hook system in agent-core |
| No session management | InteractiveSession in agent-framework with compaction |
| No CLI | agent-cli with Ink TUI |
| No SDK layer | agent-framework with runtime assembly and createAgentRuntime() |