Главная / Статьи / Практические замечания: Много рук, один перо: координация агентов без потери контроля

Практические замечания: Много рук, один перо: координация агентов без потери контроля

Пошаговое руководство по практическим заметкам: «Много рук, один перо»: координация действий агентов без потери контрактов, проверок и слотов для вставки кода для команд, использующих эту схему.

2303 слов

Используйте это как переработанную версию идей из книги «Many Hands, One Pen: Orchestrating Agents Without Losing the Plot», ориентированную на операторов: четкие этапы, упорядоченные блоки кода и записи для восстановления, сохраняющиеся при передаче задач. Этап Обзора работает лучше всего, если рассматривать его как измеримую основу. Соберите один идеальный пример работы, один случай сбоя и записи для возврата к предыдущему состоянию перед расширением объема работ. Предпочитайте небольшие, тестируемые единицы кода вместо обширных скриптов. Когда какой-то шаг терпит неудачу, причина сбоя должна указывать на конкретную ответственность, а не на запутанную цепочку операций.

071 · Используйте стандартный рабочий процесс и позвольте агенту заслужить свою автономию

Для этапа 071 «По умолчанию» необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия результатам работы, определите критерии успеха и не допускайте молчаливого частичного завершения задачи. Внедряйте утверждение человека для операций, связанных с тратой денег или изменением производственных данных. Подключение компонентов во время компиляции не гарантирует полноты выполнения бизнес-задач.

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)

072 · Пропустите оркестратор, если вы уже знаете структуру разделения задач

Для этапа 072 «Пропустить Orchestrator» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Зафиксируйте время выполнения, а также стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости заранее предотвращает неожиданные счета при переходе с демо-среды в общедоступные среды. Внесите человеческое утверждение для операций, связанных с тратой денег или изменением производственных данных. Подключение на этапе компиляции не гарантирует полноты решения с точки зрения бизнес-процессов.

# 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

073 · Разделяйте агентов по границам контекста, а не по должностям

Для модуля 073 Split Agents по этапам необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь кодовый граф. Вводите ручное утверждение для операций, связанных с тратой денег или изменением производственных данных. Подключение компонентов во время компиляции не гарантирует полноты обработки бизнес-логики. Для модуля 073 Split Agents по этапам необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Предпочитайте небольшие, тестируемые единицы кода большим, запутанным скриптам. При сбое шага он должен указывать на конкретную причину, а не на сложную структуру обработки данных.

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

074 · Оркестратор должен принимать решение, а не вызывать инструменты

При работе над этапом 074 «Оркестратор должен принимать решение» сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой чек-лист помогает сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными данными. Дайте названия результатам работы, определите критерии успеха и не допускайте безответственного частичного выполнения задачи. Фиксируйте название инструмента, хеш аргументов, время задержки и результат каждого вызова. Без такой записи отладка циклов агента занимает часы.

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)

075 · Дайте каждому подагенту контракт с типизированными выходными данными, а не свободный текст

При работе над этапом 075 «Give Every Subagent» сначала запишите условия контракта: необходимые входные данные, сигнал успешного выполнения и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Рядом с функциональными результатами занесите информацию о времени выполнения и стоимости токенов или запросов. Отображение затрат с самого начала предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Выполняйте контрольные точки после дорогостоящих шагов. Функция возобновления работы не должна снова взимать плату за один и тот же вызов LLM, когда оператор пытается выполнить последующий узел.

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")

076 · Распространение операций чтения, сбор данных через одного агента

При работе над этапом 076 Fan Out Reads сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность последующих изменений в коде. Храните конфигурацию отдельно от кода приложения. Файлы с настройками окружения, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Создавайте контрольные точки после дорогостоящих операций. Функция возобновления работы не должна повторно оплачивать один и тот же вызов большой языковой модели, когда оператор пытается выполнить следующий шаг. При работе над этапом 076 Fan Out Reads сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность последующих изменений в коде. Предпочитайте небольшие, тестируемые единицы кода большим скриптам. При сбое какого-либо шага причина неудачи должна указывать на конкретную ответственность, а не на запутанную структуру обработки данных.

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

077 · Запишите условия завершения: агенты действительно не знают, когда остановиться

Этап 077 по записи условий завершения работает наилучшим образом, если рассматривать его как измеримую основу. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными результатами. Дайте названия соответствующим документам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи. Сохраняйте структуру графа простой и типизированной. Вложенные структуры скрывают информацию о том, какой узел заполнил тот или иной поле, что приводит к нарушению последовательности выполнения после перерывов.

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")

