Subagent configuration and orchestration

Resolved 💬 70 comments Opened Feb 13, 2026 by ticoAg Closed Feb 19, 2026
💡 Likely answer: A maintainer (etraut-openai, contributor) responded on this thread — see the highlighted reply below.

What variant of Codex are you using?

cli

What feature would you like to see?

support to config which model and reasoning_effort to use for subagents in ~/.codex/config.toml
may it can be an option of slash cmd: /agents -> config

Description

Current Pain Point
Currently, I prefer using gpt-5.2 for high-level task planning and orchestration, and gpt-5.3-codex or spark for the exploration and execution of specific tasks. However, due to the lack of per-agent model configuration, users are stuck choosing a single model or using a pre-defined configuration for the entire run.

This creates a dilemma:

  1. Sacrificing Planning Quality: Using a smaller model (like Spark) everywhere results in poor high-level orchestration.
  2. Sacrificing Efficiency & Cost: Using a larger model for simple subtasks that don't require it sacrifices speed and cost-effectiveness.

Feature Suggestions

To maximize effectiveness and efficiency, I hope to configure subagent behavior and models granularly:

  1. File-based Configuration & Definition
  • Please consider supporting subagent configuration not only via ~/.codex/config.toml (global) but also via files like .agents/subagents/explore_agent.md at the repository or user level.
  • As @am-will mentioned, similar to how Claude defines subagent configurations through files, I hope subagent instructions can be defined like AGENTS.md.
  • Permission configuration also needs to be supported.
  1. Agent Management Commands
  • The slash command /agents -> list should show which subagents are currently available.
  • Support for disabling and enabling specific subagents via command.
  1. Custom Subagent Examples & Context Optimization (New)

Beyond basic configuration, I hope to support (and see examples of) custom exploration behaviors tailored to specific domains or projects. Different tech stacks require different exploration strategies:

  • Frontend Architecture: An exploration agent could be instructed to prioritize the component tree, state management (Store), and routing structure.
  • Backend Architecture: Depending on the language (e.g., Go vs. Python), the agent should prioritize API definitions, DB schemas, or middleware logic.
  • Repo-level Optimization: Allow writing custom exploration instructions for specific repositories.

Value: Optimizing exploration behavior for specific repos can effectively reduce the "task cold start time" when a user raises a new requirement in a fresh session. The agent wouldn't need to perform a generic full scan but could follow the pre-defined "map" to locate relevant context immediately.

Expected Outcome
If this subagent configuration and behavior can be supported, users can use it out of the box while also leveraging customized exploration behaviors. We'll see interesting possibilities.

View original on GitHub ↗

70 Comments

etraut-openai contributor · 5 months ago

We prioritize feature requests based on upvotes from the community. If a feature request doesn't get enough upvotes, we'll close it. It's therefore important for a feature request to have a clear and compelling title and description. Speaking from experience, I don't think this one will attract much attention. You might want to try to improve the title and provide more details.

We also recommend that feature requests focus on the problem statement rather than a solution. Describe the use case and what problem you'd like to see solved. There are often multiple ways to address a given problem.

merlinnot · 5 months ago

This is much needed given Spark's low context window. I have a pretty well optimized setup for AGENTS.md and MCPs, but Spark exhausts context window too quickly. Even competitors' 200k window feels small and results in frequent compaction, 128k results in an experience which feels like more compaction than coding (subjective). Orchestrating Spark subagents with "full" Codex, and excluding MCPs/AGENTS.md from subagent context would allow users to benefit from its speed while still being able to complete work which normally requires more context.

slobodaapl · 5 months ago

This is absolutely crucial to achieve the true power of the subagent system, using a smarter capable model for planning and smaller efficient and fast models for implementation based on the orchestrator's instructions.

am-will · 5 months ago

This is a non negotiable in my opinion. This is a must have feature.

ignatremizov · 5 months ago

I would add that config might not be only via custom config.tomls, but just directly in a running session to expose model and reasoning level for the managing agent to be able to pass, similar to current prompt and task parameters.

am-will · 5 months ago

Leaving this here since mine got closed (no worries @etraut-openai thanks)

To expand on this, this deserves to be defined more clearly, hopefully OP updates the description:

---------------

The ability to configure which model and reasoning_effort level subagents use, separately from the main agent. This could live in ~/.codex/config.toml and/or be exposed as an option under the /agents → config slash command.

Why this matters
Right now, there's no way to control what model or reasoning level subagents run with. With the introduction of Spark (which has a 128k context window), this becomes a real limitation. Spark is excellent for fast, scoped implementation tasks, but it doesn't make sense as the planning or orchestration layer given the smaller context.

Subagent support is still missing in Codex, and this issue is exactly the gap:

Skills/config define what to do, but not who does it (model, tools, permissions, prompt, memory).

Claude Code’s per-agent YAML gives a portable, version-controlled way to define that who.

And the YAML allows you to define n number of subagents, all with different models/reasoning level for different tasks. TRUE customization. Right now, we have just 1 subagent and we're stuck with that.

Proposed ideal (aligned with this issue):

A first-class agent registry in Codex (project + user scope).
Agent definition files compatible with Claude Code’s format (or a small, documented superset).
Ability to invoke agents directly or pair with skills (e.g., a skill that routes to a named agent).
Model/tools/permissions/memory/prompt all captured per agent, and checked in at the project level.
The ideal workflow looks something like this:

Planning and orchestration with a higher-capability model (e.g. base 5.3) that can hold the full project context and break work into well-defined tasks

Implementation via subagents using Spark or another lightweight model that executes those tasks quickly and cheaply

Without per-agent model configuration, users are stuck choosing a single model for the entire run. That means either sacrificing planning quality by using Spark everywhere, or sacrificing speed and cost efficiency by using a larger model for implementation subtasks that don't need it.

Proposed config example
toml[agent]
model = "codex-5.3"
reasoning_effort = "high"

[subagent]
model = "codex-5.3-spark"
reasoning_effort = "low"

Additional information

This would pair well with existing slash commands. For example, /agents config could allow toggling subagent settings mid-session for experimentation without editing the config file directly.

Thank you for reading! PLEASE UPVOTE the issue itself (first message) if you agree.

Git-on-my-level · 5 months ago

Adding my 2 cents that I think spark and future mini models would be amazing subagents for context discovery which codex already naturally loves to do. Can make the context window of stronger codex models go a lot further

jif-oai contributor · 5 months ago

Hey folks, thanks for the feedback. What would you think of something like this:

Codex can load sub-agent role definitions from agents_config.toml.

version = 1

# Default role used when spawn_agent is called without agent_type.
[agent.main]
model = "gpt-5.3-codex"
reasoning_effort = "medium"
read_only = false           # true restricts the agent to read-only sandbox policy

[agents.explorer]
# Override a built-in role by reusing its name. The description is provided to the parent model so that it can decide
# which `agent_type` to use
description = "Fast codebase explorer for investigation tasks."
model = "gpt-5.3-codex-spark"
reasoning_effort = "medium"
read_only = true

[agents.planner]
# Custom role example.
description = "Planning-focused agent that produces execution plans."
model = "gpt-5.1-codex"
reasoning_effort = "high"
read_only = false           # Fallback to false when not defined.
# Optional path to extra role-specific instructions. Those instructions will be added as DEVELOPER instructions
# to the context
instructions_file = "~/.codex/agents/planner.md"

[agents.tester]
# Another custom role with a different profile.
description = "Testing-focused agent for validation and regression checks."
model = "gpt-5.1-codex-mini"
reasoning_effort = "low"

If you think this is a good design, this can land early this week

merlinnot · 5 months ago

Can we have options for these sub-agents to configure which MCP tools are passed to those? Opt-in would be my prefference, we can also have a default behavior of "pass all": options unset = enable all, options set = only specified enabled.

Does the instruction file override or append to AGENTS.md?

If agents are configured, would Codex by allowed to use _only_ the configured ones, or would it still have access to "general" subagent (current behavior)? If the latter, how do we configure these settings for the default subagent?

slobodaapl · 5 months ago

@jif-oai

I think that's perfect, it'd achieve exactly what a subagent system needs. Only extra note is that I'd make sure it's possible to also specify config per agent -- akin to doing -c in CLI args.

