Collapse the contextual-user detection APIs into a single ContextualUserFragment trait with default marker-based matching and wrapping. Update the fragment docs and registry naming to match the new blessed path, and fix the stale arc monitor test reference exposed by the refactor. Co-authored-by: Codex <noreply@openai.com>
5.9 KiB
Model-visible context fragments
Codex injects model-visible context through two envelopes:
- the developer envelope, rendered as a single
developermessage - the contextual-user envelope, rendered as a single
usermessage whose contents are contextual state rather than real user intent
Both envelopes use the same internal fragment contract in codex-rs/core.
Envelope builders normalize text fragment boundaries by inserting \n\n between adjacent text content items, so fragments do not run together in the model-visible token stream.
Canonical rules
- All model-visible injected context must be represented as a typed
ModelVisibleContextFragment. - Turn-state context assembly always produces exactly two envelopes: one developer message and one contextual-user message.
- Contextual-user fragments must have stable detection so history parsing can distinguish contextual state from true user intent.
- If a fragment is derived from durable/current turn state and should survive history-mutating flows (resume/fork/compaction/backtracking) via re-diffing, it must implement
TurnContextDiffFragment. - Do not hand-construct model-visible
ResponseItem::Messagepayloads in new code. Use fragment conversion (into_message/into_response_input_item) and envelope builders.
Rule 2 applies to turn-state context assembly (build_initial_context / build_settings_update_items). Runtime or session-prefix events may inject standalone fragment messages, but those still must be typed ModelVisibleContextFragments (rules 1 and 5).
Blessed path
When adding new model-visible context:
- Define a typed fragment type.
- Implement
ModelVisibleContextFragmentfor it. - Set the fragment
type Roleto the correct developer or contextual-user role. - If it is a contextual-user fragment, implement contextual-user detection:
- implement
ContextualUserFragment - prefer defining
markers()so the default marker-based detection/wrapping applies - override
matches_contextual_user_text()only when matching is genuinely custom (for example AGENTS.md)
- implement
- If the fragment is derived from durable/current turn state and should be diffed/reinjected after history mutations, also implement
TurnContextDiffFragment. - Register the fragment type:
- developer turn-state fragments: add to
REGISTERED_TURN_STATE_FRAGMENT_BUILDERSincontext_manager/updates.rs. - contextual-user fragments: add once to
REGISTERED_CONTEXTUAL_USER_FRAGMENTSinmodel_visible_context.rs(this powers both history detection and optional turn-state diff assembly).
- developer turn-state fragments: add to
- Push the resulting fragments through the shared envelope builders.
Do not hand-build developer or contextual-user model-visible ResponseItems in new code.
The role lives in the fragment's associated type Role.
Choosing an envelope
Use the developer envelope for developer-role guidance:
- permissions / approval policy instructions
- collaboration-mode developer guidance
- model switch and realtime notices
- personality guidance
- subagent roster and subagent notifications
- other developer-only instructions
Use CustomDeveloperInstructions only for custom developer override text (for example config/app-server developer_instructions values).
For system-generated developer guidance (permissions, collaboration-mode wrappers, realtime notices, personality wrappers, model-switch notices), use typed developer fragments whose text comes from the neutral developer_*_text helpers in codex_protocol::models.
Use the contextual-user envelope for contextual state or runtime markers that should not count as real user turns:
- AGENTS / user instructions
- plugin instructions
- environment context
- skill instructions
- user shell command records
- turn-aborted markers
Contextual-user fragments must have stable detection because history parsing uses it to distinguish contextual state from real user intent.
Use <environment_context> only for environment facts derived from turn/session state (TurnContext) that may need turn-to-turn diffing. Today that includes cwd, shell, optional current_date, optional timezone, and optional network allow/deny domain summaries. Do not put developer policy/instructions or plugin/skill metadata into <environment_context>; those belong in their own typed fragments.
Turn-backed fragments
If a fragment is derived from durable turn/session state and should be updated/reinjected by diff after history mutation, keep its extraction, diffing, and rendering logic together by implementing TurnContextDiffFragment.
TurnContextDiffFragment exposes one build(...) method that receives:
- current
TurnContext - optional
reference_context_item(the turn context state already represented in model-visible history, if available) TurnContextDiffParamsfor shared runtime inputs (for example shell, previous-turn bridge state, exec-policy rendering context, and feature gating flags)
This is envelope-agnostic: both contextual-user state fragments and developer state-diff fragments use the same trait.
If a fragment is runtime-event/session-prefix only (for example subagent completion notification, turn-aborted marker, or user-shell-command marker), ModelVisibleContextFragment alone is enough.
That trait is the blessed path for fragments that need to:
- build full initial context when no reference context item is available
- compute turn diffs when a reference context item is available
EnvironmentContext is the canonical example. Future turn-backed contextual fragments should follow the same pattern instead of introducing one-off extraction or diff helpers.
History behavior
Developer fragments do not need contextual-user marker matching because they are already separable by message role.
Contextual-user fragments do need marker matching because they share the user role with real user turns, and history parsing / truncation must avoid treating injected context as actual user input.