# 4. Сборка агента

🔵 Полный путь создания агента — только YAML и, при желании, скрипты. Сначала —
философия, потом — механика.

---

## 4.1 Философия

Прежде чем писать `agent.yaml`, стоит понять, **что** ты вообще создаёшь.

### Агент — это описание, а не код

В Agent OS агента **собирают из YAML**: личность, инструменты, хуки, бюджет.
Код (Rhai/JS) нужен только когда понадобился кастомный инструмент или хук.
Это декларативная сборка: ты описываешь сотрудника в «должностной инструкции»,
а не пишешь его «мозг». Всю тяжёлую часть — цикл хода, обработку tool-call'ов,
учёт токенов, изоляцию, логирование, планирование — делает **ядро**.

### Пять вопросов, определяющих агента

Любой агент — это ответ на пять вопросов. Каждый — отдельная грань, отдельное
место в конфиге:

| Вопрос | Поле | Что это |
|---|---|---|
| **Кто он?** | `instructions` | роль, личность, правила поведения и формат ответа |
| **Что он умеет?** | `tools` / `tool_files` | встроенные и YAML/скриптовые инструменты |
| **Как ведёт себя в цикле жизни?** | `hooks` / `hook_files` | память, кэш, эскалация, инъекции контекста |
| **Сколько ему можно?** | `budget` / `budget_group` | лимиты токенов (это деньги) |
| **Откуда приходят?** | `telegram`, `email`, виджет, API | каналы входа |

### Разделение ответственности

Каждая грань живёт в своём файле, поэтому их можно менять **по отдельности**:
подменить инструмент, не трогая промпт; переписать промпт, не трогая хуки;
перенести лимиты в `budgets.yaml`, не трогая агента вовсе. Это и есть главный
смысл структуры:

```
config/agents/<name>/
├── agent.yaml          # личность + привязки
├── tools/*.yaml        # возможности
├── hooks/*.yaml        # жизненный цикл
├── bench/scenarios.yaml # контроль качества
└── tests/*.yaml        # тесты инструментов
```

### Агент как сотрудник

Полезная метафора: `instructions` — должностная инструкция (что делать и как
отвечать), `tools` — навыки и инструменты на рабочем месте, `hooks` — рефлексы
и правила (память, кэш частого, передача человеку), `budget` — лимиты
полномочий, каналы — линии связи.

### Принципы хорошего агента

- **Приземлённость.** Требуй источники и ссылки, запрещай выдумывать, проверяй
  инструментом. Пример `ngu-agent` *заставляет* приводить ссылки на источник;
  `trace-agent` — «никогда не угадывай, всегда проверяй вызовом».
- **Ясная роль и формат.** Фиксируй язык и структуру ответа прямо в инструкциях.
- **Экономность.** Бюджеты — это деньги: кэш (`faq_cache`) и ограничения
  контекста срезают расход.
- **Наблюдаемость.** Покрой агента бенчмарками (`bench/`) — иначе регрессии
  незаметны.

### Шаблон → процесс → сессия

Ты описываешь **шаблон** (рецепт). Ядро создаёт **процесс** по первому запросу
(lazy spawn), а разговор пользователя — это **сессия**. Меняешь шаблон — новые
сессии получают новое поведение; уже запущенные процессы сохраняют старый
системный промпт.

---

## 4.2 Структура каталога

Сервер сам находит агентов: любая подпапка `config/agents/`, содержащая
`agent.yaml` (или `agent.yml`), становится шаблоном. `tool_files` и
`hook_files` разрешаются относительно папки агента.

---

## 4.3 `agent.yaml` — справочник

