jira-agent/ARCHITECTURE.md
Andrey Epifancev 71e182a162 Агент v13: журнал изменений в refresh (#39)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 10:31:47 +04:00

7.2 KiB
Raw Blame History

Архитектура

Агент — один файл src/agent.py и запускатель src/run.py (Python 3.9, только стандартная библиотека). Тесты в tests/ работают на фейках Jira и хаба. Спека: ../jira-hub/openspec/changes/bridge-core/, план агента: docs/superpowers/plans/2026-10-11-bridge-core-agent.md (план хаба: ../jira-hub/docs/superpowers/plans/2026-10-11-bridge-core-hub.md).

Разделы файла src/agent.py

Раздел Классы и функции Ответственность Что использует
Настройки load_config читает agent.json, проверяет обязательные поля —
HTTP Http, HttpError, JiraClient, HubClient, multipart_file, wire_filename запросы; Http.send — запрос с готовым телом (JSON-вызов call идёт через него); JiraClient считает вызовы, повторяет 429/503 по Retry-After (до 5 запросов, не более 4 ожиданий), хранит время последнего вызова; JiraClient.upload — вложение multipart/form-data (таймаут не меньше 120 с); HubClient.file — файл вложения у хаба (base64 в JSON) urllib
Папка FolderTransport транспорт folder: к хабу через общую папку: req/<id>.json → реле хаба → resp/<id>.json; ошибки как у Http; HubClient делегирует ему call (спека jira-hub/openspec/changes/folder-transport/) os, uuid
Журнал Journal, _parse_at дозапись journal.jsonl: started → finished → acked; восстановление; prune (из CLI — под блокировкой журнала) файловая система
Исполнитель Executor и do_<тип> по одному методу на тип команды: читает Jira, пишет только разницу; refresh читает и отправляет состояние в хаб, затем (v13, параметр history) журнал изменений Jira — refresh_history: изменившиеся с history.since задачи этого прогона и стартовая выборка history.jql, search с expand=changelog, POST /agent/history пачками по 500; ответ не 2xx Jira или хаба — history.error/backfill_error, refresh остаётся done; ensure_attachment берёт файл вложения у хаба (hub_file: sha256 и размер, повреждение — HubError с повтором); доски — из параметров команд (ensure_sprint.board_id, refresh.boards, тип доски — board_type), board_id настроек — запасной JiraClient, HubClient (refresh и файл в ensure_attachment), настройки хаба
Самообновление read_update_state, write_update_state, Updater, launcher_version update.json; после claim без команды — проверка agent_release, скачивание, sha256, компиляция, selftest, staged; подтверждение ok новой версией; без запускателя — только подсказка в лог HubClient, subprocess
Цикл Runner claim, пауза до команды, журнал, выполнение, отчёт до подтверждения, восстановление при старте; после claim без команды спрашивает Updater, готова версия — выход с EXIT_UPDATE (75) HubClient, JiraClient, Journal, Executor
CLI cmd_run, cmd_check, cmd_selftest, cmd_prune, main команды run, check, selftest, prune (run и prune берут acquire_lock на <журнал>.lock); обработка сигналов Runner, Journal

Запускатель src/run.py

Не импортирует agent.py (тот меняется). Launcher запускает agent.py run дочерним процессом с JIRA_AGENT_LAUNCHER; коды 0/130 — выход; 75 — apply_update (копия в agent.py.prev, os.replace, trial); падение в trial или 10 минут без ok — rollback; иначе перезапуск с паузой 5…300 с. Блокировка run.py.lock; SIGINT при живом агенте игнорирует (Ctrl-C получает агент), SIGTERM пересылает. Спека — «Запускатель run.py».

reconcile при старте и in_trial перед каждым запуском сверяют update.json с sha256 agent.py (таблица — design 1 change'а agent-v9-hardening); коды агента: 75 — подмена, 73 — журнал занят (не сбой версии); OSError при подмене — rejected, запускатель не падает.

Границы

  • Код новой версии приходит только через API хаба (GET /agent/release) и пишется в каталог агента; общая папка для кода не используется.
  • Пароль и токен Jira — только в agent.json. В хаб, журнал и логи не попадают.
  • Политика темпа не хранится в агенте: зазоры (pacing) и pause_seconds приходят от хаба. Агент досыпает паузу до зазора от последнего вызова Jira (JiraClient.last_call_at).
  • Одна команда в полёте: цикл берёт следующую только после выполнения и подтверждения отчёта.
  • Отчёт до подтверждения: send_result повторяет каждые 10 с; 404/409/422 от хаба считаются закрытием.
  • Журнал пишется трижды на команду вокруг выполнения: started — после паузы, перед первым обращением к Jira; finished — после результата; acked — после подтверждения хаба. Восстановление опирается только на этот порядок.
  • Каждый обработчик Executor возвращает status/result/error и счётчики. Исключение, которое не обработано в обработчике, Runner.execute превращает в failed и не роняет цикл.
  • Тесты не ходят во внешние сервисы; фейки — tests/fake_jira.py и tests/fake_hub.py.

Открытые вопросы

  • HTTPS хаба с корпоративным CA: HubClient не принимает ca_bundle/verify_ssl (ограничение README, пункт 2).
  • Спринт задач закрытых спринтов: sprint = null (рулинг R-6).