This is because we want to ensure some subagents don't have access to some skills, which is something I have to handle via the config arg currently.

ticoAg · 5 months ago

@jif-oai
Can agents_config.toml be defined in code repo level to override the user-level?
Specific subagent, instructions_file guide it's behavior might bind the repo.

ignatremizov · 5 months ago

agents_config.toml is a fine solution - I've been using SKILL.md files and asking the orchestrator/supervisor agent to pass the skill as the personality (explorer/implementor/reviewer/etc) - telling it to just spawn the named subagents directly would be more standardized. But I agree with @ticoAg that there should be repo-level overrides for subagent definitions - although AGENTS.md files already tweak model behavior per repo/folder, it would be better for context management and focused optimization not to have a large AGENTS.md for all cases but custom subagent definition files instead.

jif-oai contributor · 5 months ago
Can we have options for these sub-agents to configure which MCP tools are passed to those? Opt-in would be my prefference, we can also have a default behavior of "pass all": options unset = enable all, options set = only specified enabled.

@merlinnot ok will add

Does the instruction file override or append to AGENTS.md?

@merlinnot this will be developer instruction (so same as AGENTS.md) because we have some gating on the system prompt

If agents are configured, would Codex by allowed to use only the configured ones, or would it still have access to "general" subagent (current behavior)? If the latter, how do we configure these settings for the default subagent?

@merlinnot It will still have access to the main one unless you override it. main is basically a copy of the parent agent

I think that's perfect, it'd achieve exactly what a subagent system needs. Only extra note is that I'd make sure it's possible to also specify config per agent -- akin to doing -c in CLI args.

@slobodaapl I think this adds an extra layer of complexity and I'm not convinced this is the right way to do (lots of caveats with this approach). I would propose a config_overrides=<...> field in the agents_config.toml that replicates the CLI flags.

Can agents_config.toml be defined in code repo level to override the user-level?

@ticoAg and @ignatremizov let's first start with the a global version in ~/.codex and we can extend in a follow-up. I want to make sure we have the right primitive for this before people start versioning this file

Any other comments?

jif-oai contributor · 5 months ago

Pretty cool to see everyone answering so quickly! Thanks folks

ignatremizov · 5 months ago

What would the app-server RPC be? Just let the agent running the turn spawn/manage its own subagents? I don't remember exactly if this is the case already, but could sub-agents be also exposed to the running threads on app-server, and to pass an agent config, like via UserInput::Agent as done now via UserInput::Skill?

merlinnot · 5 months ago

LGTM! On per-repo config - long term I would also prefer to have those at the repo level, but I get your point about starting with global. However, how would that work with MCPs when those are specified at the repo level? As an alternative, maybe we could mark it experimental and have people versioning experimental features be aware that there can be breaking changes?

jif-oai contributor · 5 months ago
What would the app-server RPC be? Just let the agent running the turn spawn/manage its own subagents? I don't remember exactly if this is the case already, but could sub-agents be also exposed to the running threads on app-server, and to pass an agent config, like via UserInput::Agent as done now via UserInput::Skill?

@ignatremizov for now the spawning is handled by the parent agent and configs are not dynamically injected. You will just have to tell your main thread something like "can you spawn a tester agent and ask it to ...

> LGTM! On per-repo config - long term I would also prefer to have those at the repo level, but I get your point about starting with global. However, how would that work with MCPs when those are specified at the repo level? As an alternative, maybe we could mark it experimental and have people versioning experimental features be aware that there can be breaking changes?

@merlinnot I don't have a solution yet for per-repo MCPs. And from my experience, people are very bad at considering any feature as "experimental" and not open 20 issues when an experimental thing does not behave 100% as expected.
But I hear you on the need for per-repo configuration and I will make sure the follow-up come soon after the main implementation. I'll design the code to make it pretty easy

ignatremizov · 5 months ago

@jif-oai ok, just it is important for "spawn a tester agent" to be deterministic :) - as with skill token injections, if I see that the prompt to the spawned agent has $skill in its prompt, I know that the relevant SKILL.md is injected. So with agents_config.toml - we need a guarantee that the subagent has the instructions_file properly set (fallback to normal AGENTS.md, plus of course passed prompt) - not only just that model/reasoning effort configured.

slobodaapl · 5 months ago

@jif-oai A config_overrides field works perfectly fine, would be very much appreciated if present, thanks!

jif-oai contributor · 5 months ago

🧑‍🍳

ticoAg · 5 months ago

Just step-by-step with agents_config.toml. First let's configure basic subagent cfg like model and reasoning_effort, manual settings can overwrite the defaults. 🫡🫡

jif-oai contributor · 5 months ago

I think something like this would fit most use-cases: https://github.com/openai/codex/pull/11917

jif-oai contributor · 5 months ago

After some internal discussions. I will move this to the main config.toml
The feature should land tomorrow

am-will · 5 months ago

FIRE thanks for moving so fast on this. ILY

z3z1ma · 5 months ago

Great to hear. Thanks @jif-oai

Nebu1eto · 5 months ago

I'm really great to hear that. Thanks @jif-oai
Can we define subagents per project also?

am-will · 5 months ago

Good job on getting this subagent feature across the line everyone.

If I could call upon you one more time to upvote this: https://github.com/openai/codex/issues/11965

My Feature Request would allow you to set MAX_THREADS to whatever you want.

This will allow you to exceed the cap of 6 agents and launch however many our own PCs can handle

It would be a variable in the CONFIG file, like this.

I have this working in my own fork. It was very easy. They could do this change in 15 minutes but we need upvotes for sure.

<img width="342" height="137" alt="Image" src="https://github.com/user-attachments/assets/83791d2d-6d2e-498d-b81e-fa1183f78749" />

As you all know, 6 is too limiting. I want 15, 20. Give me all the agents.

You know what to do. Thanks everyone!

acgxv · 5 months ago
If you think this is a good design, this can land early this week

@jif-oai

This direction looks great. Sharing how I run it today in case it helps.

With this, I can spawn up to 12 agents. It works great in my local setup.

I use a strict hierarchy with review loops:

  • Main orchestrates and makes final accept/rework decisions.
  • Main delegates execution to parallel Assistants.
  • Each Assistant delegates implementation to Coding workers.
  • Coding reports to Assistant; Assistant reviews and can assign rework.
  • Assistant reports to Main; Main reviews and can assign rework.
flowchart TD
    M[Main]
    A1[Assistant 1]
    A2[Assistant 2]
    C1[Coding 1..n]
    C2[Coding 1..n]

    M -->|delegate| A1
    M -->|delegate| A2

    A1 -->|delegate coding tasks| C1
    A2 -->|delegate coding tasks| C2

    C1 -->|result| A1
    C2 -->|result| A2

    A1 -->|review / rework| C1
    A2 -->|review / rework| C2

    A1 -->|report| M
    A2 -->|report| M

    M -->|review / rework| A1
    M -->|review / rework| A2

One thing I really like from your roadmap is per-agent config.

I currently use external codex exec coding workers because internal agents still share one default config, while coding workers need different model/reasoning settings.
Key override pattern:

codex exec \
  --sandbox danger-full-access \
  --cd /abs/trusted/repo \
  --ephemeral \
  --output-schema ~/.codex/agent-output.coding.schema.json \
  -o /tmp/coding-output.json \
  -m gpt-5.3-codex-spark \
  -c 'model_reasoning_effort="xhigh"' \
  "<coding prompt>"

One concern if coding agents become fully internal: the current concurrency cap (6) is easy to hit in real parallel runs.

Typical shape is 1 x Main + N x Assistant + N x Coding, so capacity gets exhausted quickly.

If possible, it would be great to have either:

  • higher max concurrent internal agents, or
  • separate capacity pools (orchestration vs coding workers).

<details>
<summary>Protocol excerpt from AGENTS.md</summary>

````md

2) Role Stamps (Mandatory)

Root thread:

[ROLE:MAIN] [PARENT:NONE]

Main -> Assistant:

[ROLE:ASSISTANT]
[PARENT:MAIN]
[SSOT_ID:<id>]

Assistant -> Coding:

