.agents/skills/deep-review/references/codex/main.md
Deep mode in Codex, end to end. "Subagent" below means a spawn_agent agent. Every prompt is written to be self-contained, which is correct under any Codex version.
The multi-agent API differs across Codex versions — tool names, parameters, and defaults all move. Everything in this section is a claim to verify against the session you are actually in, not a fact to rely on. Read the discovered tools' real schemas before step 2 and bind the manual's verbs and parameters to what exists.
tool_search as multi_agent_v1.spawn_agent / wait_agent / close_agent, or exposed directly as spawn_agent / wait / close_agent. Search/list the session's tools and bind the spawn/wait/close verbs used below to the names actually exposed; do not assume this manual's names exist verbatim. If no multi-agent tools can be surfaced, stop and offer light mode.fork_turns, fork_context, or a version-specific equivalent) and set it explicitly to no inheritance — never rely on the default being off. If the tool exposes no such control and inheritance cannot be turned off, say so in the report: the verify pass is then not adversarial and its confirmed verdicts are weaker than they look.agents.max_threads default is 6. The flow therefore runs dimensions as 3 composite groups in a single wave instead of one agent per dimension.process group, which is scan-heavy.Dimension rules stay one-per-file; Codex only changes how they are packed:
| Group | Dimensions (in prompt order) |
|---|---|
quality | code-style, reuse-architecture, business-logic, ux |
correctness | logic, performance, security, compatibility, release-risk |
process | workflow, skill-freshness, observability |
Pruning removes dimensions from a group; a fully pruned group is not spawned. The table is a starting point — recalibrate the packing in this file if group runtimes drift far apart. release-risk sits with performance on purpose: both read the migration files, from opposite angles (cost vs reversibility), so one agent reads them once. Keep the angle separation their dimension files define — one shared agent must still emit two distinct findings, never a merged one.
Extension packs can also add dimensions (a deep-review-*/dimensions/ file whose name matches no built-in). Added dimensions have no row in the table: collect them into a dynamic fourth group, extras. It exceeds the slot budget unless a built-in group was fully pruned, so step 3 schedules it — first freed slot in Lane A, next batch in Lane B — but it is never dropped. Its verify routing follows each dimension file's own verify frontmatter flag (default: verified).
Follow ../scoping.md. Outputs: {changes} (diff text or fetch commands), the ≤ 200-word scope summary, PR metadata in PR mode.
Same as the pruning + extension-pack procedure in SKILL.md: apply the pruning table, detect sibling deep-review-* extension packs, collect each surviving dimension's rule-file paths (built-in + extension counterpart). Map surviving dimensions onto the group table; extension-added dimensions go to the extras group (see above) so downstream rules never get silently dropped.
Per group (built-in groups plus a non-empty extras):
../review-prompt.md; instantiate:
{dimensions} → the group's surviving dimension ids (comma-separated){dimension_files} → all their rule-file paths{scope_summary} / {changes} → step 0 outputsspawn_agent.message. Prompts are self-contained, so set the inheritance control discovered in the constraints section to no inheritance — this is the step where reviewer independence is won or silently lost.Which algorithm you run depends on one thing settled during tool discovery: does the session expose a verb that closes/releases a finished agent? Pick a lane now and stay in it — do not mix.
Both lanes share the same per-result handling, referred to below as consume(agent):
```json fence and parse. Parse failure / wrong schema → reject and re-spawn that group with the same prompt. A retry is a review agent like any other — it joins Lane A's review wait set, and in Lane B it extends the review wave. Neither lane may begin verification while a retry is outstanding: that group's findings and release checks would be absent from the report entirely, which is worse than unverified.verify flag: verify: false dimensions (workflow, skill-freshness) go straight to the report pool; zero verifiable findings → done with this group. A returned release_checks array (release-risk only) also bypasses verification — never include it in {issues}; it holds questions about production state, not claims to falsify.../verify-prompt.md with {issues} = this group's verifiable findings, plus {scope_summary} / {changes}.And the same verify-return handling: validate verifications.length against the input count with ids one-to-one (Set difference). Mismatch → spawn a fresh verify agent with the missing ids' findings and the full prompt; do not loosen. Verdicts: confirmed → report pool (apply overrides / same_root_as); false_positive → drop; need_more_context → "Needs your input" appendix. The main agent never re-verifies findings itself.
Slots are held until closed, so closing promptly is what buys the pipelining.
Keep two explicit queues for the whole lane: pendingSpawn (a queued extras group, if any) and pendingVerify (verify payloads waiting for a slot). Once the wave is running, nothing is spawned except by taking from a queue — "spawn it in the next available slot" is not a step you can leave implicit, because the work item has to survive in writing until a slot actually exists.
extras goes into pendingSpawn, not into this wave.wait (wait-any — call it repeatedly). On each returned review agent: close it → consume(agent) → append its verify payload, if any, to pendingVerify → then service the freed slot (below).pendingSpawn first (an extras review joins the review wait set), otherwise the oldest entry in pendingVerify. Empty queues → leave the slot idle.pendingVerify has not been verified — its findings stay out of the report pool until its verifier returns. Reaching step 4 of the report with a non-empty queue means findings shipped unverified, which is the one failure this flow exists to prevent.Slot arithmetic: up to 3 review agents spawn together; every later spawn fills a slot freed by a close, so the flow never exceeds 3 concurrent agents and never deadlocks. When extras takes priority over pendingVerify, it delays a verifier rather than dropping it — that is exactly what the queue is for.
Nothing frees a slot on demand, so pipelining is not available. Do not emit close calls; do not reason about freed slots.
wait for all of them. More groups than slots (built-ins + extras > 3) → run them as consecutive batches, each batch fully awaited before the next spawns. extras is just another group here — it takes its turn in batch order rather than racing for a freed slot.consume(agent) for every returned review agent, collecting all verify payloads before spawning any verifier. A re-spawn from step 1 of consume stays inside the review wave: it becomes an extra batch, awaited and consumed like the rest, and the wave is over only when every group has produced a parsed result. Moving to step 3 with a retry still in flight silently drops that whole group — its dimensions vanish from the report with no unverified-dimension section to mark the hole.wait for all of them. Then apply the shared verify-return handling.This costs one barrier's worth of wall-clock versus Lane A and changes nothing about coverage or independence — which is the contract. Never drop verification to buy back the time.
Render strictly per ../report-template.md — structure, P2 cap, unverified-dimension sections, statistics, PR-mode merge verdict (main agent fills the decision table; never delegated). Run the template's pre-send self-check. Do not fall back to Codex's default findings format.
Non-empty batch → one request_user_input (allowed regardless of delegation policy): "Safe to fix now" has N low-risk findings — apply them all in one pass? with options Fix all (recommended) / Not now; free-text covers partial picks. Fix all → apply each fix option, add regression tests where need_test: true, one line per fix. Empty → skip silently.
can_auto_fix: false confirmed findings + the need_more_context appendix, when non-empty, go through request_user_input one finding at a time — P0 → P1 → P2, blocks_release: true first; offer the finding's fix_options as the choices. Apply what the user picks; park the rest. Both lists empty → skip silently and go to Step 7.
Only in-scope findings enter this loop. Everything under Hand off to owner is excluded — never offer to fix a pre-existing problem here. Low-likelihood non-blocking findings go last, and are skipped entirely on a repeat review unless the user asks.
Runs whether or not Step 6 asked anything — only condition is that Hand off to owner is non-empty. One request_user_input: N pre-existing problems belong to other owners — create Linear issues for them? with options Create all / Pick some / Not now. Create nothing before the user answers. On approval, follow the linear skill: Chinese content, one issue per finding, description carrying location + culprit commit/author/date + scenario + likelihood + verification evidence, assigned to the culprit author when identified. Report the created issue keys.
agents.max_threads shortens nothing here (the wave already fits 3 slots) — keep the group table as the execution grain for predictability.