Рабочий цикл агента в my_architect: start_task → next → complete как единый контур
Когда мы открывали MCP-интерфейс для агентов, соблазн был очевидный: дать модели CRUD над иерархией — создавай ноды, меняй статусы, пиши доки — и пусть сама разбирается. Мы так не сделали. Вместо россыпи низкоуровневых команд мы собрали узкий рабочий контур из трёх глаголов: start_task, get_next_task, complete_task. Ниже — почему именно контур, а не набор ручек, и какие компромиссы за этим стоят.
Проблема: агент без рабочего цикла деградирует
Агент, у которого есть только "создай ноду" и "поменяй статус", ведёт себя предсказуемо плохо. Он берёт задачу, забывает её отметить, оставляет статус draft на уже сделанном, плодит ноды в обход иерархии и теряет контекст между шагами. Причём каждая отдельная операция при этом возвращает честный 200 OK — система не врёт, но и не помогает. Проект как источник правды медленно расходится с реальностью.
Мы хотели, чтобы у агента был не список кнопок, а замкнутый цикл: спросил "что дальше" — взял в работу — сделал — закрыл — снова спросил "что дальше". Цикл, в котором каждый переход имеет смысл и каждый шаг оставляет след в источнике правды.
Как устроен контур
get_next_task отвечает на единственный вопрос агента в начале хода: за что браться. Он не отдаёт весь бэклог на откуп модели — он возвращает следующую разумную задачу с учётом приоритетов (P1→P2→P3) и того, что не заблокировано. Это важный сдвиг ответственности: приоритизация — работа сервера, а не догадка агента по свежести или порядку в файле.
start_task переводит выбранную ноду в работу. Это не косметика статуса — это точка, где агент официально "берёт" единицу работы, и где мы фиксируем, что она в процессе. С этого момента нода перестаёт быть кандидатом для get_next_task у параллельных агентов.
complete_task закрывает задачу — и здесь начинается самое интересное. Закрытие одной ноды не остаётся локальным событием.
Статусный каскад: почему complete_task — не просто флаг
Иерархия в my_architect — это единый источник правды (RFC-005), и статусы в ней связаны. Когда дочерняя нода закрывается, родитель автоматически продвигается по статусу — это правило R7, status cascade: child done → parent auto-progresses. Параллельно работает R8, progress rollup: процент готовности пузырьком поднимается вверх по дереву. То есть агент закрыл историю — а эпик, инициатива и корень проекта узнали об этом сами, без отдельной ручной синхронизации.
Это принципиальное решение. Мы могли бы оставить обновление родителей на агента ("не забудь после закрытия истории обновить эпик"). На практике такие "не забудь" не работают — модель забывает, и дерево расходится. Поэтому каскад живёт на сервере: он срабатывает как следствие complete_task, а не как отдельная задача агента. Дешевле сделать переход автоматическим, чем полагаться на дисциплину.
Блокеры как часть цикла, а не как исключение
Контур был бы наивным, если бы позволял закрывать что угодно в любом порядке. Поэтому в цикл встроен гейт блокеров. У требований есть тип OQ (Open Question — открытый вопрос), а у нод — рёбра dependsOn. Единый помощник getBlockers трактует и неразрешённые открытые вопросы, и незавершённые зависимости как блокеры. Сервер гейтит переходы статуса на неразрешённых блокерах — "done" жёстко недоступно на заблокированной ноде.
Для агента это значит, что get_next_task не подсунет ему то, что физически нельзя начать, а complete_task не даст закрыть то, под чем ещё висит нерешённый вопрос. block_task, в свою очередь, — легальный выход из хода: если по ходу работы всплыла новая зависимость или вопрос, агент честно блокирует задачу вместо того, чтобы имитировать прогресс. Честный blocked нам ценнее фальшивого done.
Доки внутри цикла: living source of truth
Рабочий цикл не заканчивается сменой статуса. Пока агент держит задачу в работе, он ведёт доку на ноде как живой источник правды — не постфактум-отчёт, а рабочий документ, который обновляется по ходу дела. Чтобы это не превращалось в пересылку целых простыней (и в опечатки при перетранскрибации), update_doc умеет точечные правки: find+replace одного уникального вхождения, append блока или замену тела конкретной секции по заголовку. Одна строка правки — это одна строка правки, а не переотправка всего документа.
Замыкает контекст get_project_context: в начале хода агент подтягивает структуру проекта, причём в summary-режиме — лёгкие структурные ноды без описаний, чтобы большой проект влезал в бюджет токенов, а за деталями можно уйти в get_node. Так каждый ход агента начинается с актуальной картины, а не с обрывков памяти.
Компромиссы, на которые мы пошли
Первый — узость интерфейса. Три глагола покрывают не всё; иногда агенту нужны и низкоуровневые операции. Мы их оставили (bulk_update_nodes, прямые правки), но сознательно вынесли за рамки основного цикла — в том числе bypass каскадного гейта в bulk_update_nodes сделан осознанно и задокументирован как область без гарантий. Магистральный путь — контур; сырые ручки — для исключений.
Второй — сериализация. Как только несколько операций правят один проект, всплывает гонка read-modify-write по общим YAML-файлам. Мы закрыли это per-project локом (реентерабельная async-цепочка): операции над одним проектом сериализуются, над разными — идут параллельно. Цена — потеря части параллелизма внутри проекта; выгода — цикл не теряет записи под собой.
Третий — доверие серверу. Каскад, гейт блокеров и приоритизация — это логика, которую мы забрали у агента. Это делает поведение предсказуемым, но требует, чтобы правила на сервере были правильными: недостижимая ветка в роутинге или слишком строгий гейт сразу бьют по всем агентам разом.
Что получилось
start_task → next → complete читается как один контур, а не три несвязанные команды. Агент спрашивает "что дальше", берёт, делает, ведёт доку, закрывает — и иерархия пересчитывает статусы и проценты сама. Полный разбор с тем, как статусы каскадируются по дереву проекта и как поток исполнения держит документацию актуальной, мы вынесли в отдельный материал про то, как агент проходит рабочий цикл шаг за шагом. Главный вывод для нас простой: агенту нужен не богатый API, а хороший рабочий цикл. Богатство — источник расхождений; цикл — источник правды.