[ROLE:CODING]
[PARENT:ASSISTANT]
[SSOT_ID:<id>]

Rules:

  • Missing/invalid role header => stop with blocking_reason="role_ambiguous".
  • Assistant accepts only [ROLE:ASSISTANT][PARENT:MAIN].
  • Coding accepts only [ROLE:CODING][PARENT:ASSISTANT].
  • Assistant output must include role="assistant" and parent_role="main".
  • Coding output must include role="coding" and parent_role="assistant".

3) Routing

Definitions:

  • pure_coding: repository content write (code/docs/config/scripts/tests).
  • non_coding: research/review/triage/brief/log analysis without repo writes.
  • design|mixed: architecture or strategy decisions.

Default:

  • pure_coding -> assistant
  • non_coding -> assistant
  • design|mixed -> main
  • trivial no-side-effect response -> main

Hard rules:

  • If task contains repo writes, classify as pure_coding.
  • Main must not execute coding tasks directly.

4) Dispatch Preflight (Main, Mandatory For Non-Trivial Tasks)

  • Main's first substantive output includes Dispatch Preflight.
  • Starts with one strict JSON object.
  • Must validate ~/.codex/dispatch-preflight.schema.json.
  • SSOT fields must be present and frozen: goal, non_goals, constraints, acceptance_criteria, decisions.
  • allowed_paths must be absolute and non-empty.
  • Main-level execution subtasks must use delegate_target="assistant".
  • Main-level decision-only subtasks may use delegate_target="main" (no repository writes).

Trivial only if all true:

  • no file edits
  • no commands/tests/builds/lookups
  • no design/routing decisions
  • no multi-step investigation

If Main does not dispatch delegateable non-trivial work, set exactly one routing fallback_reason:

  • scope_not_clear
  • not_independent_split
  • decision_needed
  • blocked
  • explicit_main_decision

5) Parallel Scheduler (Aggressive)

Main on Assistant slices:

  1. Build independent slices.
  2. If two or more slices are ready, do initial burst (spawn to capacity).
  3. If any spawnable slice exists, spawn before wait.
  4. After each completion: review immediately, then spawn next independent slice.

Assistant on Coding slices:

  1. Prepare coding-ready brief and verification/risk plan.
  2. Invoke Coding for each write slice.
  3. For independent write slices, run windowed parallel with wait-any + immediate replenish.
  4. Review each completed coding result immediately (review_only) before reporting upward.

6) Invocation

Assistant orchestration is native collab only:

  • spawn_agent
  • wait
  • close_agent

Coding invocation (Assistant only):

cat <<'EOF' | ~/.codex/coding.py --workdir /abs/trusted/repo --task <task-label>
[ROLE:CODING]
[PARENT:ASSISTANT]
[SSOT_ID:<id>]
<coding brief in English>
EOF

Rules:

  • Prefer --prompt-file for large prompts.
  • --workdir must be an absolute trusted repo/worktree path.
  • Validate assistant output with ~/.codex/agent-output.assistant.schema.json.
  • Validate coding output with ~/.codex/agent-output.coding.schema.json.

7) Review And Acceptance

Assistant gate:

  • Every completed write needs at least one assistant review_only verdict.
  • Accept write only if verdict is status="done" and blocked=false.

Main checks (JSON fields only):

  • blocked=false -> blocking_reason="not_applicable".
  • blocked=true -> status="awaiting_review" and blocking_reason != "not_applicable".
  • review_only=true -> rationale starts with REVIEW_ONLY:.
  • Coding status="done" -> self_check.status="pass".
  • Coding output must include triggered_by="assistant" and invoked_via="~/.codex/coding.py".

8) Stop Conditions

Stop and return blocked on:

  • Main writes repository implementation directly.
  • Main invokes Coding directly.
  • Coding accepts task from Main.
  • Parallelizable work is serialized without valid blocking reason.
  • wait-all barrier is used while wait-any/replenish is possible.
  • Missing/mismatched ssot_id or subtask_id.
  • allowed_paths violation.
  • Invalid JSON or schema-invalid delegate outputs.

````

</details>

<details>
<summary>Full coding.py</summary>

#!/usr/bin/env python3
"""Convenience wrapper around `codex exec` for the Coding agent (pinned model + reasoning)."""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path


CODING_MODEL = "gpt-5.3-codex-spark"
CODING_MODEL_REASONING_EFFORT = "xhigh"

PRESETS: dict[str, str] = {
    "coding-impl": "\n".join(
        [
            "You are the coding agent. Return ONLY valid JSON matching the coding output schema.",
            "",
            "Task: Implement the provided brief exactly.",
            "",
            "Mandatory self-check:",
            "- Run a deterministic self-check for the acceptance criteria you claim to satisfy.",
            "- Populate self_check.status, self_check.command, and self_check.evidence.",
            "- Do not set status=\"done\" unless self_check.status=\"pass\".",
            "- If self-check fails: status=\"awaiting_review\", blocked=true, blocking_reason=first failing check.",
            "",
            "Constraints:",
            "- Only modify the allowed files specified by the brief.",
            "- Do not modify any other files.",
            "- All text must be English.",
            "- Use absolute paths in workdir/input_artifacts/scope_files.",
            "- Always include ssot_id, subtask_id, allowed_paths, role, parent_role, triggered_by, and invoked_via in your JSON output.",
            "- Required coding identity fields: role=\"coding\", parent_role=\"assistant\", triggered_by=\"assistant\", invoked_via=\"~/.codex/coding.py\".",
            "- Use ssot_id=\"not_applicable\", subtask_id=\"not_applicable\", allowed_paths=[] when not applicable.",
            "",
            "Implementation brief (provided by Assistant) follows:",
        ]
    )
}


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Convenience wrapper around `codex exec` (Coding agent)")
    parser.add_argument("--workdir", required=True, help="Absolute path for codex --cd")
    parser.add_argument("--task", default="task", help="Task label for diagnostics (default: task)")
    parser.add_argument("--prompt-file", help="Path to file containing the prompt (default: stdin)")
    parser.add_argument(
        "--preset",
        choices=sorted(PRESETS.keys()),
        default="coding-impl",
        help="Preset to prepend to the prompt body (default: coding-impl).",
    )
    parser.add_argument("--out", help="Absolute path to override output JSON path")
    parser.add_argument(
        "--show-codex-output",
        action="store_true",
        help="Print codex exec output to stderr even on success.",
    )
    parser.add_argument(
        "--after",
        action="append",
        default=[],
        help="Run command after successful codex execution. May be repeated.",
    )

    return parser.parse_args()


def read_prompt(prompt_file: str | None) -> str:
    if prompt_file is None:
        return sys.stdin.read()

    path = Path(prompt_file)
    if not path.is_file():
        raise FileNotFoundError(f"Prompt file not found: {prompt_file}")
    return path.read_text(encoding="utf-8")


def validate_output(path: Path) -> None:
    if not path.is_file():
        raise FileNotFoundError(f"Output file not found: {path}")

    try:
        with path.open("r", encoding="utf-8") as handle:
            json.load(handle)
    except json.JSONDecodeError as error:
        raise RuntimeError(f"Invalid JSON in output file {path}: {error}") from error


def enforce_contract(output_obj: dict, schema_path: Path) -> None:
    if not schema_path.is_file():
        raise FileNotFoundError(f"Coding schema not found: {schema_path}")

    with schema_path.open("r", encoding="utf-8") as handle:
        schema = json.load(handle)

    required = schema.get("required", [])
    missing = [key for key in required if key not in output_obj]
    if missing:
        raise RuntimeError(f"Coding output missing required fields: {missing}")

    properties = schema.get("properties", {})
    const_mismatches: list[str] = []
    for field, prop in properties.items():
        if "const" in prop and output_obj.get(field) != prop["const"]:
            const_mismatches.append(
                f"{field}={output_obj.get(field)!r} expected {prop['const']!r}"
            )
    if const_mismatches:
        raise RuntimeError("Coding output const-field mismatch: " + "; ".join(const_mismatches))

    # Hard guard independent of schema deployment drift.
    identity_expected = {
        "role": "coding",
        "parent_role": "assistant",
        "triggered_by": "assistant",
        "invoked_via": "~/.codex/coding.py",
    }
    identity_mismatches: list[str] = []
    for key, expected in identity_expected.items():
        actual = output_obj.get(key)
        if actual != expected:
            identity_mismatches.append(f"{key}={actual!r} expected {expected!r}")
    if identity_mismatches:
        raise RuntimeError("Coding output identity mismatch: " + "; ".join(identity_mismatches))

    # Enforce nested self_check shape used by Main acceptance checks.
    self_check = output_obj.get("self_check")
    if not isinstance(self_check, dict):
        raise RuntimeError("Coding output self_check must be an object")
    nested_required = ["status", "command", "evidence"]
    nested_missing = [key for key in nested_required if key not in self_check]
    if nested_missing:
        raise RuntimeError(f"Coding output self_check missing required fields: {nested_missing}")


