Перейти к содержимому
Ступень 88.3 Агенты

Планирование, память и мультиагентность

Как агенты справляются с длинными задачами — планирование (Plan-and-Solve), рефлексия (Reflexion), краткосрочная и долгосрочная память, оркестратор с субагентами и Agent Swarm в Kimi K2.5 — и когда всё это лишнее.

УРОКstage-8/8-3-agents/03-planning-memory.mdx1 ч 50 минУрок 3 из 4
После урока вы сможете
  • Объяснить Plan-and-Solve / Plan-and-Execute и Reflexion и понять, почему рефлексии нужен внешний сигнал
  • Спроектировать память агента — что держать в контексте, что во внешнем хранилище и как извлекать нужное
  • Сравнить одиночного агента и схему «оркестратор + субагенты» по задержке, токенам и надёжности
  • Решать, нужен ли агент вообще, — с помощью оценки накопления ошибок

ReAct из урока 8.3.1 хорошо работает на задачах в 3–10 шагов. На задачах в 50–200 шагов (починить баг в большом репозитории, собрать отчёт по десяткам источников) он начинает сыпаться:

  • теряет цель — каждый шаг выбирается «жадно», без взгляда вперёд;
  • повторяет ошибки — неудачная попытка ничему не учит следующую;
  • переполняет контекст — история растёт квадратично по токенам (урок 8.3.1), а важное тонет в середине;
  • работает медленно — всё строго последовательно.

Против каждой проблемы есть приём: планированиепланирование (у агента)EN planning смотрит вперёд, рефлексиярефлексия (самокритика агента)EN reflectionМодель оценивает свою попытку и записывает вывод «что исправить», который используется в следующей попытке (Reflexion, Self-Refine). — назад, память агентапамять агентаEN agent memoryКраткосрочная (short-term memory) — то, что в контекстном окне; долгосрочная (long-term memory) — внешнее хранилище, откуда нужное извлекается по запросу. хранит важное вне окна, а мультиагентная системамультиагентная системаEN multi-agent system делит работу. У каждого приёма есть цена, поэтому в конце урока разберём, когда без них лучше обойтись.

Статья Plan-and-Solve (Wang и др., ACL 2023) начинала с наблюдения: в задачах с цепочкой рассуждений (цепочка рассужденийцепочка рассужденийEN chain of thought (CoT)) модель часто пропускает шаги. Лекарство оказалось простым — заменить «давай подумаем шаг за шагом» на промпт, который требует сначала составить план:

Let's first understand the problem and devise a plan to solve the problem.
Then, let's carry out the plan and solve the problem step by step.
(Сначала поймём задачу и составим план решения. Затем выполним план шаг за шагом.)

В версии PS+ к этому добавляются указания «выдели нужные переменные и промежуточные величины» и «считай аккуратно». На задачах по математике это заметно сократило ошибки пропущенных шагов по сравнению с обычным zero-shot CoT.

Для агентов идею развили в архитектуру из трёх ролей. Ими может быть одна и та же модель с разными промптами:

flowchart LR
T["Задача"] --> P["Планировщик:<br/>список шагов"]
P --> E["Исполнитель (ReAct):<br/>шаг i с инструментами"]
E --> R{"Перепланировщик:<br/>готово?"}
R -->|"нет: правим план"| E
R -->|"да"| A["Ответ"]
Plan-and-Execute: план составляется заранее и правится по ходу

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

Современные кодовые агенты используют облегчённый вариант: план — это инструмент. Модель ведёт список задач (todo list), отмечает выполненные пункты и дописывает новые, а список постоянно лежит в контексте. Это дёшево и не мешает гибкости ReAct.

Рефлексия: учиться на своих ошибках без обучения

Заголовок раздела «Рефлексия: учиться на своих ошибках без обучения»

Self-Refine (Madaan и др., 2023): модель пишет черновик, сама его критикует и переписывает. Цикл «генерация → отзыв → исправление» без внешних инструментов.

