# Agents

URL: https://cogitator.app/docs/core/agents

> Configure and run AI agents with tools, instructions, and model parameters.

## Overview

An `Agent` is a configured LLM instance with a name, system prompt, tools, and model parameters. Agents are stateless blueprints -- the `Cogitator` runtime handles execution, memory, and lifecycle.

```typescript
import { Agent } from '@cogitator-ai/core';

const agent = new Agent({
  name: 'researcher',
  model: 'anthropic/claude-sonnet-5-5',
  instructions: 'You are a research assistant. Find accurate information and cite sources.',
});
```

## AgentConfig

```typescript
interface AgentConfig {
  id?: string;
  name: string;
  description?: string;
  model?: string; // default: llm.defaultModel of the Cogitator
  provider?: string;
  instructions: string;
  tools?: Tool[];
  skills?: Skill[]; // bundles of tools + instructions, merged in
  temperature?: number; // default: 0.7
  topP?: number;
  maxTokens?: number;
  stopSequences?: string[];
  responseFormat?: ResponseFormat;
  reasoning?: ReasoningConfig; // effort and summaries for reasoning models
  handoffs?: Array<Agent | Handoff>; // agents it can hand the conversation over to
  maxIterations?: number; // default: 10
  onIterationLimit?: 'answer' | 'stop'; // default: 'answer'
  timeout?: number; // default: limits.defaultTimeout of the Cogitator, else 120000 (2 min)
}
```

### Model String Format

The `model` field uses a `provider/model-name` convention to route to the correct backend:

```typescript
model: 'openai/gpt-6.1-sol';
model: 'anthropic/claude-sonnet-5-5';
model: 'ollama/llama3.3:latest';
model: 'google/gemini-3.8-flash';
```

If you omit the provider prefix (or the prefix is not a known provider, backend or plugin), the whole string goes to the `defaultProvider` from your runtime config, Ollama when unset. If you omit `model` altogether, the agent runs on `llm.defaultModel`; a run fails with a `CONFIGURATION_ERROR` when neither is set. `cog.resolveModel(agent)` returns the model a run would use.

### Provider Override

For OpenAI-compatible APIs (OpenRouter, local vLLM, etc.), use the `provider` field to explicitly route:

```typescript
const agent = new Agent({
  name: 'openrouter-agent',
  model: 'meta-llama/llama-3.3-70b-instruct',
  provider: 'openai',
  instructions: '...',
});
```

## Agent with Tools

```typescript
import { Agent, tool } from '@cogitator-ai/core';
import { z } from 'zod';

const searchWeb = tool({
  name: 'search_web',
  description: 'Search the internet for current information',
  parameters: z.object({
    query: z.string().describe('The search query'),
    limit: z.number().default(5),
  }),
  execute: async ({ query, limit }) => {
    const results = await searchAPI.search(query, limit);
    return results.map((r) => ({ title: r.title, url: r.url }));
  },
});

const researcher = new Agent({
  name: 'researcher',
  model: 'openai/gpt-6.1-sol',
  instructions: 'You are a research assistant. Use tools to find accurate information.',
  tools: [searchWeb],
  maxIterations: 15,
});
```

The agent loops up to `maxIterations` times. Each iteration the LLM may call tools or produce a final response. The loop ends when the LLM responds without tool calls. An empty turn (no text and no tool calls) is not taken as the final response: the run asks the model again, up to twice.

When tool calls use up `maxIterations` before the model answers, the run gets one more turn with `toolChoice: 'none'` and an instruction to answer from what it has, so it still ends with an answer and `structured` output can be parsed. Tools the model asks for on that turn are not run. `result.iterationLimitReached` is `true` either way, and `onIterationLimit: 'stop'` ends the run at the last tool turn instead, without an answer.

`result.truncated` is `true` when the model's last answer stopped at the output token limit (`maxTokens`): the output may be cut off, or empty when a reasoning model spent the whole limit thinking. Every provider reports it, so it is the way to tell a cut-off answer from a short one.

## Agent with Structured Output

Force the LLM to respond with a specific JSON shape using Zod schemas:

```typescript
const analyzer = new Agent({
  name: 'analyzer',
  model: 'openai/gpt-6.1-sol',
  instructions: 'Analyze the given text and extract structured information.',
  responseFormat: {
    type: 'json_schema',
    schema: z.object({
      summary: z.string(),
      sentiment: z.enum(['positive', 'negative', 'neutral']),
      keyPoints: z.array(z.string()),
    }),
  },
});
```

See [Structured Outputs](/docs/core/structured-outputs) for details on `json` and `json_schema` modes.

## Cloning Agents

Create variations of an agent with `clone()`:

```typescript
const baseAgent = new Agent({
  name: 'coder',
  model: 'anthropic/claude-sonnet-5-5',
  instructions: 'You are a senior software engineer.',
  temperature: 0.3,
});

const creativeAgent = baseAgent.clone({
  name: 'creative-coder',
  temperature: 0.9,
});

const fastAgent = baseAgent.clone({
  model: 'anthropic/claude-haiku-4-5',
});
```

A clone gets a new `id` unless the overrides set one.

## Serialization

Agents can be serialized to JSON for storage and restored later:

```typescript
import { writeFile } from 'node:fs/promises';

const snapshot = agent.serialize();
await writeFile('agent.json', JSON.stringify(snapshot, null, 2));
```

Restore with the tool implementations:

