Start with a normal request: ask Codex to fix a bug in the current repository and run a check if needed. If Codex were only a chat shell, the implementation could forward that sentence to a model and stream text back. A real coding agent has to do more. It may read files, edit files, run commands, inspect test output, obey AGENTS.md, wait for approval, retry inside a sandbox, return tool output to the model, and keep the client informed with reviewable progress.

That changes the first source-reading question. Do not ask which directory is "the important one." Ask instead: when a user request enters the system, which component decides the next step? When the request moves from entry code to protocol, session, tool execution, or event handling, the source-reading route should move with it.

The overview claim is simple: Codex is not a UI shell, and the model does not directly control the filesystem. Codex is a structured agent runtime: entries accept intent, protocol types carry operations, session and turn code run the work, approval and sandbox checks precede local actions, and events plus rollout records preserve progress and recovery data.

OpenAI's "Unrolling the Codex agent loop" gives this route its product-level frame: Codex runs a continuing agent loop across user intent, model reasoning, tool calls, and environment feedback. This source note reads the public harness code by asking which structure takes over at each step and what record it leaves behind.

Source scope. This article uses official documentation for product behavior and the public openai/codex source for implementation details. Source links point to one fixed public snapshot. It does not infer the topology or behavior of private OpenAI services.

The reading goal is concrete: narrate one turn as a sequence of handoffs, not a pile of folder names. The route follows five questions:

  1. Why is entry code not the architecture by itself?
  2. What typed value holds user intent after it leaves the entry code?
  3. Which object manages context, cancellation, model streaming, and the tool loop for a turn?
  4. Why is model output not the same thing as command or file authority?
  5. Why do client-visible progress and persistent recovery records use different data?

1. Follow a Request Before Walking the Tree

The Codex repository is large enough to make almost any starting point feel central. The CLI shows commands. The TUI shows user-visible state. The app-server exposes multi-client protocol. The tools directory shows shell and patch behavior. The sandbox code shows containment. All of those entry points are real, but none of them is the whole system.

The safer move is to follow the bug-fix request. At each stage, stop reading the current layer once you know what it handed to the next component.

Turn stage Reading question Start here Later article
Entry Which product entry accepted the request, and what does it refuse to decide locally? CLI, TUI, app-server, MCP server, remote-control. Entries and clients.
Typed carrier Which operation carries intent, and which event carries output back? Submission, Op, Event, EventMsg. Protocol and event stream.
Session / turn Which code manages context, cancellation, pending input, model streaming, and tool continuation? SessionIo facade, Session, run_turn. Session and turn loop.
Context / model Which records are stored, and which messages are sent to the model? ContextManager, TurnContext, for_prompt(). Context management.
Tool authority When the model asks for action, who can approve, reject, sandbox, retry, and record output? ToolRouter, ToolOrchestrator, exec, apply_patch, hooks. Tools, permission, and sandboxing.
Client output / recovery How is the client view separated from durable evidence? Events, turn items, rollout, app-server mapping, TUI rendering. Client rendering and recovery.

Read this table as a gear-shift rule. When responsibility moves to another component, change files. Otherwise, keep reading the current layer. This keeps the first file you understand from becoming the fake center of the architecture.

2. Entry code accepts intent; protocol creates typed operations

Codex has several entry points, but the first important change happens when entry code converts a request into a typed operation. SessionSource already places Cli, VSCode, Exec, Mcp, and SubAgent in the same session-source vocabulary. That is more than an entry inventory. It says an entry is a source label before work enters the shared runtime.

2.1 Submission and Op Turn Intent into Typed Data

At the protocol layer, Submission is not chat text. It carries an id, an op, an optional client user-message id, and trace context. Op is not a single prompt field either. It can represent user input, thread settings, approval, interrupt, realtime conversation, tool refresh, and other operation families.

That lifts the request out of the UI sentence. A bug-fix request can travel with working directory, thread settings, additional context, output schema, or approval policy. The source claim should therefore be precise: an entry submitted a typed operation with a correlation id.

2.2 The SessionIo Facade Is a Queue Pair, Not a Widget

After protocol, the operation crosses the queue interface in SessionIo. It holds tx_sub, rx_event, agent status, and the background submission-loop termination signal. submit() allocates an id and enqueues the operation; next_event() reads runtime output. Successful submission confirms acceptance into the queue; completion is established by later events.

This is the first invariant: clients submit operations; the runtime emits events. Once that queue pair is visible, a TUI state machine or app-server handler becomes one client entry, not the whole agent runtime.

3. A Turn Is the Control Unit

Inside the session, the turn is where context, model streaming, tool calls, pending input, and cancellation meet. run_turn states the sampling loop in plain terms: the model returns function calls or an assistant message. If it returns a function call, the runtime executes the tool and sends output back in the next sampling request. When no tools or other continuation remain, the runtime proceeds to turn completion.

The deeper point is not that the code calls a model in a loop. The point is which code runs before and after that loop. Before sampling, the turn handles pre-sampling compaction, context updates, skills and plugins, hooks, input recording, and previous-turn settings. Only then does it project history through for_prompt() into model input. Later articles unpack those names; here the important point is that they sit around sampling and decide whether the work unit can continue.

