Context Compression and Caching

Hermes Agent uses a dual compression system and Anthropic prompt caching to manage context window usage efficiently across long conversations.

Source files: agent/context_engine.py (ABC), agent/context_compressor.py (default engine), agent/prompt_caching.py, gateway/run.py (session hygiene), run_agent.py (search for _compress_context)

Pluggable Context Engine

Context management is built on the ContextEngine ABC (agent/context_engine.py). The built-in ContextCompressor is the default implementation, but plugins can replace it with alternative engines (e.g., Lossless Context Management).

context:
  engine: "compressor"    # default — built-in lossy summarization
  engine: "lcm"           # example — plugin providing lossless context

The engine is responsible for: - Deciding when compaction should fire (should_compress()) - Performing compaction (compress()) - Optionally exposing tools the agent can call (e.g., lcm_grep) - Tracking token usage from API responses

Selection is config-driven via context.engine in config.yaml. The resolution order: 1. Check plugins/context_engine/<name>/ directory 2. Check general plugin system (register_context_engine()) 3. Fall back to built-in ContextCompressor

Plugin engines are never auto-activated — the user must explicitly set context.engine to the plugin's name. The default "compressor" always uses the built-in.

Configure via hermes plugins → Provider Plugins → Context Engine, or edit config.yaml directly.

For building a context engine plugin, see Context Engine Plugins.

Dual Compression System

Hermes has two separate compression layers that operate independently:

                     ┌──────────────────────────┐
  Incoming message      Gateway Session Hygiene   Fires at 85% of context
  ─────────────────►    (pre-agent, rough est.)   Safety net for large sessions
                     └─────────────┬────────────┘
                                                                                           ┌──────────────────────────┐
                        Agent ContextCompressor   Fires at 50% of context (default)
                        (in-loop, real tokens)    Normal context management
                     └──────────────────────────┘

1. Gateway Session Hygiene (85% threshold)

Located in gateway/run.py (search for Session hygiene: auto-compress). This is a safety net that runs before the agent processes a message. It prevents API failures when sessions grow too large between turns (e.g., overnight accumulation in Telegram/Discord).

The gateway hygiene threshold is intentionally higher than the agent's compressor. Setting it at 50% (same as the agent) caused premature compression on every turn in long gateway sessions.

2. Agent ContextCompressor (50% threshold, configurable)

Located in agent/context_compressor.py. This is the primary compression system that runs inside the agent's tool loop with access to accurate, API-reported token counts.

Configuration

All compression settings are read from config.yaml under the compression key:

compression:
  enabled: true              # Enable/disable compression (default: true)
  threshold: 0.50            # Fraction of context window (default: 0.50 = 50%)
  target_ratio: 0.20         # How much of threshold to keep as tail (default: 0.20)
  protect_last_n: 20         # Minimum protected tail messages (default: 20)

# Summarization model/provider configured under auxiliary:
auxiliary:
  compression:
    model: null              # Override model for summaries (default: auto-detect)
    provider: auto           # Provider: "auto", "openrouter", "nous", "main", etc.
    base_url: null           # Custom OpenAI-compatible endpoint

Parameter Details

| Parameter | Default | Range | Description | |lang: ru


-----|---------|-------|-------------| | threshold | 0.50 | 0,0-1,0 | Сжатие срабатывает, когда токены подсказки ≥ threshold × context_length | | target_ratio | 0.20 | 0,10-0,80 | Управляет бюджетом токена защиты хвоста: threshold_tokens × target_ratio | | protect_last_n | 20 | ≥1 | Всегда сохраняется минимальное количество последних сообщений | | protect_first_n | 3 | (жестко запрограммировано) | Системное приглашение + первый обмен всегда сохраняется |

Вычисленные значения (для контекстной модели 200 000 по умолчанию)

context_length       = 200,000
threshold_tokens     = 200,000 × 0.50 = 100,000
tail_token_budget    = 100,000 × 0.20 = 20,000
max_summary_tokens   = min(200,000 × 0.05, 12,000) = 10,000

Алгоритм сжатия

Метод ContextCompressor.compress() следует четырехфазному алгоритму:

Этап 1: обрезка результатов использования старых инструментов (дешево, без необходимости обращения в LLM)

