Skip to content

Latest commit

 

History

History
435 lines (267 loc) · 49.8 KB

File metadata and controls

435 lines (267 loc) · 49.8 KB

codex-harness-internals

Язык: Русский · English

Reverse-engineered спецификация harness Codex CLI (OpenAI) — механики агентского цикла, tools registry, компакции и управления контекстом. Извлечена из исходников репозитория и оформлена как навигируемый knowledge graph для тех, кто строит AI-агенты или пишет инструкции для них.

Компаньон к серии статей OpenAI «Unrolling the Codex agent loop» / «Harness engineering» / «Unlocking the Codex harness».


5 мест, где ваши инструкции режутся молча

Автор инструкций обычно не знает, что harness применяет жёсткие числовые отсечки внутри собственной логики сборки prompt'а. Усечение происходит тихо — ни warning, ни ошибки. Ниже — пять мест, где это реально случается.

1. 5 000 байт — лимит на текст approved command prefixes. Когда вы описываете политику утверждения shell-команд через список разрешённых префиксов (git commit, npm install, и т.д.), harness рендерит их в developer-инструкции как блок текста. Как только этот текст превышает 5 000 байт — он обрезается до последнего корректного UTF-8 символа, и в конец добавляется маркер ...[Some commands were truncated]. Модель получит неполный список, но вы об этом не узнаете. Что делать: группируйте префиксы (git * вместо пяти отдельных git commit, git push…), либо уносите критичные правила в base_instructions текстом.

2. 100 штук — лимит на количество элементов в prefix list. Даже если байтов меньше 5 000, harness всё равно возьмёт максимум 100 префиксов после сортировки по (длина, суммарная длина, алфавит). Остальные отбрасываются без предупреждения. Что делать: если у вас больше 100 правил — это уже не список префиксов, а политика, и её следует выражать декларативно в base_instructions, а не перечислением.

3. 20 000 токенов — лимит на user-messages после компакции. Когда harness запускает авто-компакцию, он сохраняет только самые свежие user-сообщения — ровно столько, сколько укладывается в 20 000 токенов суммарно. Всё, что старше, растворяется в одном summary-абзаце. Если вы «раскрывали» правила последовательно через десяток user-сообщений — начало серии после компакции исчезнет. Что делать: каркас правил держите в base_instructions (они пересобираются заново на каждый turn), а пошаговое уточнение — понимайте, что оно живёт только до следующей компакции.

4. 90% от context window — порог авто-компакции. Harness сам запускает компакцию, когда суммарное потребление токенов превышает 9/10 размера окна модели. После компакции все developer-сообщения выбрасываются целиком — фильтр should_keep_compacted_history_item квалифицирует их как «stale/duplicated instructions». Это значит: правила, которые harness подложил через developer-инъекции для одного turn'а (approval policy, sandbox-инструкции, экранное окружение), перестанут существовать в истории после первой же компакции. Что делать: не полагайтесь на то, что developer-инструкция «помнится»; если правило важно — оно должно быть в base_instructions.

5. 1 MiB — жёсткий лимит на user-input. Любой user-input длиннее 1 048 576 символов будет отклонён harness'ом до отправки в модель. Для текстовых запросов это много, но если вы программно склеиваете большие куски (лог-файлы, диффы, выгрузки) — легко упереться. Что делать: предварительно усекайте большие вставки на своей стороне, не надейтесь что harness «что-нибудь придумает».


5 параметров, определяющих поведение harness

Для harness-инженера полезны не «81 тип событий», а значения, которые определяют контракт с вашим клиентом.

1. Канал submission — capacity 512. Harness принимает операции от клиента через канал с фиксированным размером 512. Когда канал заполнен — клиент блокируется (backpressure). Если вы строите свой оркестратор — аналогичный параметр надо выбирать осознанно: слишком маленький приводит к затыкам UX, слишком большой — маскирует медленную обработку. Codex выбрал 512 как компромисс между отзывчивостью и защитой от «захлёба» heavy-turn'ами.