one user turn, shape-level:
  submit typed Op
  -> session accepts Submission
  -> run_turn owns this request
  -> update context / skills / hooks
  -> project history into model input
  -> stream model response
  -> execute requested tools after approval and sandbox checks
  -> emit events and durable evidence

TurnContext retains the turn identity, initial settings, and control state. Each model sampling request captures a StepContext: one immutable version of model settings, environments, MCP connections, and the tool router. A turn can contain several sampling requests. Later requests may capture updates, while an in-flight request keeps its captured version so advertised tools and execution remain consistent.

4. Model Output Requests Action; Tool Gate Grants Authority

The easiest phrase to say is "the model ran a command." The source route is stricter. The model can request a tool call only through the model-visible tool specs for this sampling request. build_prompt() builds a Prompt from projected input, step_context.tool_router.model_visible_specs(), parallel tool-call capability, base instructions and output schema. Tool schema is model-visible capability, not execution.

Those specs come through built_tools() and ToolRouter: MCP tools, plugins, apps, dynamic tools, and extension executors are merged into a request-scoped router. When the model returns a tool call, router dispatch hands it to the registered executor.

Before concrete execution, authority still crosses another layer. The ToolOrchestrator module header names the sequence directly: approval, sandbox selection, attempt, and escalation retry on sandbox denial. The implementation then checks approval policy, filesystem sandbox policy, tool requirements, hooks, workspace roots, network policy, and platform sandbox settings before an effect can happen.

"The model edited a file" is a user-facing shortcut. Source-level wording is: the model requested a tool call, and runtime authority routed, approved, sandboxed, executed, and recorded it.

5. The Client View Is a Projection, Not the Only Source of Truth

Runtime output is typed too. Event and EventMsg cover warnings, context compaction, legacy rollback records, turn lifecycle, assistant deltas, tool calls, token counts, hook lifecycle, and more. A client can render them as chat bubbles, progress rows, diffs, approval prompts, or status text. Those screens present the same typed runtime facts in different forms.

For example, one shell command can produce events that let the UI show command start, output deltas, and command completion in real time. The rollout material saved afterward serves a different purpose: it gives reload and resume enough evidence to reconstruct what happened.

Some facts are more durable. RolloutItem stores session metadata, response items, compacted records, turn context, WorldState, token usage, and event messages. That is why resume, legacy rollback recovery, and compact cannot be explained only by what remains visible on screen. The UI is the current display; rollout and history support recovery and later inspection.

6. Reading From Here

This overview only establishes the route. Later articles follow the same turn sequence and focus on one component at a time. The goal is not a source encyclopedia; it is a sequence a reader can replay. Each stop returns to the same three questions: who receives the work, who records the fact, and who renders the view?

Part Main question Owner to track
I. Overview How does one user turn move through entry, protocol, runtime, tool checks, and recovery records? Submission, SessionIo, run_turn, ToolOrchestrator.
II. Context management How do history, compaction, and recovery produce the next model request? ContextManager, TurnContext, for_prompt(), compaction.
III. Protocol and events How do clients and runtime share facts through typed requests and events? Op, EventMsg, app-server protocol, generated schema.
IV. Tools How do tool specs enter the model, and how are tool calls dispatched, parallelized, and recorded? ToolRouter, registry, MCP, dynamic tools, extension tools.
V. Permission and sandboxing Where do approval, hooks, sandboxing, and exec policy intercept side effects? ToolOrchestrator, exec policy, hooks, sandboxing, apply_patch.
VI. Client rendering How do TUI, app-server, and persisted records render the same runtime evidence? Event mapping, turn items, rollout, thread status.
VII. Extensions and multi-agent How do skills, plugins, MCP, and subagents enter as bounded runtime inputs for the current turn? Skills manager, plugins manager, MCP exposure, tool_search, AgentControl.

7. Common Misreadings

Misreading Better reading Why it matters
The CLI is the center of Codex. The CLI is one entry point; runtime handoff uses Submission and Event. Argument parsing should not be mistaken for session or turn semantics.
A turn is one chat exchange. A turn is a control unit that may contain multiple sampling requests, tool calls, pending input, and compaction. Otherwise one user request with several model calls looks mysterious.
The model runs commands directly. The model requests tool calls; execution authority lives in router, approval, hooks, sandbox, and handlers. This is where the most important safety and audit boundaries sit.
The UI history is runtime truth. UI renders events and state; recovery also depends on rollout, history, and turn context. Resume, legacy rollback replay, and compaction are not display-layer behavior.

8. Transferable Rules

Use the same questions when reading another agent system:

Reading move Question to ask first Invariant protected
Read entry code. Which typed carrier receives intent? UI text does not get mistaken for runtime work.
Read runtime code. Who owns context, cancellation, state changes, and completion? The real control point is not hidden behind the model call.
Read tool code. Which checks sit between model output and side effects? Capability exposure, approval, sandbox, execution, and evidence stay separate.
Read client code. Which events and persisted records produce the screen? Presentation, recovery, and model-visible views are not collapsed.
Read the series. Which component is this article explaining? The route stays gradual instead of expanding every concept at once.

The next article starts with context management. Codex context is not a plain chat log: it is stored history that can be prepared for a model request, rewritten by compaction, and recovered from rollout. With this overview in place, ContextManager, TurnContext, and for_prompt() become parts of the route rather than isolated names.

Sources