Результаты старых инструментов (>200 символов) за пределами защищенного хвоста заменяются на:

[Old tool output cleared to save context space]

Это дешевый предварительный проход, который экономит значительные токены от многословного инструмента. выходные данные (содержимое файла, вывод терминала, результаты поиска).

Этап 2: определение границ

┌─────────────────────────────────────────────────────────────┐
│  Message list                                               │
│                                                             │
│  [0..2]  ← protect_first_n (system + first exchange)        │
│  [3..N]  ← middle turns → SUMMARIZED                        │
│  [N..end] ← tail (by token budget OR protect_last_n)        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Защита хвоста основана на бюджете токенов: идет назад от конца, накопление токенов до исчерпания бюджета. Возвращается к фиксированному protect_last_n подсчитайте, позволит ли бюджет защитить меньшее количество сообщений.

Границы выровнены, чтобы избежать разделения группtool_call/tool_result. Метод _align_boundary_backward() проходит мимо последовательных результатов инструмента. чтобы найти сообщение помощника для родителей, сохраняя группы нетронутыми.

Этап 3: Создание структурированного резюме

⚠️ Warning

Длина контекста сводной модели Сводная модель должна иметь контекстное окно **по крайней мере такого же размера**, как и у основной модели агента. Вся средняя часть отправляется в сводную модель одним вызовом `call_llm(task="compression")`. Если контекст сводной модели меньше, API возвращает ошибку длины контекста — `_generate_summary()` улавливает ее, регистрирует предупреждение и возвращает `None`. Затем компрессор пропускает средние отрезки **без резюме**, молча теряя контекст разговора. Это наиболее распространенная причина ухудшения качества уплотнения.

Средние витки суммируются с помощью вспомогательной ЛЛМ со структурированной шаблон:

## Goal
[What the user is trying to accomplish]

## Constraints & Preferences
[User preferences, coding style, constraints, important decisions]

## Progress
### Done
[Completed work  specific file paths, commands run, results]
### In Progress
[Work currently underway]
### Blocked
[Any blockers or issues encountered]

## Key Decisions
[Important technical decisions and why]

## Relevant Files
[Files read, modified, or created  with brief note on each]

## Next Steps
[What needs to happen next]

## Critical Context
[Specific values, error messages, configuration details]

Суммарный бюджет зависит от объема сжимаемого контента: - Формула: content_tokens × 0.20 (константа _SUMMARY_RATIO) - Минимум: 2000 токенов - Максимум: токены min(context_length × 0.05, 12,000)

Этап 4. Сборка сжатых сообщений

Список сжатых сообщений: 1. Заголовочные сообщения (с примечанием, добавленным к системному приглашению при первом сжатии) 2. Сводное сообщение (роль выбрана во избежание последовательных нарушений одной и той же роли) 3. Хвостовые сообщения (без изменений)

Потерянные парыtool_call/tool_result очищаются _sanitize_tool_pairs(): - Результаты инструмента ссылаются на удаленные вызовы → удалены. - Вызовы инструментов, результаты которых были удалены → добавлен результат-заглушка

Итеративное повторное сжатие

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

Поле _previous_summary экземпляра компрессора хранит последнюю сводку. текст для этой цели.

Пример «до/после»

До сжатия (45 сообщений, ~95 тыс. токенов)

[0] system:    "You are a helpful assistant..." (system prompt)
[1] user:      "Help me set up a FastAPI project"
[2] assistant: <tool_call> terminal: mkdir project </tool_call>
[3] tool:      "directory created"
[4] assistant: <tool_call> write_file: main.py </tool_call>
[5] tool:      "file written (2.3KB)"
    ... 30 more turns of file editing, testing, debugging ...
[38] assistant: <tool_call> terminal: pytest </tool_call>
[39] tool:      "8 passed, 2 failed\n..."  (5KB output)
[40] user:      "Fix the failing tests"
[41] assistant: <tool_call> read_file: tests/test_api.py </tool_call>
[42] tool:      "import pytest\n..."  (3KB)
[43] assistant: "I see the issue with the test fixtures..."
[44] user:      "Great, also add error handling"

После сжатия (25 сообщений, ~45 тыс. токенов)

[0] system:    "You are a helpful assistant...
               [Note: Some earlier conversation turns have been compacted...]"
