From ed9c32a739da394575a524ab6dbf9007d912e998 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 14 Oct 2026 22:38:59 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20agent-self-update=20=E2=80=94?= =?UTF-8?q?=20=D1=81=D0=B8=D0=B3=D0=BD=D0=B0=D0=BB=D1=8B=20run.py=20=D0=B8?= =?UTF-8?q?=20=D0=B2=D1=8B=D1=85=D0=BE=D0=B4=200=20=D0=B1=D0=B5=D0=B7=20Ct?= =?UTF-8?q?rl-C=20(R-20),=20=D1=80=D1=83=D0=BB=D0=B8=D0=BD=D0=B3=D0=B8=20R?= =?UTF-8?q?-19=E2=80=A6R-21=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- openspec/changes/agent-self-update/design.md | 10 ++++++++-- .../agent-self-update/specs/agent/spec.md | 16 ++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/openspec/changes/agent-self-update/design.md b/openspec/changes/agent-self-update/design.md index bfd88b6..fdad7a5 100644 --- a/openspec/changes/agent-self-update/design.md +++ b/openspec/changes/agent-self-update/design.md @@ -110,11 +110,14 @@ 15. **Дочерний процесс.** `[sys.executable, "-u", <каталог>/agent.py, "run", *аргументы]` с переменной окружения `JIRA_AGENT_LAUNCHER=`; stdout/stderr наследуются (лог агента и запускателя — в одном месте). Агент передаёт `launcher_version` в `claim`. Строки запускателя — с префиксом «run:» и временем, как у агента. -16. **Сигналы.** SIGINT запускатель игнорирует (Ctrl-C в терминале и так приходит дочернему процессу — общая группа). +16. **Сигналы.** SIGINT при живом агенте запускатель только запоминает как «остановил человек» (Ctrl-C в терминале и так + приходит дочернему процессу — общая группа); без агента (пауза перед перезапуском) — выход. После сигнала остановки + новый агент не запускается. SIGTERM (`kill`, `launchctl unload`) пересылается дочернему процессу; повторный SIGTERM — пересылается снова (агент выходит немедленно, 130). После выхода дочернего процесса по сигналу запускатель выходит. 17. **Коды выхода дочернего процесса:** - - `0` или `130` — остановка человеком: запускатель выходит с тем же кодом; + - `0`, `130` или `-SIGINT` после Ctrl-C (или SIGTERM запускателю) — остановка человеком: запускатель выходит с тем + же кодом; те же коды без сигнала запускателю — падение (R-20); - `75` — обновление: если `update.json` = `staged` и sha256 `agent.py.new` совпадает — копия `agent.py` в `agent.py.prev` (временный файл + `os.replace`), затем `os.replace(agent.py.new, agent.py)` (файл `agent.py` существует всегда), `update.json` = `trial` `{from_version, to_version, sha256, at}`, запуск новой версии. @@ -192,3 +195,6 @@ | R-16 (design #15) | 2026-10-14 | stdout/stderr дочернего процесса наследуются, в `rolled_back` — только код и время | трейсбек остаётся в логе агента; захват вывода усложняет запускатель | причину отката видно в логе на виртуалке, в хабе — только код | | R-17 (design #13) | 2026-10-14 | Агент — версия 5, `min_agent_version` не поднимается | v4 продолжает работать до ручного обновления | нет | | R-18 (design #1) | 2026-10-14 | Публикация через CLI хаба, не через MCP | публикация ставит код на банковскую виртуалку — только явной командой в терминале | нет | +| R-19 (агент, исполнение) | 2026-10-14 | `tests/test_folder.py::test_version_is_4` переименован в `test_version_is_5` (AGENT_VERSION = 5) | план не учёл старый тест версии; спека требует v5 | нет | +| R-20 (design #16, #17) | 2026-10-14 | Выход агента с 0/130 считается остановкой человеком, только если запускатель сам получил SIGINT/SIGTERM; иначе — падение (в `trial` — откат, вне — перезапуск с паузой); Ctrl-C при выходе с 75 — подмена и выход, `trial` при следующем старте; агент, получивший Ctrl-C во время подготовки, выходит с 0 | финальное ревью агента I-1: версия, сама вышедшая с 0, останавливала мост без отката (под launchd — вечный цикл `trial`); Ctrl-C в терминале запускатель получает всегда (общая группа) | `kill ` теперь перезапускает агента — останавливать нужно `run.py` (README) | +| R-21 (финальное ревью) | 2026-10-14 | Находка M-1 ревью агента (отрицательный `sleep` в `pause` роняет запускатель) повышена до Important и исправлена | `run.py` обновляется вручную, падение запускателя останавливает мост до человека | нет | diff --git a/openspec/changes/agent-self-update/specs/agent/spec.md b/openspec/changes/agent-self-update/specs/agent/spec.md index 016327d..a1065db 100644 --- a/openspec/changes/agent-self-update/specs/agent/spec.md +++ b/openspec/changes/agent-self-update/specs/agent/spec.md @@ -88,14 +88,18 @@ README агента SHALL описывать: первую установку ( `python3 run.py [аргументы]` SHALL работать с файлами в каталоге самого `run.py` (`agent.py`, `agent.py.new`, `agent.py.prev`, `update.json`) и запускать дочерним процессом `agent.py run` с переданными аргументами, переменной окружения `JIRA_AGENT_LAUNCHER = LAUNCHER_VERSION` и унаследованными stdout/stderr. Запускатель SHALL брать -неблокирующую блокировку `run.py.lock`; занята — «запускатель уже работает», код 1. SIGINT запускатель SHALL -игнорировать, SIGTERM — пересылать дочернему процессу. +неблокирующую блокировку `run.py.lock`; занята — «запускатель уже работает», код 1. SIGINT при живом дочернем процессе +запускатель SHALL только запоминать как остановку человеком (Ctrl-C получает и агент), без дочернего процесса (пауза +перед перезапуском) — завершаться; SIGTERM — пересылать дочернему процессу. После сигнала остановки запускатель MUST NOT +запускать агента снова. По коду выхода дочернего процесса: -- `0` или `130` — запускатель завершается с тем же кодом; +- `0`, `130` или завершение по SIGINT после Ctrl-C (или после SIGTERM запускателю) — запускатель завершается с тем же + кодом; те же коды без сигнала запускателю — падение; - `75` — при `update.json` = `staged` и совпадении sha256 `agent.py.new`: копия `agent.py` в `agent.py.prev`, атомарная замена `agent.py` на `agent.py.new`, `update.json` = `trial`, запуск новой версии; иначе `agent.py.new` удаляется, - `update.json` = `rejected` «файл изменился после проверки», запуск текущей версии; + `update.json` = `rejected` «файл изменился после проверки», запуск текущей версии; если перед этим был Ctrl-C — + после подмены запускатель завершается, пробный период начнётся при следующем старте; - иной код при `update.json` = `trial` — откат; иначе — перезапуск той же версии с паузой 5 с, удваиваемой до 300 с при каждом следующем падении и сбрасываемой после 10 минут работы. @@ -119,6 +123,10 @@ README агента SHALL описывать: первую установку ( - **THEN** запускатель посылает SIGTERM, через 60 с при необходимости SIGKILL, делает откат с причиной «не вышла на связь за 10 мин» +#### Scenario: Новая версия сама вышла с кодом 0 +- **WHEN** `update.json` = `trial` для v7, дочерний процесс вышел с кодом 0, сигналов запускатель не получал +- **THEN** откат на v6 с причиной «код 0 через с», как при падении + #### Scenario: Падение после подтверждения - **WHEN** `update.json` = `ok`, агент дважды подряд упал с кодом 1, проработав меньше минуты - **THEN** запускатель перезапускает ту же версию через 5 с, затем через 10 с; отката нет