def normalize_identity_fields(output_obj: dict) -> None:
    # These fields are fixed by wrapper orchestration; normalize to prevent model drift.
    output_obj["role"] = "coding"
    output_obj["parent_role"] = "assistant"
    output_obj["triggered_by"] = "assistant"
    output_obj["invoked_via"] = "~/.codex/coding.py"


def read_all_from_fd(fd: int) -> bytes:
    chunks: list[bytes] = []
    while True:
        data = os.read(fd, 8192)
        if not data:
            break
        chunks.append(data)
    return b"".join(chunks)


def run_codex(
    args: argparse.Namespace, out_path: Path | None, prompt: str
) -> tuple[subprocess.CompletedProcess[str], str]:
    schema_path = Path.home() / ".codex/agent-output.coding.schema.json"

    pass_fds: tuple[int, ...] = ()
    out_spec: str
    out_fd_r: int | None = None
    out_fd_w: int | None = None

    if out_path is not None:
        out_spec = str(out_path)
    else:
        # Avoid disk writes by routing `-o` to an inherited pipe FD via /dev/fd/N.
        if not Path("/dev/fd").exists():
            raise RuntimeError("/dev/fd is not available; use --out to write a JSON file")
        out_fd_r, out_fd_w = os.pipe()
        os.set_inheritable(out_fd_w, True)
        pass_fds = (out_fd_w,)
        out_spec = f"/dev/fd/{out_fd_w}"

    command = [
        "codex",
        "exec",
        "--sandbox",
        "danger-full-access",
        "--cd",
        args.workdir,
        "--ephemeral",
        "--output-schema",
        str(schema_path),
        "-o",
        out_spec,
    ]

    # The Coding agent is pinned for anti-drift. Do not allow caller overrides here.
    command.extend(["-m", CODING_MODEL])
    command.extend(["-c", f'model_reasoning_effort="{CODING_MODEL_REASONING_EFFORT}"'])
    command.append(prompt)

    proc = subprocess.Popen(
        command,
        text=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        pass_fds=pass_fds,
    )

    if out_fd_w is not None:
        os.close(out_fd_w)

    stdout, stderr = proc.communicate()
    result = subprocess.CompletedProcess(command, proc.returncode, stdout, stderr)

    out_json_text = ""
    if out_fd_r is not None:
        try:
            out_bytes = read_all_from_fd(out_fd_r)
            out_json_text = out_bytes.decode("utf-8", errors="replace").strip()
        finally:
            os.close(out_fd_r)

    return result, out_json_text


def run_after_commands(commands: list[str]) -> int:
    for index, command in enumerate(commands, start=1):
        result = subprocess.run(command, shell=True)
        if result.returncode != 0:
            raise RuntimeError(f"After command failed (#{index}): {command}")
    return 0


def main() -> int:
    args = parse_args()
    prefix = f"[{args.task}] "

    if not os.path.isabs(args.workdir):
        print(prefix + "--workdir must be an absolute path", file=sys.stderr)
        return 1

    if not Path(args.workdir).is_dir():
        print(prefix + f"--workdir must be an existing directory: {args.workdir}", file=sys.stderr)
        return 1

    if args.out is not None and not os.path.isabs(args.out):
        print(prefix + "--out must be an absolute path", file=sys.stderr)
        return 1

    try:
        body = read_prompt(args.prompt_file)
    except FileNotFoundError as error:
        print(prefix + str(error), file=sys.stderr)
        return 1

    body = body.rstrip()
    prompt = PRESETS[args.preset] + "\n\n" + body + "\n"

    out_path: Path | None
    if args.out is not None:
        out_path = Path(args.out).expanduser().resolve()
    else:
        out_path = None

    try:
        result, out_json_text = run_codex(args, out_path, prompt)
    except RuntimeError as error:
        print(prefix + str(error), file=sys.stderr)
        return 1

    if result.returncode != 0:
        if result.stdout:
            print(result.stdout, end="", file=sys.stderr)
        if result.stderr:
            print(result.stderr, end="", file=sys.stderr)
        if out_path is None and out_json_text:
            print(prefix + "partial -o output (truncated):", file=sys.stderr)
            print(out_json_text[:4000], file=sys.stderr)
        print(prefix + f"codex exec failed (exit {result.returncode})", file=sys.stderr)
        return result.returncode

    try:
        if out_path is not None:
            validate_output(out_path)
            with out_path.open("r", encoding="utf-8") as handle:
                output_obj = json.load(handle)
        else:
            if not out_json_text:
                raise RuntimeError("Missing JSON output from codex (-o pipe was empty)")
            output_obj = json.loads(out_json_text)
        normalize_identity_fields(output_obj)
        schema_path = Path.home() / ".codex/agent-output.coding.schema.json"
        enforce_contract(output_obj, schema_path)
        if out_path is not None:
            out_path.write_text(
                json.dumps(output_obj, ensure_ascii=False, separators=(",", ":")),
                encoding="utf-8",
            )
        run_after_commands(args.after)
    except (FileNotFoundError, json.JSONDecodeError, RuntimeError) as error:
        print(prefix + str(error), file=sys.stderr)
        if result.stdout:
            print(result.stdout, end="", file=sys.stderr)
        if result.stderr:
            print(result.stderr, end="", file=sys.stderr)
        if out_path is None and out_json_text:
            print(prefix + "invalid -o output (truncated):", file=sys.stderr)
            print(out_json_text[:4000], file=sys.stderr)
        return 1

    print(json.dumps(output_obj, ensure_ascii=False, separators=(",", ":")))

    if args.show_codex_output:
        if result.stdout:
            print(result.stdout, end="", file=sys.stderr)
        if result.stderr:
            print(result.stderr, end="", file=sys.stderr)

    return 0


if __name__ == "__main__":
    sys.exit(main())

</details>

---

As you all know, 6 is too limiting. I want 15, 20. Give me all the agents.

@am-will

Yeah, we need more!

jif-oai contributor · 5 months ago
As you all know, 6 is too limiting. I want 15, 20. Give me all the agents.

@am-will this is already supported in the config

Can we define subagents per project also?

@Nebu1eto yes because it is being moved to the config.toml you can use this: https://developers.openai.com/codex/config-basic#configuration-precedence

lucamorettibuilds · 5 months ago

Really glad to see this getting official attention — @jif-oai the agents_config.toml proposal looks solid.

Building on @merlinnot's question about per-agent MCP tool access — the allowed_tools list is a good start, but there's a related dimension that's easy to overlook: credential scoping.

When you have multiple subagents with different MCP tool sets, the underlying credentials those tools use also need to be scoped. Example:

[agents.explorer]
model = "spark"
read_only = true
allowed_tools = ["github_search", "read_file"]

[agents.implementor]  
model = "gpt-5.3-codex"
read_only = false
allowed_tools = ["github_search", "read_file", "write_file", "github_push"]

The explorer agent has github_search but should probably only get a read-only GitHub token. The implementor needs push access. If both use the same PAT, the read_only sandbox policy is bypassed at the API level — the explorer can make write API calls through the MCP tool even if file writes are blocked locally.

Suggestion: credential profiles per agent role

[agents.explorer]
model = "spark"
read_only = true
allowed_tools = ["github_search", "read_file"]
credential_profile = "read-only"  # only read-scoped tokens available

