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).
- Threshold: Fixed at 85% of model context length
- Token source: Prefers actual API-reported tokens from last turn; falls back
to rough character-based estimate (
estimate_messages_tokens_rough) - Fires: Only when
len(history) >= 4and compression is enabled - Purpose: Catch sessions that escaped the agent's own compressor
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 и кэшируется все крутится. Не изменяйте его в середине разговора (сжатие добавляет примечание только при первом уплотнении).
-
Порядок сообщений имеет значение: попадания в кэш требуют сопоставления префиксов. Добавление или удаление сообщений в середине делает недействительным кеш для всего последующего.
-
Взаимодействие с кэшем сжатия: после сжатия кэш становится недействительным. для сжатой области, но кэш системных подсказок сохраняется. прокатка Окно из 3 сообщений восстанавливает кэширование в течение 1-2 ходов.
-
Выбор 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% контекстного окна модели.