```yaml
name: my-agent
welcome: |
  👋 Привет! Чем помочь?
instructions: |
  You are a helpful assistant.
  Use my_tool to answer questions about X.
  Answer in Russian.
tools: [http_fetch]
tool_files:
  - tools/my_tool.yaml
hooks: []                    # имена хуков, зарегистрированных при старте
hook_files:
  - hooks/faq_cache.yaml
route_files:                # HTTP-маршруты, которые добавляет агент (http_route / static_route)
  - routes/api.yaml
max_context_tokens: 32000
model: ""                   # именованная модель из server.yaml → models:
output_schema: null         # JSON Schema структурированного завершения
budget:                     # опциональное перекрытие бюджета
  refill_per_sec: 30
  max_bucket: 10000
  max_total: 200000
budget_group: my-shared
budget_messages:
  session_exhausted: "Лимит диалога исчерпан. Начните новый."
  user_exhausted: "Лимит на сегодня исчерпан."
  group_exhausted: "Общий лимит исчерпан. Попробуйте позже."
  rate_limited: "Слишком много запросов. Подождите."
inherit_budget: false
parent: null
cron: []                    # задания по расписанию этого агента
telegram:
  token_env: MY_TG_BOT_TOKEN
  allowed_user_ids: []
  show_typing: true
  show_tool_calls: true
email:
  imap_host: imap.example.com
  imap_port: 993
  imap_username: support@example.com
  imap_password_env: EMAIL_PASSWORD
  allowed_senders: []
  smtp:
    host: smtp.example.com
    username: support@example.com
    password_env: SMTP_PASSWORD
    from: support@example.com
  notify: []
  poll_secs: 60
vk:
  token_env: VK_GROUP_TOKEN
  group_id: 123456789
  allowed_user_ids: []
  show_typing: true
  show_tool_calls: true
  poll_wait: 25
```

### Справочник полей

| Поле | Обязат. | По умолч. | Описание |
|---|---|---|---|
| `name` | да | — | уникальное имя шаблона |
| `instructions` | да | — | системный промпт |
| `welcome` | нет | — | приветствие виджета |
| `tools` | нет | `[]` | встроенные инструменты (`http_fetch`, `fs_read`, `job_done`) |
| `tool_files` | нет | `[]` | пути к YAML-инструментам (относительно папки агента) |
| `hooks` | нет | `[]` | имена хуков (зарегистрированы при старте) |
| `hook_files` | нет | `[]` | пути к YAML-хукам (относительно папки агента) |
| `route_files` | нет | `[]` | пути к YAML HTTP-маршрутам `http_route`/`static_route` (относительно папки агента; см. §21) |
| `max_context_tokens` | нет | `32000` | лимит контекстного окна |
| `model` | нет | пусто | именованная модель из `server.yaml → models:` (пусто = дефолтный провайдер) |
| `output_schema` | нет | — | JSON Schema завершения: заменяет параметры `job_done` |
| `budget` | нет | — | `{refill_per_sec, max_bucket, max_total}` |
| `budget_group` | нет | — | имя общей бюджетной группы |
| `budget_messages` | нет | — | сообщения исчерпания (4 уровня) |
| `inherit_budget` | нет | `false` | дети наследуют группу родителя |
| `parent` | нет | — | имя родителя (дерево процессов) |
| `cron` | нет | `[]` | задания по расписанию, запускающие этого агента |
| `telegram` | нет | — | `{token_env, allowed_user_ids, show_typing, show_tool_calls}` |
| `email` | нет | — | входящий email-канал (см. ниже) |
| `vk` | нет | — | входящий VK-бот сообщества (см. ниже) |

### Каналы ввода/вывода