[agents.implementor]
model = "gpt-5.3-codex"  
allowed_tools = ["github_search", "read_file", "write_file", "github_push"]
credential_profile = "full-access"

[credential_profiles.read-only]
github = "read"    # maps to a read-only PAT or GitHub App installation token
jira = "read"

[credential_profiles.full-access]
github = "write"
jira = "write"

This way the permission model extends beyond the sandbox (file system) to external API access, which is where most real-world damage from a misbehaving subagent would happen.

The MCP ecosystem is starting to converge on this pattern — separate credential injection from tool execution, with per-agent identity determining which credentials get injected. Curious if this is something the Codex team has considered as part of the read_only / permission design, or if it's being deferred to the MCP server layer.

jif-oai contributor · 5 months ago

We are making a release right now (0.102.0) that should contain a first version. Basically you can update your config.toml to have something like this

[agents.planner]
config_file = "agents/planner_config.toml"
description = "Foo foo foo"

[agents.worker]
config_file = "somewhere.toml"
description = "Foo foo foo"

And the config_file that is reference will be used as an overlay for the config of the sub-agent.

This is a first version but let me know the corner cases you find or how would you like to see this evolving

am-will · 5 months ago
We are making a release right now (0.102.0) that should contain a first version. Basically you can update your config.toml to have something like this `` [agents.planner] config_file = "agents/planner_config.toml" description = "Foo foo foo" [agents.worker] config_file = "somewhere.toml" description = "Foo foo foo" ` And the config_file` that is reference will be used as an overlay for the config of the sub-agent. This is a first version but let me know the corner cases you find or how would you like to see this evolving

Thank you brother, can you please leave a full example of an agent config so we can know all the parameters it accepts? thanks :)

am-will · 5 months ago

I have the following setup as a test:

[agents.documenter]
config_file = "agents/documenter.toml"
description = """Use for writing and maintaining documentation.
Rules:
- Match the project's existing documentation style.
- Keep explanations concise and example-driven.
- Never modify logic, only add or update comments and docs."""

in documenter.toml:

model = "gpt-5.2"
model_reasoning_effort = "medium"

Whenever I check the agent session for the documenter agent, it does not appear to be using the model I requested.

I know this was not the case before, because I made subagent models configurable in a fork of codex, and it correctly showed the model (I used Spark) in both the OpenAI codex "box" at the top of the agent session, as well as the statusline.

It appears to me like its just using the inherited model right now unless I have it misconfigured.

<img width="1141" height="1027" alt="Image" src="https://github.com/user-attachments/assets/8cc5b8f0-17ea-4cd2-92e0-72b9b574786d" />

@jif-oai

You can see an example of what i'm talking about in this video at 2:40, where I used 5.3 Codex as orchestrator, and Spark as subagents.

https://x.com/LLMJunky/status/2022712980090105883?s=20

Codex log confirms 5.3 Codex was used and not the configured model from the configuration file:

 You ran grep -iE "config_file|overlay|role|model.*5\.2|documenter" ~/.codex/log/codex-tui.log | tail -30
  └ 2026-02-17T07:16:02.851299Z  WARN codex_core::agent::role: failed to read user-defined role config_file: Os
    { code: 2, kind: NotFound, message: "No such file or directory" }
    documenter|agents_config\" /home/willr/.codex/memories/MEMORY.md | head -n 120","max_output_tokens":4000}
    thread_id=019c6d47-0137-7062-89c7-5a065b59d266
    2026-02-17T20:26:58.423858Z  INFO session_loop{thread_id=019c6d47-0137-7062-89c7-5a065b59d266}:
    codex_core::stream_events_utils: ToolCall: exec_command {"cmd":"rg -n \"agents.roles|custom subagent|
    spawn_agent|agent_type|builtins_agents_config|roles\" /home/willr/Applications/codex1/codex-rs /home/willr/
    Applications/codex 2>/dev/null | head -n 200","max_output_tokens":6000}
    thread_id=019c6d47-0137-7062-89c7-5a065b59d266
    2026-02-17T20:27:04.870615Z  INFO session_loop{thread_id=019c6d47-0137-7062-89c7-5a065b59d266}:
    codex_core::stream_events_utils: ToolCall: exec_command {"cmd":"sed -n '1,260p' /home/willr/Applications/
    codex1/codex-rs/core/src/agent/role.rs","max_output_tokens":7000}
    thread_id=019c6d47-0137-7062-89c7-5a065b59d266
    codex1/codex-rs/core/src/agent/role.rs","max_output_tokens":7000}
    thread_id=019c6d47-0137-7062-89c7-5a065b59d266
    2026-02-17T20:27:09.549546Z  INFO session_loop{thread_id=019c6d47-0137-7062-89c7-5a065b59d266}:
    codex_core::stream_events_utils: ToolCall: exec_command {"cmd":"rg -n \"agents_config.toml|agents.roles|\
    \[agents\\.roles\\]|custom role|spawn_agent\" /home/willr/Applications/codex1 /home/willr/Applications/codex
    2>/dev/null | head -n 250","max_output_tokens":8000} thread_id=019c6d47-0137-7062-89c7-5a065b59d266
    2026-02-17T20:27:15.463535Z  INFO session_loop{thread_id=019c6d47-0137-7062-89c7-5a065b59d266}:
    … +100 lines
    2026-02-17T20:53:35.888898Z  INFO session_loop{thread_id=019c6d60-cdb6-7031-86a5-5b98f700600f}:
    codex_core::stream_events_utils: ToolCall: spawn_agent {"agent_type":"documenter","message":"Quick
    connectivity test. Reply with exactly: OK"} thread_id=019c6d60-cdb6-7031-86a5-5b98f700600f
    2026-02-17T20:53:38.962252Z DEBUG
    Some("msg_050c19c588a114dd016994d552dee48191810147bf36c655e2"), role: "assistant", content: [], end_turn:
    None, phase: None }
    2026-02-17T20:53:39.024543Z DEBUG
    session_loop{thread_id=019c6d60-cdb6-7031-86a5-5b98f700600f}:session_loop{thread_id=019c6d61-4016-7ba1-95c4-
    d097409f6797}: codex_core::stream_events_utils: Output item item=Message { id:
    Some("msg_050c19c588a114dd016994d552dee48191810147bf36c655e2"), role: "assistant", content: [OutputText
    { text: "OK" }], end_turn: None, phase: Some(FinalAnswer) }
    2026-02-17T20:53:43.461317Z DEBUG session_loop{thread_id=019c6d60-cdb6-7031-86a5-5b98f700600f}:
    codex_core::stream_events_utils: Output item item=Message { id:
    Some("msg_005c5b57c9aa3316016994d55761f08191830ab46bc6c11be7"), role: "assistant", content: [], end_turn:
    None, phase: None }
    2026-02-17T20:53:43.620728Z DEBUG session_loop{thread_id=019c6d60-cdb6-7031-86a5-5b98f700600f}:
    codex_core::stream_events_utils: Output item item=Message { id:
    Some("msg_005c5b57c9aa3316016994d55761f08191830ab46bc6c11be7"), role: "assistant", content: [OutputText
    { text: "" }], end_turn: None, phase: Some(FinalAnswer) }
    2026-02-17T20:53:59.074087Z DEBUG session_loop{thread_id=019c6d60-cdb6-7031-86a5-5b98f700600f}:
    codex_core::codex: Submission sub=Submission { id: "019c6d61-9aa2-7b43-8694-8a24704a5dcd", op:
    RunUserShellCommand { command: "grep -iE \"config_file|overlay|role|model.*5\\.2|documenter\" ~/.codex/log/
    codex-tui.log | tail -30" } }
acgxv · 5 months ago
Thank you brother, can you please leave a full example of an agent config so we can know all the parameters it accepts? thanks :)

I used to read the code myself to enable under-development features. Or find the hidden parameters.

Later I realised I could simply instruct my codex agent to do it. It works perfectly.

I do this because I use Nix to manage configurations and files are immutable so the UI can’t update them. Now I use codex and Nix together to manage those configurations.

am-will · 5 months ago
> Thank you brother, can you please leave a full example of an agent config so we can know all the parameters it accepts? thanks :) I used to read the code myself to enable under-development features. Or find the hidden parameters. Later I realised I could simply instruct my codex agent to do it. It works perfectly. I do this because I use Nix to manage configurations and files are immutable so the UI can’t update them. Now I use codex and Nix together to manage those configurations.

thank you. i tried that earlier and it wasn't able to tell me.

but what i was able to do was clone the repo and run tests that way.

I can confirm it is using the correct models, but it is not displaying it when you open those agent sessions (it did before)

it would be great if when you opened an agent session using /agent that it would appropirately show the model/reasoning that it actually used.

right now, its still showing the parent agent's model/reasoning instead of what it used for that subagent.

jif-oai contributor · 5 months ago
it would be great if when you opened an agent session using /agent that it would appropirately show the model/reasoning that it actually used.

@am-will Will fix

jif-oai contributor · 5 months ago

Actually it works on my side. Can you make sure the config is properly setup and you're up to date? I will add a config validation layer

Here is my config.toml

model = "gpt-5.3-codex"
model_reasoning_effort = "medium"
tui.status_line = ["model-with-reasoning", "context-remaining", "codex-version", "session-id", "memory-progress"]

[features]
steer = true
multi_agent = true
remote_models = true

[agents]
max_threads = 24

[agents.planner]
config_file = "agents/tester_config.toml"
description = "Agent to be used to write tests"

And my tree

~/.codex/
├── agents
│   └── tester_config.toml
└── config.toml
acgxv · 5 months ago
I use a strict hierarchy with review loops: Main orchestrates and makes final accept/rework decisions.

Main delegates execution to parallel Assistants.
Each Assistant delegates implementation to Coding workers.
Coding reports to Assistant; Assistant reviews and can assign rework.
Assistant reports to Main; Main reviews and can assign rework.

This won’t work anymore if switch from the codex exec external agent to the internal agent.
An assistant agent can’t spawn a coding agent.
Codex doesn’t allow "nested" subagents.

I’ll need to adjust the prompt to use the main agent to relay messages between the assistant agent and the coding agent. Otherwise, it won’t be truly "parallel" because the main agent needs to review every coding agent’s work in a single thread.

Nested can maximize the subagent power. But for safety, we can have a config like max_depth or hardcode it to 2.

am-will · 5 months ago
> it would be great if when you opened an agent session using /agent that it would appropirately show the model/reasoning that it actually used. @am-will Will fix

Thanks Jif. I shouted you out here:

In this MULTI AGENT SETUP GUIDE

I appreciate this feature so much. THANK YOU!

jif-oai contributor · 5 months ago

Btw if you have good agents you would like us to land as built-ins so that everyone can use them. Feel free to shoot

slobodaapl · 5 months ago

@jif-oai I've been using a custom search skill as a subagent before this feat. dropped that's been tremendously useful, because it produces explicit spans the agent can use.

Tl;dr -- It is provided explicit instructions to be a search agent and follow instructions to retrieve specific spans and explanations / summaries on how they relate to the search query.

It is read-only, search-enabled (if allowed top-level), and outputs a JSON based on schema:

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "search_summary": {
      "type": "string",
      "description": "Summary of the search results, highlighting details that address the query, including how files relate to each other."
    },
    "relevant_files": {
      "type": "array",
      "description": "Relevant files found during the search, along with extracted spans and context.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file_path": {
            "type": "string",
            "description": "Path to the file from the repository root."
          },
          "search_summary": {
            "type": "string",
            "description": "Summary of why this file is relevant, including what was found in the extracted spans."
          },
          "spans": {
            "type": "array",
            "description": "Extracted spans from the file that are relevant to the query.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "line_start": {
                  "type": "integer",
                  "description": "Line number where the span begins."
                },
                "line_end": {
                  "type": "integer",
                  "description": "Line number where the span ends."
                },
                "description": {
                  "type": "string",
                  "description": "Short description of the span contents and its relevance to the query."
                },
                "contents": {
                  "type": "string",
                  "description": "The extracted contents of the span."
                }
              },
              "required": ["line_start", "line_end", "description", "contents"]
            }
          }
        },
        "required": ["file_path", "search_summary", "spans"]
      }
    },
    "other_files": {
      "type": "array",
      "description": "Other files that may be related to the query but were not selected for detailed span extraction.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "file_path": {
            "type": "string",
            "description": "Relative path to the file from the repository root."
          },
          "description": {
            "type": "string",
            "description": "Reason this file may be relevant to the query."
          }
        },
        "required": ["file_path", "description"]
      }
    }
  },
  "required": ["search_summary", "relevant_files", "other_files"]
}

