Когда у тебя уже есть полностью рабочая AI-инфраструктура, внешняя зависимость начинает раздражать особенно сильно.

Что здесь важно шире одного API#

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

Для команды это снижает зависимость от внешней API-схемы. Для инфраструктуры добавляет ответственность: такой слой нужно логировать, ограничивать, обновлять и тестировать как обычный продакшен-сервис.

Hermes Agent, Hindsight и GBrain у меня уже работали. Hindsight должен был переварить большой архив из Cursor — полезные чаты, планы, принятые решения, всю историю работы. Embeddings и reranker крутились на локальных моделях, но извлечение фактов и retain-операции всё равно требовали LLM-запросов.

До этого все запросы уходили через внешний AI-роутер. Это рабочий путь, но он стоит денег и добавляет ещё одну зависимость в контур. При этом отдельного OpenAI API-ключа у меня никогда не было: доступ к моделям шёл через Codex OAuth и уже оплаченную подписку.

Тут сошлись две вещи. С одной стороны, не хотелось платить роутеру за запросы, которые можно было провести через уже имеющийся доступ. С другой — в документации и исходниках Hermes неожиданно обнаружилась возможность поднять локальный HTTP-сервер для внешних OpenAI-compatible клиентов, используя подписки провайдеров, которыми управляет сам Hermes.

Отсюда и выросла идея: а давай подниму свой OpenAI-compatible API поверх того, что уже есть. Чтобы Hindsight получал привычный интерфейс, а авторизация, логи, ограничения скорости и остальное оставались под моим контролем.

В этой статье я расскажу не только про финальный рабочий endpoint, а именно весь путь: от первой идеи, когда Hermes Agent предложил простой standalone-прокси, через найденные community-репозитории и PR сообщества в Hermes, переход к нативному openai-codex и проверку границ такого решения.

От «мне нужен LLM для Hindsight» до рабочего API поверх подписки#

У меня не было отдельного ключа официального OpenAI API. Основной доступ шёл через Codex OAuth и подписку. Это не единственная причина всей затеи, но важное ограничение: нельзя было просто положить API-ключ в .env и закончить работу.

Сначала схема проблемы выглядела так:

Как появилась задача: от Hindsight до Codex-подписки
runtime memory
Hindsight retain
Для извлечения фактов нужен LLM
external dependency
AI-роутер
OpenAI-compatible API, но запросы стоят денег
discovery
Hermes proxy
Локальный HTTP-слой для внешних клиентов
existing access
Codex
OAuth и подписка уже есть

Для Hindsight нужен был не «полный OpenAI API вообще», а конкретный инженерный адаптер: совместимый HTTP-интерфейс, защищённый Bearer-токеном, нужная модель и предсказуемая потоковая выдача. Сначала хватало трёх маршрутов:

GET  /health
GET  /v1/models
POST /v1/chat/completions

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

Сначала был простой прокси#

Первая идея была гораздо проще финального решения. Hermes Agent предложил сделать локальный standalone-прокси: взять уже доступный Hermes Codex OAuth, спрятать его за HTTP-слоем с OpenAI-совместимым интерфейсом и дать Hindsight обычный base_url с Bearer-авторизацией.

Тогда схема выглядела как короткий переходник:

Первый вариант: отдельный standalone-прокси
client
Hindsight
Хочет обычный base_url
extra service
Proxy
Ещё один процесс и auth boundary
upstream
Codex
Подписка и OAuth

Это был разумный первый шаг для проверки гипотезы, но не конечная архитектура. Прокси нужно отдельно запускать, обновлять и защищать. Учётные данные, авторизацию клиентов и совместимость с Responses API тоже пришлось бы контролировать отдельно. Оставлять критическую часть контура внешним standalone-костылём не хотелось, если тот же путь можно сделать нативно внутри Hermes.

Что уже было в сообществе#

Сначала мы искали обычный standalone-прокси. Потом я специально стал искать решение внутри Hermes — и там обнаружилась более интересная ветка. В официальной документации Hermes subscription proxy описан как локальный HTTP-сервер для внешних OpenAI-compatible клиентов, который использует подписку провайдера, управляемую Hermes. На публичной странице proxy на тот момент прямо фигурировали nous и xai. Отдельно в документации провайдеров Hermes был описан openai-codex: вход через ChatGPT OAuth/device code и хранение учётных данных в Hermes auth store.

То есть документация подтверждала две части по отдельности: сам proxy-механизм и наличие Codex OAuth как Hermes-managed provider. А возможность использовать именно openai-codex внутри hermes proxy пришлось проверять уже по PR, исходникам, CLI и локальным тестам.

