# Agent Core

The foundation layer of the Robota SDK. Provides the `Robota` agent class, abstract base classes for providers/tools/plugins, the permission system, hook system, event services, and error hierarchy.

## Installation

```bash
npm install @robota-sdk/agent-core
```

## Quick Start

```typescript
import { Robota } from '@robota-sdk/agent-core';
import { AnthropicProvider } from '@robota-sdk/agent-provider/anthropic';

const provider = new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY });

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, world!');
console.log(response);
```

## Key Features

- **Robota class**: AI agent with conversation history, tool execution, and plugin support
- **ConversationStore**: Append-only conversation history with streaming buffer (`beginAssistant`/`appendStreaming`/`commitAssistant`)
- **IBaseMessage**: Every message has a unique `id` (UUID) and `state` (`'complete'` | `'interrupted'`)
- **Multi-provider**: Register multiple providers, switch dynamically with `setModel()`
- **Provider capabilities**: Provider-neutral capability reports distinguish local tools from provider-native hosted web search/fetch
- **AbstractAIProvider.streamWithAbort()**: Standard streaming wrapper for all providers — handles AbortSignal, returns partial content on abort
- **Permission system**: Deterministic 3-step policy evaluation (`evaluatePermission`)
- **Hook system**: Shell command-based lifecycle hooks (`runHooks`)
- **Plugin system**: `AbstractPlugin` base class with lifecycle hooks (beforeRun, afterRun, onError, etc.)
- **Event services**: Unified event emission with owner path tracking
- **Error hierarchy**: Typed errors extending `RobotaError` (ProviderError, RateLimitError, etc.)
- **Model definitions**: Central `CLAUDE_MODELS` registry with context windows, output limits, and human-readable names
- **callProviderWithCache**: Accepts `Partial<IChatOptions>` overrides for per-call configuration
- **AbortSignal propagation**: Signal flows through the entire execution chain (Session -> Robota -> Provider)
- **Execution boundary events**: `Robota.run()` can emit provider/tool provenance events through `onExecutionEvent`
- **Type safety**: Strict TypeScript, zero `any` in production code

## Robota API

```typescript
import { Robota } from '@robota-sdk/agent-core';
import type { IAgentConfig } from '@robota-sdk/agent-core';

declare const config: IAgentConfig;
const agent = new Robota(config);

// Send a message (executes tool calls automatically)
const response = await agent.run('Hello');

// Conversation history
const history = agent.getHistory(); // TUniversalMessage[]
agent.clearHistory();

// Switch provider/model mid-conversation
agent.setModel({ provider: 'openai', model: 'gpt-4o' });
```

### Structured Output

`run(input, { output })` returns a schema-validated object instead of a string. Zod schemas give a
typed result; the schema is forwarded to the provider's native structured-output surface where one
exists, and the response is always validated core-side with a bounded retry on violation
(`outputRetries`, default 2). Exhausted retries throw `StructuredOutputError`.

```typescript
import { z } from 'zod';
import { Robota } from '@robota-sdk/agent-core';
import type { IAgentConfig } from '@robota-sdk/agent-core';

declare const config: IAgentConfig;
const agent = new Robota(config);

const reportSchema = z.object({
  title: z.string(),
  score: z.number(),
  summary: z.string(),
});

// Typed result: { title: string; score: number; summary: string }
const report = await agent.run('Summarize the meeting as a report.', {
  output: reportSchema,
});

// Streaming variant: deltas stream as usual; the validated object is the
// generator's return value (the final { done: true, value } iterator result).
const stream = agent.runStream('Summarize again.', { output: reportSchema });
const iterator = stream[Symbol.asyncIterator]();
let next = await iterator.next();
while (!next.done) {
  process.stdout.write(next.value);
  next = await iterator.next();
}
const streamedReport = next.value;
console.log(streamedReport.title);
```

A raw JSON-schema wrapper is also accepted: `{ output: { jsonSchema: { type: 'object', properties: { answer: { type: 'string' } }, required: ['answer'] } } }`.

### Model Options per Run

`run`/`runStream` accept run-scoped model options that win over `defaultModel.*`:
`maxTokens`, `temperature`, and `toolChoice`. `toolChoice` directs tool invocation —
`'auto'` (model decides), `'none'` (suppress tool calls), `'required'` (must call some
tool), or `{ tool: name }` (must call the named tool). A named tool missing from the run's
tool list throws immediately; nothing is silently ignored. Forcing directives apply to the
run's first model call only — rounds after tool results revert to `'auto'` so the model can
consume the results and finish.

```typescript
import { Robota } from '@robota-sdk/agent-core';
import type { IAgentConfig } from '@robota-sdk/agent-core';

declare const config: IAgentConfig;
const agent = new Robota(config);

// Force the model to answer via the router tool (decision-agent pattern)
const decision = await agent.run('Route this request.', {
  toolChoice: { tool: 'route-request' },
  allowToolOnlyCompletion: true,
});

// Suppress tools for a plain-text turn, capped at 100 output tokens
const summary = await agent.run('Summarize the discussion.', {
  toolChoice: 'none',
  maxTokens: 100,
});
console.log(decision, summary);
```

