Skip to content

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.

typescript
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

FieldTypeRequiredDescription
namestringyesAgent name (for logging and identification)
aiProvidersIAIProvider[]yesOne or more provider instances
defaultModelobjectyesDefault provider, model, and system message
defaultModel.providerstringyesProvider name (must match an aiProviders entry)
defaultModel.modelstringyesModel identifier (e.g., claude-sonnet-4-6)
defaultModel.systemMessagestringnoSystem prompt for the agent
toolsIToolWithEventService[]noTools the agent can call
pluginsIPluginContract[]noPlugins for lifecycle hooks
systemMessagestringnoTop-level system message (alternative to defaultModel.systemMessage)

Key Methods

MethodDescription
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)

typescript
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

typescript
import { OpenAIProvider } from '@robota-sdk/agent-provider/openai';

const provider = new OpenAIProvider({
  apiKey: process.env.OPENAI_API_KEY,
});

Gemini

typescript
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

typescript
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

typescript
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

typescript
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

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

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

typescript
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

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

ToolDescription
bashToolExecute shell commands
readToolRead file contents with line numbers
writeToolWrite content to a file
editToolReplace a string in a file
globToolFind files by glob pattern
grepToolSearch file contents with regex
webFetchToolFetch URL content (HTML-to-text)
webSearchToolWeb 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

typescript
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

HookTimingPurpose
beforeRunBefore LLM callInput transformation, validation
afterRunAfter LLM responseOutput processing, recording
onErrorOn execution errorError handling, recovery
onStreamChunkDuring streamingChunk processing
beforeToolExecutionBefore tool callTool input validation
afterToolExecutionAfter tool resultTool output processing

Available Plugins

EventEmitterPlugin is built into agent-core. 8 plugins are also available via @robota-sdk/agent-plugin:

Plugin ExportPurpose
LoggingPluginMulti-backend logging
UsagePluginToken usage and cost tracking
PerformancePluginMetrics collection
ExecutionAnalyticsPluginExecution analytics
ErrorHandlingPluginError recovery strategies
LimitsPluginRate limiting
ConversationHistoryPluginPersistent history
WebhookPluginHTTP notifications

Creating Custom Plugins

typescript
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

typescript
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 text

Conversation History

Robota maintains conversation history across run() calls. Every message has a unique id and a state ('complete' or 'interrupted').

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

StateMeaning
'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:

  1. beginAssistant() — opens a new streaming buffer for the assistant turn
  2. appendStreaming(delta) — accumulates text from onTextDelta callbacks
  3. commitAssistant(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:

typescript
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.0v3.0.0
Plugins built into agent-core8 plugins available in @robota-sdk/agent-plugin
FunctionTool in agent-coreMoved to @robota-sdk/agent-tools
ToolRegistry in agent-coreMoved to @robota-sdk/agent-tools
MCPTool / RelayMcpTool in agent-coreMoved to @robota-sdk/agent-tool-mcp
No permission/hook systemPermission evaluation + shell hook system in agent-core
No session managementInteractiveSession in agent-framework with compaction
No CLIagent-cli with Ink TUI
No SDK layeragent-framework with runtime assembly and createAgentRuntime()

Released under the MIT License.