mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
379 lines
14 KiB
Markdown
379 lines
14 KiB
Markdown
# Model-visible context
|
|
|
|
This document describes how model-visible prompt context is represented in the
|
|
current `codex-rs` codebase, and how to add new context without breaking
|
|
resume, compaction, backtracking, or history parsing.
|
|
|
|
The key modules are:
|
|
|
|
- [`codex-rs/core/src/model_visible_context.rs`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/model_visible_context.rs):
|
|
shared fragment abstractions, roles, marker helpers, and diff params
|
|
- [`codex-rs/core/src/model_visible_fragments.rs`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/model_visible_fragments.rs):
|
|
concrete fragment definitions, the central registry, shared contextual-user
|
|
detection, and turn-state fragment assembly
|
|
- [`codex-rs/core/src/context_manager/updates.rs`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/context_manager/updates.rs):
|
|
the shared developer/contextual-user envelope builders and steady-state
|
|
per-turn diff assembly
|
|
- [`codex-rs/core/src/codex.rs`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/codex.rs):
|
|
initial-context assembly and the normal runtime path that decides whether to
|
|
inject full context or only diffs
|
|
|
|
## Core model
|
|
|
|
Model-visible prompt context falls into three buckets:
|
|
|
|
1. Turn-state fragments.
|
|
These are derived from current durable turn/session state and are the ones
|
|
that must survive history-mutating flows such as resume, compaction,
|
|
backtracking, and fork by being rebuilt from current state plus an optional
|
|
persisted baseline.
|
|
2. Registered runtime fragments.
|
|
These are not derived from `TurnContext` diffs, but they are still modeled
|
|
as typed fragments because they are emitted into model-visible history and,
|
|
for contextual-user fragments, must still be recognized during later history
|
|
parsing.
|
|
3. Developer-only one-off text injections.
|
|
These are not currently represented as dedicated registered fragments. They
|
|
are developer-role text added in a few call sites where no contextual-user
|
|
detection or turn-state diffing is needed.
|
|
|
|
The single most important distinction is whether the model-visible state is:
|
|
|
|
- durable turn/session state that should be rebuilt from `TurnContext`
|
|
- or a one-off event/message that is only relevant because it just happened
|
|
|
|
That determines whether the fragment needs `build(...)`.
|
|
|
|
## Two-envelope rule
|
|
|
|
Turn-state context assembly always produces exactly two envelopes:
|
|
|
|
- one `developer` message
|
|
- one contextual `user` message
|
|
|
|
Those envelopes are assembled by the shared builders in
|
|
[`updates.rs`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/context_manager/updates.rs).
|
|
Adjacent text content items are explicitly separated with `\n\n` because, from
|
|
the model's perspective, adjacent text `ContentItem`s inside one message are
|
|
effectively concatenated.
|
|
|
|
This two-envelope rule applies to turn-state context assembly:
|
|
|
|
- [`Codex::build_initial_context(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/codex.rs#L3342)
|
|
- [`build_settings_update_items(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/context_manager/updates.rs#L105)
|
|
|
|
It does not mean every model-visible message in the system is forced into those
|
|
two envelopes. Runtime/session-prefix messages may still be emitted as separate
|
|
messages when the event itself is what needs to be recorded, and some
|
|
initial-only contextual/developer additions are appended to the envelopes
|
|
outside registry-driven `build(...)` assembly.
|
|
|
|
## Current architecture
|
|
|
|
### 1. Shared fragment trait
|
|
|
|
Every named fragment type implements
|
|
[`ModelVisibleContextFragment`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/model_visible_context.rs#L180).
|
|
|
|
That trait owns:
|
|
|
|
- `type Role`
|
|
- `render_text()`
|
|
- optional `build(...)` for turn-state fragments
|
|
- optional contextual-user detection via `contextual_user_markers()` or
|
|
`matches_contextual_user_text()`
|
|
- standard conversions such as `into_message()` and `into_response_input_item()`
|
|
|
|
Roles are:
|
|
|
|
- `DeveloperContextRole`
|
|
- `ContextualUserContextRole`
|
|
|
|
### 2. Central registry
|
|
|
|
Current fragment types are registered exactly once in
|
|
[`REGISTERED_MODEL_VISIBLE_FRAGMENTS`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/model_visible_fragments.rs#L123).
|
|
|
|
That registry is used for:
|
|
|
|
- contextual-user fragment detection during history parsing
|
|
- turn-state fragment assembly for both envelopes by calling `build(...)`
|
|
|
|
Registration is therefore an integration step, not just bookkeeping. Defining
|
|
the type is not enough; the fragment is not part of the system until it is
|
|
registered.
|
|
|
|
### 3. Build semantics
|
|
|
|
[`ModelVisibleContextFragment::build(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/model_visible_context.rs#L187)
|
|
is the canonical hook for turn-state fragments.
|
|
|
|
It receives:
|
|
|
|
- the current `TurnContext`
|
|
- `reference_context_item: Option<&TurnContextItem>`
|
|
- `TurnContextDiffParams`
|
|
|
|
`reference_context_item` is the persisted durable baseline already represented
|
|
in model-visible history. A turn-state fragment should compare against that
|
|
baseline to avoid emitting duplicate prompt state. When there is no baseline
|
|
(`None`), the fragment should decide whether to emit full current context or
|
|
fall back to `TurnContextDiffParams` for other relevant previous-turn/session
|
|
state such as `previous_turn_settings`.
|
|
|
|
Turn-state fragments return:
|
|
|
|
- `Some(fragment)` when model-visible state should be injected
|
|
- `None` when the current state is already represented and no update is needed
|
|
|
|
Fragments that are not turn-state-derived leave `build(...)` as the default
|
|
`None`.
|
|
|
|
## Initial context vs steady-state diffs
|
|
|
|
The code intentionally treats these differently.
|
|
|
|
### Full initial context
|
|
|
|
[`Codex::build_initial_context(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/codex.rs#L3342)
|
|
always builds turn-state fragments with `reference_context_item = None`. In
|
|
other words, it asks every turn-state fragment for the full current prompt
|
|
state, not a diff.
|
|
|
|
That is the right behavior for:
|
|
|
|
- first real user turn in a thread
|
|
- any path that must re-establish canonical prompt context from scratch
|
|
|
|
There is also
|
|
[`build_initial_context_without_reference_context_item(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/codex.rs#L3445),
|
|
which currently delegates to the same full-context behavior and exists to make
|
|
the “ignore any existing baseline” intent explicit in compaction rebuild paths.
|
|
|
|
### Steady-state turn updates
|
|
|
|
[`build_settings_update_items(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/context_manager/updates.rs#L105)
|
|
passes the current `reference_context_item` into the registry-driven turn-state
|
|
builder loop, so registered turn-state fragments can emit only the minimal
|
|
developer/contextual-user diffs for the next turn.
|
|
|
|
### Normal runtime path
|
|
|
|
[`record_context_updates_and_set_reference_context_item(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/codex.rs#L3479)
|
|
uses this rule:
|
|
|
|
- if there is no baseline, inject full initial context
|
|
- otherwise inject only turn-state diffs
|
|
|
|
After each real user turn it persists the latest `TurnContextItem` and advances
|
|
the in-memory baseline even if no model-visible diff message was emitted. That
|
|
is what keeps later diffing aligned with current state.
|
|
|
|
## Contextual-user detection
|
|
|
|
Contextual-user fragments share the `user` role with real user messages, so
|
|
history parsing must be able to distinguish:
|
|
|
|
- contextual state
|
|
- true user intent
|
|
|
|
That is why contextual-user fragments need stable detection.
|
|
|
|
Preferred path:
|
|
|
|
- implement `contextual_user_markers()` when the fragment has stable fixed
|
|
wrappers
|
|
|
|
Fallback path:
|
|
|
|
- override `matches_contextual_user_text()` when detection is genuinely custom
|
|
(for example the current AGENTS.md wrapper)
|
|
|
|
The shared detection entrypoint is
|
|
[`is_contextual_user_fragment(...)`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/model_visible_fragments.rs#L843).
|
|
It first checks the current registry, then applies a very small legacy shim for
|
|
historical user-role wrappers that were shipped previously but are no longer
|
|
current fragment types.
|
|
|
|
Current legacy shim:
|
|
|
|
- old user-role `<subagent_notification>...</subagent_notification>` messages
|
|
|
|
Those legacy checks should stay bounded and local to detection. Do not create
|
|
fake current fragment types just to recognize old persisted history.
|
|
|
|
## Current fragment inventory
|
|
|
|
### Registered turn-state developer fragments
|
|
|
|
These implement `build(...)` and participate in both full initial context and
|
|
steady-state diffs:
|
|
|
|
- `ModelInstructionsUpdateFragment`
|
|
- `PermissionsUpdateFragment`
|
|
- `CustomDeveloperInstructionsUpdateFragment`
|
|
- `CollaborationModeUpdateFragment`
|
|
- `RealtimeUpdateFragment`
|
|
- `PersonalityUpdateFragment`
|
|
|
|
### Registered runtime developer fragments
|
|
|
|
These are typed and registered, but not built from `TurnContext` diffs:
|
|
|
|
- `SubagentRosterContext`
|
|
- `SubagentNotification`
|
|
|
|
### Registered turn-state contextual-user fragments
|
|
|
|
These implement `build(...)` and participate in both full initial context and
|
|
steady-state diffs:
|
|
|
|
- `UserInstructionsFragment`
|
|
- `AgentsMdInstructions`
|
|
- `JsReplInstructionsFragment`
|
|
- `SkillsSectionFragment`
|
|
- `ChildAgentsInstructionsFragment`
|
|
- `EnvironmentContext`
|
|
|
|
Some of these are true steady-state diff fragments (`UserInstructionsFragment`,
|
|
`AgentsMdInstructions`, `EnvironmentContext`). Others intentionally rebuild only
|
|
when there is no baseline and therefore behave as initial-context fragments
|
|
expressed through the same `build(...)` hook (`JsReplInstructionsFragment`,
|
|
`SkillsSectionFragment`, `ChildAgentsInstructionsFragment`).
|
|
|
|
### Registered runtime contextual-user fragments
|
|
|
|
These are typed and registered for rendering/detection, but not built from
|
|
`TurnContext` diffs:
|
|
|
|
- `SkillInstructions`
|
|
- `PluginInstructions`
|
|
- `UserShellCommandFragment`
|
|
- `TurnAbortedMarker`
|
|
|
|
`PluginInstructions` is currently appended to the initial contextual-user
|
|
envelope during full initial-context assembly. `SkillInstructions`,
|
|
`UserShellCommandFragment`, and `TurnAbortedMarker` are currently emitted as
|
|
standalone contextual-user messages.
|
|
|
|
## Current non-registry developer text injections
|
|
|
|
The current codebase still has a few developer-role prompt injections that are
|
|
not dedicated registered fragments. These are accurate descriptions of current
|
|
behavior, not the preferred path for new work.
|
|
|
|
Current examples include:
|
|
|
|
- memory-tool initial developer instructions
|
|
- apps guidance in the initial developer envelope
|
|
- commit-attribution guidance in the initial developer envelope
|
|
- explicit plugin-mention developer hints
|
|
- a few one-off runtime developer messages such as stop-hook and hook-provided
|
|
additional context
|
|
|
|
These are acceptable because they are developer-role only and do not need
|
|
contextual-user detection or turn-state diff reconstruction. But for new
|
|
model-visible context, prefer a dedicated typed fragment unless the message is
|
|
truly an isolated one-off developer event.
|
|
|
|
## Choosing the right representation
|
|
|
|
### Use a registered turn-state fragment when:
|
|
|
|
- the context is derived from `TurnContext` / durable session state
|
|
- resume, fork, compaction, or backtracking should be able to rebuild it
|
|
- it belongs in one of the two turn-state envelopes
|
|
|
|
Examples:
|
|
|
|
- permissions policy
|
|
- collaboration mode
|
|
- realtime start/end state
|
|
- custom user instructions
|
|
- AGENTS.md instructions
|
|
- JS REPL guidance
|
|
- skills catalog guidance
|
|
- child-AGENTS guidance
|
|
- environment context
|
|
|
|
### Use a registered runtime fragment when:
|
|
|
|
- the context is model-visible
|
|
- it is not derived from `TurnContext` diffs
|
|
- it still benefits from standardized rendering and, for contextual-user
|
|
fragments, shared detection
|
|
|
|
These fragments may either:
|
|
|
|
- be appended to an envelope in a non-diff initial-context path
|
|
- or be emitted as standalone runtime/session-prefix messages
|
|
|
|
Examples:
|
|
|
|
- turn-aborted marker
|
|
- user shell command record
|
|
- skill injection
|
|
- plugin session instructions
|
|
- subagent roster / subagent notification
|
|
|
|
### Use a plain developer text wrapper only when:
|
|
|
|
- the message is developer-role only
|
|
- it is a narrow one-off or initial-only addition
|
|
- it does not need contextual-user detection
|
|
- it does not need to participate in turn-state diff reconstruction
|
|
|
|
If you are tempted to use a plain `String` or `DeveloperTextFragment`, stop and
|
|
verify that this is really just an isolated developer-only event and not new
|
|
shared prompt state.
|
|
|
|
## Environment context rule
|
|
|
|
Use `<environment_context>` only for environment facts derived from
|
|
`TurnContext` that may need turn-to-turn refresh semantics.
|
|
|
|
Current fields are:
|
|
|
|
- `cwd`
|
|
- `shell`
|
|
- optional `current_date`
|
|
- optional `timezone`
|
|
- optional network allow/deny summaries
|
|
|
|
Do not put other guidance in `<environment_context>`, especially:
|
|
|
|
- permissions/policy instructions
|
|
- skills or plugins
|
|
- AGENTS instructions
|
|
- subagent notices
|
|
|
|
Those should stay in their own fragment types.
|
|
|
|
## Blessed path for new work
|
|
|
|
When adding new model-visible context:
|
|
|
|
1. Decide whether the model should see it at all.
|
|
2. Decide whether it is durable turn/session state or a one-off runtime event.
|
|
3. If it should be represented as a named fragment, define a typed fragment in
|
|
[`model_visible_fragments.rs`](/Users/ccunningham/code/codex-worktree-tria/codex-rs/core/src/model_visible_fragments.rs).
|
|
4. Implement `ModelVisibleContextFragment`.
|
|
5. Set `type Role` correctly.
|
|
6. If it is turn-state context, implement `build(...)`.
|
|
7. If it is contextual-user, provide stable detection with
|
|
`contextual_user_markers()` or custom `matches_contextual_user_text()`.
|
|
8. Register it exactly once in `REGISTERED_MODEL_VISIBLE_FRAGMENTS`, in prompt
|
|
order.
|
|
9. Use the shared envelope builders or fragment conversions rather than
|
|
hand-constructing model-visible `ResponseItem::Message` payloads in new
|
|
turn-state paths.
|
|
|
|
Rule of thumb:
|
|
|
|
- “This is durable prompt state” => registered typed fragment, usually with
|
|
`build(...)`
|
|
- “This is a one-off contextual/runtime marker” => registered typed fragment,
|
|
usually without `build(...)`
|
|
- “This is an isolated developer-only text event” => plain developer text is
|
|
sometimes acceptable, but it is the exception, not the default
|