Шаблон может привязать **каналы** — входящие источники, маршрутизирующие
сообщения в этого агента: виджет, Telegram-бот, email, HTTP API, webhook.
В `agent.yaml` это поля `telegram` и `email`. Полный разбор всех каналов
(встраивание виджета, конфигурация Telegram/email, sink'и) — в §19.

### Структурированный вывод (`output_schema`)

Задай `output_schema`, чтобы агент возвращал фиксированную форму вместо
свободного текста. Ядро заменяет параметры `job_done` этой схемой (добавляя
`job_done`, если его нет в `tools:`), и модель обязана завершиться вызовом
`job_done(...)` с подходящим значением:

```yaml
output_schema:
  type: object
  properties:
    needs_action: { type: boolean }
    summary: { type: string }
  required: [needs_action, summary]
```

Аргументы `job_done` валидируются до завершения; некорректный вызов
отклоняется (модель видит ошибку и может исправиться в том же цикле).
Проверенное значение доступно вызывающему (например, `subagent_tool` кладёт
его в `data` результата).

---

## 4.4 Пошагово

### Шаг 1 — скелет

```bash
mkdir -p config/agents/mybot/tools config/agents/mybot/hooks config/agents/mybot/bench config/agents/mybot/tests
```

Напиши `config/agents/mybot/agent.yaml` (см. §4.3).

### Шаг 2 — инструмент

Создай `config/agents/mybot/tools/my_tool.yaml` (см.
[справочник инструментов](05-tools-reference.md)) и перечисли его в `tool_files:`.
Проверь без сервера:

```bash
site_tool config/agents/mybot/tools/my_tool.yaml '{"query":"..."}'
```

### Шаг 3 — хук (опционально)

Создай `config/agents/mybot/hooks/<hook>.yaml` (см. `06-hooks-reference`) и
перечисли его в `hook_files:`. Проверь без сервера:

```bash
hook_tool config/agents/mybot/hooks/<hook>.yaml user "Привет"
```

### Шаг 4 — бюджетная группа

Добавь группу в `config/budgets.yaml` и привяжи шаблон:

```yaml
budget_groups:
  - name: mybot-shared
    refill_per_sec: 30
    max_bucket: 30000
    max_total: 3000000
agents:
  - template: my-agent
    group: mybot-shared
```

### Шаг 5 — сценарии бенчмарка

Создай `config/agents/mybot/bench/scenarios.yaml`:

```yaml
name: "MyBot"
model: "my-agent"

scenarios:
  - name: "Простой вопрос"
    turns:
      - user: "Как мне ...?"
        assert:
          contains: [ожидаемое, слово]
          min_links: 1
          valid_urls: true
```

Типы проверок: `contains` (подстроки, без учёта регистра), `min_links` /
`min_urls` (число markdown-ссылок), `valid_urls` (все ссылки http/https).

Запуск (см. `09-operations`):

```bash
agent_bench --port=3001 --agent=my-agent --max=3
```

### Шаг 6 — тесты инструментов (опционально)

Fixture-тесты (без сети) в `config/agents/mybot/tests/my_tool_tests.yaml`:

```yaml
kind: site_tool_tests
tool: tools/my_tool.yaml
tests:
  - name: "извлекает заголовок"
    input: { url: "https://example.com/p/1" }
    fixture: { productData: { title: "Widget", price: 99 } }
    assert:
      - { path: /title, equals: "Widget" }
      - { path: /price, equals: 99 }
```

Live-тесты (реальный HTTP: `kind: site_tool_live_tests`,
`archive_tool_live_tests`, `rss_tool_live_tests`) вместо `fixture:` задают
`fetch: true`. Как их запускать — в `09-operations`.

---

## 4.5 Практики инструкций

- Называй **какой инструмент когда вызывать**, включая цепочки из нескольких
  шагов и поиск по slug/id (живой пример — `config/agents/drom/agent.yaml`).
- Требуй копировать `links[]` из вывода инструмента в ответ; запрещай голые URL.
- Фиксируй язык и формат ответа.
- Используй `__GEOIP__` для подстановки города пользователя (см. `drom`).
- Держи агента «приземлённым»: «никогда не угадывай, всегда проверяй вызовом»
  (см. `trace-agent`).

---

## 4.6 Live reload

При `live_reload: true` (по умолчанию) можно править `agent.yaml`, инструменты
и хуки на лету: новые сессии берут новые инструкции, перерегистрированные
инструменты/хуки действуют со следующего вызова. Уже запущенные процессы
сохраняют исходный системный промпт.