Вокруг добавления openai-codex в hermes proxy были открыты community/upstream PR. На момент статьи это ещё не была бесшовно поставляемая зависимость, которую можно просто подключить без проверки:

  • PR #54877 переиспользовал существующий пул Codex-учётных данных и helper для Cloudflare-заголовка, переводил Chat Completions в Responses. При ревью всплыли потеря tools/tool_choice, text-only/non-streaming completion и расхождения между документацией и --help.
  • PR #62297 добавлял upstream-пул Codex OAuth. На ревью отдельно проверяли логирование client attribution и документацию; после follow-up атрибуцию убрали, а owner-only key file и пути описали явно. В тестах было 49 passed.
  • PR #62510 добавлял аутентифицированный Codex upstream. Там проверяли обновление только через пул, права 0600 на файл токена, проверку типа файла и расхождения документации. Важная граница безопасности: downstream Authorization не пробрасывается наверх.

Ценность этих PR была не в том, чтобы скопировать любую ветку целиком. Они показали реальные границы решения: OAuth нельзя просто прокинуть наружу, токен клиента нельзя отправлять upstream, а повторные попытки, ротация учётных данных и совместимость с Responses API требуют отдельной проверки.

Какие готовые прокси ещё смотрели#

До перехода на нативный путь мы перебрали несколько самостоятельных реализаций. Это помогло сравнить архитектурные подходы, но standalone-репозитории и Hermes PR — разные ветки поиска, не один и тот же продукт.

Самым близким к исходной задаче оказался mehdic/codex-proxy. Он оборачивает официальный Codex app-server через stdio JSON-RPC и отдаёт небольшой локальный HTTP API, совместимый с OpenAI-клиентами. Важная идея совпадала с нашей: использовать подписку через авторизацию Codex, не копируя OAuth-токены в другое приложение.

Также посмотрели:

  • dvcrn/codex-oauth-proxy — Go-прокси, который выставляет Codex/ChatGPT Plus или Pro через обычный OpenAI API;
  • Securiteru/codex-openai-proxy — Rust-прокси для CLINE, Claude Code и других клиентов с OpenAI-совместимым API;
  • EvanZhouDev/openai-oauth — OAuth-провайдер, CLI и локальный прокси, работающие с локальными файлами авторизации;
  • icebear0828/codex-proxy — более широкий OpenAI-совместимый прокси для Codex Responses API;
  • David-Factor/codex-responses-proxy — небольшой Go-прокси для Responses API поверх Codex CLI и авторизации подписки ChatGPT.

У проектов разные границы совместимости, схемы авторизации и степень готовности. Поэтому мы не стали бездумно выбирать один репозиторий и тащить его в рабочий контур. Эти проекты подтвердили другое: задача типовая. Многим нужен OpenAI-совместимый слой поверх Codex-подписки. Но у каждого решения приходится отдельно проверять границы авторизации, потоковую выдачу, Responses API и вызовы инструментов. Туда же относятся повторные попытки, хранение учётных данных и поддержка нескольких клиентов.

После этого направление изменилось. Вместо «быстро поднимем отдельный прокси» мы пошли к нативному openai-codex внутри hermes proxy, сохранив внешний OpenAI-совместимый API для Hindsight. Локальная реализация в итоге опиралась на ветку native-codex-single, основанную на PR #54877 и скорректированную после его ревью. Проверены health, models, chat completions, streaming, отсутствие credential forwarding и 49 passed в тестах.

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

Первый тест: сервис действительно жив#

Начали с простой проверки доступности.

Пустой запрос без учётных данных должен был сразу получать отказ:

/health     → 401
/v1/models  → 401

С авторизацией из защищённого файла:

/health     → 200
/v1/models  → 200

/v1/models возвращал JSON в формате OpenAI, а моделью по умолчанию была gpt-5.5.

После этого проверили главное — настоящий запрос к модели через:

POST /v1/chat/completions

Он прошёл. Затем отдельно проверили stream=true. Это важный тест: многие совместимые прокси умеют вернуть один JSON-ответ, но ломаются на потоковой выдаче, которую используют агентские инструменты.

В какой-то момент стало понятно, что старый standalone-прокси не стоит оставлять единственной опорой. В Hermes добавили нативный путь через hermes proxy с провайдером openai-codex, сохранив прежнюю внешнюю поверхность и маршрут для клиента. Под капотом реализация изменилась, но стабильный внешний интерфейс остался тем же. Старый standalone-сервис был только локальной reference/fallback-реализацией, а не отдельным продуктовым маршрутом.

