Агентские команды
Ева-тимлид поручает часть работы подагентам на выбранных моделях и
переписывается с ними в обе стороны. Это не оффлоад: у code_task и навыка
с моделью-исполнителем один поход к провайдеру, ноль инструментов и ноль
истории — подчинённый там не может ничего вызвать, переспросить и не живёт
дольше вызова.
Подагент — это чат
id агента равен id его чата, и оттуда бесплатно берётся всё: своя модель,
история, компакция, набор инструментов и учёт расхода
(spend ключуется по чату, так что деньги команды считаются сами).
Чат подагента persistent и лежит в папке agents. Из общих списков она
скрыта — это рабочая механика, а не переписка; спрошенная явно, папка
отдаётся, ею и живут панели клиентов.
Дольше чата тимлида команда не живёт. Временный чат тимлида, домолчавший до
chats.ephemeral_ttl, жнец уносит вместе с подагентами: их чатами, почтой и
незаконченными турами. Подагент, которому некому докладывать, — призрак:
чата, куда шли письма, нет, и никто их уже не прочтёт.
Удаление чата тимлида (DELETE /v1/chats/{id}) снимает его живых
подагентов: чат ушёл из списков, доклады летели бы в корзину, а работа
подагента стоит денег. Удаление мягкое, и restore вернёт переписку — но не
воскресит команду: снятым ставится stopped, письмо о снятии уходит наверх,
а работу нужно поручать заново.
Вниз и наверх
Тимлид (всё M):
agent_spawn—name,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 — значок в шапке чата.