Память и контекст¶
Отказы доставки журнала Control Plane в память, деградированный контекст для harness, ошибки авторизации Memory Service, медленный или неточный поиск. Статья для инженера эксплуатации и оператора, у которого агенты получают пустой или устаревший контекст.
Как устроен путь данных¶
flowchart LR
subgraph cp[Control Plane]
J[(журнал событий)] --> A[context-adapter<br/>singleton, курсор на tenant]
API[control-plane-api<br/>/api/v1/context]
end
A -->|наблюдения, IAM-токен или MEMORY_API_KEY| M[memory-service]
API -->|сборка контекста, таймаут CP_CONTEXT_TIMEOUT_SECONDS| M
M --> DB[(memory-db<br/>AGE + pgvector)]
M -->|эмбеддинги, реранк| LLM[LLM-провайдер]
Два независимых пути:
- Доставка (асинхронная):
context-adapterчитает журнал и пишет наблюдения в namespacetenant:<tenant-id>. Отказ не блокирует координацию — растёт отставание или tenant паркуется. - Сборка контекста (синхронная, на пути harness):
control-plane-apiзапрашивает память с коротким таймаутом (CP_CONTEXT_TIMEOUT_SECONDS, по умолчанию 3 с). Если память не ответила — контекст отдаётся деградированным, счётчикcontext_degraded_totalрастёт.
Быстрая диагностика¶
curl -s http://127.0.0.1:18001/healthz
curl -s http://127.0.0.1:18000/metrics | grep -E '^context_'
control-plane ops adapter status
docker compose logs --since 30m context-adapter | tail -50
docker compose logs --since 30m memory-service | grep -iE 'error|timeout|401|403'
# проверить статический ключ памяти (эндпоинт требует авторизации)
curl -s -H "Authorization: Bearer $MEMORY_API_KEY" http://127.0.0.1:18001/api/brain/health
Доставка в память¶
| Симптом | Причина | Решение |
|---|---|---|
context_adapter_parked_tenants > 0, freshness.memoryIngest.status = "parked" |
Провайдер памяти отверг событие; причина — в parkedReason (control-plane ops adapter status) |
Устранить причину и control-plane ops adapter redrive <tenant-id> --reason "…". Redrive повторяет ту же позицию, курсор не двигается |
parkedReason содержит 401 |
Ядро ходит в память с неверным credential: сменился MEMORY_API_KEY без пересоздания ядра или отозван service account |
Пересоздать control-plane-api control-plane-worker context-adapter; при необходимости перевыпустить service account (см. Секреты и ротация) |
parkedReason содержит 403 про namespace |
Credential ядра не имеет гранта на tenant:<id> |
Service account ядра должен иметь scope memory:tenants; повторный bootstrap приводит потолок к реестру |
parkedReason про неизвестный вид или схему наблюдения |
Версии ядра и памяти рассинхронизированы | Выровнять сабмодули на ревизиях суперпроекта, пересобрать, redrive |
context_adapter_lag растёт, парковки нет |
Память медленно принимает (провайдер эмбеддингов), адаптер отстаёт | Проверить задержки провайдера; CB_EMBEDDING_TIMEOUT (в compose — 60 с) |
context_adapter_lag_capped = 1 |
Отставание больше 1000 событий | То же; после устранения адаптер догонит сам |
| Ядро продолжает ходить статическим ключом после bootstrap | CP_CONTEXT_AUTH=auto выбирает IAM, только если при создании контейнера был secrets/control-plane-iam.env |
docker compose up -d control-plane-api control-plane-worker context-adapter; проверка: в выводе docker compose exec context-adapter env есть CP_IAM_CLIENT_ID |
| Память восстановлена из старого бэкапа, контекст неполный | Курсор адаптера в базе Control Plane впереди содержимого памяти | POST /api/v1/operations/context-adapter/<tenant-id>:rebuild; память дедуплицирует повторы |
| Наблюдения приняты, но в графе их нет | Проекция наблюдения не удалась (сырые записи не теряются) | GET /api/memory/observations?status=failed&namespace=…, затем идемпотентный redrive POST /api/memory/consolidate |
Координация от памяти не зависит
Пока доставка стоит, claims, runs, approvals и завершение задач работают как обычно. Парковка одного tenant не мешает доставке остальных.
Сборка контекста¶
| Симптом | Причина | Решение |
|---|---|---|
Harness получает контекст без памяти, растёт context_degraded_total |
Память не ответила за CP_CONTEXT_TIMEOUT_SECONDS (3 с) или недоступна |
Проверить memory-service; если медленно из-за реранкера или провайдера — выбрать более быструю модель (LLM_MODEL) или выключить MEMORY_RERANK_ENABLED |
Растёт context_provider_failures_total |
Память отвечает ошибкой на интерактивный запрос | Логи memory-service, коды ниже |
| Контекст собран, но не тот | Нужно понять, почему выбраны именно эти фрагменты | GET /api/memory/context/trace/{trace_id} — трасса сборки; run_id запроса (X-Run-Id) попадает в трассы памяти |
Ошибки Memory Service¶
Memory Service отвечает {"detail": "<текст>"}; ниже — тексты ответов.
Статус и detail |
Причина | Решение |
|---|---|---|
401 Требуется корректный Authorization: Bearer <key> |
Нет заголовка, неверный статический ключ или невалидный IAM-токен (подпись, срок, iss, aud не memory-service) |
Проверить ключ; для IAM-токена — audience memory-service при обмене |
503 Проверка IAM-токена недоступна (JWKS/конфигурация IAM) |
JWKS IAM недоступен (fail closed); статические ключи при этом продолжают работать | Восстановить IAM; CB_IAM_JWKS_URL должен указывать на внутренний адрес http://iam-service:8010/.well-known/jwks.json |
403 Нет прав на namespace: <ns> (…) |
Грант credential не покрывает namespace | IAM-токен даёт доступ к tenant:<tenant_id> и его поддереву; ключам реестра — гранты по префиксу |
403 Маршрут доступен только identity ядра (memory:service / CB_CORE_IDENTITIES) |
Маршрут ядра (reconcile, пакеты видов, виды namespace) вызван не ядром | Вызывать через Control Plane; service account ядра имеет scope memory:service |
403 Нужен service scope (регистрация пакетов видов) |
Регистрация пакета видов без service scope | То же: через ядро |
403 Namespace вне видимости principal: … |
Включена видимость по principal (MEMORY_POLICY_ENABLED=true), principal не видит namespace |
Проверить роли в policy-service |
503 policy-service недоступен — видимость памяти не определена |
Видимость включена, а policy-service недоступен (fail closed) | Поднять профиль policy или выключить MEMORY_POLICY_ENABLED |
503 БД недоступна: … на /healthz |
memory-db не отвечает |
docker compose logs memory-db, диск, память контейнера |
Ошибка graph with oid … does not exist |
База восстановлена логическим дампом в новый кластер | Исправление каталога AGE, см. Резервное копирование |
404 на /console |
Консоль памяти выключена (MEMORY_CONSOLE_ENABLED=false) |
Включать только вместе с аутентификацией на прокси; наружу в промышленной раскладке память не публикуется |
Качество и скорость поиска¶
| Симптом | Причина | Решение |
|---|---|---|
| Поиск возвращает случайные фрагменты | Работают офлайн-провайдеры по умолчанию: MEMORY_EMBEDDING_PROVIDER=fake, MEMORY_LLM_PROVIDER=echo |
В промышленной установке — openai (OpenAI-совместимый endpoint) и ключ провайдера. Знания, загруженные с fake-эмбеддингами, после переключения стоит загрузить заново |
| Запросы к памяти занимают несколько секунд | Реранкер (MEMORY_RERANK_ENABLED=true) делает LLM-вызов на запрос; медленная модель |
Выбрать быструю модель для LLM_MODEL и замерить на реальных запросах; либо выключить реранк |
| Таймауты при загрузке знаний | Провайдер эмбеддингов даёт пики задержки | CB_EMBEDDING_TIMEOUT (в compose — 60 с) |
Порог уверенности не срабатывает: у всех результатов score около 0,03 |
score — результат RRF-слияния каналов, а не вероятность |
Порог уверенности строить по rerank_score (0..1), он есть только при включённом реранке |
Ошибки провайдера 402/429 в логах памяти |
Закончился баланс или лимит у LLM-провайдера | Пополнить или сменить ключ; после восстановления — redrive доставки |
Размерность эмбеддингов фиксирована
Индекс создаётся под CB_EMBEDDING_DIM (в compose — 1536). Смена модели
эмбеддингов на модель другой размерности требует пересоздания индекса и
повторной загрузки знаний.