# Архитектура Агент — один файл `src/agent.py` и запускатель `src/run.py` (Python 3.9, только стандартная библиотека). Тесты в `tests/` работают на фейках хаба и Eva. Спека: `../jira-hub/openspec/changes/wiki-module/`; решения агента: `openspec/changes/eva-agent-mvp/design.md`. Общие части — копия jira-agent v14 (`../jira-agent/ARCHITECTURE.md`). ## Разделы файла `src/agent.py` | Раздел | Классы и функции | Ответственность | Что использует | |---|---|---|---| | Настройки | `load_config` | читает `agent.json`, проверяет обязательные поля (`hub_url`, `hub_token`, `eva.base_url`, `eva.token`) | — | | Самообновление (состояние) | `read_update_state`, `write_update_state` | `update.json` рядом с `agent.py` | `os`, `json` | | HTTP | `Http`, `HttpError`, `RedirectAway`, `TooBig`, `_SameOriginRedirect` | запросы; `Http.send` — запрос с готовым телом; `fetch_bytes` — содержимое файла байтами, не больше предела; перенаправление на другой адрес — `RedirectAway` без перехода | `urllib` | | Eva | `EvaClient` | клиент Eva (Bearer-токен): счётчики вызовов и 429, время последнего вызова. Каркас; JSON-RPC и повторы — в задачах 2–3 | `Http` | | Папка | `FolderTransport` | транспорт `folder:` к хабу через общую папку: `req/.json` → реле хаба → `resp/.json`; ошибки как у `Http` | `os`, `uuid` | | Хаб | `HubClient` | `claim`, `release`, `settings`, `command`, `result`, `upload_file` (`POST /agent/files`), `wiki_pages` (`POST /agent/wiki/pages`); при `hub_url = folder:…` делегирует `FolderTransport` | `Http`, `FolderTransport` | | Журнал | `Journal`, `_parse_at` | дозапись `journal.jsonl`: `claimed` → `started` → `finished` → `acked`; восстановление; `prune` | файловая система | | Исполнитель | `Executor`, `HubError` | по методу `do_<тип>` на команду: `do_wiki_sync`, `do_wiki_fetch` (в каркасе — `failed` «не реализовано»); сбой хаба внутри команды — `HubError` | `EvaClient`, `HubClient` | | Самообновление | `Updater`, `launcher_version` | после `claim` без команды — проверка `agent_release`, скачивание, sha256, компиляция, `selftest`, `staged`; подтверждение `ok` новой версией | `HubClient`, `subprocess` | | Цикл | `Runner` | claim, пауза до команды (по времени последнего вызова Eva), журнал, выполнение, отчёт до подтверждения, восстановление при старте; выход `EXIT_UPDATE` (75) | `HubClient`, `EvaClient`, `Journal`, `Executor` | | CLI | `cmd_run`, `cmd_check`, `cmd_selftest`, `cmd_prune`, `main`, `acquire_lock` | команды `run` (`--once` — выйти при первом пустом claim), `check` (хаб `GET /agent/settings` + Eva `CmfDocument.list`; код 0 или 3), `selftest`, `prune` (`run` и `prune` берут блокировку `<журнал>.lock`); сигналы | `Runner`, `Journal` | ## Запускатель `src/run.py` Дословная копия запускателя jira-agent (`LAUNCHER_VERSION = 2`, переменная окружения `JIRA_AGENT_LAUNCHER`). Не импортирует `agent.py`. Запускает `agent.py run` дочерним процессом; код 75 — подмена на `agent.py.new` с пробным периодом и откатом; иначе перезапуск с паузой. Проверка идентичности — `test_run_py_is_copy`. ## Границы - Код новой версии приходит только через API хаба (`GET /agent/release`); общая папка для кода не используется. - Токены хаба и Eva — только в `agent.json`; в журнал, хаб, логи и тексты ошибок не попадают. - Темп вызовов Eva задаёт хаб (`wiki_pacing` в `GET /agent/settings`), агент его не хранит. - Одна команда в полёте: следующая берётся после выполнения и подтверждения отчёта. - Отчёт до подтверждения: `send_result` повторяет каждые 10 с; 404/409/422 от хаба считаются закрытием. - Команды только читают Eva, поэтому после перезапуска выполняются заново (design 9). - Поля протокола `jira_calls`/`rate_limited` — общие для агентов хаба; агент кладёт в них число вызовов Eva и 429. - Тесты не ходят во внешние сервисы. ## Сквозной прогон `scripts/e2e-hub.sh`: настоящий хаб (`jira-hub serve` из worktree `jira-hub/.wt/wiki`, временный `JIRA_HUB_HOME`, свободный порт) + `scripts/fake_eva_serve.py` + `agent.py run --once` по HTTP. Живой хаб и его данные не затрагиваются. ## Открытые вопросы - Живая проверка на виртуалке с настоящей Eva (после merge hub#92).