Skip to content

セッションスレッドとメッセージ履歴

源码版本rust-v0.145.0

CodexThread は codex が上位(UI、app-server、extension)に晒すセッションハンドルだ。一つの thread は連続する対話履歴、一つの session loop、一つの rollout ファイル、そして紐付く作業ディレクトリと権限設定に対応する。それ自身はモデルループを走らせず、Op を裏側の Session に投げ、イベントストリームを呼び出し側に返すだけだ。実際に履歴を保存し、圧縮し、agent loop を走らせるのは Session とその LiveThread だ。

責務

  1. 外部 Op(ユーザメッセージ、steer、cancel、shutdown)を受け取り、submit / submit_with_traceSession の mailbox に投函する (codex-rs/core/src/codex_thread.rs:205-275)。
  2. イベントストリームの露出:next_eventEvent を取り、agent_status はイベントを AgentStatus に折り畳んで UI が idle/running/completed を表示できるようにする (codex-rs/core/src/codex_thread.rs:414-422)。
  3. モデル可視 item の注入:inject_response_itemsResponseItem を履歴に差し込むが新たに turn を始めず、inject_if_running は turn 進行中に割り込む (codex-rs/core/src/codex_thread.rs:306-483)。
  4. thread 設定 snapshot の管理:config_snapshot / preview_thread_settings_overrides が sandbox、approval、environment、model などのランタイム設定を上位にまとめて渡す (codex-rs/core/src/codex_thread.rs:355-361)。
  5. 永続化の代理:全ての rollout/thread-store 操作は live_thread 経由で転送され、load_history / read_thread / append_rollout_items / update_thread_metadata は全て LiveThread を通る (codex-rs/core/src/codex_thread.rs:504-558)。

設計動機

初期 codex は「対話」と「セッションループ」を一つに結合していたが、後に CodexThread を facade として分離した。主な理由は extension と app-server が作業するための安定したハンドルを必要としたことだ——それらが Session 内部の channel を直接触るべきではない。CodexThreadSessionIo(下位 mpsc)を自分の中に封じ込み、submitnext_event の二方向だけを露出する。これがドキュメントの言う「bidirectional stream of messages that compose a thread」だ。

履歴永続化が thread-store crate として独立しているのは、実装を差し替え可能にするためだ:ローカル rollout ファイル、リモート service、インメモリ mock。ThreadStore trait はライフサイクルメソッド(create_thread / resume_thread / append_items / persist_thread / flush_thread / shutdown_thread / discard_thread)だけを規定し、コアコードはバックエンドがどこにあるかを気にしない。LiveThread は session が持つハンドルで、コアの「書くべきなら書く」判断を store 呼び出しに翻訳する。

message-history crate は別の次元だ:thread ごとの rollout ではなく、~/.codex/history.jsonl というグローバルに thread をまたぐ JSONL ファイルで、一行が {session_id, ts, text}。用途は TUI の履歴コマンド検索であって、対話の復元ではない。だから thread-store とは独立し、advisory lock と trim を自分で処理する。

TryStartTurnIfIdleError の設計は言及する価値がある:extension が「スレッドが空いたら自動で一 turn 始める」をやろうとする時、スレッドが忙しい、Plan モード、すでにユーザが発火した turn が並んでいる、といった状態が起こる。エラー戻り値には reason元の items を不変で含め、呼び出し側が破棄するか再試行するか理由を記録するかを決める——入力を黙って飲み込むことはない。

主要ファイル

codex-rs/core/src/codex_thread.rs:162-203CodexThread 構造体定義と new コンストラクタ。Arc<Session>SessionIoSessionConfiguredEvent を保持。codex-rs/core/src/codex_thread.rs:86-124TryStartTurnIfIdleRejectionReasonTryStartTurnIfIdleError。自動 idle turn の三つの拒否理由。codex-rs/core/src/codex_thread.rs:446-483inject_user_message_without_turninject_response_items。履歴に差し込むが新しい turn は始めない。codex-rs/core/src/agent/status.rs:6-21agent_status_from_eventEventMsgAgentStatus に折り畳む。codex-rs/thread-store/src/store.rs:36-94ThreadStore trait。全 thread 永続化バックエンドの契約。codex-rs/thread-store/src/live_thread.rs:35-108LiveThread 構造体と create。session が持つ store ハンドル。codex-rs/thread-store/src/live_thread.rs:48-90LiveThreadInitGuard。session 初期化失敗時に live writer を安全に破棄。codex-rs/message-history/src/lib.rs:104-189append_entry。グローバル history.jsonl へのアトミック追加。codex-rs/message-history/src/lib.rs:61-83HistoryEntry / HistoryConfig。グローバル履歴のデータ構造。