Reflexion (Shinn и др., 2023) делает шаг дальше. После проваленной попытки агент пишет словесный вывод («тест на пустом списке упал, нужно обработать этот случай») и кладёт его в эпизодическую память. На следующей попытке этот вывод стоит в промпте. Авторы называют это словесным подкреплением (verbal reinforcement): сигнал об ошибке превращается не в градиент, а в текст. На HumanEval (задачи на программирование с тестами, урок 7.3.1) такой агент получил 91% pass@1 против 80% у GPT-4 без рефлексии.

Reflexion работает, потому что узнаёт о провале извне: из тестов, компилятора, среды. Если же модель оценивает свой ответ сама, без проверки, она часто «исправляет» верное на неверное. Этому посвящена работа «Large Language Models Cannot Self-Correct Reasoning Yet» (Huang и др., 2023). Практическое правило: рефлексия полезна ровно настолько, насколько надёжен сигнал о неудаче.

def evaluate(code):
"""Внешний сигнал: исполняем код и прогоняем тесты (вместо LLM-самооценки)."""
env = {}
# exec выполняет строку как Python-код; созданные им функции попадают в словарь env.
# Код написала модель — значит, он недоверенный (урок 8.3.2).
exec(code, env) # в реальной системе — только в песочнице!
for xs, want in [([1, 2, 3], 2.0), ([], None), ([5], 5.0)]:
try:
got = env["mean"](xs)
except Exception as e:
return False, f"mean({xs}) → {type(e).__name__}: {e}"
if got != want:
return False, f"mean({xs}) = {got!r}, ожидалось {want!r}"
return True, "все тесты пройдены"
def fake_llm(task, reflections):
"""Заглушка LLM: «читает» рефлексии и учитывает их. Настоящая модель делает это сама."""
code = "def mean(xs):\n return sum(xs) / len(xs)\n"
if any("ZeroDivisionError" in r for r in reflections):
code = "def mean(xs):\n if not xs:\n return None\n return sum(xs) / len(xs)\n"
return code
def reflexion(task, generate, max_trials=3):
memory = [] # эпизодическая память: выводы из неудач
for trial in range(1, max_trials + 1):
code = generate(task, memory)
ok, feedback = evaluate(code)
print(f"попытка {trial}: {'успех' if ok else 'провал'} — {feedback}")
if ok:
return code
memory.append(f"Попытка {trial}: {feedback}. Учти этот случай в следующий раз.")
return None
reflexion("Напиши mean(xs); для пустого списка верни None", fake_llm)
попытка 1: провал — mean([]) → ZeroDivisionError: division by zero
попытка 2: успех — все тесты пройдены

Это паттерн evaluator-optimizer: генератор и оценщик в цикле. Замените fake_llm на вызов модели из урока 8.3.1, и получите настоящего агента, который чинит код по тестам.

Краткосрочная память — это просто контекстное окноконтекстное окноEN context window: системный промпт, история шагов, наблюдения. Она дорогая (каждый токен оплачивается на каждом шаге) и ненадёжная: модели хуже используют информацию из середины длинного контекста, чем из начала и конца (эффект «lost in the middle», Liu и др., 2023). Поэтому окно нужно активно чистить — это часть контекст-инжинирингконтекст-инжиниринг (context engineering)EN context engineeringРешение, какие токены и в каком порядке попадают в окно на каждом вызове — инструкции, примеры, документы, история, результаты инструментов. (урок 8.1.2):

ПриёмЧто делаемЧем платим
Скользящее окнодержим последние kk шаговтеряем раннее, в том числе важное
Сжатие (compaction)старые шаги заменяем сводкой от LLMлишний вызов модели, детали теряются
Обрезка наблюденийот вывода инструмента оставляем сутьможно отрезать нужное
Блокнот (scratchpad)агент сам пишет заметки в файл и перечитывает ихнужен инструмент и дисциплина

Anthropic в статье «Effective context engineering for AI agents» (2025) называет три главных приёма для долгих задач: сжатие, структурированные заметки и мультиагентную архитектуру. До последней мы дойдём ниже.