Чек-лист операций

При работе над этапом чек-листа операций сначала запишите условия контракта: необходимые входные данные, сигнал о успехе и действия при частичном сбое. Такой чек-лист обеспечивает честность последующих изменений в коде.

Документируйте одновременно успешный сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не последующим доработкам.

Создавайте контрольные точки после дорогостоящих операций. Функция возобновления не должна снова взимать плату за один и тот же вызов ЯИИ, когда оператор пытается выполнить последующий элемент.

Зафиксируйте версии зависимостей и сохраните хэш изображения, с использованием которого выполнялась демонстрация. Воспроизводимость важнее коллективных знаний.

Предпочитайте небольшие, тестируемые модули большим скриптам. При сбое какого-либо шага причина должна быть связана с конкретной функцией, а не с запутанной цепочкой операций.

Создавайте контрольные точки после дорогостоящих операций. Функция возобновления не должна снова взимать плату за один и тот же вызов ЯИИ, когда оператор пытается выполнить последующий элемент.

Перед внедрением стека заморозьте версии, сделайте копию «золотого» отчета для критической цепочки операций и уточните шаги возврата к предыдущему состоянию. В совместных средах необходимы ограничения по частоте запросов, проверки принадлежности пользователя и четко определенный ответственный за обновление секретов. Лучше выбирать надежность, даже если она кажется скучной, чем умные одноразовые демонстрации.

Примечание для пакетной обработки с идентификатором 64be605517f1: не храните ключи поставщика в репозитории, установите лимит токенов на одну сессию и сохраняйте отчеты рядом с фикстчерами для оценки, чтобы последующие замены моделей можно было сравнивать.

Для этапа 0 по усилению безопасности определите входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Документируйте как успешный, так и восстановительный пути работы. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки.

Деталь усиления безопасности 0/768: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменение на основе фиксированного набора вопросов, а не личных наблюдений.

При работе над первым этапом записи по усилению безопасности сначала запишите контракт: необходимые входные данные, сигнал успешного выполнения и что происходит при частичной неудаче. Такой чек-лист обеспечивает честность последующих изменений в коде. Рассматривайте этот этап как контракт между входными данными и проверенными выходными данными. Дайте названия элементам, определите критерии успеха и не допускайте безответственного частичного выполнения задач.

Деталь усиления безопасности 1/768: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменение на основе фиксированного набора вопросов, а не личных наблюдений.

Второй этап усиления безопасности работает наилучшим образом, если рассматривать его как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код.

Подробность усиления безопасности 2/768: измеряйте время выполнения, класс ошибки и расход токенов для этой записи, затем принимайте решение о сохранении изменений на основе определенного набора критериев, а не на основе единичных примеров.

Для третьего этапа усиления безопасности необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии системы. Желательно использовать небольшие, тестируемые единицы вместо обширных скриптов. При сбое шага причина должна быть связана с конкретной областью ответственности, а не с запутанной структурой обработки данных.

Подробности усиления безопасности 3/768: измеряйте время выполнения, тип ошибок и расход токенов для данного этапа, затем принимайте решение о сохранении изменений на основе установленного набора критериев, а не на основе единичных наблюдений.

При работе над четвертым этапом инструкции по усилению безопасности сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения, стоимость токенов или запросов. Очевидность затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды.

Подробности усиления безопасности 4/768: измерьте время выполнения, класс ошибки и расход токенов для данной инструкции, затем решите, следует ли сохранять изменение на основе определенного набора критериев, а не на основе устных замечаний.

Четвертый этап инструкции по усилению безопасности работает наилучшим образом, когда его рассматривают как измеримую поверхность. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объема работ. Документируйте как успешный путь выполнения, так и путь восстановления. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не элементами последующей доработки.

Подробности усиления безопасности 5/768: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменения на основе фиксированного набора критериев, а не на основе единичных примеров.

На этапе 6 процедуры усиления безопасности определите входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Укажите названия элементов, определите критерии успеха и не допускайте безответственного частичного выполнения задачи.

Подробности усиления безопасности 6/768: измерьте время выполнения, класс ошибки и расход токенов для этой записи, затем решите, следует ли сохранить изменения на основе фиксированного набора критериев, а не на основе единичных примеров.

При работе над этапом 7 инструкции по усилению безопасности сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Храните конфигурацию отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код.

Подробности усиления безопасности 7/768: измеряйте время выполнения, класс ошибок и расход токенов для данной инструкции, затем принимайте решение о сохранении изменений на основе определенного набора критериев, а не на основе устных замечаний.