The same directive can be set agent-wide via `defaultModel.toolChoice`.

### Execution Boundary Events

`run()` accepts `onExecutionEvent` in run options. The execution loop emits provider-neutral events that higher layers can persist as append-only session provenance:

- `provider_request`
- `provider_native_raw_payload`
- `provider_stream_raw_delta`
- `provider_response_raw`
- `provider_response_normalized`
- `assistant_message_committed`
- `tool_batch_started`
- `tool_execution_request`
- `tool_execution_result`
- `tool_message_committed`
- `history_mutation`

Provider-specific SDK payload capture remains provider-owned. Providers may call `IChatOptions.onProviderNativeRawPayload` with exact SDK request, response, or stream event objects; `Robota` forwards those callbacks as provider-neutral `provider_native_raw_payload` execution events without importing concrete provider SDK types. `provider_response_raw.responseKind` remains `provider-normalized-message`, which keeps common replay validation provider-neutral.

## IAgentConfig

| Field                   | Type                       | Description                    |
| ----------------------- | -------------------------- | ------------------------------ |
| `name`                  | `string`                   | Agent name                     |
| `aiProviders`           | `IAIProvider[]`            | One or more provider instances |
| `defaultModel.provider` | `string`                   | Provider name                  |
| `defaultModel.model`    | `string`                   | Model identifier               |
| `systemMessage`         | `string?`                  | System prompt (top-level)      |
| `tools`                 | `IToolWithEventService[]?` | Tools the agent can call       |
| `plugins`               | `IPluginContract[]?`       | Plugins for lifecycle hooks    |

## Architecture

```
agent-core (this package — zero workspace dependencies)
  ↑
agent-session     ← Session lifecycle
agent-tools       ← Tool implementations
agent-provider    ← AI provider implementations (consolidated, multi-vendor sub-paths)
agent-plugin      ← Plugin implementations (8 plugins, consolidated)
  ↑
agent-framework   ← Assembly layer
  ↑
agent-cli         ← Terminal UI
```

## Public API Surface

| Category        | Exports                                                                                                                                                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Core**        | `Robota`, `ConversationStore`, `AbstractAgent`, `AbstractAIProvider` (+ `streamWithAbort`), `AbstractPlugin`, `AbstractTool`, `AbstractExecutor`, `LocalExecutor`, `getProviderCapabilities`, `assertProviderNativeWebToolsAvailable` |
| **Permissions** | `evaluatePermission`, `MODE_POLICY`, `TRUST_TO_MODE`, `UNKNOWN_TOOL_FALLBACK`, `TPermissionMode`, `TTrustLevel`, `TPermissionDecision`, `TToolArgs`, `IPermissionLists`, `TKnownToolName`                                             |
| **Hooks**       | `runHooks`, `CommandExecutor`, `HttpExecutor`, `IHookTypeExecutor`, `THookEvent`, `THooksConfig`, `IHookGroup`, `IHookDefinition`, `IHookInput`, `IHookResult`                                                                        |
| **Events**      | `EventEmitterPlugin`, `IEventService`, `IOwnerPathSegment`                                                                                                                                                                            |
| **Models**      | `CLAUDE_MODELS`, `DEFAULT_CONTEXT_WINDOW`, `DEFAULT_MAX_OUTPUT`, `getModelContextWindow()`, `getModelMaxOutput()`, `getModelName()`, `formatTokenCount()`, `IModelDefinition`                                                         |
| **Context**     | `estimateContextTokensFromMessages()`, `estimateSerializedContextTokens()`, `readTokenUsageFromMessage()`, `IContextTokenEstimate`, `IMessageTokenUsage`, `IContextWindowState`, `IContextTokenUsage`                                 |
| **Types**       | `TUniversalMessage`, `IBaseMessage` (`id`, `state`), `TMessageState`, `IAgentConfig`, `IAIProvider`, `IProviderCapabilities`, `IProviderNativeWebToolRequest`, `IToolSchema`, `TTextDeltaCallback`                                    |
| **Errors**      | `RobotaError`, `ProviderError`, `RateLimitError`, `AuthenticationError`, `ToolExecutionError`, etc.                                                                                                                                   |
| **Managers**    | `AgentFactory`, `AgentTemplates`, `ConversationHistory`, `EventHistoryModule`                                                                                                                                                         |

## What Moved Out in v3

| What                                          | Moved to                     |
| --------------------------------------------- | ---------------------------- |
| `FunctionTool`, `ToolRegistry`, `OpenAPITool` | `@robota-sdk/agent-tools`    |
| `MCPTool`, `RelayMcpTool`                     | `@robota-sdk/agent-tool-mcp` |
| 8 plugins (logging, usage, performance, etc.) | `@robota-sdk/agent-plugin`   |

## License

Robota is dual-licensed under the [GNU AGPL-3.0](../../LICENSE) or a [commercial license](../../COMMERCIAL.md). See [LICENSING.md](../../LICENSING.md).