Долгосрочная память живёт вне модели — в файлах, базе данных, векторном индексе — и подгружается в окно по запросу. По сути это генерация с дополнением извлечёнными даннымигенерация с дополнением извлечёнными данными (RAG)EN retrieval-augmented generation (RAG) поверх собственного опыта агента. Обычно различают три вида:

  • эпизодическая — что происходило: прошлые сессии, удачные и неудачные траектории (как память Reflexion);
  • семантическая — факты: «пользователь пьёт чай без сахара», «в проекте используется PostgreSQL 16»;
  • процедурная — как делать: инструкции и навыки, например файл с правилами проекта, который агент читает при старте.

Самые трудные вопросы — не «как искать», а что записывать, когда забывать и как обновлять устаревшее.

MemGPT (Packer и др., 2023) предложил аналогию с операционной системой: контекст — это оперативная память, внешнее хранилище — диск, а модель сама управляет «подкачкой» с помощью вызовов функций (сохранить, найти, вытеснить).

Что достать из памяти: свежесть, важность, релевантность

Заголовок раздела «Что достать из памяти: свежесть, важность, релевантность»

В Generative Agents (Park и др., 2023, «симулятор деревни» из 25 агентов) все наблюдения пишутся в поток памяти, а для каждого запроса воспоминания ранжируются по сумме трёх оценок:

score=α1⋅свежесть+α2⋅важность+α3⋅релевантность\text{score} = \alpha_1 \cdot \text{свежесть} + \alpha_2 \cdot \text{важность} + \alpha_3 \cdot \text{релевантность}

Свежесть затухает экспоненциально (в статье множитель 0.995 за каждый игровой час с последнего обращения), важность от 1 до 10 оценивает сама LLM при записи, релевантность — косинусное сходство эмбеддингов. Каждую оценку нормируют в [0,1][0, 1], в статье все α\alpha (веса слагаемых) равны 1.

Пример с числами для свежести: воспоминанию 49 часов, его свежесть 0.99549≈0.780.995^{49} \approx 0.78; воспоминанию 2 часа — 0.9952≈0.990.995^{2} \approx 0.99. Нормировка min-max переводит набор чисел в [0,1][0, 1] по формуле x−min⁡max⁡−min⁡\frac{x - \min}{\max - \min}: самое маленькое значение станет 0, самое большое — 1. Соберём такую память:

import math
from collections import Counter
def embed(text):
"""Заглушка эмбеддинга: символьные триграммы. Терпима к падежам: «чай» ≈ «чаю»."""
t = f" {text.lower()} "
return Counter(t[i:i + 3] for i in range(len(t) - 2))
def cosine(a, b):
dot = sum(a[g] * b[g] for g in a)
norm = math.sqrt(sum(v * v for v in a.values())) * math.sqrt(sum(v * v for v in b.values()))
return dot / norm if norm else 0.0
def minmax(xs):
lo, hi = min(xs), max(xs)
return [(x - lo) / (hi - lo) if hi > lo else 1.0 for x in xs]
class LongTermMemory:
"""Поток памяти по мотивам Generative Agents: свежесть + важность + релевантность."""
def __init__(self, decay=0.995):
self.decay, self.items = decay, []
def add(self, text, hour, importance): # importance 1..10 обычно оценивает сама LLM
self.items.append({"text": text, "hour": hour, "imp": importance, "vec": embed(text)})
def retrieve(self, query, now, k=2):
q = embed(query)
# в статье свежесть считают от последнего обращения к воспоминанию, здесь для простоты — от записи
rec = minmax([self.decay ** (now - m["hour"]) for m in self.items])
imp = minmax([m["imp"] for m in self.items])
rel = minmax([cosine(q, m["vec"]) for m in self.items])
scored = sorted(zip(self.items, rec, imp, rel), key=lambda x: -(x[1] + x[2] + x[3]))
for m, r, i, s in scored:
print(f"{r + i + s:4.2f} = {r:.2f} + {i:.2f} + {s:.2f} {m['text']}")
return [m["text"] for m, *_ in scored[:k]] # m, *_ — берём первый элемент кортежа, остальные отбрасываем
mem = LongTermMemory()
mem.add("Пользователь пьёт зелёный чай без сахара", hour=1, importance=6)
mem.add("Созвон с командой перенесли на пятницу", hour=20, importance=4)
mem.add("Пользователь купил лунцзин в Ханчжоу", hour=30, importance=5)
mem.add("Спрашивал погоду в Москве", hour=47, importance=2)
mem.add("У пользователя аллергия на орехи", hour=48, importance=9)
print("В контекст попадут:", mem.retrieve("Какой чай предложить пользователю к десерту?", now=50))
2.72 = 1.00 + 1.00 + 0.72 У пользователя аллергия на орехи
1.69 = 0.59 + 0.43 + 0.67 Пользователь купил лунцзин в Ханчжоу
1.57 = 0.00 + 0.57 + 1.00 Пользователь пьёт зелёный чай без сахара
0.98 = 0.98 + 0.00 + 0.00 Спрашивал погоду в Москве
0.72 = 0.38 + 0.29 + 0.06 Созвон с командой перенесли на пятницу
В контекст попадут: ['У пользователя аллергия на орехи', 'Пользователь купил лунцзин в Ханчжоу']

