Commit Graph

114 Commits

Author SHA1 Message Date
Owen Lin
fa1d4c40d0 Gate paginated thread history on the state database (#35787)
## Why

Local thread stores without an initialized state database should not implicitly
create SQLite files or partially delete threads that have materialized history.

## What changed

- Report paginated history listing as unsupported when no state database is
  available, and skip history projection and materialization in that mode.
- Reject paginated forks without a state database.
- Validate access to materialized history before deleting rollout files, so a
  failed deletion preserves both the rollout and its history rows.

## Testing

Added coverage that a store without a state database creates no SQLite files
and preserves materialized history when deletion is unsupported. Existing
projection tests now initialize the state runtime explicitly.

GitOrigin-RevId: 2eaa0f5f0de8d5e4d84375ec50b310746a0ee68e
2026-07-28 16:00:39 +00:00
joeytrasatti-openai
85c6da1c79 Add persisted sections for organizing threads (#35722)
## What changed

- Replace the `isPinned` thread metadata and filters with an optional persisted
  `section` and `sectionId`.
- Add the paginated `threadSection/list` app-server method so clients can
  discover sections even when they contain no threads.
- Seed a stable `Pinned` section, validate section assignments, and support
  filtering for a specific section or for unsectioned threads.

## Testing

- Cover section protocol serialization, listing and pagination, metadata
  updates, filtering, persistence, migration compatibility, and operation
  without SQLite state.

GitOrigin-RevId: 7972b5471d29317b9387bfd90aa9f573f691ad4c
2026-07-28 05:26:16 +00:00
Owen Lin
8d6a91b1ff Preserve item timestamps in thread history projections (#35689)
## What changed

- Add optional start and completion timestamps to `ThreadHistoryItemChange`.
- Populate them from canonical `ItemCompleted` records during stateless thread history projection.
- Treat a zero completion timestamp as absent, and leave timestamps unset for legacy builder events that do not provide them.

## Testing

- Cover projected item timestamps, including missing start times and zero completion times.

GitOrigin-RevId: 2187530f489ad3d86b6ab65faaaab1eab5357f1b
2026-07-28 00:14:22 +00:00
Owen Lin
4d1f66bf81 Preserve paginated thread metadata across resumes (#35678)
## Why

Paginated rollout history may contain only a bounded suffix. Deriving display
metadata from that suffix can replace the thread's original preview, title, and
first user message when the thread is read or resumed.

## What changed

- Use SQLite as the canonical source of display metadata for paginated thread
  reads, including reads by an explicit rollout path.
- Seed resume metadata tracking from the persisted values so appended history
  fills only missing fields instead of overwriting existing ones.
- Prefer an explicitly requested rollout path over a stale path stored in
  SQLite while retaining the canonical metadata.

## Testing

Added coverage for reads by ID and path, bounded-history resumes across app
server restarts, preservation of existing metadata, initialization of missing
fields, and stale persisted rollout paths.

GitOrigin-RevId: b393d0727afcba70fda7f50a6f2dcca0632745b4
2026-07-27 22:12:15 +00:00
Owen Lin
af7f6f4d34 Include item start times in completion events (#35363)
## What changed

- Add an optional `started_at_ms` field to `ItemCompletedEvent`, retaining
  compatibility with older persisted events that lack it.
- Track the first start timestamp for each in-flight item and attach it when
  emitting and persisting the completion event. If no start was recorded, use
  the completion timestamp as a fallback.
- Emit a complete start/completion lifecycle for subagent activity items.

## Testing

- Cover concurrent item timing, repeated starts, turn-boundary cleanup, the
  missing-start fallback, subagent activity, and persisted web-search events.

GitOrigin-RevId: e7cec9c4f1ef6ba67f287e81fb4d7d856fcf87a7
2026-07-25 16:06:54 +00:00
Owen Lin
05f000263b Support paginated thread forks (#35220)
## Why

`thread/fork` rejected threads using paginated history, so they could not use the same fork workflow as legacy threads.

## What changed

- Create paginated forks by referencing a frozen source-history prefix while persisting only child-owned records in the new rollout.
- Support latest, `lastTurnId`, and `beforeTurnId` boundaries, including inherited lineage, model-context reconstruction, interruption markers, and approval-reviewer inheritance.
- Make paginated reads and occurrence search traverse referenced history while excluding source records added after the fork.
- Coordinate fork preparation with archive and delete operations, and materialize compressed rollouts before they become fork references.

## Testing

Added app-server and thread-store coverage for boundary selection, active turns, inherited reads and search, compressed lineage, and concurrent source lifecycle operations.

GitOrigin-RevId: 66052d69517d7fb97a3b7817ab77a39c5ad995fd
2026-07-24 17:31:55 +00:00
Owen Lin
963316583b Enforce writer ownership for thread archive and deletion (#35031)
## Why

Paginated threads allow only one app-server process to write at a time. Archive and delete operations must not mutate a thread or its spawned descendants while another process owns any of them.

## What changed

- Acquire paginated writer locks for the full archive or deletion set before changing rollout files, including descendants whose rollout has not materialized yet.
- Add a batch archive store operation so ownership conflicts are detected before any thread in the subtree is archived.
- Return ownership conflicts as JSON-RPC invalid-request errors and document the behavior.

## Testing

- Cover archive and deletion conflicts for owned threads and descendants, including unmaterialized rollouts.
- Verify deletion still removes rollouts with unreadable metadata.

GitOrigin-RevId: f62a3e0c1ee2631cea48dd9145b588a1486e2425
2026-07-23 23:18:06 +00:00
richardopenai
b834702b27 Support incremental replay of updated thread items (#35013)
## Why

Persisted item snapshots can be updated after their initial projection while retaining their creation order. Callers need a way to read those updates incrementally without replaying every item.

## What changed

- Track the rollout ordinal of each item's latest snapshot separately from its creation ordinal.
- Let thread-store callers filter items after an exclusive update watermark and sort pages by either creation or update ordinal.
- Keep existing app-server reads creation-ordered, reject incremental reads across forked lineages, and preserve compatibility with older writers.

## Testing

- Cover update-watermark filtering, pagination, sort directions, per-turn reads, cursor validation, forked lineages, migration compatibility, and refreshed item snapshots.

GitOrigin-RevId: d57de849abec27f788e9c384dbff073a0aca1053
2026-07-23 21:17:45 +00:00
Adam Perry @ OpenAI
c769a05340 Honor the configured SQLite home across state consumers (#34994)
## Why

Codex and SQLite data can use separate home directories, but state consumers
could reconstruct database paths from the Codex home instead of consistently
using the resolved SQLite configuration.

## What changed

- Pass `SqliteConfig` through the core, rollout, state runtime, and thread store
  instead of passing a directory and rebuilding the configuration downstream.
- Use that shared configuration for state, logs, memories, goals, and paginated
  thread-history database access, including integrity checks and cleanup.
- Reject state database handles whose SQLite configuration does not match the
  requesting store.

## Testing

Add coverage with separate Codex and SQLite homes that verifies startup
backfill, thread listing, and paginated history all use the configured SQLite
directory.

GitOrigin-RevId: 1de1cdd1d6ff1d70bbb6c360c8352e6543fb8ebf
2026-07-23 19:19:35 +00:00
Owen Lin
5c94796dc9 Enforce single-writer ownership for paginated threads (#34986)
## Why

Only one app-server process should keep a paginated thread open for writing at
a time, while other processes must still be able to read it.

## What changed

- Acquire a per-thread filesystem lock when creating or resuming a paginated
  thread, and retain it for the lifetime of the live recorder.
- Reject a competing `thread/resume` request with JSON-RPC error `-32600` and
  release ownership when the writer is discarded, deleted, or shut down.
- Remove stale lock files without disturbing active writers.

## Testing

Added coverage for competing processes, ownership transfer after shutdown,
stale-lock cleanup, and lock release during discard and deletion.

GitOrigin-RevId: 81d49a2fffc73ba8135752b4944f27feafa4c0d6
2026-07-23 18:22:41 +00:00
joeytrasatti-openai
400ee190c3 Add persisted thread pinning to the app server (#34840)
## What changed

- Add `isPinned` to thread responses and allow `thread/metadata/update` to pin or unpin stored threads.
- Add an `isPinned` filter to `thread/list`, including cursor-based pagination and combinations with relationship filters.
- Persist pin state in SQLite without modifying rollout files, default existing threads to unpinned, and preserve pins through reconciliation and archive transitions.

## Testing

- Cover protocol serialization, database migration and indexing, pin updates, filtered pagination, rollout reconciliation, and archive/unarchive behavior.

GitOrigin-RevId: c2eacabd6dae465e2ae6ce8a169e00740960cdb7
2026-07-22 22:54:28 +00:00
Adam Perry @ OpenAI
946ed315a4 Centralize SQLite connection configuration (#34808)
## What changed

- Add `SqliteConfig` to own the resolved SQLite home, runtime database paths,
  and shared read/write and read-only pool settings.
- Pass the configuration through state and local thread-store consumers,
  replacing standalone path helpers and duplicated connection setup.

GitOrigin-RevId: 7351b08da94b5c8b6c0bbe492f86aeeca0699d3c
2026-07-22 18:45:47 +00:00
Owen Lin
5bfd74d36c Load paginated model context across rollout lineages (#34621)
## What changed

- Resolve the full rollout lineage when loading model context for paginated threads.
- Reverse-scan each lineage segment up to its recorded byte boundary, stopping at segment metadata and preserving the requested thread's canonical session metadata.
- Add a bounded reverse-scanner constructor for scanning a frozen JSONL prefix.

## Testing

- Cover frozen-prefix reverse scans and nested lineages whose root rollout has been archived.

GitOrigin-RevId: 8b97da43a263b7bd09cb7883450bb19eba5f195f
2026-07-21 21:58:41 +00:00
Owen Lin
0b175e6439 Protect fork history references during rollout cleanup (#34566)
## Why

Paginated forks can retain a `history_base` pointer to another rollout. Deleting or compressing either side of that relationship can leave the fork without the rollout history it references.

## What changed

- Add a rollout reference index that scans active, archived, and compressed rollout metadata.
- Reject deletion when a rollout is still referenced by a fork outside the requested deletion set.
- Delete spawned thread subtrees as one store operation so internal references do not block deleting the complete subtree, while external references preflight the request before any files are removed.
- Skip referenced rollouts, fork-pointer rollouts, and rollouts with unreadable metadata during background compression.

## Testing

- Cover reference indexing across active, archived, compressed, duplicate, and self-referencing rollouts.
- Verify deletion rejects external references without partially deleting a subtree and permits batch deletion of internally referenced threads.
- Verify compression preserves fork pointer chains and continues past unreadable metadata.

GitOrigin-RevId: a8672727f8706e743d7fb3ca895a1d9ceac98a3c
2026-07-21 15:18:56 +00:00
Owen Lin
7bb13ab846 Page through inherited thread history (#34563)
## What changed

- Resolve a thread's rollout lineage when listing paginated turns and items.
- Page across parent, child, and nested fork segments in either direction while respecting each segment's ordinal bounds.
- Encode the physical segment in history cursors and reject cursors that belong to another requested thread or fall outside its lineage.
- Load turn summaries and filtered items from inherited segments.

## Testing

- Added coverage for forward and backward pagination across parent and child history, inherited summary and filtered items, cursor isolation, and nested forks.

GitOrigin-RevId: b803fff781fcb6e21d0e151189290148fc8d8b18
2026-07-21 15:14:55 +00:00
Owen Lin
175f82147f Record rollout boundaries for materialized turns (#34562)
## What changed

- Store the starting byte offset and terminal ordinal and byte offset for each turn projected into SQLite.
- Advance offsets across blank and rejected physical lines while leaving incomplete trailing lines for the next materialization pass.
- Stop later rollout records from changing a turn after it reaches a terminal state.

## Testing

- Cover active and completed turn positions, trailing partial lines, skipped invalid lines, subagent history prefixes, and records received after terminal state.

GitOrigin-RevId: a7da2f0b00c0bf47b841db874c79a0d495c18acd
2026-07-21 15:10:42 +00:00
Owen Lin
b7e39aa316 Resolve paginated rollout lineages (#34407)
## What changed

- Add a shared local thread-store resolver that follows `history_base` links into ordered, bounded rollout segments, including archived ancestors.
- Support resolving a lineage at an explicit `HistoryPosition`.
- Reject malformed lineages with cycles, missing or mismatched source rollouts, non-paginated sources, or invalid cutoff bounds.

## Testing

- Add unit coverage for nested and archived lineages, explicit history positions, cycles, missing sources, and out-of-bounds offsets.

GitOrigin-RevId: a9063ad87e81d9865bd17fc588ea5d8e9ac74c0a
2026-07-20 20:33:51 +00:00
Owen Lin
2793c826e8 Enable memories for paginated threads (#34386)
## Why

Paginated threads keep metadata updates in SQLite, while their rollout retains the initial `memory_mode`. Reconciliation could therefore overwrite the current setting with a stale value, and memory processing excluded these threads entirely.

## What changed

- Include paginated threads in stage 1 memory job selection and global memory output lookup.
- Preserve the SQLite `memory_mode` when reconciling or backfilling an existing paginated thread, while continuing to seed missing rows and restore legacy threads from rollouts.
- Omit `memory_mode` alongside Git metadata when flushing resumed paginated history.

## Testing

Added coverage for preserving disabled memory mode during reconciliation and backfill, selecting eligible paginated threads, and omitting initial metadata on paginated resume.

GitOrigin-RevId: 2a6e16068e69680728757fbec27aeefae45b8110
2026-07-20 17:41:33 +00:00
Owen Lin
19b2273d8a Keep paginated thread Git metadata in SQLite (#34382)
## Why

Paginated rollouts contain only the initial Git metadata tuple. Reusing that
tuple during later reads, reconciliation, or resume can overwrite newer SQLite
values and undo explicit field clears.

## What changed

- Treat SQLite as authoritative for Git metadata once a thread is paginated.
- Apply paginated Git metadata patches directly to SQLite without appending
  compatibility metadata to the rollout.
- Preserve explicit null values when reading by rollout path or reconciling the
  initial session metadata, and ignore the initial Git tuple during resume.

## Testing

Added coverage for updating and clearing paginated Git metadata, preserving the
result through reconciliation and rollout-path reads, and avoiding stale Git
metadata updates on resume.

GitOrigin-RevId: 4d3e68cb24965b93d337d923092a98bac9b352b5
2026-07-20 17:23:18 +00:00
Brent Traut
5a208c1fc3 Persist names for paginated threads (#34229)
## Why

Paginated threads need explicit user-facing names to remain distinct from
derived titles and previews without requiring rollout metadata writes.

## What changed

- Add a nullable `name` column to persisted thread metadata and use it as the
  canonical name for paginated threads.
- Route paginated name updates through the state database while retaining the
  name index as best-effort compatibility data and preserving legacy thread
  behavior.
- Return persisted names when reading, listing, searching, and resuming
  paginated threads, and include them in state-database search matching.

## Testing

- Cover paginated name updates, reads, list/search results, and metadata-only
  resume behavior, including stale or unavailable compatibility data.

GitOrigin-RevId: 5ac4dfa2a83682b43d59ecc87e0f515580164dca
2026-07-19 21:13:13 +00:00
Owen Lin
bcdc695877 Track inherited paginated rollout prefixes (#33930)
## What changed

- Add `HistoryPosition` to identify a source thread and an exclusive rollout
  prefix by ordinal and JSONL byte offset.
- Add optional `history_base` metadata to `SessionMeta` so a thread can record
  an inherited prefix of another paginated rollout.
- Default missing `history_base` metadata to `None` and omit it when unset to
  preserve compatibility with existing rollout metadata.

## Testing

- Verify that legacy session metadata without `history_base` deserializes with
  no inherited history position.

GitOrigin-RevId: 3b2b07a2dfee793d3b37b0a60529f856f167dbc1
2026-07-18 00:06:49 +00:00
sayan-oai
15d7d2a733 Handle audio inputs and Bazel unit test arguments (#33929)
## What changed

- Exclude remote and local audio inputs from thread-history searchable text.
- Add a `unit_test_args` option to `codex_rust_crate` and forward it to the
  generated unit-test wrapper.

GitOrigin-RevId: f0aeb7f811163c3c80aa6f8ef4104650e698347c
2026-07-17 23:41:01 +00:00
Owen Lin
c1862b8db4 Add occurrence search for paginated threads (#33907)
## What changed

- Add the experimental `thread/searchOccurrences` app-server method for case-insensitive literal search across visible user messages and final assistant messages without replaying the thread.
- Return paginated snippets with UTF-16 match ranges and `thread/turns/list` cursors for navigating to each matching turn.
- Index persisted item types to efficiently select searchable messages from paginated thread history.

## Testing

- Add an app-server integration test covering pagination, steering messages, final-answer selection, Markdown normalization, and UTF-16 offsets.

GitOrigin-RevId: e80160e0f83eb4e5db8de862307085093bee362b
2026-07-17 21:24:26 +00:00
Owen Lin
a514757e82 Use final answers in turn history summaries (#33457)
## What changed

- Track only agent messages with the `final_answer` phase as a turn's final
  summary item, so commentary is excluded.
- Recover final answers when items arrive before the turn lifecycle record, and
  fall back to phase-less agent messages for terminal turns.

## Testing

- Cover delayed turn lifecycle records and commentary-only turns when listing
  summary items.

GitOrigin-RevId: 59d9c7fd23dd32037516883e964cf5869c5932a1
2026-07-16 00:39:42 +00:00
Owen Lin
2be648ba4a Preserve paginated history for spawned subagents (#33432)
## What changed

- Inherit paginated history mode when spawning or forking a subagent from a paginated parent.
- Load the parent's model context when forking and persist it as an inherited rollout prefix, while excluding that prefix from the child's projected turns, items, and metadata.
- Record the boundary between inherited context and child history, and reject partially initialized paginated subagent rollouts on resume.
- Resume paginated subagents from their stored model context instead of legacy rollout history.

## Testing

Added coverage for fresh and forked paginated subagents, compacted-history forks, model-context resume, inherited-prefix projection, metadata isolation, and incomplete-prefix detection.

GitOrigin-RevId: e57dc37c29aa4aaaf57b052b991be591d730e5ba
2026-07-15 21:25:24 +00:00
Owen Lin
da61f7d8e1 Enable paginated thread history in app-server (#33364)
## What changed

- Allow `thread/start` to select experimental `historyMode: "paginated"` when the thread store supports turn and item pagination.
- Resume paginated threads with bounded model context and require `excludeTurns: true` instead of loading full history.
- Return `turnsBackwardsCursor` and `itemsBackwardsCursor` from `thread/resume` so clients can hydrate durable history through `thread/turns/list` and `thread/items/list` while newer records arrive through live notifications.
- Reject operations that still require full history, including `initialTurnsPage`, `thread/read` with `includeTurns: true`, `thread/rollback`, and detached review.
- Make item cursors independent of the optional `turnId` filter.

## Testing

- Cover paginated thread start, cold and live resume, backwards cursor hydration, metadata reads, unsupported operations, and stores without pagination support.

GitOrigin-RevId: c42f9b268e29d049843330f474fa3ecde12f5b8a
2026-07-15 15:29:13 +00:00
pakrym-oai
5d325ba223 Move sleep items to the extension-owned lifecycle path (#33159)
## What changed

- Define `SleepItem` in `codex-extension-items` and carry `clock.sleep` events through `TurnItem::Extension`.
- Preserve sleep persistence, thread-history reconstruction, and the app-server's `type: "sleep"` schema while reusing the extension-owned type.

## Testing

- Add serialization coverage for the `clock.sleep` extension wire shape and its protocol wrapper.
- Update sleep lifecycle and thread-history tests to exercise the extension-owned item.

GitOrigin-RevId: 34bb33ed9b90a09bf4744aafdb56e019c717a168
2026-07-14 21:01:54 +00:00
Owen Lin
e0a3641e6c Support paginated thread history in app-server list APIs (#33152)
## Why

Clients could discover threads with paginated history, but could not page their
turns through `thread/turns/list`.

## What changed

- Route `thread/turns/list` for paginated threads through the thread store,
  preserving cursors, sort direction, turn status, timing, errors, and the
  requested `summary` or `notLoaded` item view.
- Reject the `full` turn item view for paginated threads and direct clients to
  `thread/items/list` for complete item pagination.
- Return each item page entry with both its containing `turnId` and `item`, so
  clients can regroup thread-wide pages into turns.
- Expose local thread-store turn and item pagination through the `ThreadStore`
  implementation.

## Testing

Added app-server coverage for paging projected turns and items, item views,
cursors, turn metadata, and rejection of the unsupported `full` view.

GitOrigin-RevId: 9c4a127a9b4fa91313f0e87494c62fe2e3a06f19
2026-07-14 20:48:05 +00:00
Owen Lin
b24aa20107 Resume thread history projection from its SQLite checkpoint (#32928)
## Why

Thread history JSONL is the durable source of truth, while SQLite is a rebuildable view. If a SQLite projection fails after a durable append, the next write must catch up the unprojected suffix instead of skipping it.

## What changed

- Start materialization at the byte offset stored in `thread_history_projection_state`.
- Project only complete newline-terminated rollout records, leaving a trailing partial record for a later pass.
- Preserve validation for missing files, invalid offsets, and missing or out-of-order ordinals without advancing projection state.

## Testing

Add coverage for catching up after projection failure, avoiding replay when already synchronized, retaining partial lines, and rejecting invalid suffixes without changing SQLite state.

GitOrigin-RevId: 8a7200a77edd5411ddf5fe31ab6af5ee287ed7d7
2026-07-14 02:22:57 +00:00
Owen Lin
0ef9fa4d65 Materialize paginated thread history in SQLite (#32923)
## What changed

- Project durable paginated rollout records into rebuildable SQLite tables for turns, items, and projection progress while keeping JSONL as the source of truth.
- Add cursor-based local reads for turn summaries and thread or per-turn items, including archived-thread and history-mode validation.
- Serialize writes, shutdown, and deletion per thread so projection updates cannot race cleanup, and remove projected rows when a thread is deleted.
- Preserve stable item ordering while updating repeated snapshots and expose structured turn error details.

## Testing

- Cover projection, snapshot updates, summary recovery, pagination, failure handling, shutdown flushing, legacy-mode rejection, and deletion races.

GitOrigin-RevId: 34533185ece7f1f5690004c7f12b0f1f34dc2640
2026-07-14 02:00:39 +00:00
Owen Lin
592467fb96 Load model context from a bounded rollout suffix (#32896)
## Why

Reconstructing the latest model-visible context does not require replaying an
entire paginated rollout when a usable compaction checkpoint and the associated
completed-turn metadata are available.

## What changed

- Add `ThreadStore::load_latest_model_context` and `StoredModelContext` for
  loading replay-ready model context independently of full thread history.
- Reverse-scan plain paginated JSONL rollouts until the newest safe bounded
  suffix is found, while preserving canonical session metadata and chronological
  replay order.
- Fall back to complete history for legacy or compressed rollouts and whenever
  compaction or rollback records make a bounded cutoff unsafe.

## Testing

- Cover checkpoint selection, turn-metadata boundaries, agent messages,
  contextual user fragments, and full-history fallbacks.

GitOrigin-RevId: 3572f4ecc7aa4099a6d9f0d3e72d0ef432cd9497
2026-07-13 23:29:59 +00:00
Felipe Coury
769a5de257 Make advanced reasoning selection explicit in the TUI (#32746)
## Why

`Max` and `Ultra` consume usage limits faster than standard reasoning levels, so they should not be selected accidentally while navigating the normal effort scale.

## What changed

- Move `Max` and `Ultra` behind a `More reasoning…` entry with a dedicated warning and descriptions.
- Keep the reasoning shortcuts from silently increasing into advanced efforts.
- Apply `Ultra` to the active conversation without changing defaults for new threads, while preserving it across mode switches and thread resumes.
- Record applied thread settings in thread metadata, including explicit clearing of reasoning effort, so resumed threads restore their latest model settings unless the user supplied an override.

## Testing

- Add TUI coverage for the advanced picker, shortcuts, Plan mode, configuration defaults, and resumed conversations.
- Add app-server and thread-store coverage for persisting, clearing, and restoring model and reasoning settings.

GitOrigin-RevId: 6708c7c2e8d38f491bf63000ea34f83476afdfe6
2026-07-13 09:40:51 +00:00
Owen Lin
bfe31598c7 Persist paginated items in the local thread store (#32289)
## What changed

- Allow the local thread store to create paginated threads while keeping them unsupported through the app-server API.
- Filter live append items using the history mode recorded with the thread's live recorder, so paginated threads persist canonical `ItemCompleted` events instead of legacy history events.

## Testing

- Add a local-store test that creates a paginated thread and verifies that a paginated item is persisted while a legacy user-message event is omitted.

GitOrigin-RevId: 83d93660afd5f0217213d4f21b5bf2a97745ff19
2026-07-10 23:26:58 +00:00
Owen Lin
c4318c386d Include terminal errors in turn completion events (#32280)
## What changed

- Add an optional `error` payload to `TurnCompleteEvent` and omit it when a turn completes without an error.
- Preserve the full terminal `ErrorEvent` through the turn lifecycle so the completion event includes the same error details emitted separately by `EventMsg::Error`.

## Testing

- Extend the stream-error integration test to verify that `TurnCompleteEvent` contains the emitted error and that the next turn can still proceed.

GitOrigin-RevId: 8d32942a44132763076130102d8b89ab356d59d0
2026-07-10 22:04:03 +00:00
Owen Lin
bca577d69a Include start times in terminal turn events (#32263)
## What changed

- Add an optional `started_at` Unix timestamp to `TurnCompleteEvent` and
  `TurnAbortedEvent`.
- Populate the timestamp from turn timing state when a turn completes or is
  aborted.
- Preserve the timestamp when synthesizing interrupted fork history and when
  importing external sessions.

## Testing

- Verify that interrupting a turn emits a `TurnAbortedEvent` with a start
  timestamp.

GitOrigin-RevId: 75b3911b95c060e5fc1843a5e293d8fe47377186
2026-07-10 20:25:03 +00:00
Owen Lin
2342b2c2a6 feat(rollout): persist TurnItems for paginated thread rollouts (#30188)
## Description

This PR makes new threads with `history_mode = "paginated"` persist
`ItemCompleted(item: <turn_item>)` in their rollout JSONL file.

Legacy threads keep persisting the existing legacy events. Because the
format is selected per thread, a rollout is either legacy or paginated;
we do not need to support mixed rollouts containing both
representations.

This PR depends on [#31473](https://github.com/openai/codex/pull/31473).

## Why

Paginated thread history needs stable turn/item IDs and completed item
snapshots so the later SQLite projector can materialize appended rollout
JSONL without rebuilding the whole thread.

Keeping the legacy persistence policy unchanged avoids changing
historical rollouts or the readers that still consume them.

## What changed

- Made rollout filtering history-mode aware. Paginated threads keep
completed canonical `ItemCompleted` events and drop their redundant
legacy projections; legacy threads keep the existing event set.
- Made forks inherit the source thread history mode, so copied legacy
history is never filtered as paginated.
- Made paginated threads assign IDs to locally-created response items
even when `Feature::ItemIds` is off, and reject streamed output items
that arrive without server IDs.
- Updated legacy turn replay, rollout list/search, and SQLite metadata
extraction to understand completed canonical user-message items.
2026-07-08 19:55:03 -07:00
Owen Lin
812cd2bb57 ensure thread.history_mode is immutable (#30261)
## Description

This PR makes `thread.history_mode` immutable after the thread's
canonical first `SessionMeta` has been written. Later same-thread
`SessionMeta` lines are compatibility metadata writes, not a new thread
definition.

Without this, an older binary could append a `SessionMeta` that omits
`history_mode`; when a newer binary replays it, serde defaults that
missing field to `legacy` and SQLite could downgrade a paginated thread.

## Why

`history_mode` is the persisted thread storage contract.
Paginated-thread fail-closed behavior and SQLite memory filtering depend
on it staying aligned with canonical rollout metadata, especially when
multiple Codex binary versions can touch the same local rollout.

## What changed

- Stop generic rollout metadata replay from overwriting `history_mode`
from later `SessionMeta` items.
- Remove `history_mode` from `ThreadMetadataPatch`, so mutable metadata
sync and app-server metadata updates cannot rewrite it.
- When local metadata sync has to recreate a missing SQLite row, recover
`history_mode` from the rollout's canonical first `SessionMeta` instead
of from a mutable patch.
- Keep the in-memory thread store using the created thread's canonical
`history_mode` instead of metadata patches.
- Fill the one remaining core test `CreateThreadParams` initializer with
the new `history_mode` field; Bazel CI caught this after the parent
history-mode PR landed.

## Validation

- `just fmt`
- `just test -p codex-thread-store`
- `just test -p codex-state
session_meta_does_not_set_model_or_reasoning_effort`
2026-06-26 12:32:31 -07:00
Owen Lin
5267e805fb feat(app-server): add history_mode to thread (#29927)
## Description

This PR adds a new `historyMode = "legacy" | "paginated"` to `Thread`.
This will be stored in `SessionMeta` in the JSONL rollout file and as a
new column in the SQLite thread_metadata table, and exposed on
`thread/start` and on the `Thread` object in app-server.

## What changed

- Added canonical `ThreadHistoryMode` with `legacy` and `paginated`,
defaulting old and new SessionMeta to `legacy`.
- Carried `history_mode` through core session config, ThreadStore stored
metadata, local/in-memory stores, rollout metadata extraction, and the
existing SQLite `threads` table.
- Added experimental `historyMode` to app-server v2 `Thread` and
`thread/start`.
- Made paginated stored threads metadata-discoverable but unsupported
for legacy full-history reads, `load_history`, live resume, and create
paths.
- Regenerated app-server schema fixtures and added
protocol/state/thread-store/app-server coverage for persistence and
fail-closed behavior.

## Compatibility floor
Because users may be running various versions of Codex binaries on the
same machine (TUI, Codex App, etc.), we will need to establish a
compatibility floor for upcoming paginated threads, which will change
how thread storage reads and writes work.

The overall plan here:
```
Release N:
- Add historyMode to SessionMeta / Thread / SQLite metadata.
- Teach binaries to understand paginated threads.
- If a binary sees `historyMode="paginated"` but does not support the paginated contract, it refuses to resume/mutate the thread.
- Default remains `"legacy"`.

Release N+1:
- First-party clients start opting into paginated threads where appropriate.
- Internal dogfood / staged rollout.
- Measure old-client usage and paginated-thread unsupported errors.

Release N+2:
- Only after Release N+ is overwhelmingly deployed, make paginated the default.
- Accept that a small tail of N-1-or-older binaries may not understand paginated threads.
```

The important behavior change is fail-closed handling for a binary that
encounters a persisted `paginated` thread before it knows how to fully
support paginated history. In app-server, if a thread is `paginated`, we
will:

- allow metadata-only discovery paths like `thread/list` and
`thread/read(includeTurns=false)`, so clients can still see the thread
and inspect its `historyMode`
- reject legacy full-history/live-thread paths like
`thread/read(includeTurns=true)` and `thread/resume` with an unsupported
JSON-RPC error
- avoid silently treating an unknown or future `historyMode` as `legacy`

Under the hood, the ThreadStore layer also rejects legacy operations
that would need to load or replay the full thread history for a
paginated thread. That gives us the behavior we want for Release N:
future paginated threads are visible, but this binary fails closed
instead of trying to operate on them as if they were legacy threads.
2026-06-26 09:12:42 -07:00
jif
8f02973d25 Persist selected capability roots and resolve availability per model step (#29856)
## Why

`selectedCapabilityRoots` is durable thread intent: “use this capability
root from environment `worker`.”

The important product assumption is:

> One environment ID always names the same logical executor and stable
contents.

`worker` does not silently change from executor A to an unrelated
executor B. The process-local connection handle for `worker` can still
be replaced while Codex is running, though, for example when
`environment/add` registers a fresh handle for the same logical
environment.

The thread should persist only the stable selection. Each model step
should pair that selection with the exact ready handle captured for that
step.

## The boundary

```text
persisted thread intent
  plugin@1 -> environment "worker"
                |
                | capture the current step
                v
model-step view
  unavailable, or
  plugin@1 + worker's exact captured ready handle
```

The environment ID is the stable identity and cache key. The
`Arc<Environment>` is only a process-local handle retained so consumers
of one model step use the same captured environment. It is never
persisted and it does not imply different environment contents.

## What changes

### Persist the stable selection

Selected roots are written into `SessionMeta` and restored with the
thread. Forked subagents inherit the same selections, including
bounded-history forks.

Only stable data is persisted: root ID, environment ID, and root path.

### Capture readiness together with the exact handle

The environment snapshot records:

```rust
environment_id -> Some(Arc<Environment>) // ready in this step
environment_id -> None                   // still starting in this step
```

This prevents readiness and execution from coming from different
registry snapshots.

For example:

```text
step snapshot: worker -> handle A, ready
environment/add: worker -> fresh handle B for the same logical environment
current step: plugin@1 still uses captured handle A
```

Without carrying handle A in the snapshot, the resolver could combine “A
was ready” with handle B and treat B as ready before it had finished
starting.

This does not change cache invalidation. Stable capability metadata
remains identified by environment ID and capability root. Replacing a
process-local handle under the same stable environment ID does not
invalidate or rediscover that metadata.

### Resolve availability per model step

- A ready captured environment produces resolved roots using its
captured handle.
- A starting, missing, or failed environment is omitted from that step.
- A selected lazy environment that is outside the turn's captured
environment set is asked to start, and a later step can observe it as
ready.
- No capability files are scanned here.

Transient transport disconnects remain the remote client's reconnect
concern. This PR models initial attachment/readiness; it does not add
live socket-connectivity state.

## Example

```text
thread selection: plugin@1 -> environment "worker"

step 1: worker is starting -> plugin@1 unavailable
step 2: worker is ready    -> plugin@1 resolves through worker's captured handle
step 3: fresh local handle -> current step remains pinned; a later step captures its own view
```

Temporary unavailability does not discard the durable selection. Later
PRs can retain stable metadata caches while projecting only currently
available capabilities into model-visible World State.

## Compatibility

The app-server request shape does not change. Older rollouts without
`selected_capability_roots` deserialize to an empty list.

## Stack

1. **This PR:** persist stable selected roots and resolve them through
an exact model-step handle.
2. #29960: cache stable skill metadata and project available skills into
World State.
3. #29946: cache stable plugin declarations and manage the separate live
MCP runtime.
2026-06-25 17:49:43 +00:00
sayan-oai
fa036d39aa [2/3] core: persist world state in rollouts (#29835)
## Why

`WorldState` currently remembers its model-visible diff baseline only in
memory. That leaves no durable source for restoring the exact baseline
after resume, fork, rollback, or compaction.

This is the second PR in the WorldState persistence stack, built on
#29833 and following #29249. It records durable state transitions; the
next PR will replay them during rollout reconstruction.

## What

- Add a `world_state` rollout item containing either a full snapshot or
an RFC 7386 JSON Merge Patch.
- Persist a full snapshot after initial context and after compaction
establishes a new context window.
- Persist non-empty patches when later sampling steps or turns advance
the WorldState baseline.
- Write model-visible history before its matching WorldState record, so
an interrupted write can only cause a safe repeated update on replay.
- Preserve WorldState records for full-history forks while excluding
them from thread previews, metadata, and app-server history
materialization.

Older binaries read rollout lines independently, so they skip the
unknown `world_state` records while retaining the rest of the thread.

## Testing

- `just test -p codex-core
snapshot_merge_patch_changes_and_removes_nested_values`
- `just test -p codex-core
world_state_baseline_deduplicates_until_history_is_replaced`
- `just test -p codex-core
deferred_executor_compaction_preserves_then_updates_environment_once`
- `just test -p codex-protocol`
- `just test -p codex-rollout`
- `just test -p codex-state`
- `just test -p codex-thread-store`
- `just test -p codex-app-server-protocol`
2026-06-24 20:13:49 -07:00
Brent Traut
8057603d0c feat(app-server): list descendant threads by ancestor (#29591)
## Why

`thread/list` can filter direct children with `parentThreadId`, but
clients cannot request an entire spawned subtree. Discovering every
descendant requires repeated client-side requests and gives up the
database's existing filtering and pagination path.

## What changed

Experimental clients can use `ancestorThreadId` to return strict
descendants at any depth while `parentThreadId` retains its direct-child
meaning. The filters are mutually exclusive, the ancestor is excluded,
and every result preserves its immediate `parentThreadId` so callers can
reconstruct the tree.

## How it works

- **Explicit relationship:** Internal list parameters distinguish direct
children from transitive descendants without changing the meaning of
`parentThreadId`.
- **Existing graph:** Persisted parent-child spawn edges remain the
source of truth, so descendant lookup needs no schema migration or
ancestry cache.
- **Indexed traversal:** A recursive SQLite query starts from the
parent-edge index, walks each generation, and applies thread filters,
sorting, and cursor pagination in the same database request.
- **Reconstructable results:** The response stays flat and normally
ordered while carrying each descendant's immediate parent.

## Verification

Ran 550 tests across the protocol, state, rollout, and thread-store
crates, then reran the four focused state, store, and app-server
descendant-listing tests after the final diff reduction. Scoped Clippy
and formatting checks passed. Stable and experimental schema generation
was checked; the stable fixtures remain unchanged while the experimental
schema includes the new field.
2026-06-24 13:08:14 -07:00
jif
b4f0f3eff1 Persist agent messages as response items (#29829)
## Why

Inter-agent messages are recorded in live history as
`ResponseItem::AgentMessage`, but rollouts stored
`InterAgentCommunication` and rebuilt the response item during resume.
This made the rollout differ from the actual Responses history.

## What changed

- store the prepared `agent_message` response item directly
- keep `trigger_turn` in a small local metadata record for fork
truncation
- keep reading older `inter_agent_communication` rollout items
2026-06-24 15:43:10 +01:00
alexsong-oai
1acb722e8a Support thread-level originator overrides (#29477)
## Why

Work(TPP) threads can be launched from the Desktop app, but if they all
keep the Desktop app's default originator then downstream attribution
cannot distinguish local Work launches from cloud-backed Work launches.
`thread/start.serviceName` already carries that launch signal, while
`SessionMeta.originator` is the durable thread-level value that survives
resume and fork.

This change converts the Desktop Work service names into an effective
originator at thread creation time, persists that originator with the
thread, and keeps using it for later model requests and memory writes.

## What changed

- Map `CODEX_WORK_LOCAL` and `CODEX_WORK_CLOUD` service names to
per-thread originators, while preserving
`CODEX_INTERNAL_ORIGINATOR_OVERRIDE` as the highest-precedence override.
- Persist the effective originator in `SessionMeta.originator`, read it
back on resume/fork, and inherit the parent originator for subagent
spawns when there is no persisted session metadata.
- Handle truncated `SpawnAgentForkMode::LastNTurns` forks by falling
back to the live parent originator when the forked history no longer
includes `SessionMeta`.
- Thread the per-thread originator through Responses headers,
websocket/compaction request paths, thread-store creation, rollout
metadata, and memory stage-one telemetry.

## Verification

- `just test -p codex-core
agent::control::tests::spawn_thread_subagent_inherits_parent_originator_without_fork
agent::control::tests::spawn_thread_subagent_fork_last_n_turns_inherits_parent_originator_without_session_meta
thread_manager::tests::originator_override_precedes_service_name_remapping`
- `just test -p codex-core
agent::control::tests::resume_thread_subagent_restores_stored_metadata_and_effective_multi_agent_mode`
- `just test -p codex-memories-write`
- `just fix -p codex-core -p codex-memories-write`
- `git diff --check`
2026-06-23 17:23:38 -07:00
Michael Bolin
01f89c8c59 core: persist initial context window metadata (#29519)
## Why

PR #29494 made context-window IDs visible to the model by wrapping the
token-budget window payload in `<context_window>`, but rollout JSONL
consumers still could not see the initial window identity by tailing the
session file. Compacted rollout items carry window IDs only after
compaction has happened, so a session with no compaction had no durable
JSONL record for window 0.

This change gives tailing consumers a stable initial-window record at
session creation time.

## What Changed

- Added `session_meta.context_window.window_id` for the initial
context-window identity.
- `CreateThreadParams` now requires `initial_window_id: String`, so
thread-store callers cannot accidentally create new threads without
window-0 metadata.
- Live thread creation derives the persisted initial window ID from the
same `AutoCompactWindowIds` used to initialize `SessionState`, keeping
runtime state and JSONL metadata aligned.
- Rollout reconstruction uses `session_meta.context_window.window_id` as
the initial-window fallback and derives `window_number = 0`,
`first_window_id = window_id`, and `previous_window_id = None`
internally.
- Fork reconstruction intentionally uses the same rollout reconstruction
path; consumers that need to distinguish copied initial-window metadata
can use the rollout `thread_id`.
- Legacy compactions without `window_number` still use compaction-count
fallback accounting instead of being reset to window 0 by the
initial-window fallback.
- Compacted rollout metadata still takes precedence once compaction
records exist, preserving the richer chain fields there.

## JSONL Shape

Real rollout JSONL is one object per line. This example is expanded for
readability, but shows the new initial `session_meta.context_window`
record followed by the existing compacted rollout item shape that also
carries window IDs:

```jsonl
{
  "timestamp": "2026-06-22T12:00:00.000Z",
  "type": "session_meta",
  "payload": {
    "session_id": "<THREAD_ID>",
    "id": "<THREAD_ID>",
    "timestamp": "2026-06-22T12:00:00.000Z",
    "cwd": "/repo",
    "originator": "codex",
    "cli_version": "0.0.0",
    "source": "cli",
    "model_provider": "<MODEL_PROVIDER>",
    "context_window": {
      "window_id": "<INITIAL_WINDOW_ID>"
    }
  }
}
...
{
  "timestamp": "2026-06-22T12:34:56.000Z",
  "type": "compacted",
  "payload": {
    "message": "<COMPACTION_SUMMARY>",
    "replacement_history": [
      "..."
    ],
    "window_number": 1,
    "first_window_id": "<INITIAL_WINDOW_ID>",
    "previous_window_id": "<INITIAL_WINDOW_ID>",
    "window_id": "<NEXT_WINDOW_ID>"
  }
}
```

The nested `context_window` object is intentional: it gives rollout
consumers a stable namespace for context-window metadata while only
writing the non-derivable initial `window_id`. For the initial window,
`window_number`, `first_window_id`, and `previous_window_id` are derived
internally instead of being written to the rollout.

## Verification

- `just test -p codex-protocol`
- `just test -p codex-rollout
recorder_materializes_on_flush_with_pending_items`
- `just test -p codex-core reconstruct_history`
- `just test -p codex-core
record_initial_history_reconstructs_forked_transcript`
- `just test -p codex-thread-store`
- `just test -p codex-state`
- `just test -p codex-app-server
thread_read_returns_summary_without_turns`
- `just test -p codex-rollout persistence_metrics`
2026-06-23 21:50:50 +00:00
Tom
02c326f646 [codex] Instrument rollout persistence bytes (#29498)
- Add 1%-sampled rollout persistence metrics that report per-item and
per-thread JSON byte totals before and after filtering when metrics
export is enabled.
- Tag each item with its exact response or event variant, including
nested turn-item kinds for conditionally persisted completion events, so
aggregate cloud-storage impact can be estimated by policy choice.
2026-06-23 09:26:30 -07:00
Charlie Marsh
330ae6a516 Share resumed rollout history (#28426)
## Summary

Resuming a persisted thread currently deep-clones its complete rollout
history several times. `InitialHistory` is retained for the app-server
response, copied into thread persistence, and copied again by read-only
accessors. These copies scale with the complete rollout rather than the
bounded model context and add measurable latency for large sessions.

This change stores resumed rollout history in `Arc<Vec<RolloutItem>>`.
Rollout loading wraps the parsed vector once, while app-server response
construction, session initialization, and thread persistence share it
through inexpensive `Arc` clones. Read-only history access now returns a
borrowed slice, and fork paths use `Arc::unwrap_or_clone` where they
genuinely need mutable ownership. Rollout reconstruction also consumes
its temporary context instead of cloning the reconstructed model
history.

The serialized representation remains unchanged. In an artificial 123 MB
rollout benchmark, sharing resumed history reduced cold resume latency
by roughly 9–10%. The affected crates compile with their test targets,
all 80 thread-store tests pass, and the Bazel dependency lock remains
valid.
2026-06-23 10:23:25 -04:00
Owen Lin
5b95745eae core: rename metadata -> internal_chat_message_metadata_passthrough (#28968)
## Description
This PR cuts Codex over from generic `ResponseItem.metadata` (introduced
here: https://github.com/openai/codex/pull/28355) to
`ResponseItem.internal_chat_message_metadata_passthrough`, which is the
blessed path and has strongly-typed keys.

For now we have to drop this MAv2 usage of `metadata`:
https://github.com/openai/codex/pull/28561 until we figure out where
that should live.
2026-06-22 11:11:25 -07:00
jif
6d15bb3d17 Persist session IDs across thread resume (#29327)
## Summary

A cold-resumed subagent kept its durable thread ID but could receive a
new session ID, splitting one agent tree across multiple sessions after
a restart.

Persist the root session ID in every rollout `SessionMeta`, carry it
through thread creation, and restore it before initializing the resumed
`Session` and `AgentControl`.

## Behavior

For a nested agent tree:

```text
root session R
  parent thread P
    child thread C
```

The child rollout stores:

```text
session_id:       R
parent_thread_id: P
id:               C
```

After a cold resume, the child still belongs to root session `R` while
its immediate parent remains `P`. The integration coverage uses distinct
values for all three IDs so it catches restoring the session from
`parent_thread_id`.

## Legacy rollouts

Previous rollouts have `id` but no `session_id`. `SessionMetaLine`
deserialization treats a missing `session_id` as `id`, keeping those
files readable, listable, and resumable. When a legacy subagent is
resumed through its root, that synthesized child ID no longer overrides
the inherited root-scoped `AgentControl`. New rollouts always persist
the explicit root session ID.
2026-06-22 09:36:08 +02:00
pakrym-oai
d1209bddfc core: add context window lineage IDs (#29256)
## Why

The rendered `<token_budget>` fragment identifies the thread and current
context window, but it does not expose enough lineage to identify the
first window in the thread or the immediately preceding window. Those
IDs also need to remain stable across compaction, resume, and rollback.

## What changed

- Track first, previous, and current UUIDv7 context-window IDs in
auto-compaction state.
- Render `thread_id`, `first_window_id`, `previous_window_id`, and the
current window ID in the full `<token_budget>` fragment.
- Persist the first and previous window IDs in compacted rollout
checkpoints and restore them during rollout reconstruction.
- Preserve compatibility with older compacted records that do not
contain the new optional fields.
- Update focused state, rendering, reconstruction, rollback, and
serialization coverage.

## Validation

- `just test -p codex-core token_budget`
- `just test -p codex-protocol compacted_item::tests`
- `just test -p codex-core tracks_prefill_and_window_boundaries`
- `just test -p codex-core
reconstruct_history_uses_replacement_history_verbatim`
- `just test -p codex-core
thread_rollback_restores_cleared_reference_context_item_after_compaction`
2026-06-20 13:15:49 -07:00
rphilizaire-openai
a57b268d61 [codex] trace pre-sampling skill and persistence latency (#29042) 2026-06-19 10:13:27 -07:00