2. Agent job concurrency — default 16, max 64. Когда harness запускает CSV-spawn (параллельные sub-agents по таблице), дефолт — 16 параллельных воркеров, жёсткий максимум — 64. Это ограничение не про технику, а про стоимость: каждый активный sub-agent держит свой turn с собственным потреблением токенов. Если в вашем harness нет аналогичного cap'а — клиент может непреднамеренно запустить сотни параллельных агентов и поймать billing shock.

3. Lock retry — 10 попыток по 100 ms. История сессии пишется в rollout-файл с блокировкой; при конфликте harness пытается захватить lock 10 раз с паузой 100 ms. Это дизайн-решение для одновременной работы нескольких процессов (например, CLI + VS Code extension в одной сессии). Если вы пишете свой persistence — учтите, что без retry аналогичная схема развалится на первом же конкурентном доступе.

4. Max scan files при resume — 10 000. Когда harness восстанавливает сессию из rollout-архива, он сканирует максимум 10 000 файлов. Это предел глубины истории, которую можно «откатить» через resume. Для harness-инженера аналогичный предел — это про ресурсы при старте, а не про functionality; сканирование без cap'а приведёт к минутам задержки на старте клиента.

5. Wait timeout — от 10 секунд до 1 часа. Для блокирующих tool-вызовов (wait-операций) harness нормирует timeout в диапазон 10s–3600s. Меньше 10s — отклоняется (защита от busy-loop'ов), больше часа — тоже (защита от зависания сессии). Если ваш tool registry принимает произвольные timeout от модели — вы рискуете получить либо DoS (модель попросит ждать вечно), либо «дребезг» (модель будет вызывать wait по секунде).


Кому пригодится

Разработчикам инструкций и промптов для агентов (основная ЦА)

Вы пишете системные промпты, AGENTS.md, свод правил, skill-набор — и хотите знать, как harness на самом деле сложит это с историей и передаст модели. Спека отвечает на:

  • какие инструкции переживают компакцию, а какие — нет;
  • где физически окажутся ваши правила в финальном prompt;
  • какие пороги молча срежут ваш текст до того, как модель его увидит;
  • как skill-упоминания активируют инжекцию без раздувания контекста;
  • почему именно ваши правила «забываются» после N turns.

Разработчикам harness-систем

Вы строите свой агентский цикл — turn loop, streaming, tool dispatch, session persistence — и хотите эталонную реализацию. Спека даёт:

  • полную механику turn: Item lifecycle, TurnItem → legacy EventMsg mapping, TTFT/TTFM метрики;
  • двухпутьевую компакцию (inline summarization vs /responses/compact endpoint);
  • retry-политику с префиксным кэшем при ContextWindowExceeded;
  • fork / resume / rollback семантику thread'ов.

Что содержит спека

18 SRC-модулей × 3 линзы:

Линза Что Всего
rules.json Инварианты, пороги, формулы, GWT-примеры (Given / When / Then) 1 028 правил
structural.json Сущности, поля, отношения, потоки данных 588 структур
use-case.json Сценарии: шаги, Cockburn-расширения, edge-кейсы, lifecycle 325 use-cases
Итого ≈ 1 941 композиция

Диапазон покрытия: D1-protocol (типы, wire-форматы) → D2-state (сессии, треды, rollout) → D3-agent (ядро: spawn, turn loop, компакция) → D4-context (turn-context, initial context, env) → D5-tools (registry, dispatch, sub-agents).

Вне scope: sandbox, security, MCP-интеграция, hooks/plugins internals, auth, аналитика, network policy, провайдеры моделей, CLI / TUI / IDE порты.


Что Codex решил иначе

Это не «термины из кода», а решения команды, по которым Codex отличается от типичной агентной практики. Для каждого — что сделано, что делают обычно, и что это даёт автору инструкций или harness-инженеру. Критерий отбора: (1) это решение с видимой альтернативой; (2) альтернатива типична в индустрии (LangChain / AutoGen / Assistants API делают иначе); (3) различие меняет поведение ЦА, а не просто внутренняя деталь.

1. Инструкции имеют пять уровней с разным временем жизни

Что сделал Codex. Разделил инструкции на пять семантических слоёв, каждый со своей моделью жизненного цикла: base_instructions (пересобираются каждый turn), initial_context (реинжектируются после компакции), skill injections (активируются @-упоминанием), developer messages (runtime policy), user messages (task-специфика).

Что делают обычно. В LangChain / AutoGen / Assistants API есть плоский system prompt + messages[], и один system prompt для всей жизни треда. Инструкции либо вшиты раз и навсегда, либо «размазаны» в user-сообщениях без структуры.

Что даёт ЦА. Автор инструкций получает ответ на вопрос «куда положить правило, чтобы оно работало всегда». Правило в base_instructions переживёт компакцию и не зависит от истории; правило в developer-message — волатильное, пропадёт при первой компакции; правило в skill-файле — активируется только когда нужно. Это даёт осознанное проектирование ruleset'а, а не надежду «модель это запомнит».

2. Skills активируются по @-упоминанию — это lazy instructions

Что сделал Codex. Модульные наборы инструкций (skills) не попадают в prompt по умолчанию. Они подгружаются только когда пользователь явно упомянул скилл через @skill-name (collect_explicit_skill_mentions) или когда harness видел этот скилл в предыдущих turn'ах (implicit_invocation_seen_skills).

Что делают обычно. В типичном агенте все tool-описания и все правила загружаются в prompt сразу. Это работает для 5–10 tools, но ломается на 50 — модель начинает теряться, prompt раздувается до десятков тысяч токенов постоянной нагрузки.

Что даёт ЦА. Вы можете держать произвольно большой каталог правил, разбитый по темам (@testing-rules, @security-review, @refactor-style), и у каждого turn'а в контексте оказываются только те, что реально нужны. Базовый промпт остаётся маленьким — только мета-правила «когда какой skill звать». Это принципиально другая модель масштабирования инструкций.

3. Контекст обновляется диффом, а не полной реинъекцией

Что сделал Codex. Когда environment не изменился между turn'ами, harness не отправляет его снова. Для этого хранится reference_context_item — снимок последнего отправленного контекста. На новом turn'е build_environment_update_item сравнивает текущее состояние с reference и эмитит user-сообщение только с изменёнными полями, обёрнутыми в <environment_context> маркер.

Что делают обычно. Классический подход — приклеивать полный system context к каждому запросу. Модель видит одни и те же CWD, git branch, файловую систему десятки раз подряд.

Что даёт ЦА. Экономия токенов на статичных правилах, и — что важнее — модель не «теряется» в повторах. Когда одно и то же env-сообщение пересылается каждый turn, модель начинает придавать ему лишний вес; diff-обновление делает контекст «шумом, который приходит только когда что-то реально случилось». Автор инструкций, который кладёт свои environment-сведения через стандартный <environment_context> фрагмент, автоматически получает эту экономию.

4. Компакция есть, но /undo всё ещё работает

Что сделал Codex. Перед компакцией harness собирает ghost snapshots — слепки истории — и дописывает их в конец новой сжатой истории. Фильтр should_keep_compacted_history_item специально их сохраняет, даже когда удаляет всё остальное.

Что делают обычно. Компакция в большинстве систем — необратимая операция: старая история заменена на summary, и вернуться уже нельзя. Пользователь, начавший длинную сессию, теряет возможность «откатить» в момент, когда компакция уже была.

Что даёт ЦА. Для harness-инженера — паттерн дизайна compaction'а, при котором один инвариант (разумный размер prompt'а) не разрушает другой (возможность отката). Это нетривиальное решение, оно требует дополнительной бухгалтерии (снимки размечены флагом, фильтр их пропускает), но результат — безопасность длинных сессий, где пользователь не боится «переборщить» и потерять прогресс.

