TurnCaptureState: How Codex Tracks Turn Progress in the OpenAI Plugin

TurnCaptureState is the core asynchronous data structure that the OpenAI Codex plugin uses to monitor and record the lifecycle of a single interaction round (turn), tracking thread hierarchies, buffering notifications, and aggregating artifacts until the turn reaches completion.

In the openai/codex-plugin-cc repository, the Codex plugin relies on TurnCaptureState to maintain a reliable, real-time view of conversation progress. Defined in plugins/codex/scripts/lib/codex.mjs, this state object captures everything from sub-agent activity to file modifications, enabling UI progress reporting and post-turn analysis.

Core Architecture of TurnCaptureState

State Properties and Thread Management

The TurnCaptureState object maintains several critical properties to track Codex turn progress across potentially complex thread hierarchies:

  • threadId / rootThreadId: Identifies the main conversation thread the turn belongs to.
  • threadIds: A Set containing all thread IDs, including sub-threads created during the turn.
  • threadTurnIds: A Map linking each thread ID to its most recent turn ID.
  • turnId: The unique identifier for the current turn, populated when the turn starts.
  • bufferedNotifications: An array holding raw Codex notifications awaiting processing.
  • completion: A Promise that resolves when the turn finishes, delivering the final state.
  • finalTurn: The completed Turn object returned by Codex with status completed.
  • completed / finalAnswerSeen: Boolean flags indicating explicit completion and final answer detection.
  • pendingCollaborations / activeSubagentTurns: Track ongoing tool calls and sub-agent turns to prevent premature finalization.
  • reasoningSummary, messages, fileChanges, commandExecutions: Arrays accumulating high-level artifacts generated during the turn.
  • onProgress: Optional callback function for emitting progress updates to the UI.

State Initialization

The state is instantiated via createTurnCaptureState(threadId, options) at lines 302-337 of codex.mjs. This factory function initializes the tracking structures and sets up the completion promise that callers await to determine when a turn has fully concluded.

How TurnCaptureState Tracks Codex Turn Progress

Thread Registration and Turn Identification

As Codex emits notifications such as turn/started and thread/started, the plugin invokes registerThread to update threadTurnIds and expand the threadIds Set. The belongsToTurn(state, message) function filters incoming messages, ensuring only relevant notifications from the current turn are captured while ignoring stale or unrelated thread activity.

Recording Artifacts and Notifications

The recordItem function processes incoming Codex events, extracting command executions, file changes, reasoning snippets, and assistant messages. These are appended to their respective arrays within the state object. Raw notifications accumulate in bufferedNotifications until they can be processed, ensuring no data loss during high-throughput turns.

Completion Detection and Finalization

Two mechanisms handle turn finalization:

  1. Explicit Completion: When Codex sends a turn/completed notification, completeTurn(state, turn) marks the turn as finished, sets the completed flag, and resolves the completion promise with the final state.
  2. Inferred Completion: If the final answer appears but no explicit completion signal arrives, scheduleInferredCompletion(state) sets a short timeout to infer completion, preventing the state from hanging indefinitely on stalled sub-agents.

Implementation in the Source Code

The turn-tracking logic resides primarily in plugins/codex/scripts/lib/codex.mjs, which defines TurnCaptureState and implements applyTurnNotification, recordItem, and the completion handlers. For persistence across process restarts, plugins/codex/scripts/lib/state.mjs provides serialization utilities. The test suite in tests/state.test.mjs and tests/runtime.test.mjs validates the capture workflow and finalization logic.

Practical Usage Examples

Capturing a Turn and Monitoring Completion

import { captureTurn } from "./codex.mjs";

const threadId = "thr_1";
const startRequest = { /* initial prompt parameters */ };

const turnState = await captureTurn(client, threadId, startRequest, {
  onProgress: (msg) => console.log("[Progress]", msg)
});

console.log("Turn completed:", turnState.finalTurn);
console.log("Reasoning summary:", turnState.reasoningSummary);

Inspecting Captured Artifacts After Completion

console.log("Messages:", turnState.messages.map(m => m.text));
console.log("Edited files:", turnState.fileChanges.map(fc => fc.changes.map(c => c.path)));
console.log("Commands run:", turnState.commandExecutions.map(c => ({
  cmd: c.command,
  status: c.status,
  exit: c.exitCode
})));

Implementing a Custom Progress Reporter

function progressReporter(update) {
  if (typeof update === "string") {
    console.log(update);
  } else {
    console.log(`${update.phase}: ${update.message}`);
  }
}

await captureTurn(client, threadId, startRequest, { onProgress: progressReporter });

Summary

  • TurnCaptureState is the central data structure in openai/codex-plugin-cc for tracking a single Codex interaction round from start to finish.
  • It maintains thread hierarchies via threadIds and threadTurnIds, ensuring accurate attribution of messages across sub-threads and sub-agents.
  • The state aggregates artifacts including reasoning summaries, file changes, and command executions through the recordItem function.
  • Completion is handled through both explicit signals (completeTurn) and inferred timeouts (scheduleInferredCompletion), with the completion Promise providing asynchronous notification of turn finalization.
  • Located in plugins/codex/scripts/lib/codex.mjs, this architecture enables reliable UI progress bars, sub-agent coordination, and post-turn analysis.

Frequently Asked Questions

What triggers the completion of a TurnCaptureState?

The completion Promise resolves when either the completeTurn function receives an explicit turn/completed notification from Codex, or when scheduleInferredCompletion detects a final answer without an explicit completion signal and triggers a timeout-based inference. This dual mechanism ensures the state finalizes even if the Codex API omits formal completion messages.

How does TurnCaptureState handle sub-agent turns and collaborations?

The state tracks active sub-agents through the activeSubagentTurns and pendingCollaborations counters. These prevent premature completion by ensuring the completion Promise only resolves after all spawned sub-threads and tool invocations have reported their status, maintaining consistency across complex multi-turn workflows.

Can TurnCaptureState persist across process restarts?

Yes, while the primary implementation in codex.mjs manages runtime state, the companion module plugins/codex/scripts/lib/state.mjs provides utilities for serializing and restoring turn state. This allows long-running Codex interactions to survive process interruptions and resume tracking from their last known position.

Where is TurnCaptureState defined in the codebase?

The TurnCaptureState interface and its factory function createTurnCaptureState are defined in plugins/codex/scripts/lib/codex.mjs at lines 302-337. All notification processing logic, including registerThread and recordItem, resides in the same file, making it the canonical reference for turn-tracking implementation details.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →