Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Агентские команды

Ева-тимлид поручает часть работы подагентам на выбранных моделях и переписывается с ними в обе стороны. Это не оффлоад: у code_task и навыка с моделью-исполнителем один поход к провайдеру, ноль инструментов и ноль истории — подчинённый там не может ничего вызвать, переспросить и не живёт дольше вызова.

Подагент — это чат

id агента равен id его чата, и оттуда бесплатно берётся всё: своя модель, история, компакция, набор инструментов и учёт расхода (spend ключуется по чату, так что деньги команды считаются сами).

Чат подагента persistent и лежит в папке agents. Из общих списков она скрыта — это рабочая механика, а не переписка; спрошенная явно, папка отдаётся, ею и живут панели клиентов.

Дольше чата тимлида команда не живёт. Временный чат тимлида, домолчавший до chats.ephemeral_ttl, жнец уносит вместе с подагентами: их чатами, почтой и незаконченными турами. Подагент, которому некому докладывать, — призрак: чата, куда шли письма, нет, и никто их уже не прочтёт.

Удаление чата тимлида (DELETE /v1/chats/{id}) снимает его живых подагентов: чат ушёл из списков, доклады летели бы в корзину, а работа подагента стоит денег. Удаление мягкое, и restore вернёт переписку — но не воскресит команду: снятым ставится stopped, письмо о снятии уходит наверх, а работу нужно поручать заново.

Вниз и наверх

Тимлид (всё M):

  • agent_spawnname, task, необязательные context, model, toolset и report_schema. Задача должна быть завершаемой без тимлида: своего чата подагент не видит, и всё, чего ему не выяснить самому, кладётся в context — отдельной секцией первого сообщения, чтобы задача осталась задачей. report_schema (JSON Schema) делает отчёт типизированным: agent_done обязан принести один объект этой формы, и тимлид разбирает поля, а не прозу; схема едет секцией [report format] того же первого сообщения. Имена набора и модели проверяются на входе (см. «Предохранители»), рабочий каталог чата тимлида вместе с машиной достаётся чату подагента: относительные пути в задаче ведут туда же, куда у тимлида. Тур подагента идёт в фоне, тимлид не блокируется;
  • agent_send — дослать поправку или ответ на вопрос. Работающий читает письмо на ближайшем своём шаге и работает дальше, спящий им будится, законченному не пишется вовсе (отказ: заводить ему тур больше некому);
  • agent_wait — подождать первого доклада (по умолчанию 120 с, максимум 900). Нужен, только когда до доклада делать нечего;
  • agent_inbox — забрать сказанное, не засыпая;
  • agent_list — кто в команде, статус, модель, набор и расход (с долей, ушедшей в мысли). Выхлоп — JSON-объект по объявленной схеме (output_schema инструмента): тимлид ветвится по status и складывает cost_usd, а не выискивает их в строке;
  • agent_stop — снять подчинённого: статус stopped, слот команды свободен, письмо о снятии уходит тимлиду.

Подагент (только внутри своего тура, гейт agent_only):

  • agent_say — вопрос, промежуточная находка, предупреждение «задача в таком виде не выйдет». Работа продолжается;
  • agent_done — закончить с результатом.

Тимлидские шесть заперты обратным гейтом lead_only: в туре подагента их нет ни в списке, ни в исполнении. Команды у него не бывает по построению — почта и списки были бы пусты всегда, а agent_spawn открыл бы второй этаж дерева.

Пятеро из них — все, кроме agent_spawn, — заперты ещё и наличием живой команды (гейт needs_agents). Follow-up к работе, которой никто не поручал, — это четыре килобайта схем в каждом туре про кофе; без агента их нет ни в списке, ни в реестре скрытого, а вызов вслепую отвечает «spawn one with agent_spawn first». Флаг считается одним запросом на входе в тур — и поднимается прямо посреди него: завести подагента и остаться без почты до следующего хода было бы издевательством, поэтому agent_spawn открывает семейство сам, с ближайшего шага (как tool_load открывает доложенное).

Вниз письма ходят двумя путями, по состоянию читателя. Спящему письмо уезжает текстом промпта нового тура и помечается прочитанным сразу. Работающему оно приходит отдельным user-сообщением [team] Your lead says: в конце ближайшей итерации — сразу за выхлопом инструментов. Не строкой в [state]: тот летуч и тур не переживает, а поправка тимлида обязана остаться в истории подагента. Письмо, посланное на самом последнем шаге тура, доставлять внутрь уже некуда — его подхватывает следующий тур, заводимый тут же: будить подагента иначе было бы некому, тимлид видел его работающим. Снятого письмо не воскрешает: stopped ставит человек, и тур после этого не заводится вовсе, а письмо остаётся лежать непрочитанным. done и failed этой оговорки не знают — туда подагент приходит сам, и письмо тимлида вправе его продолжить.

Синхронно доклад забирает agent_wait, асинхронно непрочитанное приезжает тимлиду блоком [subagents] в [state] его следующего тура — тем же летучим механизмом, что [now] и память. Письмо забирается один раз и в каждый следующий ход не повторяется.

За один забор наверх уезжает до 8000 символов почты. Письма целые — режется пачка, а не доклад: непоместившееся остаётся непрочитанным, ждёт следующего захода и считается вслух («— N more waiting»). Письмо длиннее всего бюджета едет целиком, иначе оно застряло бы в почте навсегда.

Ожидание сделано той же схемой, что ask_questions и client_ops: oneshot в общей карте, timeout на стороне тула и снятие ожидания при отмене тура.

Промпт подагента

Тур подагента — мастерский (своего спикера у него нет), и промпт он получает Евин целиком: персона не меняется, меняется адресат. Роль ему задают два места, оба вне летучего хвоста:

  • блок [team] — самым концом стабильной части системного промпта: кто он, что его текст не читает никто, что работа кончается agent_done, а вопрос идёт через agent_say. Одной строкой в первом сообщении это не держалось: после первой же итерации она уезжала вверх истории, и побеждала персона — модель отвечала «Господину» текстом в пустоту;
  • строка в якоре тура — том самом, что переезжает на свежий tool_result каждой итерации. Самая сильная позиция в промпте, и стоит она копейки.

Неприменимое из агентского тура вычищено: блокнот (инструментов у него нет), правила о чатах проблем (заводить их он не может), правила о картинках (показывать некому) и подсказка «каталог не задан» (каталог достаётся от тимлида). Память остаётся — memory_search в наборе по умолчанию есть.

Файлы и шелл Господина

Клиентские инструменты (read_file, write_file, write_diff, apply_patch, find_pattern, local_shell) исполняет подключённый клиент, а своего клиента у подагента нет — он работает через тимлида. Флаг client_ops наследуется от тура, в котором подагента породили, а его client_op уезжает в живой стрим чата тимлида с именем просящего: клиент показывает, кто просит, и спрашивает разрешение там же, где на свои операции. Ответ находит операцию по op_id, поэтому ручка чата в маршрутизации не участвует.

Отсюда режим работы: стрим у чата тимлида есть, только пока идёт его ход. Подагент, трогающий файлы, живёт в паре с agent_wait — тимлид спавнит его и ждёт. Если хода нет, операция не висит до таймаута, а сразу отказывает с объяснением, и подагента инструктируют в таком случае доложиться через agent_say и ждать пробуждения.

Когда подагент окупается

Он несёт собственный контекст: тысячи входных токенов на каждую итерацию, и первый его ход всегда греет кэш промпта с нуля. Окупается это тремя вещами — изоляцией контекста (перелопачивает мусор, наверх едут шесть строк), параллельностью веера и специализированной моделью. И не окупается на «прочитай файл и скажи коротко»: прямой read_file в туре тимлида дешевле и надёжнее. Об этом сказано в самом описании agent_spawn — учить, когда инструмент брать, там важнее, чем перечислять поля.

Выбор исполнителя

У модели в llm.models есть description — для чего она хороша. Из описанных собирается блок ## Models в системном промпте, и только в тех турах, где agent_spawn вообще уместен: в обычном чате это мёртвый вес в каждом запросе. Модель не задана — подагент идёт моделью чата-родителя.

Предохранители

  • Глубина. Подагент не спавнит подагентов: дерево ушло бы в глубину и в деньги. Держится гейтом lead_only, не уговорами в промпте.
  • Ширина. chats.max_agents (по умолчанию 4) — сколько подагентов чат держит живыми разом. Без потолка одна неудачная формулировка задачи разворачивает веер на весь баланс.
  • Имена. Узкий набор по умолчанию держится на имени: названный набор открывает подагенту весь реестр, поэтому несуществующее имя — отказ с перечислением известных, а не молчаливая выдача шелла, памяти и крона по опечатке. Так же проверяется модель — по списку llm.models; список пуст — проверять нечем, имя уходит как есть.
  • Молчание. Пропасть незаметно подагент не может. Закончил тур, не позвав agent_done, — письмом наверх уходит последнее слово тура (задачу вида «просто ответь» модель заканчивает текстом, и в чате подагента он бы и остался); при заданной report_schema из этого текста сперва выкапывается JSON-объект — запасной разбор, чтобы тимлид получил разбираемое, а не пересказ; не нашлось — уезжает проза как есть; не сказал вовсе — так и сказано; упал — письмом уходит ошибка. Статус меняется в любом случае. Снятый через agent_stop или API шлёт тимлиду письмо об этом — тем же путём, что доклад, поэтому ждущий в agent_wait просыпается сразу, а не досиживает таймаут.

Статусы: running и idle — живой (занимает слот max_agents, читает письма, его есть смысл ждать); done — закончил agent_done; failed — тур упал; stopped — сняли. Снятый не выглядит упавшим ни в agent_list, ни в панелях клиентов. Базы прежних версий доращиваются на старте: таблица подагентов пересобирается под новый статус, переписка при этом цела.

Видно ли, что команда работает

Блок team в /v1/stats (и в usage_stats, которым Ева смотрит на себя сама) за каждое окно: сколько подагентов завели, кто чем кончил (по когорте этого окна) и как заканчивались их туры — reported (позвали agent_done) против silent. Доля молчаливых и есть метрика здоровья: доклад, оставшийся текстом в чате подагента, тимлид получает уже аварийным путём. Что фича мертва, до этого выяснялось запросом к SQLite.

Клиентам

  • GET /v1/agents?parent=<chat_id> — подагенты с моделью, набором, статусом и расходом (на каждого и итог по команде), включая reasoning_tokens — сколько из выхода ушло в мысли — и reasoning_cost, который остаётся null без price_out у модели;
  • POST /v1/agents/{id}/stop — снять;
  • живой тур подагента — обычный GET /v1/chats/{id}/live, история — GET /v1/chats/{id}/messages.

Отдельных типов SSE-событий команда не вводит: события подагента идут в его собственный стрим. В стриме тимлида они перемешались бы с его собственными, а чужой done оборвал бы доставку ответа.

В клиентах это выглядит так: панель со списком подагентов, а нажатие открывает чат подагента — он и есть чат, поэтому видна вся его работа, живая в том числе, и вторая лента поверх текущей не нужна. В eva-tui — f7 или /agents, в Android — значок в шапке чата.