5. Пользовательские инструкции и scaffolding — это разные вещи

Что сделал Codex. Harness различает шесть видов «контекстных фрагментов», каждый со своим XML-маркером: <agents_md>, <environment_context>, <skill>, <user_shell_command>, <turn_aborted>, <subagent_notification>. Эти куски текста формально не считаются пользовательским вводом — они не считаются user_turn_boundary при rollback, обрабатываются отдельно при трим-проходе, и часть из них (AGENTS_MD, SKILL) исключается из memory generation.

Что делают обычно. Весь user-role текст трактуется одинаково: и реальная команда пользователя, и boilerplate-инструкции, и env-дамп. В результате rollback до «последнего user-сообщения» может вернуть не туда, потому что «последним user-сообщением» окажется системный env-фрагмент.

Что даёт ЦА. Автору инструкций — нельзя оборачивать свои правила в произвольный XML и ожидать, что harness их распознает. Если обернуть в <my_rules> — это останется обычным user-text'ом и будет обрезано при трим-проходе в первую очередь. Правильный путь: либо положить в base_instructions, либо воспользоваться стандартным <skill> через skill-инжекцию. Этот же контракт полезен harness-инженеру: если вы строите свой агент, разделение «пользовательский ввод vs служебные вставки» должно быть первоклассной концепцией, а не соглашением в комментариях.

