Мост между агентами не должен начинаться с копирования публичных ключей и правки пяти конфигов. Оператор чеканит одноразовый инвайт — машина заходит одной командой и сама делает всё остальное.
# файл флота создастся сам, пустой — это нормально tower-relay --listen :7443 \ --authorized-keys /etc/tower/authorized_keys # чеканим приглашение для машины tower-relay invite --name om3 \ --url wss://relay.example.com/ws
tower join wss://relay.example.com/ws \ --token gsd3gs…
Ключи не копируются, authorized_keys не редактируется, конфиги CLI находятся сами. Одна машина без флота? Тогда и relay не нужен: tower setup — и агенты на ней уже общаются.
Машина попала во флот только потому, что вы выдали ей инвайт — членство и есть согласие. Никаких «одобрить сообщение» на каждый чих. Нужно строже — tower lock, и всё чужое ждёт вашего approve.
tower doctor только смотрит и говорит, что не так, — менять состояние это дело tower setup. Команда, которая чинит втихую, однажды сломает то, чего вы не просили.
Шим поднимает демона сам, если тот не запущен, но при наличии systemd-юнита уступает ему запуск. Занятый сокет — не ошибка, а «уже есть, отхожу»: никаких войн процессов и рестарт-циклов.
В tower нет «повиснуть на сокете и ждать». Ожидание — это договорённость из трёх частей: reply_to связывает вопрос с ответом, durable inbox хранит ответ на диске сколько угодно, а nudge — необязательный будильник для совсем простаивающей сессии. Никто никого не держит.
herdr держит агентов по вкладкам: в каждой — свой CLI в своём git-worktree. tower делает вкладки адресуемыми: диспетчер раздаёт задачи сообщениями, исполнители отчитываются обратно, и всё это — durable-файлы, а не хрупкий скрейпинг чужих терминалов. Одна машина — общение по UDS без сети; разнеси вкладки по om-нодам — тот же код поедет через relay.
Диспетчер не поллит исполнителей в цикле — он заканчивает ход, а отчёты будят его nudge-ем или догоняют piggyback-ом. Рестартни любую вкладку: её inbox и очереди — файлы на диске, задачи не испаряются. tower outbox в любой момент показывает, какая задача у кого «висит». А петля из двух слишком вежливых агентов гасится rate-limit-ом и дедупом повторов.
Рой не требует нового механизма. Fan-out — это N сообщений, каждое со своим durable-файлом в outbox и своим сквозным ack-ом. Воркеры — headless-сессии (claude -p, codex exec) с тем же tower-mcp: хоть во вкладках herdr, хоть под systemd на om-нодах. Упавшая нода — это queued, а не «потерялось»: очередь доезжает сама, когда нода возвращается.
Сегодня рою нужен кто-то, кто заранее запустил воркеров. В v0.2 это исчезает: сообщение объявленному, но не запущенному агенту запускает его. Рой материализуется по требованию — а из этого следует набор жёстких механических решений, каждое из которых проверено тремя независимыми моделями.
# agents.toml на om3 — команду задаёт машина, не сообщение [[unit]] name = "worker-*" command = ["claude", "-p", "$(cat prompts/worker.md)"] cwd = "/srv/wt/{{name}}" max_instances = 8 drain_deadline = "120s" remote = false # ← удалённый спавн только явно
towerd запускает ребёнка detached, пишет pid и жнёт exit-код для наблюдаемости — и никогда не рестартит. Повторный запуск — побочный эффект следующей доставки, а не политика супервизии.
Текст сообщения никогда не подставляется в argv или env: задача уже лежит в durable inbox, воркер её вычерпывает. Нет второго пути доставки — нет ни инъекций, ни секретов в ps.
Воркер запустился, но не вычерпал inbox за drain_deadline — отправителю уходит честный stalled (с exit-кодом, если ребёнок умер). Строго «не забрал сообщение», а не «провалил задачу».
Замок по имени агента коалесцирует одновременную волну в один запуск; реплеи ловит 7-дневный seen-set, проверяемый строго до пути активации. thread при этом не получает никакой lifecycle-семантики.
Сообщение — это текст, который один агент написал другому. Не история разговора, не файлы. Каждый хоп — вот такой конверт, по строке на кадр: его можно грепать голыми руками.
{
"v": 1,
"id": "m_8f3ka92c11d04b7e5c20ff41",
"from": "alice@wsl",
"to": "bob@om3",
"text": "миграция готова: колонка
tenant_id, ребейз на main безопасен",
"reply_to": "m_c41d…",
"ts": "2026-08-08T12:00:00Z",
"ttl_s": 86400
}
Durability живёт в outbox отправителя — по JSON-файлу на сообщение, на машине, которую контролируете вы. Relay не хранит ничего, поэтому его рестарт не теряет ни одного сообщения. tower outbox показывает ровно одно из шести состояний:
На диске, пересылается с backoff-ом, пока не придёт сквозной ack.
Принимающий демон положил в inbox адресата и подтвердил.
Политика приёмника отложила для оператора. Станет delivered или refused.
Политика сказала «нет» или оператор отклонил. Отправителю сообщат.
На целевой машине нет агента с таким именем.
TTL кончился раньше, чем случилась передача.
Воркер запустился по сообщению, но не вычерпал inbox за drain_deadline.
queued → delivered | held | refused | no_agent | expired held → delivered | refused (решает оператор) stalled — только про активацию, не про исход задачи
Статические Go-бинарники без runtime-зависимостей. Шим — единственное, что видит CLI; демон — единственный владелец состояния; relay — единственный, кто ходит по сети.
stdio MCP-сервер, который монтирует любой агентский CLI. Три инструмента: send_message, check_inbox, list_agents.
Каждый send заодно возвращает непрочитанное — почти push-латентность без push-механики.
Владеет реестром агентов, durable-outbox-ом, inbox-ами, очередью held, политикой и единственным соединением с relay.
Доставка внутри машины идёт по Unix-сокету и не касается сети вообще.
Аутентифицирует машины Ed25519-челленджем по authorized_keys, маршрутизирует конверты и ack-и между демонами онлайн, раздаёт ростер.
Намеренно stateless: ни диска, ни очередей, нечего бэкапить.
agents · send · inbox · outbox · held · approve · deny · doctor
Сторона одобрения default-hold живёт здесь: tower approve m_8f… --persist доставит сообщение и запишет accept-правило для этого отправителя.
Ключ на машину, строка на машину в relay, строка на отзыв — и ни одну из этих строк вы не пишете руками. Внутри флота текст сообщений остаётся читаемым на каждом хопе: это и есть отладочная поверхность, так задумано.
Ключ рождается при первом запуске, а в реестр флота попадает сам — через одноразовый инвайт. Файл выглядит так, но пишет его tower join, не вы:
# authorized_keys — реестр флота, заполняется сам MdElefiQO62…h+w= wsl lOlGyyP73aJ…JZo= om3
Нет правила — работает доверие флоту: машина зашла по вашему инвайту, значит членство и есть согласие. tower lock переключает на «держать и спрашивать», а правила дают промежуточное:
# policy.toml — нужен, только если хочется строже [[rule]] machine = "om*" # glob по верифицированной машине agent = "spam-*" action = "refuse" # accept|hold|refuse