with the prompt:

You are a specialized subagent who searches information based on user request.

Goal:
Search the codebase for relevant files and spans based on the user query. Be mindful that there may also be relevant documentation as well as code, which is equally valid to be referenced, but code should always take precendence, though can be enriched with documentation files.

Rules:
- Do not propose patches or edits.
- You cannot edit files or make temporary directories or files at all in this context, don't try, don't mention this in your results.
- Return structured JSON with the schema: search_summary, relevant_files, other_files as final response.
- Include file paths + line ranges + span contents.
- Be thorough, attempt to understand the full scope of the query and the intent behind it to retrieve rich results.

User search request:

---

Naturally the prompt is rather simplistic and could be enhanced, but this worked well for me.

As mentioned I used it as a skill so it involved referencing a script that the agent runs, if this is at all relevant or useful:

#!/usr/bin/env bash
set -euo pipefail

QUERY="$(cat)"
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SKILL_PATH="${SCRIPT_DIR}/../SKILL.md"
PROMPT="${SCRIPT_DIR}/../resources/prompt.md"
JSON_SCHEMA="${SCRIPT_DIR}/../resources/output_schema.json"
FINAL_PROMPT="$(cat "$PROMPT")$QUERY"

OUT_FILE="$(mktemp /tmp/codex_last_message.XXXXXX)"
ERR_FILE="$(mktemp /tmp/codex_stderr.XXXXXX)"
cleanup() { rm -f "$OUT_FILE" "$ERR_FILE"; }
trap cleanup EXIT

if ! codex exec \
  -m "gpt-5.1-codex-mini" \
  --config 'model_reasoning_effort=high' \
  --config "skills.config=[{path=\"${SKILL_PATH}\", enabled=false}]" \
  --color never \
  --sandbox read-only \
  --skip-git-repo-check \
  --output-schema "$JSON_SCHEMA" \
  --output-last-message "$OUT_FILE" \
  -- "$FINAL_PROMPT" \
  < /dev/null \
  > /dev/null 2> "$ERR_FILE"
then
  cat "$ERR_FILE" >&2
  exit 1
fi

cat "$OUT_FILE"

I did it this way to prevent polluting parent agent's context, since they don't need to actually see the subagent's progress on the research, only the final output.

I reckon this agent would be useful with 5.1 mini as I used it, or the new spark model, with high reasoning to ensure it can sufficiently reason about the query and all that it needs to capture in its output, while being cheap and fast.

acgxv · 5 months ago
Btw if you have good agents you would like us to land as built-ins so that everyone can use them. Feel free to shoot

My friends and I wrote an article about diving into multi-agents.

I'm eagerly anticipating the release of the "nested" agent, maybe in v0.105 or v0.106?

I'll continue testing and provide feedback!

am-will · 5 months ago
> Btw if you have good agents you would like us to land as built-ins so that everyone can use them. Feel free to shoot My friends and I wrote an article about diving into multi-agents. I'm eagerly anticipating the release of the "nested" agent, maybe in v0.105 or v0.106? I'll continue testing and provide feedback!

Nice article. They're already in the alpha

[agents]
max_depth = n

Be careful going over 2 lol

https://x.com/i/status/2024647077586731351

jif-oai contributor · 5 months ago
I'm eagerly anticipating the release of the "nested" agent, maybe in v0.105 or v0.106?

@aurexav Actually I just merged a parameter for max depth yesterday so it will be in the next release

[agents]
max_threads = 24
max_depth = 4

Just be careful with your rate limits

am-will · 5 months ago
> I'm eagerly anticipating the release of the "nested" agent, maybe in v0.105 or v0.106? @aurexav Actually I just merged a parameter for max depth yesterday so it will be in the next release `` [agents] max_threads = 24 max_depth = 4 `` Just be careful with your rate limits