6. Reviewer-агент имеет собственную политику, не наследует от caller

Что сделал Codex. Когда запускается Guardian — специальный reviewer-подагент для утверждения shell-команд — он игнорирует exec-policy, унаследованную от родителя, и использует built-in safety checks (ExecPolicyManager::default). Это отдельный case в коде: правило guardian-reviewer-bypass-exec-policy.

Что делают обычно. Sub-agents наследуют политику от caller'а. Это интуитивно — если главный агент настроен доверять git *, пусть и sub-agent доверяет. Но в случае reviewer это опасно: caller может «самоодобрить» собственные команды через reviewer с caller-specific rules.

Что даёт ЦА. Harness-инженеру — паттерн «security-critical sub-agent должен быть архитектурно изолирован». Это редкое, но принципиальное различие: reviewer не может быть подкуплен контекстом caller'а. Если вы строите агентскую систему с self-review — конкретно эту часть нельзя делать «по-простому», иначе вся идея reviewer'а разрушается. Codex это прямо закодировал как инвариант.


Как использовать — примеры запросов

Оба примера ниже — это демонстрация работы со спекой на живых запросах. Читатель приходит с произвольным тезисом и получает развёрнутый содержательный ответ, собранный из SRC-модулей. Никакой предварительной ориентации в файлах спеки не требуется.

Исходный запрос пользователя:

«И нужно сделать по спеке 2 примера из данных спеки — прямо показать как ты их создал: запрос и что получилось. 1 пример — запрос: объясни самую сложную и важную механику агента Codex. 2 пример — запрос: помоги решить проблему затухания бюджета внимания, когда инструкция содержит много правил. Эти запросы обрабатывай используя эту спеку.»

Уточнения:

«2 пример должен быть для разработчика инструкций. Из данных спек нужно делать ответы для примеров. И ещё напиши запросы, с помощью которых ответы получились — иначе пример плохой, он должен показать как использовать спеку. Перепиши примеры на обучающие: поменьше ссылок на спеку, побольше что там содержится, чтобы можно было не переходя по ссылкам суть понимать.»


Пример 1. Объяснить сложную механику

Короткий ответ: самое сложное в Codex — это авто-компакция истории беседы. Она важна, потому что держит turn loop живым при исчерпании context window; сложна — потому что объединяет пять разнородных задач: обнаружение близости к лимиту, выбор реализации под провайдера, безопасный retry при переполнении, сохранение инвариантов истории и поддержку /undo.

Когда срабатывает

Harness считает порог как 90% от context window модели, с возможностью ужесточить его в конфиге (меньшее значение выигрывает):

auto_compact_token_limit = min(config_limit, context_window × 9/10)