[1] user:      "Help me set up a FastAPI project"
[2] assistant: "[CONTEXT COMPACTION] Earlier turns were compacted...

               ## Goal
               Set up a FastAPI project with tests and error handling

               ## Progress
               ### Done
               - Created project structure: main.py, tests/, requirements.txt
               - Implemented 5 API endpoints in main.py
               - Wrote 10 test cases in tests/test_api.py
               - 8/10 tests passing

               ### In Progress
               - Fixing 2 failing tests (test_create_user, test_delete_user)

               ## Relevant Files
               - main.py  FastAPI app with 5 endpoints
               - tests/test_api.py  10 test cases
               - requirements.txt  fastapi, pytest, httpx

               ## Next Steps
               - Fix failing test fixtures
               - Add error handling"
[3] user:      "Fix the failing tests"
[4] assistant: <tool_call> read_file: tests/test_api.py </tool_call>
[5] tool:      "import pytest\n..."
[6] assistant: "I see the issue with the test fixtures..."
[7] user:      "Great, also add error handling"

Быстрое кэширование (антропное)

Источник: agent/prompt_caching.py

Снижает стоимость входных токенов примерно на 75% при многоходовых диалогах за счет кэширования префикс разговора. Использует точки останова cache_control Anthropic.

Стратегия: system_and_3

Anthropic допускает максимум 4 точки останова cache_control на запрос. Гермес использует стратегию «system_and_3»:

Breakpoint 1: System prompt           (stable across all turns)
Breakpoint 2: 3rd-to-last non-system message  ─┐
Breakpoint 3: 2nd-to-last non-system message   ├─ Rolling window
Breakpoint 4: Last non-system message          ─┘

Как это работает

apply_anthropic_cache_control() глубоко копирует сообщения и внедряет Маркеры cache_control:

# Cache marker format
marker = {"type": "ephemeral"}
# Or for 1-hour TTL:
marker = {"type": "ephemeral", "ttl": "1h"}

Маркер применяется по-разному в зависимости от типа контента:

Тип контента Куда идет маркер
Строковое содержимое Преобразовано в [{"type": "text", "text": ..., "cache_control": ...}]
Содержимое списка Добавлено в dict последнего элемента
Нет/пусто Добавлено как msg["cache_control"]
Сообщения инструмента Добавлено как msg["cache_control"] (только в родном Anthropic)

Шаблоны проектирования с учетом кэша

  1. Стабильная системная подсказка: системная подсказка имеет точку останова 1 и кэшируется все крутится. Не изменяйте его в середине разговора (сжатие добавляет примечание только при первом уплотнении).

  2. Порядок сообщений имеет значение: попадания в кэш требуют сопоставления префиксов. Добавление или удаление сообщений в середине делает недействительным кеш для всего последующего.

  3. Взаимодействие с кэшем сжатия: после сжатия кэш становится недействительным. для сжатой области, но кэш системных подсказок сохраняется. прокатка Окно из 3 сообщений восстанавливает кэширование в течение 1-2 ходов.

  4. Выбор TTL: значение по умолчанию — 5m (5 минут). Используйте 1h для длительной работы. сеансы, в которых пользователь делает перерывы между ходами.

Включение кэширования подсказок

Кэширование запросов автоматически включается, когда: - Модель представляет собой модель Anthropic Claude (определяется по названию модели). - Поставщик поддерживает cache_control (родной Anthropic API или OpenRouter).

# config.yaml — TTL is configurable (must be "5m" or "1h")
prompt_caching:
  cache_ttl: "5m"

CLI показывает статус кэширования при запуске:

💾 Prompt caching: ENABLED (Claude via OpenRouter, 5m TTL)

Предупреждения о давлении контекста

Предупреждения о промежуточной контекстной нагрузке были удалены (см. блок итерационного бюджета в run_agent.py, в котором отмечается: «Нет предупреждений о промежуточной нагрузке — они заставляли модели преждевременно «отказываться» от сложных задач»). Сжатие срабатывает, когда токены приглашения достигают настроенного compression.threshold (по умолчанию 50%) без предварительного предупреждения; Гигиена сеанса шлюза действует как вторичная система безопасности в 85% контекстного окна модели.