CodexThread::submit は一見 self.io.submit(op).await 一行だが、io: SessionIo の背後には tokio mpsc + turn id 割り当て + trace context 注入がある。上位には「Op を投げて sub_id を受け取る」だけに見え、mailbox、turn queue、shutdown 信号は全て中に封じ込まれている:

rust
// codex_thread.rs:205-207 — Op 的统一入口
pub async fn submit(&self, op: Op) -> CodexResult<String> {
    self.io.submit(op).await
}

agent 状態は CodexThread 自身が maintain するのではなく、EventMsg から派生する。TurnStarted -> RunningTurnComplete -> CompletedTurnAborted は原因で InterruptedErrored に分かれ、ShutdownComplete -> Shutdownis_finalPendingInit/Running/Interrupted を「まだ終わっていない」とし、他を終状態とする:

rust
// agent/status.rs:6-21 — 状态机完全由事件驱动
pub(crate) fn agent_status_from_event(msg: &EventMsg) -> Option<AgentStatus> {
    match msg {
        EventMsg::TurnStarted(_) => Some(AgentStatus::Running),
        EventMsg::TurnComplete(ev) => Some(AgentStatus::Completed(ev.last_agent_message.clone())),
        EventMsg::TurnAborted(ev) => match ev.reason {
            codex_protocol::protocol::TurnAbortReason::Interrupted
            | codex_protocol::protocol::TurnAbortReason::BudgetLimited => {
                Some(AgentStatus::Interrupted)
            }
            _ => Some(AgentStatus::Errored(format!("{:?}", ev.reason))),
        },
        EventMsg::Error(ev) => Some(AgentStatus::Errored(ev.message.clone())),
        EventMsg::ShutdownComplete => Some(AgentStatus::Shutdown),
        _ => None,
    }
}

グローバル history.jsonl の書き込みは興味深い詳細を持つ:ロックを保持したまま単一の write_all JSON 全行 + \n を書き込まなければならず、そうして初めて POSIX は PIPE_BUF バイト以内の書き込みがアトミックであることを保証する。複数プロセスの並発追加が交錯しない。コードはわざわざ spawn_blocking でこの同期 IO を async runtime の外に移す:

rust
// message-history/src/lib.rs:160-172 — 持锁、定位到末尾、一次写完
tokio::task::spawn_blocking(move || -> Result<()> {
    for _ in 0..MAX_RETRIES {
        match history_file.try_lock() {
            Ok(()) => {
                history_file.seek(SeekFrom::End(0))?;
                history_file.write_all(line.as_bytes())?;
                history_file.flush()?;
                enforce_history_limit(&mut history_file, history_max_bytes)?;
                return Ok(());
            }
            Err(std::fs::TryLockError::WouldBlock) => {
                std::thread::sleep(RETRY_SLEEP);
            }
            Err(e) => return Err(e.into()),
        }
    }
    // ...
})

データフロー

境界と失敗

  • Plan モードは自動 turn を拒否:try_start_turn_if_idle は Plan モードでは即座に PlanMode を返す。Plan モードは自動的なモデル turn 開始を禁じ、ユーザの明示発火のみを許す (codex-rs/core/src/codex_thread.rs:313-331)。
  • 初期化失敗時は writer を破棄:LiveThreadInitGuard は session 初期化の途中で失敗した場合、Dropdiscard を呼び、in-memory キューを durable にフラッシュすることを強要しない。tokio runtime が無ければ spawn 失敗時に warn に格下げする (codex-rs/thread-store/src/live_thread.rs:75-90)。
  • inject_response_items は空 items を拒否:直接 InvalidRequest を返し、空の rollout を黙って一回書くのを避ける (codex-rs/core/src/codex_thread.rs:460-483)。
  • history.jsonl は敏感内容を永続化しない:HistoryPersistence::None の場合、append_entry は即座に return Ok(()) する。コードコメントには敏感モードをチェックする TODO が残っている (codex-rs/message-history/src/lib.rs:109-117)。
  • trim は soft cap:HISTORY_SOFT_CAP_RATIO = 0.8。ファイルが max_bytes を超えても上限ちょうどに切るのではなく 80% まで切り詰め、再 trim の頻度を減らす (codex-rs/message-history/src/lib.rs:55-59)。

まとめ

CodexThread の核心価値は「一つのセッション」を安定したハンドルに抽象化することだ——バックエンドがローカル rollout でもリモート service でも、sub-agent があってもなくても、上位コードは submit / next_event / config_snapshot だけを見る。実際のループロジックは Agent メインループ に、永続化の詳細は thread-store trait の各実装にある。turn 内部の履歴がどう圧縮されるかを見るには コンテキスト圧縮の進化 に続く。