Если оба значения не заданы — авто-компакция выключена. Срабатывает в трёх режимах:

  • pre-turn — перед обработкой нового user-сообщения, если прошлое total usage уже за порогом;
  • mid-turn — внутри активного turn, когда модель вот-вот упрётся в окно;
  • manual — пользовательская команда /compact.

Разница принципиальна: mid-turn оставляет «якорь» на текущий turn context, чтобы следующий turn шёл diff'ом. Manual и pre-turn не ставят якорь — следующий turn делает полную реинжекцию.

Две реализации: inline vs remote

Harness сначала спрашивает провайдера: «умеешь удалённую компакцию?». Если да — вся история отправляется на серверный endpoint /responses/compact, который возвращает уже сжатый Vec<ResponseItem>. Если нет — harness делает inline summarization: подкладывает собственный SUMMARIZATION_PROMPT, гоняет обычный streaming turn и получает текст-summary.

Решение выбирается по флагу provider.supports_remote_compaction() и фиксируется на старте — в середине переключения не бывает.

Retry-цикл при переполнении окна (inline)

Самая нетривиальная часть. Inline-компакция внутри себя делает streaming-запрос и может получить от модели ContextWindowExceeded — потому что prompt с историей + summarization-инструкциями сам не помещается. Harness реагирует так:

  • если в истории больше одного itemhistory.remove_first_item(), truncated_count++, retries = 0, continue. Жертвуем самым старым item'ом, чтобы сохранить префиксный кэш провайдера и последние сообщения (где обычно свежие user-инструкции);
  • если остался один item или меньшеset_total_tokens_full, Error event, выход. Даже минимальный контекст не помещается — дальше уже не спасёт.

На другие ошибки (сеть, rate-limit) — retry с exponential backoff до provider.stream_max_retries(). Interrupted / TurnAborted — немедленный выход без retry. При pre-trim в UI эмитится:

"Trimmed N older thread item(s) before compacting..."

Что именно остаётся в истории после компакции

Harness строит build_compacted_history_with_limit:

  1. Берёт все user-messages кроме summary-сообщений (они узнаются по префиксу — тексту SUMMARIZATION_PROMPT + \n).
  2. Итерирует в reverse и набирает, пока суммарно не превысит COMPACT_USER_MESSAGE_MAX_TOKENS = 20 000.
  3. Собирает итоговый список: initial_context + набранные свежие user-messages + summary_user_message последним.

Если summary_text пуст — подставляется "(no summary available)".

Где физически окажется initial context

Модель Codex обучена видеть summary в последней позиции истории. Harness поддерживает этот инвариант:

insertion_index =
  last_real_user_index          // перед последним настоящим user message
  ?? last_user_or_summary_index // если real user не нашлось  перед summary
  ?? last_compaction_index      // если и этого нет  перед последним Compaction
                                // иначе  append в конец

GWT-пример:

  • Given: compacted_history = [compaction_item, summary_user_message], initial_context = [env_msg, dev_msg]
  • When: вставка
  • Then: last_real_user_index = None (summary не реальный), last_user_or_summary_index = 1, insertion = 1 → [compaction_item, env_msg, dev_msg, summary_user_message]

Summary остаётся хвостом — контракт сохранён.

Remote pre-trim: что можно резать, что нельзя

Перед отправкой на remote компакцию harness оценивает estimate_token_count_with_base_instructions. Если прогноз превышает окно — режет хвост истории, но только пока последний item codex-generated:

  • FunctionCallOutput / ToolSearchOutput / CustomToolCallOutput — можно резать
  • developer Message — можно
  • user / assistant Message — break, не трогаем границу

Инвариант: при удалении tool-output парный tool-call снимается через normalize::remove_corresponding_for, иначе модель получит «сиротский» call без ответа.

Ghost snapshots: сохранение /undo после компакции

Перед компакцией harness собирает GhostSnapshot items — снимки для rollback'а. После сборки нового compacted_history они дописываются в конец и помечены фильтром should_keep_compacted_history_item как сохраняемые. Иначе /undo после компакции стал бы невозможен.

