Багатыя рукі, адна ручка: сэм правіл для керавання агентамі ШІ.
Семь правіл для керавання агентамі AI: калі просты працэўны лянцужок ўспэльваецца, чаму керавальнік не мае інструментаў, вярненне набранага падагента і запісаныя умовы зупінкі.
Крыніцы: Many Hands, One Pen: Orchestrating Agents Without Losing the Plot
Уявіце Вялікія Амерыканскія війскі на морзе ў 1942 годзе, якія сталкваліся з проблемамі коордынаціі, якія добра показваюць сучасныя прыемы програмнага забезпечэння. Вайсковыя кораблі былі оснашчаны радарамі, сонарамі, апаратурамі для перэхоплення радыёсувязі і людзкімі сторожамі, і кожны звясткі прыходзіў на мостак адночасна і ў хаотычнай парады. Капітан, які намагаўся весты бой, быў пагрążаны пад непрактыкованымі сігналамі.
Адпаведзь Вялікіх Амерыканскіх війск на морзе была кімната без вікон, якая называлася Центрам бойовой інформацыі. Кожны звясткі з датчыкаў пазначаліся на аднай спольнай схеме, выкарыстоўваючы аднаковы фармат. Аператары, якія чыталі показанні прыладоў, не керавалі нічым; іх задачай было падтрымліваць точнасць схемы, тады калявоўскі офіцер сам выбіраў наступны ход корабля. Інформацыя надходзіла з усіх бакоў, а рашэнні вырабляліся паступова, а стандартны фармат звястака дазволяў пораўняваць ўсе даны між сабою.
Сістэмы з мнагае агентамі продовжваюць занова адкрываць тую камеру. Этап, калі «дадзіць ўтолькі агентаў, сколькі трэба» быў адказам на усё, вяршыўся, і 2026 год у большай частцы быў пасвячаны вычысцэнню таго, сколькі коштаў такі адказ. Лепшы падход — гэты, які выбрала Вяйскае мора: адбіраць даныя паралельна, адчытваваць іх у строгім фармаце, прымыкаць рашэнні ў аднам месцы і чытка апісваць, калі задача завершана.
Корыстная концэпцыя: большасць задач з мнагае агентамі на самай справе не ўскладненыя агентамі. Гэта задачы кантэксту і контролю прайма, пераданыя праз агентоў. Зазвычай эксперыменты заканчываюцца неудачаю таму, што два агенты прымыкаюць несумісныя рашэнні, якія ніхто з іх не мог убачыць, таму, што інформацыя трапляецца без значэння пасля пераходу праз межу, або таму, што ніхто не запісаў, як выглядае завершанае рашэнне.
Пасуюць семь практык, кожная з якіх створана на адыях у сістэмах, які ужо выкорыстоўваліся. Разам яны дапамагаюць вам адначасова выявіць, калі звычны рабочы працэс лепшы за агента, калі оркестратору трэба плаціць за своі токены, як раздзеліць працу па межах кантэксту, чаму оркестратору не трэба втручацца ў інструменты, як стварыць для падагентаў контракт на адпаведнае падтрымкі, які можна пераканаць, і як намеравана завершыць выканання, а не за бажаннем самой мадэлі.
Вярніцеся да звычных рабочых працэсаў; автаномію трэба здобыць
Што йдзе не так. Возьмімо працэс вярнення грошаў з чатырма можлівымі наследкамі, кожны з якіх ужо практыкуецца за пісаным правілам. Команда загортае ўсё гэта ў автонамны цыкл, шасць інструментаў і радок целей. Раптам тыкет з вторка прайшоў іншым шляхам, чым тыкет з панедзелкі, і кожны запуск коштае ад дзесяці до пяцідзесят разоў больш, чым стары код. Точнасць застаецца незменнай, таму што жадная частка працэсу не ўскладнена настолькі, каб была патрэбна інтэлігэнція.
Рашэнне. Спачатку накрэсліце схему прайску. Усё, што можна запланаваць загадзя, належыць да звычайнага контрольнага прайску, таму просты код можа обрабатваць кожную ветку, якую можна пазначыць. Вызывайце мадэль толькі тады, калі вхідныя даны справды падлягаюць тлумачэнню. Агент павінен атрымаць права на кераванне веткай толькі тады, калі будзе падтверджжана, што гэтую ветку немагчыма запісаць.
REFUND_LIMIT = 50.00
def llm_step(prompt: str) -> str:
"""Called only where the written rules run out."""
return model.complete(prompt).strip().lower()
def handle_refund(ticket):
if ticket.days_since_purchase > 30:
return deny(ticket, reason="outside window")
if ticket.amount <= REFUND_LIMIT:
return approve(ticket) # a rule, not a judgement call
intent = llm_step(
f"Classify as fraud, defect, or remorse:\n{ticket.body}"
)
if intent == "fraud":
return escalate(ticket, queue="risk")
if intent == "defect":
return approve(ticket)
return route_to_human(ticket)
Проблема. Можна заплутацца ў множыце вялікіх умов, які таяць правіла адгуків, якія на самай працэ немагчыма застосавіць. Калі вы пішаце пяту умову для вольнага тэксту, саме для такой неяснасці і існуе гэты мадэль.
Адмовіцеся ад оркестратора, калі прычына бягучых проблем вядома
Што йдзе не так. Чатыроэтапны працэс стварэння атласа пакуецца ў цыкл оркестратора, які мае максімум падзеяў у панздзесят. Кожная падзея выдзеляе час на планаванне, каб вырашыць, што робіць далей, і кожны такі крок прыводзіць да таго ж адказу, які вы ужо вярнуліся: запрашаць, выдліць, падсумаваць, сформатаваць. Цэна — панздзесят крокаў аналізу, кожны з якіх займае калькі тысяч токенав, каб зноў знайсці спіс, які можна было бы запісаць за пяць рэчэнняў.
Рашэнні. Абавяце, прытамульна правіла павінны быць выкарыстоўваныя пры напісанні кода. Калі ўсёі зьмест канкрэтных задач вядомы ўжо да пачатку выканання, яго можна выконваць за дапамогою простаг цыклу з фіксаваным распадам, а токены захаваць для іншых цэлей. Оркестрацыя, адчыненая для модэляў, падходзіць там, дзе сама структура задач невядома або павінна развівацца на аднойчынку з тым, што было знайдзена на пярэдніх этапах.
# Before: up to 15 planning calls to rediscover a fixed list
def run_orchestrated(doc):
state = {"doc": doc}
for _ in range(15):
decision = orchestrator.plan(state) # 1 LLM call per pass
if decision.action == "finalize":
break
state = WORKERS[decision.worker](state)
return state
# After: 0 planning calls, same four workers, same result
PIPELINE = [fetch, extract, summarize, format_report]
def run_static(doc):
state = {"doc": doc}
for step in PIPELINE: # 0 LLM calls here
state = step(state)
return state
Проблема. У гэтага савета ёсць іншая сторона. Канвей, які ў кожны месяц стварае новы спецыяльны кейс, або там, дзе выход другага крока практычна вялічы, чы будзе выкананы трэці крок, ўсамдэлі займаецца планаваннем. Адключэнне планавальніка там проста перэносіць гэтая практыку ў які-небудзь файл налаштавання, які ніхто не будзе старацца падтрымваць.
Раздзеляйце агентаў па кантэксце, а не па назве паводзка
Што йдзе не так. Версія мульты-агентнага падходу у вачыні організацыйнай схемы выглядае апрантна: планавальнік, рэалізавальнік, тэстэр, рэвізор — працуюць над адной функцыяй па спрабах. У практыцы кожная перадача задачы несе з сабою прыпускі, якія ніхто не запісаў. Тэстэр пераканальваецца ў працэсе, які рэалізавальнік ніколі не меў на увазе, а рэвізор заперачвае выбору, які планавальнік зробіў намерна. Вы платзіце чатыры разы за такія недолікі, а аднаму агенту, які б выканаў усе чатыры заведамства, было б лепяй.
Рашэнні. Разрэзайце па лініях кантэксту. Перш чым дадзіць другога агента, задайце аднае пытанне дыяграме: чы можа гэты падзадача быць завершаным у вікні кантэксту, якое не мае нічага спакойнага з вікнамі суседніх падзадач? Калі адказ — няма, то обе падзадачы належаюць аднаму агенту, і самэ гэта ўсёлкі прычына, чаму агент, які стварае функцыю, таксама павінен напісаць для яе тэсты.
from dataclasses import dataclass
@dataclass
class Subtask:
name: str
needs: set[str] # facts this subtask must read
produces: set[str] # facts this subtask decides
def should_split(a: Subtask, b: Subtask) -> bool:
"""Split only when neither side needs what the other decides."""
shared = (a.needs & b.produces) | (b.needs & a.produces)
return not shared
implement = Subtask("implement", {"spec"}, {"api_shape", "error_semantics"})
test = Subtask("test", {"spec", "api_shape", "error_semantics"}, {"cases"})
assert not should_split(implement, test) # one agent writes both
Проблема. Праўда, абсалютна ізольаваныя падзаданні ўтыклівей, чым можа здавацца з схемы організаціі, таму тэст часта пісае «адзін агент», і гэта правільная адпаведзь, а не брак выдумкі. Раздзелэнне падзаданняў ёсць карыстным для працы з шырокім спектрам задач, напрыклад, калі пяць агентаў чытаюць пяць незв’язаных джэрэл, таму ўсё, калі аднаго з яных вырашаюць змяніць, не паўтараецца для іншага.
Нехай оркестратор выдае рашэнне і ніколі не вызывае інструмент
Што йдзе не так. Оркестратор атрымвае інструмент для пошуку ў экстрэнных сітуацыях. Шырока ён перастае распадзеляць працу і начынае сам адпавядаць на падзаданні. Неапрануты выхід інструмента накапліваецца ў яго вікні, таму яго планаванне пагіршваецца самэ тады, калі трэба планаваць якомога больш, адколі планаванне мае найбольша значыць. Таксама пагіршваецца візуабільнасць, таму ўскладнена адстаўка, калі неясна, калькі агент стварыў калькі нараджэння.
Рашэнні. Ўсі планы не павінны таксама быць выкананыя, таму неякога засобу для ўпораднавання не трэба. Ён выдае толькі адна компактная, заданая рашэнні: перадаць задачу, перапланаваць яе або завершыць, а таксама інформацыю пра працоўніка, які яе выканае, і тэкст задачы. Абаковы цыкл чытае гэтае рашэнні, запускае названага працоўніка і зберагае рэзультат у спільным стане, які будзе гатовы для наступнага рашэнні.
from typing import Literal
from pydantic import BaseModel
class OrchestratorDecision(BaseModel):
next_action: Literal["delegate", "replan", "finalize"]
target_worker: str | None = None
task_description: str | None = None
reasoning: str
def orchestrator_step(state):
d = decide(state) # model has zero tools attached
if d.next_action == "finalize":
return finalize(state, d.reasoning)
if d.next_action == "replan":
return state.reset_plan(d.reasoning)
worker = WORKERS[d.target_worker] # workers own every tool
result = worker.run(d.task_description)
return state.record(d.target_worker, result)
Проблема. Без спецыяльных засобаў аранжавальнік бачыць толькі тое, што падае ад працавікаў. Працавік, які вяртае лёгкія падсумкі, заставляе аранжавальніка планаваць без інфармацыі. Неякшае рашэнне — аддаць можласць толькі чытання спільнага стану замест жывага доступу да засобаў, а таксама забезпечыць, каб адпаведныя даны, якія падаюць працавікамі, былі достатна структураваныя для планавання.
Змусіць кожнага падагента дапэўніць заданы фармат адпаведных даных
Што йдзе не так. Узяўмо прыклад доследнага падагента, які вяртае 2 500-словы дзеньнік усіх сваіх спроб, укладаючы ў яго і невялікія невыкарыстаныя варыянты. Якшча такіх працавікаў семеро, то прыблізна 20 000 токенав падае ў вікно главнага агента прычымо да пачатку якога-лібо сінтэзу. Дэлегаванне мяўа за мету захаваць гэта вікно ад занадто велікага наплыву інфармацыі. У рэальнасці ж адпрацоўка даных адбываецца два разы, а главны агент выконвае інструкцыі гэтак жа паследовна, калі гэта ўсё больш неабходна.
Рашэнні. Уважайце вярненне як межу API, дзе кожны падагент мае схему адпаведзя. Яго заканчоўчыны ў фармаце JSON, які мае іменаваныя поля: нараджэнні, джэрэгі, незаданыя запытанні і статус завершэння. Пераканаўцеся ў яго прыбыці, а ўсё, што мае некорэктную структуру або занадта вялікі розмер, адправіце назад для пракаты зноў, каб гэта ніколі не дасягло верхньага агента.
from pydantic import BaseModel, Field, ValidationError
class SubagentResult(BaseModel):
findings: list[str] = Field(max_length=5) # hard cap, one sentence each
sources: list[str]
open_questions: list[str] = []
completion_status: str # complete | partial | blocked
def parse_or_retry(worker, task, attempts=2):
for _ in range(attempts):
raw = worker.run(task, response_format=SubagentResult)
try:
return SubagentResult.model_validate_json(raw)
except ValidationError as err:
task = f"{task}\n\nRejected: {err}\nReturn only JSON in the schema."
raise RuntimeError(f"{worker.name} returned no valid result")
Проблема. Шыматлікі задаюць толькі мінімум, больш нічога. Рабоцы все равно можа запісаць абзац з 400 слоў у адну строчку з рэзультатамі і выдаты ідэнтычны „надмерны“ тэкст пад масквай JSON. Неабяжна обмежыць як колькасць элементаў, так і дужыну кожнага поля, а таксама пры адхіленні паказаць назву поля, якое пераполнілася, каб практыка перапрытку знала, што пасправляць.
Паралелізаваць чытанне і спрамоўваць запісы праз аднаго агента
Што выходзіць не так. Два агента працуюць над адной функцыяй адночасова і кожны адзін адмаўляе пра успех. Адны выкрэсляе фон у стылі Super Mario, а другі — спрайт Flappy Bird, і оба праходзяць тэсты, напісаныя для ўсабітнага відобразжэння дызайну кожнага з іх. З’еднанне не можа рашыць выборы, пра існаванне якіх ні аднаго з агента не было ведома, таму адны рэзультат, або оба, адхіляюцца.
Рашэнні. Чытанне можа выконвацца паралельна; напісанне — ня можа. Обмежыце кожнага працоўніка спісам аднальных інструментаў, што дазволяе фазу збору дадзеных расшырваць настолькі, насколькі трэба, і ніколі не давайце працоўніку жадных інструментаў для запісу. Пасля чаго рэзультат ствараецца ў аднам безусловным вызове, які прыме якія-небудзь рэзультаты кожнага працоўніка разам.
import asyncio
READ_TOOLS = ["search_repo", "read_file", "fetch_docs"]
async def gather_context(subtasks):
workers = [Agent(name=t.name, tools=READ_TOOLS) for t in subtasks]
return await asyncio.gather(
*(w.run(t.prompt) for w, t in zip(workers, subtasks))
)
async def build_feature(spec, subtasks):
findings = await gather_context(subtasks) # wide, parallel, read-only
writer = Agent(name="writer", tools=["write_file", "apply_patch"])
return await writer.run(spec, context=findings) # one writer, one pass
Проблема. Ўны ўводзічы ёсць вузкі месца і яму трэба адразу перадаваць усі рэзультаты працавікаў, таму вы страждзеце ад нехапакі кантэйнера для токенаў задоўжы якога-небудзь логічнага ліміту. Калі артыфакт дзе-небудзь справды дзельна дзеліцца, як у трох файлах, якія не маюць спільнага інтэрфейсу, прызначыце кожнаму файлу свога ўводзічы і паставіце тую ж дысцыпліну ў кожны з іх.
Запісуйце умовы завершэння запісу
Што йдзе не так. Заўершаецца запуск, ніякія памылкі не з’яўляюцца, а заданне застае незавершаным. Аналіз 1,600 траекцый мнагаагентных сістэм у семі фрэймворках паказаў, што 12,4% неудачаў сталося праз тое, што агенты не ведалі пра сваія умовы зупінкі, а ўшысткі 6,2% зупініліся ранейш часу. Такім чынам, майжэ адна з пяці неудач выглядае як апрантны завершэння з напаловым рэзультатам.
Рэшэнь. Ператворыце лінію фінішу на ўжотычны элемент, а не на простае адчуванне. Да кожнам заданню стварыце чыткі спіс пунктав для завершэння, а таксама разместыце пункт апраўдкі, які будзе ацэніваць дасягненне первачнай мэты, на ўжоўдынам шляху да канечнага стану. У тым жа даследжэнні стваралася, што вікорыстоўванне механізма апраўдкі падняло рэўалізацыю успеху ChatDev на 9,4%, а перакананне ў досягненні высокага рэвэлю мэтаў — на 15,6%.
CHECKLIST = [
"every requested section exists",
"each claim cites a retrieved source",
"open questions are listed, or explicitly none",
]
def verify_complete(objective, checklist, output) -> bool:
for item in checklist:
verdict = judge(f"Objective: {objective}\nCheck: {item}\n\n{output}")
if not verdict.passed:
log.info("termination blocked by: %s", item)
return False
return judge(f"Does this satisfy the objective?\n{objective}\n\n{output}").passed
def finish(state):
if verify_complete(state.objective, CHECKLIST, state.draft):
return state.done()
return state.keep_working(reason="checklist not satisfied")
Проблема. Калі дазволіць модэлі, якая вырабатвае рэшэнні, самай оцэніваць свой спіс пераконтроўкі, яна працягне сваю працу далей без пераконтроўкі. Як толькі можна, вярніцеся да механічных пунктав, такіх як наявнасць файлу, заполнены поле чы вырашэнне пытання з цітатай. Вората, якія ніколі не ачынаюцца, таксама ўважаюцца адмовамі, таму обмежыце колькасць спроб і паднесіце паўтарную памылку, калі ўсе спробы будуць выкараныя.
З чаго пачаць
Калі вы хочаце змяніць толькі адну рэч, выберыце найдэшавшы варыянт: пісьмова задаць вакуум, што значыць «завершанне». Спісак пераконтроўкі плюс крок перакантролю на шляху да канечнага стану займае каля паўдзені, падходзіць як для аднаго агента, так і для цэлага каляду, і усуне цэлы клас тых, што працуюць без проблем, але залішаюцца непазначанымі. Зробіце гэта задачэм на гэты тыдзень.
Далей прыходзіць практыка адзьёмнага выкарыстоўвання. Аналізавайце вашага оркестратора і пераканаўцеся, чыя ў яго варыянты зводзяцца да фіксаванага спіску і функцыі аграгавання. Калі так, адключыце яго; спадае выкарыстоўванне токенав і затрымка, а рэзультаты застаюцца стабільнымі. Паколькі яго лёгка адменаваць, гэта ўпэўненны спосаб для першага крока.
Адкладзіце болей сложныя рашэнні, пакуль гэта не стане проблемай. Контракты з вярненнем дадзеных практычныя пры болей чым пяці працоўніках, а спіс дазволеных дзеянняў толькі для чытання мае значэнне, калі два агентаі практычна пішуць адночасна. Аўтаматычны оркестратор — адзін аўтазахад: пачаткова ён практычна безкоштовны, але пазней яго дадаць вельмі складна.
Сістэмы, якія застаюцца эфектывнымі ў 2026 годзе, не маюць максымальнага колькасці агентаў. У іх з’ёднанні агентаў ёсць самая малае і найболей ясна адзначанае частка праекта. Менш людзей, строгіяйшыя контракты, адна ручка.
Супаўзеўныя матэрыялы
- Агенты для паралельнага кодавання з Git Worktrees, дырэктар і Antigravity 2.0 — Як коордынатарны агент, git worktrees для кожнага агента і тракты, базаваныя на задачах, дазволяюць стварыць дзесятак функцыяй SRE за апошнія пяць гадзін, а таксама урокі па кантэксту пытанняў HITL і правілах злучэння даных.
- Агентны RAG з Agno Teams: перапісваць запіты, цыкл крытыкі і Qdrant — Коордынатарная команда перапісвае запіты, павтарае працэс выкарыстоўвання даных за дапамогою крытыка і дае адпаведзі толькі на адзначаны кантэкст, выкарыстоўваючы гібрыдны пошук на основе dense, BM25 і ColBERT у Qdrant.