Посмотрите на результат внимательно. Аллергия на орехи к десерту — отличная находка, её вытащила важность. А самое релевантное воспоминание («зелёный чай без сахара») не попало в топ-2: оно самое старое, и min-max нормировка дала ему свежесть ровно 0. Это типичная проблема настройки. Лечится весами α\alpha (поднять вес релевантности), более медленным затуханием или большим kk. В реальной системе вместо триграмм будет эмбеддинг-модель вроде BGE (урок 8.2.1), а важность оценит LLM.

  1. Параллельность. Пять независимых подзадач можно решать одновременно.
  2. Чистые контексты. Каждый субагентсубагентEN subagent получает только свою подзадачу и возвращает короткую сводку. Главный агент не тонет в сотнях страниц сырых результатов поиска.
  3. Специализация. Разные промпты, инструменты и даже модели: дорогая модель планирует, дешёвые ищут.
flowchart TB
U["Задача"] --> O["Оркестратор:<br/>план и раздача подзадач"]
O --> W1["Субагент 1"]
O --> W2["Субагент 2"]
O --> W3["Субагент 3"]
W1 --> S["Оркестратор:<br/>сводит результаты"]
W2 --> S
W3 --> S
S --> A["Ответ"]
Оркестратор и исполнители: субагенты работают параллельно, каждый в своём контексте
  • оркестратороркестратор (ведущий агент)EN orchestratorАгент, который разбивает задачу на подзадачи, раздаёт их субагентам и собирает результаты. и исполнители — ведущий агент сам решает, на что разбить задачу и сколько субагентов запустить. Так устроены системы «глубокого исследования».
  • Конвейер ролей — агенты передают работу по цепочке: «аналитик → архитектор → программист → тестировщик». Это подход китайских проектов MetaGPT и ChatDev (о них ниже). Он предсказуем, но по сути это workflow.
  • Оценщик и исполнитель — как Reflexion, только роли разнесены по разным агентам.
  • Дебаты и голосование — несколько агентов решают независимо, затем спорят или голосуют.

Anthropic в разборе своей исследовательской системы (2025) приводит показательные цифры. Связка из ведущего агента (Claude Opus 4) и субагентов (Claude Sonnet 4) превзошла одиночного Claude Opus 4 на 90.2% во внутреннем исследовательском эвале. Цена — токены: агенты тратят примерно в 4 раза больше токенов, чем обычный чат, а мультиагентные системы — примерно в 15 раз. На бенчмарке BrowseComp (поиск трудно находимых фактов в вебе) один только объём потраченных токенов объяснил 80% разброса качества. Вывод авторов: мультиагентность окупается на ценных задачах, которые хорошо распараллеливаются, и не окупается там, где агентам нужен общий контекст.