Что выживает фильтр should_keep_compacted_history_item (remote)

Remote endpoint может вернуть items любого вида — harness фильтрует:

Выбрасывается: developer messages (stale/duplicated instructions), user-role messages, не являющиеся UserMessage/HookPrompt (session-prefix wrappers), Reasoning, FunctionCall/Output, ToolSearchCall/Output, CustomToolCall/Output, LocalShellCall, WebSearchCall, ImageGenerationCall, GhostSnapshot, Other.

Сохраняется: assistant messages, Compaction items, user-role warnings и summary messages.

Практический вывод: всё, что автор инструкций заложил в «разовые» developer-инъекции, после компакции будет выкинуто.

Telemetry и фича-гейтинг

Все события компакции эмитятся с захардкоженным strategy = CompactionStrategy::Memento — константа на стороне analytics, независимо от реальной реализации. Отправка gate'ится через Feature::GeneralAnalytics: флаг фиксируется в CompactionAnalyticsAttempt::begin и не перепроверяется в track(). Выключение аналитики посреди turn'а не отменит уже начатый event.

Finalizer переводит Result в CompactionStatus:

  • OkCompleted
  • Err(Interrupted) / Err(TurnAborted)Interrupted
  • прочие ErrFailed

Побочный UX-эффект, важный для автора инструкций

По завершении inline-компакции harness эмитит warning toast:

"Heads up: Long threads and multiple compactions can cause the model to be less accurate. Start a new thread when possible to keep threads small and targeted."

Это сигнал деградации: каждый новый раунд компакции отбрасывает дополнительный слой контекста, потому что developer-инструкции не переживают фильтр, а summary — это уже «пересказ пересказа». Отсюда правило: системные инструкции должны жить в base_instructions (пересобираются заново на каждый turn), а не в runtime developer-инъекциях.

Почему это «самая сложная» механика

Один путь исполнения пересекает шесть независимых инвариантов:

  1. Token arithmetic (estimate_tokens_with_base_instructions)
  2. Провайдер-детект (remote vs inline)
  3. Сетевой retry с backoff и префиксным кэшем
  4. History invariants (call/output pair, user/assistant boundary, summary last)
  5. Rollback (ghost snapshots)
  6. Feature-flags и analytics state machine

Ошибка в любом — либо OOM на контексте, либо silent-деградация качества модели.


Пример 2. Решить проблему затухания внимания в инструкциях

Короткий ответ: harness уже решает задачу «много правил vs ограниченное окно», но не прощает авторам инструкций размещение правил в «волатильных» зонах. Если правила сложить не туда — модель получит усечённую версию молча. Ниже шесть практических правил, каждое с механикой и конкретными цифрами.

Правило 1. Держитесь ниже 90% context window

Harness автоматически запускает компакцию при used_tokens > context_window × 9/10. После компакции:

  • сохраняются только user-messages последние 20 000 токенов суммарно (константа COMPACT_USER_MESSAGE_MAX_TOKENS);
  • всё старше растворяется в summary;
  • developer-сообщения (в том числе с вашими правилами) выбрасываются полностью как «stale/duplicated instructions».

Практика: если у вас окно 200k токенов — безопасная зона до 180k. Если вы «раскрываете» правила пошагово через десяток user-сообщений — начало серии исчезнет после первой же компакции.

Правило 2. Каркасные правила — только в base_instructions

Harness резолвит base_instructions по приоритету:

base_instructions =
    config.base_instructions                         // явный override
    ?? conversation_history.get_base_instructions()  // из rollout session_meta
    ?? model_info.get_model_instructions(personality) // model default

Ключевое свойство: base_instructions пересобираются на каждый turn заново. Они не живут в истории — приходят как system-инструкции отдельным полем запроса. Поэтому переживают компакцию и не зависят от того, какая часть истории ещё в контексте.