```typescript
import { readFile } from 'node:fs/promises';
import { Agent } from '@cogitator-ai/core';
import type { AgentSnapshot } from '@cogitator-ai/types';

const saved = JSON.parse(await readFile('agent.json', 'utf-8')) as AgentSnapshot;
const restored = Agent.deserialize(saved, {
  tools: [searchWeb, readUrl],
});
```

Tools are stored by name in the snapshot. During deserialization, each tool name is resolved from `toolRegistry` (any `{ get(name) }`, such as a `ToolRegistry`) when given, otherwise from the `tools` array; a missing tool throws `AgentDeserializationError`. `overrides` replaces any config field of the restored agent.

A snapshot keeps the model, instructions, sampling parameters, `reasoning`, `maxIterations`, `onIterationLimit` and `timeout`, but not `handoffs` or `skills` (their tools are kept by name). A `json_schema` response format is saved by name only, so pass the Zod schema back as `overrides.responseFormat`. `Agent.validateSnapshot(value)` checks a parsed snapshot; it rejects snapshots without a `model`, so give agents you serialize an explicit model.

## Skills

A skill bundles tools with the instructions for using them. `skills` on an agent adds each skill's tools (unless the agent already has a tool of that name) and appends its instructions to the agent's:

```typescript
import { defineSkill } from '@cogitator-ai/core';

const weatherSkill = defineSkill({
  name: 'weather',
  version: '1.0.0',
  description: 'Current weather and forecasts',
  tools: [getWeather],
  instructions: 'Use get_weather for any question about the weather.',
  env: ['WEATHER_API_KEY'],
});

const assistant = new Agent({
  name: 'assistant',
  model: 'openai/gpt-6.1-sol',
  instructions: 'You are a helpful assistant.',
  skills: [weatherSkill],
});
```

`validateSkill(skill)` reports missing fields, environment variables and dependencies; the CLI scaffolds and validates skill packages with [`cogitator skill`](/docs/cli#cogitator-skill).

## Agents as Tools

Wrap an agent as a tool so other agents can delegate to it:

```typescript
import { agentAsTool } from '@cogitator-ai/core';

const researchTool = agentAsTool(cogitator, researcher, {
  name: 'research',
  description: 'Delegate a research task to the research agent',
});

const orchestrator = new Agent({
  name: 'orchestrator',
  model: 'openai/gpt-6.1-sol',
  instructions: 'You coordinate tasks. Use the research tool for information gathering.',
  tools: [researchTool],
});
```

The tool takes a single `task` string and returns `{ output, success, error? }`; `includeUsage` and `includeToolCalls` add the inner run's usage and tool calls, and `timeout` limits it. The inner run inherits the caller's `userId` and abort signal. Calls of the inner agent that need approval are declined unless you pass `onApproval`, since nobody can answer them mid-delegation. See [Agent as Tool](/docs/tools/agent-as-tool).

## Reasoning

Reasoning models think before they answer. `reasoning` sets how hard, in one vocabulary for every provider, and can ask for a readable summary of the thinking:

```typescript
const analyst = new Agent({
  name: 'analyst',
  model: 'anthropic/claude-opus-5-5',
  instructions: 'Answer questions about the quarterly numbers.',
  reasoning: { effort: 'high', summary: true },
});

const result = await cog.run(analyst, {
  input: 'Why did margins drop in Q3?',
  stream: true,
  onReasoning: (delta) => process.stdout.write(delta), // the summary, while it streams
});

result.reasoning; // the whole summary
result.usage.reasoningTokens; // thinking tokens, billed as output
```

`effort` goes from `none` to `max`; a run can override it with `cog.run(agent, { reasoning })`. How each provider applies it is in [LLM Backends](/docs/core/llm-backends#reasoning).

## Handoffs

An agent can hand the conversation over to another one. `handoffs` lists the agents it may pass to; each becomes a `transfer_to_<name>` tool described by the target's `description`. When the model calls it, the rest of the run goes on **as the target agent** — its instructions, tools, model and reasoning — with the whole conversation so far:

```typescript
const billing = new Agent({
  name: 'billing',
  description: 'Invoices, payments and refunds',
  model,
  instructions,
  tools: [lookupInvoice],
});
const support = new Agent({
  name: 'tech_support',
  description: 'Login problems and outages',
  model,
  instructions,
  tools: [serviceStatus],
});

const triage = new Agent({
  name: 'triage',
  model,
  instructions: 'Greet the customer and hand the conversation to the right specialist.',
  handoffs: [billing, support], // or { agent: billing, toolName: 'ask_billing', description: '...' }
});

const result = await cog.run(triage, {
  input: 'How much do I owe on INV-204?',
  onHandoff: console.log,
});
result.handoffs; // [{ from: 'triage', to: 'billing', reason: '...' }]
result.finalAgent; // 'billing' — send the next message of this thread to it
```

Unlike [agents as tools](#agents-as-tools), where a sub-agent answers the caller and the caller keeps the conversation, a handoff gives the conversation away. Unlike [swarms](/docs/swarms), there is no coordinator: each agent decides when to pass on. A run that pauses for [approval](/docs/tools/approvals) after a handoff resumes in the agent it was handed to.

## Temperature Guidelines

| Use Case                  | Temperature | Notes                       |
| ------------------------- | ----------- | --------------------------- |
| Code generation           | 0.0 -- 0.2  | Deterministic, correct code |
| Planning / classification | 0.2 -- 0.4  | Consistent but flexible     |
| General assistant         | 0.5 -- 0.7  | Balanced (default: 0.7)     |
| Creative writing          | 0.8 -- 1.2  | More varied output          |
| Brainstorming             | 1.0 -- 1.5  | Maximum creativity          |