You're the best. I don't know how I'm going to use this yet but obviously this opens a new world of possibility

Qq though

Is max threads a total agent budget?

Or is it per session

If I have it set too 12 threads and 2 depth, can each subagent on the first level down open 12 more agents?

12*12?

Or is 12 the cap across all depth?

Thx!

acgxv · 5 months ago

It should be 24 in total. It's dynamic.

Just an example without too much thought.

The system spawns nested subagents dynamically based on task complexity, and usually runs far below the cap.

Example expansions (all ≤24): 2×12, (3×4)+(2×4)+(1×4), or a sparse nested flow like 2(research)×2(plan)×2(task)×2(worker)=16 plus a small memory pipeline (validate/correct → upsert → organize) to stay within budget.

Key rules: stage gates for structured outputs, diversified parallel roles (no duplicates), and memory writes only after validation to avoid locking in errors.
am-will · 4 months ago

@jif-oai one more thing, its only slightly related but i noticed in your original proposal, you seemed to have provision to configure the plan mode's model/reasoning level, can you please add that in? i dont think most people want to plan on medium. prob not a subagent thing, but still kinda related. appreciate it.

acgxv · 4 months ago
@jif-oai one more thing, its only slightly related but i noticed in your original proposal, you seemed to have provision to configure the plan mode's model/reasoning level, can you please add that in? i dont think most people want to plan on medium. prob not a subagent thing, but still kinda related. appreciate it.

https://github.com/openai/codex/issues/11013#issuecomment-3938100469

slobodaapl · 4 months ago

@jif-oai Heyo, I was looking through code and I noticed one minor issue. If I understand correctly, since each agent can have their own config version, in effect we can do per-subagent sandboxing.. Same with web access configuration, tool selection, etc. -- What's missing right now is per-agent allowlist. It's possible to already do per-agent approval configuration (ask/always forbid/full access) but no way to specific a path to a custom approved command ruleset / allowlist.

Would this be possible?

slobodaapl · 4 months ago

One more thing worth nothing is that it seems sandboxing doesn't currently allow granular file-level sandboxing, only entire directories, which is one more issue to solve before the subagent harness will become super powerful and secure.

Do you think I should open an issue for this?

am-will · 4 months ago

Good stuff

> @jif-oai one more thing, its only slightly related but i noticed in your original proposal, you seemed to have provision to configure the plan mode's model/reasoning level, can you please add that in? i dont think most people want to plan on medium. prob not a subagent thing, but still kinda related. appreciate it. #11013 (comment)

They also address the subagents getting 'stuck' with agent injection notifications.

Now when agents are done, they inject a message back into the parent that tells them if they're blocked or done, picks it up at the next thinking step.

Commit: https://github.com/openai/codex/commit/2daa3fd44fe5787aee79016a1019fe8cec369151

Additionally, they added names to all agents, and also list their [agent.role] as well, even if the agents go 2+ depth, they all show up on the top layer.

<img width="1225" height="434" alt="Image" src="https://github.com/user-attachments/assets/5e59fccc-2580-413d-9dd4-5355899e60ba" />

One change I'd still love to see though is for the agents to not disappear from the /agents menu when close_agent is called. Although I think he said he was working on this. I would like to be able to audit all threads, even if they are closed. Could probably check session logs but typing a slash command is far easier.

0.105.0 is a huge QoL update for subagents all around.

This should fix the issue where too many open agents prevents the orchestrator from opening more.

noname-oni · 4 months ago

Is there a way to control which skills are available to each subagent? Ideally, I want each subagent to have a private set of skills not accessible to other agents.

am-will · 4 months ago
Is there a way to control which skills are available to each subagent? Ideally, I want each subagent to have a private set of skills not accessible to other agents.

No, mcp and apps yes but not skills. The only thing you can do is make them available project or subfolder scopes. Hopefully they add that but codex is quite good at calling them in general so it's not the end of the world. Agree though

am-will · 4 months ago

Jif, the people have spoken.

<img width="604" height="604" alt="Image" src="https://github.com/user-attachments/assets/777d45df-98af-4ecb-847f-cb18a53ca678" />

https://x.com/SIGKITTEN/status/2025232589288583208?s=20

jif-oai contributor · 4 months ago
per agent allow-list

@slobodaapl adding on my todo. Should be done end of this week

granular file-level sandboxing

@slobodaapl this is planned but I'm not the one working on this so I don't have an exact timeline in mind tbh

agent names and notification system

@am-will happy you like it. This is the first nano-step to make them easier to handle

One change I'd still love to see though is for the agents to not disappear from the /agents menu when close_agent is called. Although I think he said he was working on this. I would like to be able to audit all threads, even if they are closed. Could probably check session logs but typing a slash command is far easier.

@am-will this is by design for now. Otherwise it would make it way harder to follow which agent is doing what. But I'll try to propose something for this

Is there a way to control which skills are available to each subagent? Ideally, I want each subagent to have a private set of skills not accessible to other agents.

@cogscides not for now. Do you have a very concrete use-case that required this? If so, I can work on it

jif-oai contributor · 4 months ago

Btw, if you have some thoughts on this: https://github.com/openai/codex/discussions/12567

noname-oni · 4 months ago

@jif-oai

Yes — I have a concrete use case. Right now skills are effectively global, and in practice they accumulate quickly across projects. I’m already at the point where my global skill set is getting large and heterogeneous (design skills, development skills, local machine environment skills, server skills, etc.).

What I’d like is an agent-scoped skill model similar to how OpenClaw structures agents: each agent has its own folder/config and its own skills that aren’t automatically visible to other agents. Once your skill base grows to ~50+ skills, it becomes important to explicitly control which agent gets which subset, both for correctness and for boundary/permission hygiene.

Today there isn’t a real workaround. You can add additional skills via a per-project basis. You can also define agents per project, but that still doesn’t let you control which skills an agent can see—it only ever expands what’s available. As a result, you can’t isolate capabilities per subagent, and skill management gets increasingly messy as the global set grows.

Concretely, I’m looking for (1) an explicit per-agent skill allowlist / registry so each subagent only sees the skills it should, and (2) a way to select the agent at invocation time. The analogy to codex exec is that it already lets you pass execution-time parameters (e.g., working directory / config overrides / model provider). I’d like a similar mechanism for agents: run codex exec with --agent <name> (or equivalent), where that agent’s own config and skill set are what get loaded—rather than every agent inheriting the global skill pool.

jif-oai contributor · 4 months ago

@cogscides ok thanks for the details. I will work on this later this week and come with something

slobodaapl · 4 months ago

@jif-oai Similar to the previous comment, one issue I ran into is that subagents inherit global instructions file and there's no way to set "this only applies to root agent"

In my fork I added a simple config option for agents that's just:
inherit_instruction_file either true or false, default true.

And I found that skill config in agents' configs doesn't work, so can't disable per subagent skills. (I think this is already covered by others above)

mrclrchtr · 4 months ago
>_ OpenAI Codex (v0.105.0-alpha.18)
model:     gpt-5.3-codex medium 

I noticed something that I'm not sure why it happens:
When I send the following prompt:

› Spawn a generic 'default' subagent and instruct it to list the available MCP context (tools/functions) (it should not do anything else)!


• Spawned Begonia [default]
  └ Please list the available MCP context (tools/functions) and do not perform any other actions.

• Begonia confirms the request and will list the available MCP context.

the “default” agent is used, which calls toolcodex.list_mcp_resources and does not see any MCP servers:

› Please list the available MCP context (tools/functions) and do not perform any other actions.


• Called codex.list_mcp_resources({})
  └ {"resources": []}

• No MCP resources are currently available in this environment.

When I send the following prompt:

› Spawn a generic 'worker' subagent and instruct it to list the available MCP context (tools/functions) (it should not do anything else)!


• Spawned Marigold [worker]
  └ List the available MCP context (tools/functions). Do not do anything else.

• Waiting for Marigold [worker]

• Finished waiting
  └ Marigold [worker]: Completed - Available tools/functions in this session: - `web.run` (web search/open/click/find/screenshot/image/finance/weather/sports/time) **`functions` namespace** - `exec_command`