Финальная проверка тогда выглядела так:

  • /v1/models — OpenAI-формат;
  • /v1/chat/completions200;
  • streaming — работает;
  • Bearer через внешний интерфейс — работает;
  • тесты Hermes — 49 passed;
  • credential forwarding отсутствует.

Это уже был не сырой скрипт с ответом на /health, а сервис для подключения к другим компонентам.

Зачем понадобился Hindsight#

Параллельно я разбирал большой архив из Cursor.

Сырой экспорт нельзя было просто залить в память. В нём были рабочие чаты, планы, служебные файлы, MCP, инструменты, skills, assets, кэши и прочий мусор. Сначала мы отфильтровали корпус, вытащили полезные разговоры и планы, добавили redaction, проверили секретные паттерны и только потом собрали JSONL-бандл для импорта.

Итоговый корпус получился немаленьким:

полезных документов: 4 594
Hindsight chunks:     7 271
размер бандла:        около 192 MB

Импорт сделали возобновляемым. Отдельный файл состояния хранит последний индекс. Если процесс остановить, он продолжит с этого места, а не начнёт всё сначала.

Секреты при этом не передавались через argv. После отдельной проверки утечек в командной строке получили:

argv secret leaks = 0

Для Hindsight внешний LLM нужен только на этапе retain и извлечения фактов. Embeddings и reranker у нас работают локально:

embeddings: BAAI/bge-m3
reranker:   BAAI/bge-reranker-v2-m3

Поэтому отсутствие /v1/embeddings у нашего прокси не стало препятствием. API не обязан полностью повторять OpenAI API. Для этого контура Hindsight хватило chat completions.

До своего API: почему внешний роутер всё-таки был полезен#

До переключения мы использовали внешний OpenAI-совместимый роутер с маршрутом модели openai/gpt-5.6-luna.

Выбор модели был не просто гонкой за минимальной ценой. openai/gpt-5.6-luna оказалась самой дешёвой моделью в 5.6-семействе у этого роутера и при этом достаточно качественной для retain: аккуратно извлекала факты, держала стабильный формат ответа и не плодила мусорные записи в памяти. По опубликованному тарифу это было 101 ₽ за 1M входных токенов и 608 ₽ за 1M исходящих. Зафиксированный импорт GBrain израсходовал 1 644 544 входных и 460 421 исходящих токенов — примерно 446 ₽ по этому тарифу. Полный импорт из Cursor мы тогда не обсчитали: в логах остался объём chunks, но не суммарный token usage.

Сначала retain выполнялся с одним параллельным запросом. Затем мы сделали аккуратную проверку на двух запросах одновременно:

HINDSIGHT_API_LLM_MAX_CONCURRENT=4
HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT=2
HINDSIGHT_API_CONSOLIDATION_LLM_MAX_CONCURRENT=1
BATCH_SIZE=2

На участке 154 → 168 получили:

14 chunks / 423,7 секунды ≈ 1,98 chunks/min

До этого было примерно 1,0–1,1 chunks/min.

При этом в логах не увидели 429, 5xx или timeout. Hindsight оставался в состоянии healthy, а пакеты возвращали status=200.

Но скорость — не единственный критерий. Для памяти важнее качество извлечённых фактов, стабильность схемы и отсутствие тихих потерь. Поэтому мы не стали сразу поднимать число параллельных запросов до 3 или 4.

Качество важнее красивой цифры в пропускной способности.

Пробный запуск на своём API#

После проверки API подключили свою точку входа к Hindsight не напрямую на основной импорт, а через отдельный canary-bank. Canary здесь — изолированный пробный контур: тест не должен загрязнять основной результат и не должен ломать возобновляемый импорт.

Временно переключили конфигурацию на:

HINDSIGHT_API_LLM_BASE_URL=https://your-openai-compatible-endpoint.example/v1
HINDSIGHT_API_LLM_MODEL=gpt-5.5
HINDSIGHT_API_RETAIN_LLM_MAX_CONCURRENT=1

Первая проверка сразу нашла не проблему API, а проблему инфраструктуры: PostgreSQL внутри Docker упёрся в стандартный /dev/shm размером 64M.

Ошибка выглядела примерно так:

could not resize shared memory segment
No space left on device

Это хороший пример того, почему нельзя проверять только HTTP-ответ прокси. Снаружи API был жив, Hindsight умел до него достучаться, но сама операция записи в память падала на другом участке цепочки.

