docs(agent): журнал claimed, повторы при обрыве связи, остановка в паузе, версия, 3.9

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
This commit is contained in:
Andrey Epifancev 2026-10-12 00:36:46 +04:00
parent 2e6f47a514
commit 22018318d1
4 changed files with 24 additions and 17 deletions

View File

@ -1,7 +1,7 @@
# Архитектура
Агент — один файл `src/agent.py` (Python 3.9, только стандартная библиотека). Тесты в `tests/` работают на фейках
Jira и хаба. Спека: `../jira-hub/openspec/changes/bridge-core/`, план: `../jira-hub/docs/superpowers/plans/2026-10-11-bridge-core-hub.md`.
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`

View File

@ -10,7 +10,7 @@
## Стек и ограничения
- Python 3.9.6 (как на виртуалке), только стандартная библиотека, без venv и зависимостей.
- Запрещено: синтаксис 3.10+ и сторонние импорты.
`tests/test_py39.py` ловит синтаксис 3.10+ (match и т. п.) и сторонние импорты; `X | Y` в аннотациях и API новее 3.9 подтверждает только прогон на /usr/bin/python3 3.9.6.
`tests/test_py39.py` ловит синтаксис 3.10+ (match и т. п.) и сторонние импорты; `X | Y` — синтаксис 3.10: в 3.9 аннотации функций вычисляются при импорте, и прогон тестов на /usr/bin/python3 3.9.6 это ловит; аннотации локальных переменных не вычисляются, их и API новее 3.9 проверяет только ревью.
- `agent.json` содержит учётные данные Jira — не коммитить, не отправлять в хаб, не выносить с виртуалки.
Шаблон без секретов — `src/agent.example.json`.
@ -42,7 +42,7 @@
## Первая подсистема
Исполнитель команд и цикл агента (спека `agent/spec.md`, `commands/spec.md`) — реализованы целиком: все типы команд,
журнал, восстановление после перезапуска, CLI `run|check|prune`.
План и спека живут в `jira-hub`: `openspec/changes/bridge-core/` и `docs/superpowers/plans/2026-10-11-bridge-core-hub.md`.
План и спека живут в `jira-hub`: `openspec/changes/bridge-core/` и `docs/superpowers/plans/2026-10-11-bridge-core-hub.md`; план агента лежит здесь: `docs/superpowers/plans/2026-10-11-bridge-core-agent.md`.
## OpenSpec и superpowers
- Вход в любую задачу — superpowers:brainstorming. Skills openspec-propose и openspec-explore сами по себе не запускать.

View File

@ -56,8 +56,8 @@ nohup python3 -u agent.py > agent.log 2>&1 &
```
Остановка: Ctrl-C или `kill <pid>`. Первый сигнал — агент доделывает текущую команду, отправляет отчёт и выходит.
Повторный сигнал — немедленный выход (код 130). Команда, которая не успела записаться в журнал `started`, при этом
остаётся `claimed` в хабе (см. ограничение 2).
Повторный сигнал — немедленный выход (код 130). Остановка во время паузы перед командой безопасна: команда уже
записана в журнал как `claimed`, при следующем запуске агент запрашивает её у хаба и выполняет через проверку (без паузы).
## Автозапуск через launchd (опционально)
@ -95,11 +95,12 @@ launchctl unload ~/Library/LaunchAgents/ru.aepif.jira-agent.plist
## Журнал
`journal.jsonl` лежит рядом с `agent.py`. Это только дозапись: на каждую команду пишутся `started` (после паузы,
перед обращением к Jira), `finished` (результат и счётчики вызовов) и `acked` (хаб подтвердил приём отчёта).
По журналу агент после перезапуска досылает неподтверждённые отчёты и находит команды, начатые, но не завершённые.
`journal.jsonl` лежит рядом с `agent.py`. Это только дозапись: на каждую команду пишутся `claimed` (сразу после получения
из хаба, до паузы), `started` (после паузы, перед обращением к Jira), `finished` (результат и счётчики вызовов) и `acked` (хаб подтвердил приём отчёта).
По журналу агент после перезапуска досылает неподтверждённые отчёты и находит команды, полученные или начатые, но не завершённые.
Журнал содержит параметры команд, то есть тексты задач и комментариев. Не выносить его с виртуалки.
В журнале нет параметров команд (текстов задач и комментариев): только id команды, статус, `result`, `error` и счётчики
вызовов. Но `result` и `error` могут содержать ключи задач и фрагменты ответов Jira, поэтому журнал лучше не выносить с виртуалки.
Очистка подтверждённых записей старше 30 дней:
@ -131,8 +132,13 @@ python3 agent.py prune
- **Отчёт до подтверждения.** Отчёт повторяется каждые 10 с, пока хаб не подтвердит. Ответы 404/409/422 хаб
выдаёт, когда команда уже отменена или удалена: агент считает её закрытой и не зацикливается.
- **429 и 503.** Агент ждёт `Retry-After` из ответа Jira и повторяет запрос: до 5 запросов, то есть не более 4 ожиданий;
на пятой неудаче — ошибка команды. Если заголовка нет или он в формате HTTP-date (не разбирается), ждёт 60 с, умноженные на номер попытки. Число ожиданий идёт в `rate_limited` отчёта.
- **Версия.** Константа `AGENT_VERSION` в `agent.py` (сейчас 1) отправляется в каждом `claim`.
на пятой неудаче — ошибка команды. Если заголовка нет или он в формате HTTP-date (не разбирается), ждёт 60 с, умноженные на номер попытки. Число ответов 429/503 идёт в `rate_limited` отчёта.
- **Обрыв связи с Jira (R-10).** Если Jira оборвала соединение или ответила 5xx, исход записи неизвестен: агент не пишет
`finished`, ждёт не меньше 30 с и повторяет команду через проверку («проверь, потом сделай») до 3 раз. Счётчики
суммируются. После исчерпания — `failed` «результат неизвестен (обрыв связи с Jira), повтор безопасен». При остановке
по сигналу команду подхватит восстановление.
- **Версия.** Константа `AGENT_VERSION` в `agent.py` (сейчас 1) отправляется в каждом `claim`, в каждом отчёте
(`agent_version`) и в `/agent/state`.
Если хаб назначил `min_agent_version` выше, он пишет в `agent_status.warning`: «обновите агента»; команды
при этом выдаются по-прежнему.
@ -157,9 +163,10 @@ python3 agent.py prune
1. **`prune` не запускать при работающем агенте.** Блокировки нет. Запись, добавленная агентом во время `prune`,
может пропасть при перезаписи файла. Сначала остановить агента.
2. **Ctrl-C во время паузы перед командой.** Команда остаётся `claimed` в хабе, в журнале о ней ничего нет.
Хаб сам её не переиздаёт. Лечение: в хабе отменить команду с force и повторить:
`POST /commands/{id}/cancel` с телом `{"force": true}`, затем `POST /commands/{id}/retry`.
2. **Остановка во время паузы или между claim и выполнением (R-9).** Команда остаётся `claimed` в хабе, но в журнале
есть запись `claimed`: при перезапуске агент запрашивает её у хаба и выполняет без паузы. Отмена с force и повтор
больше не нужны. Если журнал потерян, команду придётся отменить с force (`POST /commands/{id}/cancel`,
`{"force": true}`) и повторить (`POST /commands/{id}/retry`).
3. **HTTPS хаба с корпоративным CA не поддержан.** `HubClient` работает без `ca_bundle` и `verify_ssl`: `hub_url`
должен быть `http://`. Для Jira `verify_ssl` и `ca_bundle` работают.
4. **Спринт в `refresh` — только активные и будущие спринты доски.** Поле `sprint` берётся из Agile API по
@ -171,4 +178,4 @@ python3 agent.py prune
записи. Наблюдать на реальной Jira.
7. **429/503 без потолка.** Агент ждёт `Retry-After` в секундах сколько бы он ни был (без верхней границы): не более 4 ожиданий,
на пятой неудаче — ошибка. Длинный `Retry-After` держит очередь. Формат HTTP-date в `Retry-After` не разбирается:
тогда ждётся 60 с × номер попытки.
тогда ждётся 60 с × номер попытки. Нецелый `Retry-After` (например, `1.5`) тоже не разбирается: те же 60 с × попытка.

View File

@ -5,8 +5,8 @@ import unittest
from _common import SRC
# Ограничение проверки: ast.parse(feature_version=(3, 9)) ловит синтаксис 3.10+ (match и т. п.) и сторонние
# импорты; API и конструкции, новые в 3.9 (например, `X | Y` в аннотациях), не проверяет. Их подтверждает только
# прогон на /usr/bin/python3 (3.9.6).
# импорты; API и конструкции новее 3.9 (например, `X | Y` — синтаксис 3.10: аннотации функций
# вычисляются при импорте и их ловит прогон на /usr/bin/python3 3.9.6, аннотации локальных переменных не вычисляются), не проверяет.
class Py39Test(unittest.TestCase):