SKILL.state: как хранить состояние LLM-агента без бесконечной истории сообщений

от автора

в
Время чтения: 4 мин.

Большинство LLM-агентов устроены примерно одинаково: модель получает задачу, выполняет действие через tool, получает результат, рассуждает дальше и вызывает следующий tool. Вся эта последовательность постепенно накапливается в истории диалога.

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

Авторы работы SKILL.state: Scalable Long-Horizon Agent Skills предлагают отказаться от истории как основного механизма хранения состояния агента.

Вместо этого агент на каждом шаге получает только три вещи:

  • описание выполняемой процедуры;
  • текущее структурированное состояние;
  • последнее наблюдение из внешней среды.

Идея кажется простой, но результаты экспериментов показывают довольно большую разницу.

Источник: https://arxiv.org/html/2608.26263

В чём проблема обычного LLM-агента

Рассмотрим типичный цикл работы агента:

User
 ↓
LLM
 ↓
Tool
 ↓
Observation
 ↓
LLM
 ↓
Tool
 ↓
Observation
 ↓
...

Чтобы модель понимала, что происходило раньше, runtime передаёт ей историю предыдущих действий.

Условно первый запрос выглядит так:

SYSTEM
USER
OBSERVATION 1

Через несколько шагов:

SYSTEM
USER

ACTION 1
OBSERVATION 1

ACTION 2
OBSERVATION 2

ACTION 3
OBSERVATION 3

...

А через сотню шагов в prompt уже находится значительная часть истории выполнения задачи.

Проблема здесь не только в количестве токенов.

Модель должна каждый раз восстанавливать текущее состояние мира из набора сообщений:

Что уже сделано?
Что ещё нужно сделать?
Какие данные актуальны?
Какие результаты устарели?
Какие гипотезы уже проверялись?

Фактически состояние программы оказывается размазано по текстовой истории.

Авторы SKILL.state предлагают сделать его явным.

Conversation != State

Основную идею SKILL.state можно свести к следующему:

conversation != state

История разговора и состояние выполняемой задачи — разные вещи.

В SKILL.state каждый вызов модели получает:

Procedure
+
Current State
+
Latest Observation

Математически авторы описывают это так:

A(t) = (P, Σ(t), O(t))

где:

P    — описание процедуры
Σ(t) — текущее состояние
O(t) — последнее наблюдение

После обработки модель генерирует:

Reasoning
State Patch
Action

Runtime проверяет изменение состояния, применяет его и выполняет действие.

Получается цикл:

State + Observation
        ↓
       LLM
        ↓
State Patch + Action
        ↓
     New State
        ↓
       Tool
        ↓
   Observation

При этом reasoning нужен модели только внутри текущего шага.

После выполнения перехода состояния он удаляется.

На следующем шаге модель его уже не получает.

Как может выглядеть State

Допустим, агент работает с репозиторием и пытается исправить ошибку.

Вместо хранения всей последовательности команд:

прочитал auth.ts
запустил тесты
получил ошибку
проверил JWT
изменил middleware
снова запустил тесты
...

можно хранить состояние:

{
  "working_dir": "/app",
  "active_files": [
    "src/auth.ts",
    "src/user.ts"
  ],
  "tested_hypotheses": [
    "JWT expiration bug"
  ],
  "cmd_summary": "Auth tests passed, user tests failing"
}

Следующее наблюдение:

User tests failed in UserService.

Модель может вернуть изменение:

{
  "state_patch": {
    "cmd_summary": "Auth fixed. Failure remains in UserService."
  },
  "action": {
    "tool": "read_file",
    "path": "src/user.ts"
  }
}

Runtime применяет patch.

Следующий вызов модели уже получает новое состояние:

{
  "working_dir": "/app",
  "active_files": [
    "src/auth.ts",
    "src/user.ts"
  ],
  "tested_hypotheses": [
    "JWT expiration bug"
  ],
  "cmd_summary": "Auth fixed. Failure remains in UserService."
}

История того, как агент пришёл к этому состоянию, ему больше не нужна.

Состояние становится источником истины

Это довольно важное архитектурное изменение.

В обычном агенте модель фактически делает:

History
   ↓
LLM
   ↓
Current State?
   ↓
Next Action

Она сама восстанавливает состояние из истории.

В SKILL.state:

Current State
+
Observation
     ↓
    LLM
     ↓
New State + Action

Structured state становится canonical state выполнения.

LLM при этом можно рассматривать почти как функцию перехода:

(state, observation)
        ↓
       LLM
        ↓
(new state, action)

По смыслу это уже гораздо ближе к обычному state machine или reducer, чем к бесконечному чату.

Почему это экономит токены

У history-based агента размер prompt увеличивается с каждым шагом.

Если первый шаг содержит условно:

1 блок истории

второй:

2 блока

а сотый:

100 блоков

то суммарное количество переданного контекста растёт примерно как:

