Hand-offs, shared memory, and conflicts
Control transfer via Command and parallelism via Send, blackboard and message-passing shared-memory patterns, and conflict resolution through reducers and an arbiter.
Lesson content is currently available in Russian.
What you will learn
- →Реализовывать hand-off между агентами через Command с передачей структурированного контекста.
- →Строить map-reduce поверх Send и сливать параллельные результаты редьюсерами.
- →Выбирать схему общей памяти и разрешать конфликты агентов арбитражем, а не молчаливой перезаписью.
Hand-off через Command
Hand-off — это передача управления от одного агента другому вместе с нужным контекстом. В LangGraph узел может вернуть не просто обновление состояния, а объект Command, который совмещает обновление и переход: «запиши это в состояние И иди туда».
from typing import Literal
from langgraph.types import Command
def triage(state: State) -> Command[Literal["billing", "tech"]]:
verdict = llm.invoke(TRIAGE_PROMPT + state["question"])
target = "billing" if "оплат" in verdict.content else "tech"
return Command(
goto=target,
update={"handoff_brief": {
"task": verdict.content,
"constraints": state["constraints"],
"done_criteria": "ответ со ссылкой на политику",
}},
)
Для swarm-архитектур hand-off оформляют инструментом: агент вызывает tool transfer_to_billing, а тот возвращает Command(goto=..., graph=Command.PARENT) — переход выполняется в родительском графе, если агенты обёрнуты в подграфы. Аннотация Command[Literal[...]] не косметика: по ней LangGraph понимает возможные переходы и корректно рисует граф.
Главная дисциплина hand-off — содержимое передачи. Плохой hand-off — голое «займись этим» или, наоборот, вся история диалога на 40 000 токенов. Хороший — структурированный бриф: задача, ограничения, критерий готовности, ссылки на артефакты. Cognition формулирует принцип жёстче: вместе с задачей передавайте контекст РЕШЕНИЙ — почему выбран этот путь, — иначе получатель молча примет собственные конфликтующие решения.
Send: параллельный fan-out и map-reduce
Command передаёт эстафету одному получателю. Когда работу надо раздать N воркерам одновременно — причём N известно только в рантайме, — используется Send: условное ребро возвращает список конвертов, и каждый конверт запускает отдельный экземпляр узла со СВОИМ приватным входом.
import operator
from typing import Annotated, TypedDict
from langgraph.types import Send
class State(TypedDict):
sources: list[str]
findings: Annotated[list[str], operator.add]
def fan_out(state: State):
return [Send("researcher", {"source": s}) for s in state["sources"]]
builder.add_conditional_edges("planner", fan_out, ["researcher"])
builder.add_edge("researcher", "reduce")
Это канонический map-reduce: planner раскладывает задачу, экземпляры researcher работают параллельно в изолированных контекстах, а результаты сливаются в общий ключ findings благодаря редьюсеру operator.add — списки конкатенируются, и никакая ветка не затирает чужой вклад. Узел reduce получает уже полный список и синтезирует ответ.
Именно эта схема дала мультиагентной research-системе Anthropic выигрыш на широких запросах: суб-агенты читают источники одновременно, и время исследования на сложных запросах сокращается до 90%. Но заметьте асимметрию: Send сокращает wall-clock, а не стоимость — токены всех веток суммируются. Fan-out на 10 воркеров — это десятикратный расход на этап, поэтому ширину веера ограничивают явно и планировщику запрещают плодить воркеров без бюджета.
Общая память: blackboard против message passing
У мультиагентной системы есть два фундаментальных способа делиться знанием. Message passing — знание едет вместе с передачей управления: hand-off-бриф, результат задачи, сообщение в общей истории. Blackboard («классная доска») — знание лежит в общем хранилище, и каждый агент читает и пишет туда независимо. В LangGraph доска реализуется двумя слоями: общий объект состояния — доска внутри одного прогона, а Store — долговременная память между прогонами и тредами, с иерархическими неймспейсами вида ("project", "vorcl", "decisions") и семантическим поиском по эмбеддингам.
Компромиссы зеркальны. Message passing даёт трассируемость: всегда видно, кто, что и кому передал, — но знание доступно только по цепочке передач, и агент в соседней ветке о нём не узнает. Blackboard даёт всеобщую видимость — любой агент может подсмотреть выводы любого, — но появляются гонки записи, устаревшие чтения и главный риск: доска превращается в свалку, где полезный факт тонет в мусоре.
Рабочая схема из практики: оперативный контекст задачи — через message passing (бриф в hand-off), а на доску кладут только структурированные, подписанные записи трёх сортов — принятые решения с обоснованием («выбрали Postgres, потому что…»), контракты интерфейсов между рабочими областями и статусы задач. Свободный текст на доску не пишут. Каждая запись имеет автора и время: без этого разрешать конфликты в следующей секции будет нечем.
Конфликты: редьюсеры, арбитр, голосование
Конфликты в мультиагентной системе бывают двух уровней, и лечатся они по-разному. Технический конфликт — две параллельные ветки пишут в один ключ состояния. Здесь ответ механический и уже знакомый: редьюсер. operator.add копит вклады, кастомный редьюсер может мёржить словари по ключам или выбирать запись с поздним таймстемпом. LangGraph не даст молча потерять данные — ключ без редьюсера при конкурентной записи роняет прогон с InvalidUpdateError, и это правильное поведение: лучше упасть, чем тихо затереть.
Семантический конфликт глубже: агенты противоречат друг другу по содержанию. Ресёрчер А пишет «конкурент поднял цены», ресёрчер Б — «цены не менялись»; кодер реализовал интерфейс не так, как ожидал автор спеки. Редьюсером это не решается — нужен арбитраж. Три рабочих паттерна по возрастанию цены. Первый — правило приоритета: побеждает источник с большим доверием (свежая дата, первичный источник против пересказа); дёшево, детерминированно, покрывает большинство случаев. Второй — агент-арбитр: отдельный узел получает оба утверждения с их основаниями и выносит вердикт с обоснованием; это ещё один LLM-вызов, зато вердикт логируется и его можно ревьюить. Третий — голосование N независимых оценщиков для критичных решений; дорого (×N вызовов) и полезно только при действительно независимых оценках.
Главное правило: конфликт — это сигнал, а не шум. Система, которая молча перезаписывает противоречие, хуже системы, которая его фиксирует, потому что скрытое противоречие всплывёт у пользователя.
Practice
Map-reduce с конфликтом и арбитром
Соберите граф: planner раздаёт через Send три источника двум-трём researcher-экземплярам, результаты копятся редьюсером operator.add. Подложите в источники противоречащие факты и добавьте узел-арбитр, который находит противоречие и выносит вердикт с обоснованием до финального синтеза.
Deliverable
Код графа, лог прогона с параллельными ветками и вердикт арбитра: какие утверждения конфликтовали, какое принято и почему.
Checkpoint
Спроектируйте hand-off-бриф для передачи задачи от архитектора кодеру. Что в него входит и почему не подходит ни «одна строка», ни «вся история»?
- ✓ Бриф содержит задачу, ограничения, критерий готовности и контекст принятых решений.
- ✓ Объяснено, что одна строка теряет решения, а полная история раздувает контекст и стоимость.
- ✓ Упомянут принцип Cognition: передавать не только вывод, но и почему выбран путь.
Check yourself
Sources