Противоположную позицию сформулировала Cognition (авторы агента Devin) в заметке «Don’t Build Multi-Agents» (2025): субагенты, не видящие решений друг друга, принимают несовместимые решения. Пример оттуда: задачу «сделай клон Flappy Bird» делят пополам. Один субагент рисует фон в стиле Super Mario Bros, другой — птицу, которая не похожа на игровой спрайт и двигается не так, как в Flappy Bird. Склеить такие части в одну игру почти невозможно. Их принципы: передавайте субагенту полный контекст и историю, а не одну короткую задачу, и помните, что любое действие несёт неявные решения. Обе позиции совместимы: распараллеливайте чтение и поиск, а запись и согласованные изменения (код, документ) делайте одним агентом.

Если субагенты работают параллельно, задержку определяет самый медленный из них. Moonshot в описании PARL (об этом ниже) формализует это метрикой Critical Steps: для каждого этапа берётся число шагов оркестратора плюс максимум шагов среди параллельных субагентов, и всё суммируется:

CriticalSteps=∑t(Smain(t)+max⁡iSsub,i(t))\text{CriticalSteps} = \sum_{t} \Bigl( S_{\text{main}}^{(t)} + \max_i S_{\text{sub},i}^{(t)} \Bigr)
def critical_steps(stages):
"""Длина критического пути: этап = (шаги оркестратора, [шаги каждого субагента])."""
return sum(orch + max(subs, default=0) for orch, subs in stages)
subtasks = [12, 8, 15, 6, 9] # шагов на каждую из 5 подзадач
single = 2 + sum(subtasks) + 4 # один агент: план, всё подряд, итог
swarm = critical_steps([(2, subtasks), (4, [])]) # оркестратор: план → 5 субагентов параллельно → итог
print(f"один агент: {single} шагов, рой: {swarm} шагов, ускорение ×{single / swarm:.2f}")
print("Вероятность пройти n шагов без единой ошибки:")
for p in (0.99, 0.95, 0.90):
print(f" p = {p:.2f}: " + " ".join(f"n={n}: {p ** n:.3f}" for n in (5, 10, 20, 50)))
один агент: 56 шагов, рой: 21 шагов, ускорение ×2.67
Вероятность пройти n шагов без единой ошибки:
p = 0.99: n=5: 0.951 n=10: 0.904 n=20: 0.818 n=50: 0.605
p = 0.95: n=5: 0.774 n=10: 0.599 n=20: 0.358 n=50: 0.077
p = 0.90: n=5: 0.590 n=10: 0.349 n=20: 0.122 n=50: 0.005

Ускорение ×2.67, хотя подзадач пять: всё упирается в самую длинную (15 шагов) и в последовательные этапы оркестратора. Это закон Амдала в мире агентов: ускорение от параллельности ограничено той частью работы, которую распараллелить нельзя. Вторая половина вывода — к следующему разделу.

Обычно мультиагентную схему проектирует человек: сколько субагентов, какие роли. В Kimi K2.5 (январь 2026) Moonshot учит этому саму модель. По заявлению компании, в режиме Agent Swarm K2.5 сама разбивает задачу и запускает до 100 субагентов, выполняющих до 1500 вызовов инструментов, а время выполнения сокращается до 4.5 раза по сравнению с одиночным агентом. Это цифры разработчиков, независимой проверки мы не нашли.

Метод обучения называется PARL (Parallel-Agent Reinforcement Learning). Интересны три инженерных решения из техотчёта:

  • Обучается только оркестратор. Субагенты — «замороженные» копии модели из промежуточных чекпоинтовчекпоинт (контрольная точка)EN checkpointСохранённое состояние обучения: веса модели, состояние оптимизатора, номер шага. Позволяет продолжить обучение после сбоя., их траектории не участвуют в оптимизации. Так проще распределить заслугу (credit assignment): за успех или провал отвечает декомпозиция задачи.
  • «Последовательный коллапс» (serial collapse). Без специальных стимулов оркестратор скатывается к привычному одиночному режиму и почти не запускает субагентов параллельно. Поэтому награда на ранних этапах отдельно поощряет параллельный запуск, а затем этот бонус постепенно убирают, и остаётся награда за решённую задачу.
  • Оптимизация задержки. Целевая метрика — критические шаги из формулы выше, а не общее число шагов.

О том, как вообще устроено обучение агентов с подкреплением, — в следующем уроке.

Вернитесь к выводу программы выше. Если каждый шаг агента успешен с вероятностью pp, а ошибки не исправляются, то вероятность пройти nn шагов без единой ошибки равна pnp^n. Даже при p=0.95p = 0.95 за 20 шагов это всего 36%. Агентность — это умножение рисков. Рефлексия и проверки поднимают эффективное pp, но не бесплатно.

Поэтому поднимайтесь по лестнице сложности только при необходимости:

УровеньКогда достаточноПример
Один вызов LLMзадача решается за один ответклассификация обращений, перевод, извлечение полей
Один вызов + RAGнужны знания, но путь фиксированответы по документации компании
Workflow (цепочка, маршрутизация, параллельные вызовы)шаги известны заранее«распознать → проверить → сформировать ответ»
Один агент с инструментамипуть заранее неизвестен, результат можно проверитьисправить баг, пока тесты не пройдут
Мультиагентная системазадача ценная, широкая и хорошо параллелитсяобзор рынка по сотне источников

Вопросы перед тем, как строить агента:

  1. Можно ли описать путь решения заранее? Если да — нужен workflow, а не агент.
  2. Можно ли автоматически проверить результат (тесты, схема, сверка)? Без этого агента трудно и отлаживать, и улучшать.
  3. Какова цена ошибки и нужно ли подтверждение человека?
  4. Укладываются ли 5–15-кратный расход токенов и задержка в бюджет?
Проверь себя6 вопросов
1 Агент делает 10 шагов, каждый успешен с вероятностью 0.9, ошибки не исправляются. Какова вероятность пройти все шаги без ошибки? Округлите до двух знаков.
2 Почему Reflexion хорошо работает на задачах программирования, а «самокритика» без проверок часто не помогает в рассуждениях?
3 Какие преимущества даёт схема «оркестратор + субагенты»?Можно выбрать несколько ответов
4 Оркестратор тратит 3 шага на план, затем параллельно работают субагенты, которым нужно 10, 7 и 12 шагов, затем оркестратор тратит 2 шага на итог. Сколько критических шагов?
5 Пользователь полгода назад сказал агенту, что у него аллергия на орехи. Где правильно хранить этот факт?
6 Как Moonshot называет режим сбоя, при котором обучаемый оркестратор перестаёт распараллеливать работу и всё делает последовательно? (два слова, по-английски или по-русски)
  • Планирование смотрит вперёд: Plan-and-Solve в промпте, Plan-and-Execute с перепланированием или план-список задач как инструмент.
  • Рефлексия смотрит назад: Reflexion записывает выводы из неудач в память. Работает, когда есть внешний сигнал (тесты, среда).
  • Память: краткосрочная — окно, которое надо чистить; долгосрочная — внешнее хранилище с извлечением по свежести, важности и релевантности.
  • Мультиагентность даёт параллельность и чистые контексты ценой в разы большего расхода токенов и риска несогласованных решений. Kimi K2.5 учит оркестрацию через RL (PARL).
  • Ошибки в цепочке перемножаются (pnp^n). Начинайте с простейшей схемы и усложняйте по измеримой необходимости.

Схемы из этого урока на настоящих фреймворках (LangGraph, OpenAI Agents SDK, Claude Agent SDK) и то, как держать мультиагентную систему в продакшене — бюджеты, ретраи, трассы, оценка траекторий, — разобраны в модуле 8.5, особенно в уроке 8.5.4.

Дальше: как обучают агентные модели (синтез сред в Kimi K2, асинхронный RL в slime у GLM), почему промпт-инъекции — главная угроза агентам и как оценивать агентов.