1 + 2 + 3 + ... + T

то есть:

O(T²)

В SKILL.state размер состояния предполагается ограниченным.

Каждый шаг выглядит примерно одинаково:

Procedure + State + Observation

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

O(T)

Разница становится особенно заметна на длинных задачах.

Что получилось в экспериментах

Авторы сравнивали несколько вариантов runtime:

  • обычный ReAct с растущей историей;
  • историю с периодическим summary;
  • Stateful/LangGraph-style подход;
  • SKILL.state.

На синтетическом Warehouse Management benchmark при длине выполнения в 200 шагов результаты получились такими:

RuntimeAccuracyСредний promptВсего токенов
ReAct0.7448 0072 608 755
Summary Memory0.8484 3646 175 509
LangGraph-style0.8872 3055 041 164
SKILL.state0.941 811122 384

То есть SKILL.state не просто использовал меньше контекста, но и показал лучшую точность.

Особенно интересно поведение среднего размера prompt.

При увеличении задачи:

10 шагов  → 1775 токенов
25 шагов  → 1736
50 шагов  → 1773
100 шагов → 1905
200 шагов → 1811

Размер практически не меняется.

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

Почему просто обрезать историю недостаточно

Можно задать очевидный вопрос: зачем вообще придумывать отдельное состояние?

Можно просто оставить последние несколько сообщений.

Авторы проверили и это.

Для задачи в 100 шагов они ограничили разные подходы примерно одинаковым бюджетом в 1800 токенов.

Результат:

ПодходAccuracy
Sliding Window0.18
Summary0.52
ReAct + LLMLingua0.22
SKILL.state0.94

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

Например агент узнал на втором шаге:

customer_id = 182

Если просто удалять старые сообщения, эта информация рано или поздно исчезнет.

Structured state позволяет сохранить именно факт:

{
  "customer_id": 182
}

не сохраняя весь разговор, в котором он появился.

А почему не Summary?

Другой популярный вариант — периодически просить LLM делать summary истории.

Например:

Пользователь хочет создать заказ.
Выбран клиент #182.
Добавлено два товара.
Адрес доставки пока отсутствует.

Это уже значительно лучше полной истории, но остаётся текстом.

В нём могут потеряться идентификаторы, связи между сущностями или другие данные, которые модель посчитает незначительными.

Structured state задаёт эти данные явно:

{
  "goal": "create_order",
  "customer_id": 182,
  "items": [431, 512],
  "missing_fields": [
    "delivery_address"
  ]
}

LLM уже не решает каждый раз, что означает этот текст.

Runtime знает структуру данных.

Чем это отличается от LangGraph

На первый взгляд идея напоминает LangGraph и другие stateful agent frameworks.

Structured state там тоже существует.

Но авторы SKILL.state обращают внимание на важное различие.

В LangGraph-style архитектуре состояние может существовать вместе с историей сообщений:

Structured State
+
Conversation Transcript
+
Latest Observation

То есть модель всё равно продолжает использовать transcript как один из основных источников информации.

SKILL.state предлагает более радикальный вариант:

Structured State
+
Latest Observation

Старого transcript вообще нет.

После каждого шага полезный результат reasoning должен превратиться в изменение состояния.

Остальное удаляется.

Интересная проблема: устаревшая информация

У такого подхода есть ещё одно преимущество.

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

item A → shelf 10

Позже другой процесс переместил товар:

item A → shelf 37

В длинной истории модели всё ещё могут находиться десятки упоминаний:

item A → shelf 10

И только одно новое:

item A → shelf 37

Модель должна понять, какой факт актуален.

В structured state происходит обычное изменение:

{
- "itemA": "shelf10"
+ "itemA": "shelf37"
}

После обновления старого значения больше нет.

В экспериментах авторов history-based агенты после неожиданного изменения внешнего состояния тратили ещё 5–8 шагов на восстановление корректного поведения.

SKILL.state в проверенных сценариях восстанавливался сразу.

Практический пример: агент для работы с постами

Рассмотрим близкий веб-разработке сценарий.

Есть административная панель с постами и LLM-агент, которому пользователь пишет:

Подготовь пост о запуске нового продукта и запланируй его публикацию на 15 октября.

Агент открывает редактор, выбирает нужный раздел, заполняет заголовок и текст, устанавливает дату публикации и после подтверждения сохраняет пост.

Обычный вариант — хранить всё это в conversation history.

Но можно определить state:

interface AgentState {
    goal: string;

    route: {
        name: string;
        params: Record<string, unknown>;
    };

    post?: {
        id?: number;
        title?: string;
        body?: string;
        categoryId?: number;
        publishAt?: string;
        status?: "draft" | "scheduled" | "published";
    };

    form?: {
        values: Record<string, unknown>;
        missing: string[];
        errors: Record<string, string>;
    };