- `write_stdin` - `list_mcp_resources` - `list_mcp_resource_templ...

• Tools summary from worker subagent “Marigold”: web.run (various search helpers), the functions namespace (exec_command, JetBrains helpers, Context7, Stitch, apply_patch, etc.), and
  multi_tool_use.parallel. Let me know if you need anything else.

a custom “worker” agent (only model and reasoning set) is used, which does not call any tool and sees all MCP servers that the parent has:

› List the available MCP context (tools/functions). Do not do anything else.


• Available tools/functions in this session:

  - web.run (web search/open/click/find/screenshot/image/finance/weather/sports/time)

  functions namespace

  - exec_command
  - write_stdin
  - list_mcp_resources
  - list_mcp_resource_templates
....
- mcp__JetBrains__build_project
  - mcp__JetBrains__create_new_file
  - mcp__JetBrains__execute_run_configuration
  - mcp__JetBrains__execute_terminal_command
...

Should I open an issue?

acgxv · 4 months ago

I’m seeing what looks like a global subagent-thread cap shared across multiple concurrent codex resume processes, which IMO should not be the case.

Observed behavior

  • Config: max_threads = 32
  • In one Codex session, I attempted to spawn 32 operator subagents in parallel.
  • The runtime started rejecting additional spawns with the exact error:
  • collab spawn failed: agent thread limit reached (max 32)
  • However, the session only managed to spawn 25 operators before hitting the limit.

Evidence suggesting cross-session contention

  • At the time, the machine had multiple Codex sessions running:
  • ps -Ao pid,etime,command | rg '\\bcodex( resume)?\\b'
  • This showed several concurrent codex resume processes.

This strongly suggests the “max 32” limit is enforced across all running Codex sessions (or a shared runtime pool), rather than per independent Codex process/session. That makes concurrency testing and real workloads unpredictable unless users manually ensure all other sessions are closed and all subagents are close_agent’d.

Expected behavior

  • Each independent Codex process/session should have its own max_threads = 32 budget (or the UI/config should make it explicit that the limit is global/shared and provide tooling to introspect/kill leaked subagents).
acgxv · 4 months ago

Update after tightening our protocol: we enforce strict parent->direct-subordinate spawning only (no skip-level spawns, no same-level spawns). Under that valid hierarchy, depth=3 still fails because the nested orchestrator lacks the spawn_agent tool.

Protocol hierarchy (strict)

  • Director (main) -> Auditor
  • Auditor -> Orchestrator
  • Orchestrator -> (Coder | Operator | Awaiter)
  • (Coder | Operator | Awaiter) -> (spawn nothing)

Environment

  • codex-cli: 0.105.0-alpha.23
  • macOS: 26.3 (Darwin 25.3.0, arm64)
  • Config: max_threads = 32, max_depth = 3

Repro (valid chain, no forbidden edges)
1) From main, spawn an Auditor (spawn_agent(agent_type=auditor)).
2) Ask the Auditor to spawn an Orchestrator (spawn_agent(agent_type=orchestrator)).
3) Ask that Orchestrator to spawn a Coder or Operator (spawn_agent(agent_type=coder_spark) or operator).

Expected
Step 3 should succeed (Orchestrator spawning its direct subordinate is allowed by protocol and should be allowed by runtime when max_depth >= 3).

Actual
Step 3 fails because the nested Orchestrator reports that spawn_agent is not available (e.g. blocked=true reason=no_spawn_agent_tool).

This blocks any strict-hierarchy depth=3 workflow (main -> auditor -> orchestrator -> coder/operator).

acgxv · 4 months ago

Additional finding: this behavior lines up with the recently-merged agents.max_spawn_depth change from PR #10935.

PR #10935 introduces agents.max_spawn_depth (default appears to be Some(2)) and uses it to decide when to disable Collab features for spawned subagents.

In our strict chain main -> auditor -> orchestrator -> coder/operator, the orchestrator is a thread-spawned subagent at depth=2, and it consistently reports no_spawn_agent_tool when asked to spawn its direct subordinate.

Hypothesis:

  • agents.max_depth controls structural nesting.
  • agents.max_spawn_depth controls how deep Collab tools (including spawn_agent) remain available to subagents.
  • With max_spawn_depth=2, a nested orchestrator may lose Collab, so it cannot spawn leaf agents even though the hierarchy is valid and max_depth is sufficient.

Potential fix/workaround:

  • Document the distinction clearly.
  • Ensure agents.max_spawn_depth defaults and enforcement match expected multi-agent workflows (depth=3 chain).
  • Consider defaulting max_spawn_depth >= 3 when multi-agent is enabled, or provide a clearer error when Collab is disabled due to spawn depth.
etraut-openai contributor · 4 months ago

@aurexav, please do not have your agent post to our issue tracker.

acgxv · 4 months ago
@aurexav, please do not have your agent post to our issue tracker.

I instruct it to post the update here. This is not a self-automated process.

  • A feature request regarding the upper limit. Almost every user will assume the maximum limit is bound to a single codex run rather than being globally applicable.
  • A broken change reminder for other testers. And the two similar-named parameters require comprehensive documentation upon release. Really confuse me.

This is in line with the other feedback provided.

jif-oai contributor · 4 months ago

Please ignore agent_max_spawn_depth for now. This parameter is orthogonal and should have been documented in another way. I will check where does it come from and fix

ggoss0289-alt · 4 months ago

****

ThinkOffApp · 4 months ago

We run 9 agents across providers (OpenAI, Anthropic, Google and lower grade ones such as Moonshot, Qwen and Mistral) with per-agent model assignment in a single config file. Each agent gets its own model, behavioral constraints, and tool permissions.

The practical reason this matters: we've switched individual agents between providers 6+ times in the last month. Anthropic credits ran out, so ether moved to Gemini. Gemini rate-limited us, so klaus, mecha, and jean-clawde moved to Moonshot. sally moved to GPT-5.2 for better tool use. None of these changes required touching the other agents.

A single model for all subagents can break down fast in production. Different tasks need different cost/quality tradeoffs, and provider availability changes constantly. Per-agent config is not a nice-to-have, it is the first thing you need when running more than 2 agents.

Also I wonder, do senseless, hallucinatory feedback loops trigger easier when models are the same? It is not a big surprise that three Kimi K2.5 Openclaw Bots have managed this, but I've seen it happen also betweem Codex with 5.3 Codex and Openclaw with Codex 5.3. It sounded more intelligent than the Kimi loop but it was equally worthless, the Frontier Codexes just figured out a smarter way to ping pong rapid fire technical ACK hallucinatory nonsense.

noname-oni · 1 month ago

@eternalrights that looks good. Does it work with codex/cc?

i really like this idea and i believe codex and cc need to lean in this direction as well.

it reminds me of the new eve by vercel. basically both are covering my main pain points listed 4m ago in this thread:

https://github.com/openai/codex/issues/11701#issuecomment-3944145609

p.s. seems post to what i replied to was deleted.

but i'd still raise the discussion of the bad DX with agent definition and lifecycle we have in codex right now.

EternalRights · 1 month ago
@EternalRights that looks good. Does it work with codex/cc? i really like this idea and i believe codex and cc need to lean in this direction as well. it reminds me of the new eve by vercel. basically both are covering my main pain points listed 4m ago in this thread: #11701 (comment) p.s. seems post to what i replied to was deleted. but i'd still raise the discussion of the bad DX with agent definition and lifecycle we have in codex right now.

Sorry, accidentally nuked my earlier comment.

To answer your question — agenthatch compiles SKILL.md into standalone Python agents. It's not tied to any specific platform, so it works alongside Codex, CC, whatever you're using. Each compiled agent is its own pip-installable package with scoped permissions.

One thing I should add: I'd actually love to see this embedded into tools like Codex and CC. Imagine Codex spawning a compiled skill agent as a subagent — it gets its own process, its own tool permissions, no prompt bloat. The orchestrator doesn't need to carry 20 SKILL.md files in context. I think that's a direction that benefits the whole ecosystem, not just one project.

https://github.com/agenthatch/agenthatch