Skip to content

4. Tasks and Turns

From User Input to Request Construction

Overview

This document describes the internal request processing flow of Codex, focusing on how user input is transformed into Task and Turn abstractions, and how the final LLM prompt is constructed.

Input Processing: From Op::UserTurn to Task Creation

Input Entry Point

User interactions are encapsulated as Op::UserTurn (or the legacy Op::UserInput) and sent to the Daemon. Op::UserTurn contains the configuration required for a specific execution cycle:

// codex-rs/protocol/src/protocol.rs
pub enum Op {
    UserTurn {
        items: Vec<UserInput>,
        cwd: PathBuf,
        approval_policy: AskForApproval,
        sandbox_policy: SandboxPolicy,
        model: String,
        effort: Option<ReasoningEffortConfig>,
        summary: Option<ReasoningSummaryConfig>,
        service_tier: Option<Option<ServiceTier>>,
        final_output_json_schema: Option<Value>,
        collaboration_mode: Option<CollaborationMode>,
        personality: Option<Personality>,
    },
    // ...
}

Input Routing Logic

When a new input arrives, the Session attempts to route it using the following priority:

  1. Steering: The session calls steer_input. If a task is currently active and accepts the steering (i.e., the expected_turn_id matches or is None), the input is queued into the pending_input of the active turn.
  2. Task Spawning: If no task is active or steering fails, the session creates a new ActiveTurn and spawns a RegularTask.

ContextManager: Centralized State History

The ContextManager (located in codex-rs/core/src/context_manager/history.rs) serves as the Single Source of Truth for the conversation history.

  • Storage: It maintains a Vec<ResponseItem> containing messages, tool calls, and outputs.
  • Filtering: The for_prompt method prepares the history for the LLM. It acts as a safety boundary, normalizing items and filtering out internal states (like compression snapshots) before they hit the API. (We will explore the details of this transformation pipeline in Chapter 7).
  • Token Tracking: It tracks token usage but does not trigger compaction; compaction is an external task triggered by the session logic.

Abstractions: Task vs. Turn

Task: The Execution Controller

A Task is a unit of control flow, typically running as a tokio::spawn handle. It implements the SessionTask trait.

  • Responsibility: Manages the lifecycle (start, cancel, finish), handles CancellationToken propagation, and reports status to the Session.
  • Types: Includes RegularTask (standard turns), ReviewTask, CompactTask, and UndoTask.

Turn: The Data Flow Executor

A Turn (represented by the run_turn function in codex-rs/core/src/codex.rs) is the logic execution unit within a task.

  • Responsibility: Executes the sampling loop, interacts with the ModelClient, handles tool execution, and records results into the ContextManager.
  • Events: Emits EventMsg (e.g., OutputTextDelta, ToolCallStarted) to the UI using the turn_id for scoping.

The run_turn Sampling Loop

Session State Transition

The Session tracks execution via active_turn. Only one ActiveTurn exists per user request cycle.

sequenceDiagram
    participant S as Session
    participant AT as ActiveTurn State
    participant T as Task (JoinHandle)
    participant R as run_turn Loop

    Note over S: Op::UserTurn received
    S->>S: Check active_turn
    alt No active_turn
        S->>AT: Create ActiveTurn
        S->>T: Spawn Task
        T->>R: Invoke run_turn()
        loop Sampling
            R->>R: Sampling & Tool Execution
        end
        R-->>T: Return last_agent_message
        T->>S: on_task_finished()
        S->>AT: Remove Task/Cleanup
        S->>S: active_turn = None
    else Steering
        S->>AT: Push to pending_input
    end

Sampling Logic

run_turn executes a while loop. In each iteration: 1. Input Consumption: Consumes any pending_input queued via steering. 2. Prompt Rebuild: Reconstructs the complete Prompt (history + tools + instructions). 3. Model Interaction: Calls the LLM and streams responses. 4. Follow-up Determination: Sets needs_follow_up to true if tool calls are pending or more input was steered.

Termination Criteria

The loop terminates when SamplingRequestResult.needs_follow_up is false.

  • false: Model returned only text, or a terminal error occurred.
  • true: Model requested tool calls, or token limits triggered an auto-compaction that requires a re-sampling.

Prompt Construction

The Prompt Structure

The Prompt (defined in codex-rs/core/src/client_common.rs) is the payload sent to the LLM:

pub struct Prompt {
    pub input: Vec<ResponseItem>,
    pub(crate) tools: Vec<ToolSpec>,
    pub(crate) parallel_tool_calls: bool,
    pub base_instructions: BaseInstructions,
    pub personality: Option<Personality>,
    pub output_schema: Option<Value>,
}

Tool Loading Mechanics

Tools are dynamically injected into each sampling request via the ToolRouter. Codex avoids overloading the LLM context by loading MCP/App tools only when necessary.

  1. Built-in Tools: Always loaded based on session Config (e.g., local_shell, read_file).
  2. Explicit Mentions: If the user mentions a tool explicitly (e.g., @github), it is added to the active tool list via filter_connectors_for_input.
  3. Implicit Discovery (BM25): The search_tool_bm25 is always injected. If the LLM needs a tool not currently loaded, it calls this search tool. The discovered tools are saved to the session state and become available in subsequent requests.
sequenceDiagram
    participant Turn as run_turn
    participant Session as Session State
    participant LLM as Model Client

    Note over Turn: 1. Initialization
    Turn->>Turn: Load Built-in Tools (Shell, File ops)

    Note over Turn: 2. Resolve Active Tools
    Turn->>Session: Get user mentions & prior tool selections
    Session-->>Turn: Active tool identifiers (e.g., "@github")

    Note over Turn: 3. Final Assembly
    Turn->>Turn: Combine Built-in, Mentioned, and Selected tools
    Turn->>Turn: Always Add search_tool_bm25
    Turn->>LLM: Send Prompt (with Active Tools)

    opt 4. LLM Discovers New Tools
        LLM->>Turn: ToolCall: search_tool_bm25(query="jira")
        Turn->>Session: Search & Save "jira" to active selections
        Note over Turn,Session: Discovered tools will be included <br/>in the next sampling round automatically.
    end

Skill Loading and Injection

Skills consist of metadata and the SKILL.md content.

  • Phase 1 (Discovery): At session start, the SkillsManager scans roots. It reads SKILL.md files to extract YAML frontmatter (name, description). It also checks agents/openai.yaml for interface, policy, and dependencies.
  • Phase 2 (Injection): During a turn, if a skill is mentioned via $skill-name, build_skill_injections reads the full SKILL.md content and records it into the conversation history as SkillInstructions.
sequenceDiagram
    participant R as run_turn
    participant S as SkillsManager
    participant FS as File System
    participant CM as ContextManager

    Note over R: Check for $skill-name in input
    R->>S: Resolve SkillMetadata
    S->>FS: Read SKILL.md (content)
    FS-->>S: File Content
    S-->>R: SkillInstructions Item
    R->>CM: record_conversation_items(SkillInstructions)
    Note over CM: Skill content is now in history

Final Construction Flow

  1. Instructions: Fetch BaseInstructions from Session.
  2. History: Fetch filtered history from ContextManager.
  3. Tools: Build ToolRouter (Built-in + Mentioned MCP + BM25).
  4. Skills: Inject content of mentioned skills into history.
  5. Assembly: Combine all components into the Prompt and dispatch via ModelClient.