    plan: {
        completed: string[];
        pending: string[];
    };

    pendingConfirmation?: {
        action: string;
        payload: unknown;
    };
}

После нескольких действий состояние может выглядеть так:

{
  "goal": "create_post",
  "route": {
    "name": "post-create",
    "params": {}
  },
  "post": {
    "title": "Запуск нового продукта",
    "body": "Мы запускаем новый продукт...",
    "categoryId": 7,
    "publishAt": "2026-10-15T09:00:00Z",
    "status": "draft"
  },
  "form": {
    "values": {
      "title": "Запуск нового продукта",
      "category_id": 7,
      "publish_at": "2026-10-15T09:00:00Z"
    },
    "missing": [],
    "errors": {}
  },
  "plan": {
    "completed": [
      "open_post_editor",
      "select_category",
      "fill_content",
      "set_publish_date"
    ],
    "pending": [
      "confirm",
      "schedule_post"
    ]
  }
}

Теперь модели не требуется помнить исходное сообщение:

Подготовь пост о запуске нового продукта и запланируй его публикацию на 15 октября.

Информация из него уже преобразована в состояние:

{
  "title": "Запуск нового продукта",
  "category_id": 7,
  "publish_at": "2026-10-15T09:00:00Z"
}

А если пользователь скажет:

Давай перенесём публикацию на 20 октября.

агенту достаточно изменить:

{
- "publishAt": "2026-10-15T09:00:00Z"
+ "publishAt": "2026-10-20T09:00:00Z"
}

Старое значение перестаёт существовать в execution context.

Но состояние придётся проектировать

У SKILL.state есть очевидная цена.

Если раньше можно было просто складывать сообщения в массив:

messages.push(message);

то теперь необходимо решить:

Что агент должен помнить?

Какие данные являются временными?

Какие данные понадобятся позже?

Какие поля можно удалить?

Какие значения являются источником истины?

То есть часть ответственности переносится с context window модели на архитектуру приложения.

Авторы называют schema состояния first-class частью runtime.

При этом схема необязательно должна быть огромной. Например, для всех 100 задач InterCode CTF использовалась одна схема всего из пяти полей:

discovered_flags
tested_hypotheses
active_files
working_dir
cmd_summary

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

Результаты на реальных agent benchmarks

SKILL.state проверяли не только на синтетическом складе.

На InterCode CTF результаты составили:

ReAct       43.2%
Summary     46.4%
LangGraph   41.8%
SKILL.state 54.2%

На τ-Bench Retail:

ReAct       48.2%
Summary     29.9%
LangGraph   51.7%
SKILL.state 58.3%

На τ-Bench Airline:

ReAct       21.8%
Summary     23.6%
LangGraph   28.1%
SKILL.state 32.4%

Во всех трёх случаях SKILL.state показал лучший результат.

Это интересно, потому что речь идёт уже не просто об оптимизации стоимости prompt.

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

Что из этого можно использовать уже сейчас

SKILL.state — скорее архитектурный паттерн, чем технология, которую обязательно нужно устанавливать отдельной библиотекой.

Простейший runtime можно представить примерно так:

while (!state.completed) {
    const observation = await environment.observe();

    const result = await llm.run({
        procedure,
        state,
        observation,
    });

    validateStatePatch(result.statePatch);

    state = applyPatch(
        state,
        result.statePatch
    );

    await tools.execute(result.action);
}

Здесь особенно важно разделение ответственности:

LLM
→ reasoning и выбор следующего действия

State
→ долговременное состояние выполнения

Tools
→ взаимодействие с внешним миром

Runtime
→ validation и применение переходов состояния

При необходимости отдельно могут существовать RAG, долговременная память пользователя, база данных и история разговора.

Но они не обязаны быть execution state агента.

Вместо вывода

Главная идея SKILL.state на самом деле довольно старая для обычной разработки:

не нужно восстанавливать состояние приложения из его логов.

Conversation history для LLM-агента во многом является именно таким логом.

Она полезна для отладки, аудита и общения с пользователем, но это не означает, что её нужно целиком передавать модели при каждом следующем действии.

Для короткого чат-бота разница может быть несущественной.

Но если агент должен автономно выполнить 50, 100 или 200 действий, работать с файлами, API, браузером, CRM или административной панелью, явное состояние выглядит гораздо более масштабируемой архитектурой:

Conversation → события

State → текущая истина

LLM → переход состояния

Tools → изменение внешнего мира

Возможно, следующий этап развития agent frameworks будет заключаться не в создании всё более сложных механизмов сжатия длинного контекста, а в том, чтобы перестать складывать в контекст то, что изначально должно было быть состоянием программы.


Источник: Sanket Badhe, Priyanka Tiwari, Jonghyun Chung — SKILL.state: Scalable Long-Horizon Agent Skills, 2026.

https://arxiv.org/html/2608.26263


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Сколько будет 1 + 5?