Практика:

  • неизменные правила поведения, стиль ответа, хард-инварианты → base_instructions;
  • runtime policy (sandbox, approvals) → developer messages (harness положит сам);
  • task-specific детали → user message (последние 20k переживут компакцию).

Правило 3. Список approved command prefixes имеет жёсткие лимиты

Если вы настраиваете политику утверждения shell-команд через список префиксов, harness рендерит их в developer instructions с двумя усечениями:

  • байтовый лимит: MAX_ALLOW_PREFIX_TEXT_BYTES = 5 000. При превышении — текст обрезается до последнего корректного UTF-8 char и добавляется маркер ...[Some commands were truncated].
  • штучный лимит: MAX_RENDERED_PREFIXES = 100. Берутся первые 100 после сортировки по (len, combined_str_len, alphabetical). Остальные просто не попадают в prompt.

Если вы перечислили 200 разрешённых команд — модель увидит только 100. Какие именно — зависит от лексикографического порядка. Harness не предупреждает.

Приёмы обхода:

  • группировать префиксы (git * вместо git commit, git push, git pull отдельно);
  • критичные запреты выносить в base_instructions текстом, а не в exec policy.

Правило 4. Используйте skill-упоминания вместо раздутого системного промпта

Harness поддерживает lazy-инжекцию skill-инструкций. Последовательность внутри turn-а:

  1. collect_explicit_skill_mentions(user_input) — находим @-упоминания skills в тексте пользователя;
  2. resolve_skill_dependencies_for_turn — проверяем env-переменные, которые нужны этим skills;
  3. build_skill_injections — собираем инструкции только для упомянутых skills;
  4. record_conversation_items — добавляем в историю turn-а.

У вас может быть десятки skills с детальными правилами, но в prompt попадают только те, что пользователь явно позвал. Это прямой способ держать большой ruleset вне постоянного контекста.

Практика: разбейте свод правил на модули по темам (@testing-rules, @security-review, @refactoring-style). Базовый промпт содержит только мета-правила «когда какой skill звать». Активный turn тянет детали по требованию.

Правило 5. Уважайте contextual fragment markers

Harness различает обычный user-текст и контекстные фрагменты — их шесть видов с жёсткими тегами-обёртками:

  • <agents_md>...</agents_md> (AGENTS_MD_FRAGMENT)
  • <environment_context>...</environment_context>
  • <skill>...</skill> (SKILL_FRAGMENT)
  • <user_shell_command>...</user_shell_command>
  • <turn_aborted>...</turn_aborted>
  • <subagent_notification>...</subagent_notification>

При нормализации истории и при pre-trim перед компакцией harness специально обрабатывает эти метки: видит их как «имеющие смысл» и иначе относится к их обрезке. Environment и subagent-notification фрагменты сохраняются в memory inputs.

Что произойдёт, если обернуть правила в свой XML-тег <my_rules>...? Harness разметит их как обычный user-text и при триме обрежет первыми — без различия, что там критичное. Хотите, чтобы правила выжили? Либо base_instructions, либо стандартный SKILL_FRAGMENT через skill-инжекцию.

Правило 6. initial context инжектируется в определённом месте

После mid-turn компакции harness вставляет initial_context по цепочке приоритета:

  1. перед последним настоящим user message (не summary);
  2. если настоящих нет — перед последним user-like (включая summary);
  3. если и их нет — перед последним Compaction item;
  4. иначе — append в конец.

Итоговый порядок для модели — [..., initial_context, last_real_user, summary] или [..., initial_context, summary]. Summary всегда остаётся последним — это контракт с обученной моделью.

Практика: не пытайтесь положить что-то «после» последнего user message — harness всё равно переложит. Размещайте контекстные правила внутри initial_context блоков (env, agents.md), а не как trailing user-reminder в конце истории — после компакции их там не будет.

Шаблон правильного распределения правил

┌─────────────────────────────────────────────────────────────┐
 base_instructions        инварианты: стиль, безопасность,  
                           рамки поведения                   
                           (пересобираются каждый turn)      
