Skip to content

Support first-class per-thread skill/plugin selection in app-server thread/start #30967

Description

@jyokotori

What variant of Codex are you using?

CLI

What feature would you like to see?

Support first-class per-thread skill/plugin selection in app-server thread/start

Summary

App-server hosts that run multiple independent Codex agent sessions need a way to choose different skill/plugin sets for each thread/start request without mutating the user's global/project config.toml and without running each agent type under a separate CODEX_HOME.

Current main has useful partial mechanisms, but none provide positive per-thread selection of installed plugins/skills by logical identity, nor a clear replace/restrict mode for the user's existing capability set.

Checked against local main at beca198b8a (telemetry: log structured direct tool-call timing (#30334)).

Why this matters

Some hosts launch many Codex sessions for different task types:

  • a code-review agent with review-only skills/plugins
  • a planning agent with project-management integrations
  • a support/debugging agent with log or incident-response integrations
  • an internal automation agent with only a narrow allowlist of tools

The host should be able to start each thread with the intended capabilities. It should not need to rewrite a user's config.toml, ask the user to toggle plugins globally, or create one synthetic CODEX_HOME per agent type just to isolate plugin/skill loading.

Current mechanisms and gaps

thread/start.selectedCapabilityRoots

selectedCapabilityRoots is an experimental field for environment-owned capability roots. The current shape is SelectedCapabilityRoot { id, location }, and CapabilityRootLocation only has Environment { environmentId, path }. The roots are stored in thread extension data and SessionMeta; current code preserves them across resume/fork and copies them for spawned child threads. The README now documents the basic behavior for environment-owned plugin or standalone-skill roots.

This is useful, but it is still path/root based and additive. The host must know an environment-native absolute path, there is no local-only or installed-capability locator, and there is no way to reference an installed plugin by marketplace/plugin ID or a skill by logical name. It also cannot replace or restrict the user-config-derived skill/plugin set.

thread/start.config

thread/start.config is loaded as a ConfigLayerSource::SessionFlags layer. That gives hosts some per-thread control:

  • features.plugins = false disables all plugin loading for that thread.
  • [mcp_servers.*] can add or override MCP servers for that thread.
  • [[skills.config]] name/path rules can disable specific skills for that thread, because skill_config_rules_from_stack reads SessionFlags.

The plugin path is asymmetric: [plugins.*] state from SessionFlags is ignored because configured_plugins_from_stack goes through project_effective_user_config() and effective_user_config(), which only merge ConfigLayerSource::User layers. Project .codex/config.toml does not provide a project-scoped plugin profile either; it can disable all plugins with features.plugins=false, but it cannot replace the user's enabled plugin set. Skill roots have a similar limitation for adding new roots: they are derived from system/user/project config folders, and SessionFlags has no config folder.

Other related fields

thread/start.environments can make environment-owned selected roots available or unavailable, but it is not a selector for user-installed skills/plugins. thread/start.dynamic_tools adds host-defined tools for one thread, but does not restrict existing skills/plugins. permissions selects permission profiles, not skill/plugin capability sets. ThreadSettingsUpdateParams can update runtime settings after start, but cannot alter the thread's capability selection.

Combined, a host can currently turn all plugins off, add ad-hoc MCP servers, and selectively disable named skills. It cannot positively select or replace a per-thread set of installed skills/plugins by logical identity.

Request

Please either document/stabilize the existing mechanisms as the intended app-server answer for per-thread skill/plugin selection, or add a first-class thread/start API for selecting capabilities by logical identity.

Option A: Clarify or extend selectedCapabilityRoots

I've been waiting for an official surface for this kind of per-thread capability selection. Seeing selectedCapabilityRoots was encouraging, but its current shape seems focused on environment-owned roots rather than the installed skill/plugin selection use case described here.

If selectedCapabilityRoots is intended to become the official per-thread capability-selection surface, it would help to:

  • add a local or installed-capability locator, so hosts do not have to express every root as an environment path
  • clarify that today's behavior is additive, and add a mode that can restrict/replace the user-config-derived skill/plugin set
  • support referencing installed plugins/skills by logical ID instead of only absolute paths
  • document lifecycle behavior for resume/fork/spawn, trust/ownership/policy validation, and the experimental-gate stability plan

If selectedCapabilityRoots is only meant for environment-owned capability paths, please document that boundary explicitly so hosts do not treat it as a general capability profile API.

Option B: Add logical per-thread capability selection

For installed plugins/skills, a higher-level request field could select by logical IDs or named profiles:

{
  "method": "thread/start",
  "params": {
    "capabilitySelection": {
      "mode": "replace",
      "plugins": ["github@openai", "linear@internal"],
      "skills": ["repo-review", "incident-debugging"],
      "mcpServers": ["github", "linear"]
    }
  }
}

This would let the app-server host choose a capability profile per agent session while Codex still owns installation lookup, policy checks, config precedence, and environment resolution.

This is the direction I am currently implementing locally because mutating config.toml cannot safely support concurrent agents with different capability sets. It would be much better for app-server to expose this as a native, documented contract.

Option C: Extend SessionFlags to cover [plugins.*]

A narrower fix would be to let configured_plugins_from_stack include the SessionFlags layer for [plugins.*] enable/disable state, similar to how skill_config_rules_from_stack already handles SessionFlags. Then a host could pass [plugins.foo] enabled = false per thread the same way it can disable skills today.

Desired behavior

An app-server host can start two threads against the same Codex server and same user CODEX_HOME, but with different effective capability sets:

  1. thread/start for agent A enables/selects only review-related skills/plugins.
  2. thread/start for agent B enables/selects only planning-related skills/plugins.
  3. The selection is persisted with the thread and preserved across resume/fork/spawn according to documented rules.
  4. The user's global installed/enabled plugin state is not mutated.
  5. The host does not need to create one CODEX_HOME per agent type.

Related issues

These look adjacent but not exact duplicates:

This issue is specifically about app-server thread/start choosing different skill/plugin capability sets per new agent thread.

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIapp-serverIssues involving app server protocol or interfacesenhancementNew feature or requestskillsIssues related to skills

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions