Агенты умирают в закрытых терминалах. agryp — не спит.
agryp — минимальный init-супервизор, который запускает AI-агентов как процессы
первого класса: с политикой перезапуска, бюджетом попытки и graceful shutdown.
Один файл C, только libc, ноль зависимостей — этика LFS: собрано с нуля,
ничьего чужого.
Подсказка заказчика: «посмотри, как занято слово ἰδιώτης». Проверили —
занято даже на PyPI (пакет idiotes, 2024, с описанием «частное лицо, не
участвующее в общественных делах»). Урок принят: словарные греческие и
латинские слова выжжены насквозь — брендовым мусором занято всё, вплоть до
абсурдных слов.
Но семантика подсказки — диагностична. ἰδιώτης (от ἴδιος — «свой, не-публичный, не-разделяемый») — это «частник»: не выходит на агору, никого не пасёт, полезен только себе. Перикл в надгробной речи говорил о таких: кто не участвует в общих делах — «бесполезен» (ἀχρεῖον). Это точный диагноз агентскому стеку топ-100 GitHub (40 из 100 репо): агентские процессы — ἰδιῶται, частники в личных терминалах, не участвующие в общей жизни машины.
Антидот — из того же языка: ἀγύπνος — «неспящий» (ἀ- + ὕπνος, бог сна Гипнос). Так звали неусыпных: монахи-«бессонные» (ἀκοίμητοι, akoimetoi) с начала V века держали службу посменно, день и ночь — службу, которая не останавливается никогда. Словарь при этом даёт точное определение нашей функции: agrypnode — «любой агент, вызывающий бодрствование».
Имя: стем agryp (от ἀγύπνος) в юникс-фонетике — ряд grep / grip / crypt:
5 букв ASCII, читается «АГ-рип», без культурного багажа и сленговых ловушек,
агент-парсится и компонуется (agrypctl, agryp-unit).
Квитанции проверок (ноябрь 2026):
| Кандидат | Что нашла сеть | Вердикт |
|---|---|---|
idiotes |
пакет на PyPI | занят |
protagon |
protagon.space, Protagon USA | занят |
agentor |
3 проекта, вкл. crates.io-рантайм для агентов | занят |
moirai |
Salesforce Moirai 2.0 | занят |
pidgen |
PyPI pidgen2, PIDgen.dll Windows | занят |
agrypnos |
мод Slay the Spire на GitHub | занят |
agryp |
только никнеймы на форумах; ноль в GitHub/npm/PyPI/crates | свободен |
pidfather (было) |
свободен, но бренд «для людей»: мем с крестным отцом | заменён |
- не спит:
poll()-цикл с дедлайнами бюджета — сутки напролёт, как у монахов-«бессонных» (akoimetoi); - служба не прерывается:
restart = always— попытка за попыткой, смена за сменой; - казначей бодрствования:
budget-sec— попытка дольше лимита усыпляется SIGKILL-ом; - собирает мёртвых:
waitpid(-1)— зомби не копятся; - готов стать PID 1: в контейнере усыновляет осиротевшие процессы сам.
Топ-100 GitHub на 2026-11-06 (см. docs/top100-by-category.md):
| Категория | Репо из 100 |
|---|---|
| AI-агенты и скиллы | 31 |
| ML-модели и инференс | 9 |
| Агентский стек всего | 40 |
| Из них на уровне процессов (init/supervisor/pid) | 0 |
Полный свод инструментов-исполнителей — 2444 позиции, 34 категории, без дублей, всё по алфавиту: docs/tool-catalog.md, машиночитаемая версия — docs/tool-catalog.csv.
Весь агентский стек списка живёт в userland: скиллы (№12, 13, 20, 36, 48, 62, 99), CLI-агенты (№22, 56, 79, 93), воркфлоу-платформы (№25, 51, 55), обвязка LLM (№65, 68). Их запускают руками из терминала; закрылся терминал — агент умер; агент завис — никто его не убьёт и не поднимет.
Ближайшие аналоги и почему это не они:
- systemd / supervisord — знают процессы, но не знают агентов: нет понятия
«бюджет попытки», нет формата
.agent, нет подсчёта попыток. - Оркестраторы уровня фреймворков (LangGraph Supervisor, Paperclip и т.п. — то, что выдаёт поиск в 2026-м) — про агентов знают всё, но живут внутри приложения, а не на уровне процессов: их «супервизор» умирает вместе с процессом.
- Сверстники-2026 процессного слоя (zeptopm — PM2 для LLM; stdiobus —
NDJSON-роутер; lemon/spectre — BEAM) — доказывают спрос, но живут на
рантаймах и сокетах: нет ни одного из пяти свойств agryp (живая сверка —
docs/TEACHERS.md§V).
agryp занимает клетку, которую не занял ни один из них:
PID1-ядро жизненного цикла агента.
Сверка со средой обитания (docs/TEACHERS.md):
учителя — systemd, s6, runit, tini, supervisord, Kubernetes, launchd;
сверстники-2025/26 app-уровня — Claude Code, Codex, MCP, LangGraph,
E2B/Modal, OTel GenAI, OpenHands, Letta. Принято четыре инварианта
(лестница перезапуска, предохранитель crash-loop, subreaper, PDEATHSIG),
семь подтверждены как «уже было», остальное отвергнуто с причинами —
сингулярность держится на отказах не хуже, чем на принятиях.
Живая сверка ниши (2026-11-07, GitHub API с токеном Хозяина,
docs/TEACHERS.md §V): процессный слой для агентов
возник — zeptopm («PM2 для LLM», Rust), stdiobus (C-роутер NDJSON),
lemon и spectre (Elixir/OTP); уровнем выше — paperclip ★111k, herdr ★48k,
openhuman ★42k, agent-orchestrator ★14k. Ниша размечена слоями, и клетку
PID1-ядра жизненного цикла не занял никто: у сверстников нет ни одного
из наших пяти свойств — один файл C, ноль зависимостей, файл-как-API,
самоверифицирующийся журнал, гарантии ядра (группа попытки, subreaper,
PDEATHSIG, клетка cgroup).
# examples/hello.agent
name = hello
exec = bash demo/hello_agent.sh
restart = always # always | on-failure | no
budget-sec = 25 # time-бюджет попытки (Ст. 7)
token-budget = 5000 # токен-бюджет: burn-rate агент отдаёт сам (Ст. 14)
cooldown-ms = 1000 # база лестницы перезапуска (Ст. 8)
# finish = rm -rf /tmp/$AGRY_UNIT-worktree # хук после каждой смерти (Виток 6)
# finish-timeout-ms = 600 # потолок хука per-unit, 0/нет = дефолт 5000 (Виток 11, s6 timeout-finish)
# mem-max-mb = 64 # клетка cgroups v2 (Ст. 15), 0 = без клетки
# jail = pid,mount # комнаты имён попытки: pid | mount | pid,mount (Виток 7)
# max-attempts = 0 # 0 = без лимита
make && ./agryp --dir examples
# другой терминал — живой NDJSON-срез семьи (Ст. 13):
./agryp status --dir examples
# остановить/поднять юнит — файл-договор, не сокет (Виток 5):
./agrypctl stop hello # кладёт examples/.agryp-ctl/hello.down → ctl-down
./agrypctl start hello # снимает → ctl-up, новая попытка
# поменять бюджет/добавить/удалить *.agent и перечитать договоры без смерти детей (Виток 6):
kill -HUP $(pgrep -f 'agryp --dir examples')
Логи — JSON-строки в stdout (boot, spawn, exit, restart, stopped,
failed, budget-kill, token-kill, ctl-down, ctl-up, ctl-drop,
ctl-kill, finish, reload, reload-done, reload-kill, ready,
unit-added, unit-removed, cgroup-skip, jail-skip, report-drop,
subreaper, reap-orphan, shutdown, summary) — удобно направлять
в любой сборщик.
Сценарий (make demo): поднимаются два агента; на 5-й секунде мы убиваем
hello через kill -9 всей группе процессов; на 15-й снимаем agryp status
в живой семье; на 25-й crashy добирается до терминального failed;
на 35-й супервизору уходит SIGTERM.
Факты из demo/demo-transcript.jsonl (реальный прогон 2026-11-07):
- Ст. 15 в песочнице:
cgroup-skip(cgroupfs read-only, errno 30) — лимит памяти объявлён вслух, супервизор работает дальше; - Ст. 9 при загрузке:
subreaperс"on":true— супервизор объявил себя собирателем сирот; каждый ребёнок уходит в жизнь со страховкойPR_SET_PDEATHSIG; - внешняя смерть поймана:
"event":"exit","by_signal":9→"event":"restart"сbackoff_ms:1000— база лестницы; - Ст. 8, лестница
crashy:backoff_ms1000 → 2000 → 4000 → 8000 (streak 1→4), пятая своя смерть — терминальныйfailed(streak 5) иstopped reason crash-loop; спавнов послеfailed— ноль; - Ст. 8, лестница
hello: казни закона её не растят — после двухtoken-killюнит продолжает с базой 1000 мс; попытка, прожившая ≥ 10 с, гасит счётчик (streak1 → 0); - Ст. 14: hello дважды усыпан токенами — 2 ×
token-kill, оба приtokens:5000 == budget:5000;budget-kill— 0: токены дороже 25 секунд; - Ст. 13:
demo/status-snapshot.jsonl— срез в t=15:crashyRUNNING (attempt 4, streak 3 — на верхней ступени перед падением),helloRUNNING 4500/5000 — за 0,3 с до казни; - graceful shutdown: TERM → ожидание → KILL, затем честный summary:
{"unit":"crashy","event":"summary","attempts":5,"exits_ok":0,"exits_fail":5,"budget_kills":0,"token_kills":0,"tokens_total":0,"restarts":4}
{"unit":"hello","event":"summary","attempts":4,"exits_ok":0,"exits_fail":4,"budget_kills":0,"token_kills":2,"tokens_total":17000,"restarts":3}
{"event":"shutdown","reason":"signal"}
Бонус, найденный при переименовании: вывод агентов и JSON-логи родителя идут в
один файл, поэтому agryp выставляет O_APPEND на stdout при старте — флаг
наследуется детьми через fork+exec, и все записи атомарно идут в конец. Без
него stdio родителя пишет по устаревшей позиции, пока дети уехали вперёд — в
логе появляются разреженные дыры из NUL-байтов (44 КБ мусора в первом прогоне
после рефакторинга имён). Транскрипт теперь — 6.6 КБ: 43 JSON-события + 43
строки агентов одним файлом, ноль NUL; это проверяет аудитор
demo/audit_transcript.py — он же сверяет каждую ступень лестницы с конфигом
юнитов и терминальность failed; make audit печатает OK. Аудитор —
fail-closed (Виток 11, тикет 018): нет журнала, пустой каталог, юнит без
договора — красный с честной причиной; проверяльщик проверяет себя:
make audit-neg — 13 негативных проб обязаны краснеть по делу, канарейка —
быть зелёной.
Управление юнитом — без сокетов, FIFO и HTTP, только файловая система
(школа s6-svc): agrypctl stop <unit> кладёт <dir>/.agryp-ctl/<unit>.down
— супервизор в ближайшей итерации казнит попытку TERM'ом группы и
замораживает restart-политику, пока файл жив (события ctl-down, остановка
с reason:ctl-down — казнь закона, лестницу не растит); agrypctl start
снимает файл — ctl-up и новая попытка с чистой лестницей, минуя cooldown.
.down с прошлой жизни уважается на boot: юнит не встаёт до снятия файла.
agrypctl status не дублирует Ст. 13 — читает тот же файл семьи. Сам
agrypctl — один POSIX sh-файл, ноль зависимостей кроме coreutils;
запись файла-договора атомарна (tmp + rename, как у статус-среза).
Дисциплину доказывает второе демо (make demo-ctl && make audit-ctl):
worker казнён без единого рестарта, сосед-ghost не тронут (заморозка
пер-юнит), после start — attempt 2; сирот 0. Аудитор знает ctl-события
и ловит подделки: спавн при живом .down, ctl-up без ctl-down и ctl-kill
без трупа — отрицательный тест demo/negative_ctl_test.py скармливает
ему все три и требует точного приговора.
Три урока учителей одним витком, тремя атомами:
- finish-хук (школа s6
./finish): ключfinish = <строка sh>— после КАЖДОЙ смерти попытки, до лестницы перезапуска; хук получает envAGRY_UNIT,AGRY_EXIT_CODE,AGRY_BY_SIGNALи живёт вне группы попытки (казнь группы его не трогает); потолок — per-unit ключfinish-timeout-ms(Виток 11, школа s6timeout-finish; 0/не задан = дефолт ядраFINISH_MS5000), упор — громкое событиеfinishс"timeout":true,"ms":<потолок юнита>; сбой хука лестницу не трогает. Типовое применение фермы:finish = rm -rf /tmp/$AGRY_UNIT-worktree— каждый новый заход агента начинается в чистом рабочем пространстве (make demo-finish: 3 смерти → 3 хука, след уборки вdemo/finish-sweep.log). - Эскалация ctl-stop (школа systemd
TimeoutStopSec): агент, игнорирующий TERM (trap '' TERM), живёт ровноGRACE_MSс момента.down, затемctl-killи SIGKILL группы — остановка доводится до конца даже упрямым; казнь закона лестницу не растит. Нюанс юнит-автору: trap обязан жить в лидере группы —exec = . script.sh(source), иначеsh script.shплодит второго dash, а казнят по лидеру (make demo-ctl: stubborn получил ctl-kill через 3006 мс и умер по сигналу 9 без рестартов). - SIGHUP-reload (школа systemd daemon-reload):
kill -HUP— каталог*.agentперечитывается без смерти детей; изменённые ключи применяются сразу (дедлайн бюджета пересчитывается от живой попытки), новый*.agent— юнит появляется и стартует (с комнаткой cgroup, Ст. 15), удалённый — graceful-stop (stopped reason reload); событиеreloadнесёт числа добавлено/изменено/удалено/пропущено и gen (Виток 11, тикет 017). Скобка обязана закрыться:reload-doneсказан, когда все удалённые попытки мертвы, с теми же числами, gen и честнымmsоседания; второй SIGHUP скобку не перекрывает; упрямому удалённому (TERM игнорирован GRACE_MS) — казньreload-kill, и скобка закрывается всё равно (make demo-reload: бюджет 5→60 на той же попытке, beta удалён graceful, gamma добавлен, stubborn сtrap '' TERMдобит по GRACE_MS — reload 1/1/2 → reload-done 1/1/2, ms ≥ 3000). Перезапуск конкретного юнита — не здесь, это agrypctl stop/start.
sd_notify говорит сокетом, s6 notification-fd — пайпом; весь мир
строит мосты между сокетами. Здесь готовность — файл-договор, как всё
в этом доме (Ст. 13): каждая попытка получает env AGRY_READY_PATH,
агент, ставший готовым, создаёт файл (touch "$AGRY_READY_PATH"),
супервизор даёт событие ready с честной задержкой от старта попытки
(ready_ms — и в журнале, и в срезе статуса). Мёртвая попытка не
остаётся «готовой»: ready-файл снимает супервизор при смерти и при
новом спавне — чужой или старый файл попытку не переживает. Молчун не
готов — и это факт, а не нарушение: готовность объявляется, а не
додумывается (make demo-ready: quickready объявился за 501 мс,
neverready честно 0; аудитор сверяет ms с хронологией spawn→ready).
Три функции третьего витка, исполнены автором лично (коллеги не делают):
- Namespace-клетка попыток (
jail = pid | pid,mount): вход в новую pid-комнату — только родиться в ней, поэтому вместоfork()—clone(CLONE_NEWPID[|CLONE_NEWNS]): ребёнок супервизора и есть PID 1 своей комнаты, а геометрия попытки не меняется — та же группа (kill(-pgid)режет семью и сквозь стену), тот жеPDEATHSIG, тот жеwaitpid. Mount-ветка сразу делает/рекурсивно private — propagation наружу закрыт. Это не Docker: без образов, сетей и слоёв; user-ns не трогаем. Среда не дала unshare — loudjail-skipс errno и работа дальше без клетки, как уcgroup-skip(Ст. 15):make demo-jail— в песочницеjail-skipс errno 1, юнит живёт, сироты 0; аудитор ловит «молчаливую усечку» и «двух правд». - Токен-будильник (Ст. 14, тикет 009): квант итерации 250 мс съедал
латентность казни — агент, жгущий 10k токенов/с, умирал с задержкой
до тика. Теперь агент после записи рапорта бьёт
SIGUSR2супервизору (пид — в envAGRY_SUPER_PID); файл остаётся единственной правдой, сигнал — только звонок (тот же приём, что у статус-среза, Ст. 13). FIFO отвергнут: в этом доме сокет, FIFO и HTTP запрещены. Старый протокол работает как работал: молчащий агент собирается раз в итерацию. Доказательство —make demo-stream: стример жжёт 20k токенов/с, казнь приходит в пределах кванта от пересечения (прогон: < 5 мс, в token-kill tokens = 5000 — ровно линия; приёмка ≤ 300 мс). - Мост на язык systemd (тикет 008): у фермы с systemd уже есть
супервизор — не пере-делывать его (Ст. 5), а разговаривать на его
языке.
tools/agent2unit.shиtools/unit2agent.sh(POSIX sh) — файл порождает файл; ядро не растёт ни на строку. Эквивалентность не обещается — обещается честная таблица потерь; test туда-обратно:make bridge-test(systemd-analyze verify — зелёный).
Таблица соответствия и потерь (мост, тикет 008):
.agent |
.service |
Потеря |
|---|---|---|
name |
имя файла + Description=agryp unit … |
— |
exec |
ExecStart=/bin/sh -c '<exec>' |
— |
restart |
Restart= (always/on-failure/no) |
— |
budget-sec N |
RuntimeMaxSec=N / infinity |
у нас SIGKILL группы попытки, у systemd TERM→KILL сервиса |
cooldown-ms |
RestartSec= (с точностью до мс) |
— |
max-attempts N |
StartLimitBurst=N + StartLimitIntervalSec=0 |
у systemd — в окне интервала, у нас — за жизнь юнита |
token-budget |
— | у systemd нет второй валюты: честно в шапке .service (Ст. 14) |
mem-max-mb |
MemoryMax=N M |
— |
finish |
ExecStopPost=/bin/sh -c '…' |
env: у нас AGRY_EXIT_CODE/AGRY_BY_SIGNAL, у systemd $EXIT_CODE/$EXIT_STATUS |
finish-timeout-ms |
s6 timeout-finish (файл в servicedir) |
у systemd потолок ExecStopPost не настраивается per-unit; дефолт 5000 = канон s6 |
| — | Type=notify / sd_notify READY=1 |
готовность — файл-договор: env AGRY_READY_PATH, готов = создал файл (Виток 11) |
| — | Type=notify-reload (RELOADING=1 → READY=1) |
скобка reload→reload-done в журнале: числа + gen + честный ms оседания; упор — reload-kill по GRACE_MS (Виток 11, тикет 017) |
jail |
— | комнаты имён не переносятся: это не контейнер |
| — | WorkingDirectory= (каталог .agent) |
обратная трансляция cwd не восстанавливает — это не договор |
Одинаковая ферма 100 тихих агентов, одинаковые испытания (протокол —
docs/spec/TZ-8.md, полная таблица с границами честности —
docs/BENCHMARK-8.md, сырые числа — docs/bench/*.jsonl):
| agryp | stdiobus v2.0.3 | |
|---|---|---|
| бинарь | 39 800 Б, libc only | 97 032 Б, +pthread+rt |
| RSS фермы 100 | 1 936 КБ | 3 016 КБ |
| сироты после kill -9 супервизора | 0 из 100 | 100 из 100 |
| рестарт убитого агента | да (101-й spawn) | да (101-й Worker started) |
Граница названа честно (Ст. 11): агент, породивший собственных детей,
оставляет их сиротами при kill -9 супервизора (PDEATHSIG — один
уровень; тот же предел у tini). Дом-ответ — jail = pid: смерть init
pid-комнаты убивает всю комнату — и в витке 9 это принято числами
(M8, make demo-jail-host): контроль без клетки — 20 сирот из 20,
jail = pid в userns-обёртке среды — 0 из 20, ядро не менялось
(Поправка №10). zeptopm в замере не участвовал:
релизов нет, тулчейна нет; их README-числа — в BENCHMARK-8.md с пометкой
«их слова».
Конверты крови/событий клетки подписываются настоящим ed25519
(RFC 8032). Примитив — вендоренная verbatim реализация orlp/ed25519
(zlib, лицензия и provenance — vendor/ed25519/VENDOR-NOTES.md),
собранная из локальных файлов в единственный CLI границы клетки
tools/ed25519tool (ядро src/agryp.c не выросло ни на строку;
ldd → только libc — Ст. 1/2 целы, Поправка №12). Правильность
доказана машиной векторами RFC 8032 §7.1 (TEST 1/2/3 — до последнего
байта) и перекрёстной сверкой с python-cryptography.
- Завод юнита решает подпись:
OverseerGate(tools/signed_envelope.py) проверяет конверт fail-closed — подделка любого байта payload, чужой ключ, повторseq, незнакомый алгоритм, мусор, даже отсутствие примитива —RefusedUnit, отказ завода юнита. Ключи заперты списком доверия при рождении клетки, цепьseqобязана расти (replay закрыт): супервизор, которому можно верить без сети. Легаси-конверты ×4 (sha256-wots) проверяются честно, но в ed25519-дверь по умолчанию не входят. - A2A AgentCard из коробки:
./agrypctl card(≡./agrypctl status --card) строит карту из живого статуса супервизора и свежего.agryp-metrics.promи валидирует её схемойschemas/a2a-agentcard.schema.jsonдо выдачи — карта либо живая и валидная (identity/skills/endpoints,x-agryp.signing: ed25519), либо её нет; endpoint — живойfile://артефакт (Ст. 13). - Приёмка:
make test-p37(42 теста: RFC-векторы, fail-closed-матрица, схема, дрейф копии схемы заперт) иmake demo-envelope(годный ed25519-конверт заводит юнита живьём; подделка/чужой ключ/повтор/легаси — отказ;agrypctl cardиз живого супервизора). Тестовые ключи — одноразовые генераты, помечены TEST ONLY; рабочих ключей в репо нет.
Правила репозитория — в CONSTITUTION.md: 16 статей в
пяти разделах (Тело · Дело · Люди · Процесс · Поправки), и каждая статья
снабжена командой проверки — статья без команды проверки не считается
статьёй. Поправка №1: потолок Ст. 1 поднят 512 → 576 — цена трёх статей
процесса (Ст. 13–15). Поправка №2 («урок учителей», 2026-11-06): Ст. 8
получила лестницу перезапуска с предохранителем, Ст. 9 — страховку от
сирот (PDEATHSIG + subreaper), Ст. 1 — потолок 576 → 608; всё одним
атомом кода, статей и проверок по процедуре Ст. 16, источник —
docs/TEACHERS.md. Аудит «журнал не лжёт»
(Ст. 6) — отдельная цель:
make audit
Конституция — не декорация. Первый же прогон демо под новой конституцией
вскрыл нарушение Ст. 9 («детей не бросаем»): супервизор бил по одному pid
и сиротил потомков агента — в системе жили 20 осиротевших hello_agent.sh
из прошлых прогонов, продолжавшие жечь CPU и писать в транскрипт. Фикс:
попытка стала группой процессов (setpgid в ребёнке и родителе,
kill(-pgid) в бюджет-килле и shutdown), а run_demo.sh в конце сам
считает сирот: ноль.
AGENTS.md— конституция агентов-коллег: инварианты дома, контракты субъектов (ЯДРО/АУДИТОР/РАЗВЕДЧИК/ДОКТОР), протокол витка (≤8 КБ);docs/spec/TZ-9.md— техзадание витка 9 «v1.0: пакет и закрытие границы» с приёмками и отчётом о исполнении;docs/spec/TZ-8.md— техзадание витка 8 «меряем против сверстников» (бенчмарк-транскрипт) с отчётом о исполнении;docs/spec/TZ-7.md— техзадание витка 7 «Изоляция и мосты» с приёмками и отчётом о исполнении;docs/spec/TZ-6.md— техзадание витка 6 «Чистота между смертью и рестартом» с приёмками и отчётом о исполнении;docs/spec/TZ-5.md— техзадание витка 5 «Руки без сокетов» с приёмками и отчётом о исполнении;docs/spec/TZ-4.md— техзадание витка 4 «От ученика к учителю» с приёмками и отчётом о исполнении;docs/spec/ROADMAP.md— дорожная карта витков 4→8;tasks/open/— очередь тикетов для субъектов (open → done).
Один поток, один файл: poll()-цикл с таймаутом до ближайшего дедлайна бюджета;
waitpid(-1, …, WNOHANG) собирает статусы (супервизор — subreaper, чужих
сирот хоронит событием reap-orphan); состояния юнита — STOPPED,
RUNNING, COOLDOWN; restart-политики — always, on-failure, no,
пауза — лестница: своя смерть удваивает cooldown-ms до BACKOFF_CAP_MS,
прожитые SUSTAIN_MS гасят счётчик, CRASH_LIMIT подряд — терминальный
failed; агент — обычный fork+execl("/bin/sh","-c",…), попытка — группа
процессов (setpgid в ребёнке и в родителе): бюджет-килл и shutdown
режут семью целиком через kill(-pgid), каждый ребёнок носит
PR_SET_PDEATHSIG — сирот не оставляем даже при SIGKILL супервизора.
Ст. 13–15: статус семьи — файл <dir>/.agryp-status (tmp + rename каждую
итерацию, unlink на выходе; agryp status читает); метрики — файл
<dir>/.agryp-metrics.prom в формате Prometheus/OpenMetrics (tmp + rename,
unlink на выходе; agryp metrics / agrypctl metrics читают тот же файл,
Виток 10); летопись — файл <dir>/.agryp-journal.ndjson, двойная вместе
с stdout, ротация с пломбой-печатью (sha256 отрезанного хвоста первой
строкой нового журнала; аудитор — tools/journal_audit.sh, Виток 10);
сигнатуры договоров — unit-sign (sha256 байт *.agent + mtime) при boot
и каждом reload (Виток 10); токены — агент пишет
<unit> tokens <n> в файл из env AGRY_TOKEN_REPORT и будит петлю
SIGUSR2 (пид — в env AGRY_SUPER_PID, Виток 7); память — memory.max
в собственной делегированной ветке cgroup v2, агент переезжает до exec;
комнаты имён (Виток 7) — попытка с ключом jail рождается в собственных
pid/mount namespace'ах через clone(CLONE_NEWPID[|CLONE_NEWNS]) — без
смены геометрии попытки; отказ среды — loud jail-skip и работа без
клетки.
Витки и тикеты живут в docs/spec/ROADMAP.md и
tasks/open/ (норма НОУС: очередь воли). Кратко:
- виток 11 — исполнен (2026-11-10): per-unit
finish-timeout-ms(s6timeout-finish) и readiness файл-как-API (AGRY_READY_PATH— sd_notify без сокетов); ядро 1263/1280, Поправки №13–№14 (тикеты 015–016); - виток 10 — исполнен (2026-11-08): метрики .prom, журнал-ротация с пломбой, сигнатуры договоров; tool-catalog (2444 позиции) восстановлен и влит (тикет 012);
- виток 5 — исполнен (2026-11-07): agrypctl — start/stop юнита файлами-договорами (школа s6-svc); ядро 654/656, Поправка №4;
- виток 6 — исполнен (2026-11-07): finish-хук (s6), эскалация
ctl-stop до SIGKILL (systemd TimeoutStopSec), SIGHUP-reload
(daemon-reload); ядро 818/820, Поправки №5–№7; тег
v0.6.0; - виток 7 — исполнен (2026-11-07): namespace-клетка попыток
(
jail = pid|pid,mount, clone вместо fork), токен-будильник (SIGUSR2, envAGRY_SUPER_PID— казнь за миллисекунды, не тик), мост.agent↔ systemd unit (tools/, таблица потерь); ядро 876/880, Поправка №8; тегv0.7.0; - виток 8 — исполнен локально (2026-11-07): бенчмарк против
сверстников (
docs/BENCHMARK-8.md) — ферма 100: бинарь 39 800 Б, RSS 1 936 КБ, сироты 0 из 100 (у stdiobus v2.0.3 — 100 из 100); вскрыт и починен ложныйup_msжурнала (Ст. 6); поймана граница Ст. 9 (внуки переживают kill -9 — дом-ответjail = pid); Поправка №9 (MAX_UNITS 128); ядро 877/880. - виток 9 — исполнен (2026-11-07, после обновления токена
Хозяином): пакет v1.0 по ЦУ 010 — тег
v1.0, релиз с таблицей бенчмарка, Settings по runbook, перевод в public после скана секретов; закрытие границы Ст. 9 по ЦУ 011 (M8): контроль 20/20,jail = pidв userns-обёртке среды — 0/20, ядро не менялось; Поправка №10; ТЗ —docs/spec/TZ-9.md.
MIT. Собрано с нуля в соответствии с философией Linux From Scratch: понять слой — значит написать слой.