├─────────────────────────────────────────────────────────────┤
 initial_context          env, AGENTS.md, постоянные skill- 
                           metadata                          
                           (реинжектируются после компакции) 
├─────────────────────────────────────────────────────────────┤
 skill injections         модульные детальные ruleset'ы,    
                           активируются @-упоминанием        
                           (не живут в постоянном контексте) 
├─────────────────────────────────────────────────────────────┤
 developer messages       runtime policy (approval/sandbox) 
                           (волатильные, лимит 5000 байт /   
                           100 prefixes, усекаются молча,    
                           выбрасываются при компакции)      
├─────────────────────────────────────────────────────────────┤
 user messages            task-специфика                    
                           (последние 20 000 токенов         
                           переживают компакцию)             
└─────────────────────────────────────────────────────────────┘

Как проверить, что правило «дошло» до модели

  • пережило компакцию → оно в base_instructions, initial_context, или в последних 20k user-токенов;
  • не усечено silent-truncate'ом → developer-prefix-list ≤ 5000 байт и ≤ 100 штук;
  • появляется при нужном контексте → завёрнуто в стандартный SKILL_FRAGMENT и активируется упоминанием.

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


Структура и навигация

knowledge/
├── SRC-0001/ ... SRC-0018/        # 18 модулей, в каждом:
   ├── rules.json                 #   инварианты и формулы
   ├── structural.json            #   сущности и отношения
   └── use-case.json              #   сценарии
├── scaffold_index.json            # плоский индекс composition ID по SRC
├── graph.json                     # рёбра triggers / depends_on / contains
└── README.md

Композиции связаны ссылками вида →triggers SRC-0011/uc.sub/auto-compact-inline — по ним можно переходить через scaffold_index.json и graph.json.

Методика запросов к спеке (grep + jq-рецепты): см. HOW-TO-QUERY.md.


Keywords

English: openai codex harness · codex cli internals · codex agent loop · harness engineering · agent conversation turn lifecycle · tool registry · thread turn item · context compaction · auto-compact · responses compact endpoint · codex app server · prompt engineering reference · AI agent architecture spec · codex reverse engineering · base_instructions priority · initial context injection · SUMMARIZATION_PROMPT · CompactionStrategy::Memento · skill injection · prompt cache key · context window management

Русский: openai codex харнесс · codex cli внутреннее устройство · агентский цикл codex · harness engineering · реверс-инжиниринг codex · спецификация harness · жизненный цикл turn агента · tool registry codex · компакция контекста · автокомпакция · база инструкций агента · инструкции для AI-агентов · промпт-инжиниринг для агентов · управление контекстным окном · разработка AI-агентов · как работает codex · архитектура AI-агентов · harness для LLM-агентов · skill-инжекция · initial context инжекция

Reference specification for Codex harness (OpenAI Codex CLI) extracted as a knowledge graph: rules, use-cases, structural entities across 18 SRC modules covering protocol, session state, agent core, turn context, and tools registry. Companion to OpenAI's «Unrolling the Codex agent loop» series. Use cases: writing agent instructions, building your own agent harness, reverse-engineering compaction strategies, understanding turn lifecycle.

Спецификация harness Codex CLI (OpenAI), извлечённая как knowledge graph: 1 028 правил, 588 структур, 325 use-cases по 18 SRC-модулям — протокол, состояние сессии, ядро агента, управление контекстом, tools registry. Компаньон к серии статей OpenAI о harness-engineering. Для кого: авторы инструкций и промптов AI-агентов, разработчики собственных harness-систем, все, кто изучает архитектуру агентов-кодеров.


Понравилась работа?

Если спека оказалась вам полезна — поставьте звезду репозиторию. Это помогает другим разработчикам агентов и авторам инструкций находить её в поиске GitHub.

Ваш фидбек и предложения по расширению покрытия (новые SRC-модули, уточнения формул, недостающие edge-кейсы) приветствуются в Issues.