To help anyone else working around the recent changes. Big thanks to robertmsale and INFUSION on the Codex discord sub-thread for help debugging the last 48hrs!
Purpose
Some Codex builds reserve the provider-visible spawn_agent schema when multi_agent_v2 is enabled. Adding role, model, or reasoning fields directly to that visible schema can prevent a task from starting:
Function 'collaboration.spawn_agent' is reserved for use by this model
and must match the configured schema.
The working pattern is to preserve the reserved schema, encode the requested role in task_name, and inject trusted role, model, and reasoning metadata through a PreToolUse hook before the local spawn handler creates the child.
Compatibility note:
multi_agent_v2is evolving. Validate this pattern against the exact Codex build you deploy and retain rollout-level acceptance tests.
1. Use a v2-only project configuration
model = "gpt-5.6-sol"
model_reasoning_effort = "xhigh"
model_instructions_file = "instructions/prime.md"
[features]
hooks = true
[features.multi_agent_v2]
enabled = true
max_concurrent_threads_per_session = 30
min_wait_timeout_ms = 10000
default_wait_timeout_ms = 30000
max_wait_timeout_ms = 3600000
tool_namespace = "collaboration"
hide_spawn_agent_metadata = true
non_code_mode_only = true
Remove conflicting legacy configuration from active project and named TOMLs:
# Remove legacy v1 feature flags such as:
multi_agent = false
# Remove legacy [agents] controls such as:
[agents]
max_depth = 1
max_threads = 8
job_max_runtime_seconds = 1200
Remove fork_context from launch packets, skills, and active documentation. A fresh bounded v2 child uses fork_turns = "none".
2. Declare each role under .codex/agents/
name = "forager_agent"
description = "Read-only evidence gathering agent."
model = "gpt-5.6-luna"
model_reasoning_effort = "high"
model_instructions_file = "../instructions/forager.md"
sandbox_mode = "read-only"
nickname_candidates = ["forager_agent"]
developer_instructions = """
You are forager_agent.
Complete the supplied evidence task personally.
Do not delegate further.
Return only the requested evidence packet.
"""
The filename, profile name, and hook-injected agent_type must agree exactly.
3. Encode role selection in task_name
With spawn metadata hidden, keep the provider-visible call inside the reserved v2 contract:
{
"task_name": "forager_agent_evidence_scan",
"message": "Inspect the supplied evidence packet.",
"fork_turns": "none"
}
Use an explicit routing allowlist and longest-prefix matching:
ROLE_ROUTES = {
"forager_agent": {
"model": "gpt-5.6-luna",
"reasoning_effort": "high",
},
"worker_agent": {
"model": "gpt-5.6-terra",
"reasoning_effort": "medium",
},
"review_agent": {
"model": "gpt-5.6-terra",
"reasoning_effort": "xhigh",
},
}
Do not accept arbitrary role, model, or reasoning values from the caller.
4. Rewrite the call with PreToolUse
Register the hook in .codex/hooks.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "(^Agent$|spawn_agent$)",
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_spawn_agent_v2.py\"",
"timeout": 15
}
]
}
]
}
}
The hook resolves the role from task_name and returns trusted local metadata:
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"updatedInput": {
"task_name": "forager_agent_evidence_scan",
"message": "Inspect the supplied evidence packet.",
"fork_turns": "none",
"agent_type": "forager_agent",
"model": "gpt-5.6-luna",
"reasoning_effort": "high"
}
}
}
Unknown, retired, and prohibited roles should return an explicit denial:
{
"continue": false,
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Unknown or retired role"
}
}
For compatibility across adjacent builds, accept snake-case and camel-case hook envelope fields. Some builds may flatten a namespaced tool name to a value such as collaborationspawn_agent, so suffix matching is prudent.
5. Restart and attest the child rollout
Open a fresh task after changing configuration, hooks, profiles, or instruction files. Verify the resulting child rollout rather than trusting hook stdout alone:
agent_role == requested role
model == routed model
effort == routed reasoning level
multi_agent_version == v2
fork_turns == none
base instructions == intended role base
unwanted parent identity markers are absent
Test every role and model combination, caller-supplied metadata overwrite, malformed input denial, unknown-role denial, retired-role denial, and agreement between the routing table and profile TOMLs.
Security boundary
This pattern restores deterministic role, model, and reasoning assignment. It is not a tamper-proof authorization system. Privileged writes, publication, credentials, and constitutional authority still require independently enforced policy or an external verifier.