Для базы добавили shm_size: 1g, пересоздали контейнеры и повторили проверку. После этого:

  • Hindsight поднялся со статусом healthy;
  • соединение с моделью прошло;
  • запись в bank вернула 200;
  • retain-пакет 240 → 242 тоже вернул 200.

Функционально связка заработала.

Но по скорости этот короткий тест дал примерно:

2 chunks / 262,2 секунды ≈ 0,46 chunks/min

Это заметно хуже предыдущего участка через внешний роутер. При этом замер был загрязнён параллельными задачами consolidation, поэтому объявлять его полноценным сравнением было бы нечестно.

Именно здесь пришлось остановиться, чтобы не превращать рабочий импорт в бесконечный эксперимент. Проверку остановили, основной конфиг Hindsight вернули на стабильный роутер, а импорт из Cursor оставили на паузе с сохранённым состоянием. Он может продолжиться с того же checkpoint после чистого сравнения.

Что мы в итоге получили#

Здесь есть несколько разных результатов. Их важно не смешивать.

Получили точно#

  • собственная OpenAI-совместимая точка входа поверх Codex OAuth;
  • закрытый доступ с 401 без учётных данных;
  • рабочие /health и /v1/models;
  • рабочий chat/completions;
  • проверенная потоковая выдача;
  • нативный openai-codex в Hermes;
  • Hindsight умеет подключаться к точке входа;
  • отдельный canary-bank не загрязняет основную память;
  • проблема Docker/PostgreSQL с общей памятью найдена и исправлена;
  • основной импорт умеет возобновляться после остановки.

Пока не доказали#

  • что собственная точка входа быстрее внешнего роутера на чистом одинаковом тесте;
  • что качество извлечённых фактов полностью совпадает на длинной выборке;
  • что её стоит делать постоянной именно для Hindsight;
  • что текущего API с поддержкой chat completions достаточно для всех будущих клиентов.

Это нормальный результат инженерного эксперимента. API уже рабочий, но решение «перевести на него всё» нужно принимать после чистого сравнения, а не после одного удачного 200.

Что дальше#

Сейчас это не MVP отдельного продукта и не заготовка полноценного SaaS. Это личная инфраструктура: сначала нужно довести до ума контур для Hindsight, а уже потом смотреть, пригодится ли тот же слой для support-бота или других клиентов. Цель — «средний нормальный уровень»: безопасно, наблюдаемо и операционно вменяемо, без попытки построить платформу на все случаи жизни.

Дальше план такой:

  1. Завершить backfill Cursor через checkpoints и resume, а не привязывать следующий шаг к случайному промежуточному индексу.
  2. Отдельно сделать чистое сравнение: одинаковые 10–20 chunks, одна и та же модельная задача, без параллельного consolidation.
  3. Сравнить внешний роутер и собственную точку входа по скорости, 401/403/429/5xx, timeout и качеству фактов.
  4. Решить, какой маршрут оставить для Hindsight после проверки фактами, а не по одному удачному 200.
  5. Определить нужную поверхность совместимости: достаточно ли chat completions, или понадобятся /v1/embeddings, Responses API и более полный набор функций для будущих клиентов.

Пока стабильная внешняя поверхность остаётся прежней, а решение о постоянной эксплуатации не маскируется под уже доказанный результат. Это и есть нормальная точка остановки: сначала воспроизводимый тест, потом выбор маршрута, потом расширение.

Когда OpenAI-compatible — это уже инженерная граница#

Самый важный вывод здесь не про «свой OpenAI API» как красивый URL. Когда Hindsight нужен LLM для retain, а Codex-подписка уже есть, локальный proxy-слой действительно может убрать лишнюю платную зависимость. Но сам по себе HTTP-ответ ничего не доказывает.

Под API лежат авторизация, потоковая выдача, выбор модели, systemd, таймауты, ограничения скорости, память Docker, состояние импорта и поведение после перезапуска. Рабочая система — это когда ты понимаешь, что произойдёт при 401, 429, падении базы, остановке процесса и повторном запуске через несколько часов.

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

Если вы строите OpenAI-совместимый слой поверх Codex OAuth, подключаете Hermes Agent или Hindsight либо просто хотите снизить стоимость и риски своей AI-инфраструктуры, напишите мне в Telegram или на почту. Помогу разобрать текущий контур, определить нужные границы совместимости, проверить безопасность и довести API до состояния, где он работает в реальной эксплуатации, а не только в тесте с красивым 200.