Skip to content

Cache stable executor skills and project them per model step - #29960

Closed
jif-oai wants to merge 13 commits into
mainfrom
jif/activate-selected-capability-skills
Closed

Cache stable executor skills and project them per model step#29960
jif-oai wants to merge 13 commits into
mainfrom
jif/activate-selected-capability-skills

Conversation

@jif-oai

@jif-oai jif-oai commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Why

The model must see the same skills that Codex can read from the current executor. Environment availability can change between sampling steps, but an environment ID and the contents behind its selected root are stable.

Therefore skill metadata should be discovered once, not reread every time the environment reconnects or the model samples again.

The flow

stable selected root from #29856
             |
             | first ready step: discover once
             v
session-lifetime catalog cache
             |
             | project only when environment is available
             v
one SkillsSnapshot for the model step
  ├─ World State
  ├─ explicit skill selection
  ├─ skills.list
  └─ skills.read

The same snapshot decides what the model sees and which current environment handle reads a selected skill body.

Stable cache identity

The cache key is stable data only:

struct ExecutorSkillCatalogCacheKey {
    selected_root: SelectedCapabilityRoot,
    restriction_product: Option<Product>,
}

It deliberately excludes the process-local environment Arc.

Each cached entry also owns the stable SkillSourceIdentity used for instruction deduplication. Reconnecting the same environment therefore does not make an unchanged skill look like a new instruction that must be injected again.

first ready step for worker -> discover and cache catalog
worker disconnects          -> remove its skills from current World State
worker reconnects           -> reuse catalog and source identity
session ends                -> drop cache

Catalogs containing warnings are not cached, so transient read failures can retry. There is intentionally no file-watcher or filesystem invalidation: selected environment contents are stable for the session.

Metadata versus reads

Metadata is reusable, but a skill body is read through the current ready connection:

let cached = cache.catalog_for_stable_root(root, restriction).await;
let source = ExecutorSkillSource::new(root.clone(), restriction, cached.identity());
  • Catalog cache: which skills exist under this stable selected root?
  • Current source: which ready connection reaches that stable environment now?

This avoids repeated catalog RPCs without converting executor resources into ambient host paths.

World State behavior

  • The first snapshot renders the full available-skills section.
  • Later snapshots render additions and removals.
  • Temporary unavailability removes the environment's skills from the model-visible projection but keeps their cached metadata.
  • Reconnection re-adds the same skills without rediscovery or duplicate instruction injection.
  • Selected-skill instructions remain bounded to 8 items and 32 KB per turn, with a hard per-item cap.

World State assembly remains in core in this PR. The skills extension continues to own its orchestrator source and skills.list / skills.read tools. Moving the World State section fully into the extension still requires a general per-step World State contributor API and is intentionally separate from this cache contract.

Verification

The remote-environment integration coverage starts with the environment unavailable, observes the skill after it becomes ready, then samples again and verifies that executor reads do not increase after the initial catalog discovery and selected-body read.

Stack

  1. Persist selected capability roots and resolve availability per model step #29856: persist stable selected roots and resolve availability per model step.
  2. This PR: cache stable skill metadata and project available skills into World State.
  3. Cache stable plugin metadata separately from live MCP runtimes #29946: cache stable plugin declarations and manage the separate live MCP runtime.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1531dc319e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/ext/skills/src/selected.rs Outdated
Comment thread codex-rs/ext/skills/Cargo.toml Outdated
@jif-oai
jif-oai force-pushed the jif/activate-selected-capability-skills branch from 1531dc3 to d04c1d5 Compare June 25, 2026 11:18
@jif-oai
jif-oai requested a review from a team as a code owner June 25, 2026 11:18
@jif-oai jif-oai changed the title Activate selected executor skills atomically Extract runtime skill source boundary Jun 25, 2026
@jif-oai
jif-oai changed the base branch from jif/cover-selected-capability-lifecycle to jif/thread-scoped-executor-connectors June 25, 2026 11:18
@jif-oai
jif-oai force-pushed the jif/activate-selected-capability-skills branch from d04c1d5 to 6c9dde7 Compare June 25, 2026 12:07
@jif-oai
jif-oai force-pushed the jif/activate-selected-capability-skills branch from 6c9dde7 to dbc53c5 Compare June 25, 2026 13:07
@jif-oai
jif-oai force-pushed the jif/thread-scoped-executor-connectors branch from 6ed45b0 to 740c8d8 Compare June 25, 2026 13:07
@jif-oai jif-oai changed the title Extract runtime skill source boundary Use one per-step runtime for skills and World State Jun 25, 2026
@jif-oai
jif-oai force-pushed the jif/activate-selected-capability-skills branch from 0015e2c to cffa959 Compare June 25, 2026 16:16
@jif-oai
jif-oai force-pushed the jif/activate-selected-capability-skills branch from cffa959 to addf6b5 Compare June 25, 2026 16:45
@jif-oai jif-oai changed the title Use one per-step runtime for skills and World State Cache stable executor skills and project them per model step Jun 25, 2026
Base automatically changed from jif/thread-scoped-executor-connectors to main June 25, 2026 17:49
jif-oai added a commit that referenced this pull request Jun 25, 2026
…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.
jacks0n pushed a commit to jacks0n/codex that referenced this pull request Jul 6, 2026
…step (openai#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. openai#29960: cache stable skill metadata and project available skills into
World State.
3. openai#29946: cache stable plugin declarations and manage the separate live
MCP runtime.
@github-actions

Copy link
Copy Markdown
Contributor

Closing this pull request because it has had no updates for more than 14 days. If you plan to continue working on it, feel free to reopen or open a new PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant