eva-agent/ARCHITECTURE.md
Andrey Epifancev cf7c7bde8c Документация и решения 36–43: выкатка --module wiki, base_url, launchd (I-3, T6.1, T6.4, M-2, M-6, #1)
- CLAUDE.md: `jira-hub agent publish --module wiki src/agent.py`, порог `wiki_min_agent_version`.
- README: `wiki_min_agent_version` вместо `min_agent_version`; `eva.base_url` — только origin; агент только для macOS
  (fcntl), пример launchd `ru.aepif.eva-agent`; строка про проверку SSO на первой живой синхронизации (M-5).
- ARCHITECTURE: строки Eva и Executor по реализованному, строки ссылок (`_LinkParser`, `classify_links`) и дерева
  (`build_children`, `walk`).
- design.md: решения 36–43, в том числе почему M-5 отложен до живой проверки.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 05:56:57 +04:00

8.1 KiB
Raw Blame History

Архитектура

Агент — один файл 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, EvaError, ForeignHost, _quote_url_part клиент Eva (Bearer-токен): JSON-RPC 2.2 call (POST /api/?m=<метод>, error в ответе — EvaError), повторы 429/502/503/504 и сетевых сбоев (4 попытки, Retry-After), пауза pacing между вызовами, токен вырезается из текстов ошибок; get_doc, list_docs (страницы по 200, стоп, если Eva не соблюдает slice), download (только адрес своего хоста, иначе ForeignHost; путь кодируется перед запросом; предел 10 МБ); счётчики вызовов и 429, время последнего вызова Http
Ссылки _LinkParser, _ParseError, classify_links _LinkParser (html.parser) собирает img[src] и a[href] в порядке появления, нестандартные <![…]> пропускает; classify_links делит их на вложения {ref, url, name} (свой хост, /files/…, миниатюра → файл) и ссылки на страницы {ref, code} (DOC-<цифры>); чужой хост, static/ и битые адреса пропускает html.parser, urllib.parse
Дерево build_children, walk, _ref_code, _ref_id, _orderno build_children — {код родителя: [дети]}: с проектом — один список документов проекта и связи parent/tree_parent (ссылка разрешается в код через карту id → код), без проекта — обход в ширину по tree_parent.code; соседи по (orderno, name, code). walk — обход в глубину, корень первым, каждый код один раз (циклы, два родителя) EvaClient
Папка FolderTransport транспорт folder: к хабу через общую папку: req/<id>.json → реле хаба → resp/<id>.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: корень, build_children + walk, по странице CmfDocument.get и classify_links, пачки в стадию хаба (50 страниц или 4 МБ; 413 — ошибка с кодом страницы/границами пачки), отчёт {root_title, codes, eva_calls}. do_wiki_fetch: download → POST /agent/files, отчёт {attachment_id, file, size}; «чужой хост», «файл больше 10 МБ», «файл не найден в Eva». Ошибки: сбой хаба — HubError («хаб: …»), EvaError — «Eva: …», 5xx и сеть — повтор через Runner EvaClient, HubClient, classify_links, build_children, walk
Самообновление 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).
  • Ответ Eva на протухший токен (перенаправление на SSO, страница входа с кодом 200 или 401) — проверить при первой живой синхронизации (M-5, design 42).