Skip to content

docs(agents): document the five-model spawn_agent window and its V1/V2 difference - #1815

Merged
lidge-jun merged 2 commits into
devfrom
codex/compat-v2-five-cap
Aug 16, 2026
Merged

docs(agents): document the five-model spawn_agent window and its V1/V2 difference#1815
lidge-jun merged 2 commits into
devfrom
codex/compat-v2-five-cap

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Documents what the five-model spawn_agent window actually is, and settles whether it
behaves identically on the v2 surface. No production code changes — opencodex already
matches upstream on both surfaces, and this layer records the proof plus the SoT sync.

The cap is 5 on both surfaces, but the window's contents are not. The eligibility filter
runs before .take(5) and behaves differently per surface
(multi_agents_common.rs:36-42): on a V1 call model_supports_multi_agent_backend
short-circuits true for every row, including disabled ones; a V2 call drops
Some(Disabled) first, which lets a later row move into the five. Same catalog, different
advertised list — the doc carries a worked seven-row example.

Also records what the 5 is not. Its only two consumers are the tool-description list
(multi_agents_spec.rs:789) and the unknown-model error suggestions
(multi_agents_common.rs:448), and the latter runs inside the ok_or_else closure after
the lookup already failed. The success path scans the whole catalog with neither the cap nor
a show_in_picker filter, so a model outside the advertised five is still accepted when
named exactly. Concurrency is an unrelated number: V1 6 children (root excluded), V2 total 4
including root → 3 children.

Two further V1/V2 differences: the list gate is hide_agent_type_model_reasoning on V1
(hard-coded false at registration, so V1 always advertises) but
expose_spawn_agent_model_overrides on V2 (default true; when false the list is omitted
and the model/reasoning_effort schema fields are removed). V2's
hide_spawn_agent_metadata defaults true, which removes service_tier.

A rejected "bug"

A subagent reported that sorting the roster by SPAWN_PRIORITY_FIELD instead of the
rewritten priority diverges from upstream's window, and called it a real bug. Rejected.
The divergence is real but is the documented purpose of modelPickerOrder (#1649): it is
display-only and must never change spawn candidate membership. Two existing tests pin that
contract. Comparing against upstream alone is not sufficient to call something a bug — local
design intent has to be checked first.

Verification

  • bun x tsc --noEmit → exit 0.
  • Full suite on ssh lidge: 12561 pass / 13 skip / 0 fail.
  • No source or test files change in this layer; it adds 013_five_cap_v1_vs_v2.md,
    012_c1_implementation_evidence.md, and the structure/03 section.
  • Every claim carries a file:line verified against codex-rs 49db349ff.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Stack (merge bottom-up):

# PR Layer Review focus
4 #1815 codex/compat-v2-five-cap ← you are here the five-model window doc + SoT sync
3 #1814 codex/compat-v2-daybreak global native row + fixture reversals
2 #1813 codex/compat-v2-roster roster predicate
1 #1812 codex/compat-v2-docs upstream analysis, no code

Depends on #1814. Review this PR's diff only.

…2 difference

Answers the question "does the five behave identically on v2?" from upstream
source. Short answer: the cap is 5 on both surfaces, but the window's CONTENTS
differ, because the eligibility filter runs before .take(5) and behaves
differently per surface.

On a V1 call model_supports_multi_agent_backend short-circuits true for every row
(including "disabled" ones); a V2 call drops Some(Disabled) first, which lets a
later row move into the five. Same catalog, different advertised list - the doc
carries a worked seven-row example.

Also records what the 5 is NOT: it is neither a concurrency limit nor an
eligibility limit. Its only two consumers are the tool-description list and the
unknown-model error suggestions, and the latter runs inside the ok_or_else closure
after the lookup already failed. The success path scans the whole catalog with
neither the cap nor a show_in_picker filter, so a model outside the advertised
five is still accepted when named exactly. Concurrency is a separate number
entirely (V1 6 children, V2 total 4 including root -> 3 children).

opencodex needs no code change here: effectiveSubagentRoster already filters with
surface !== "v2" || isEligibleV2SubagentEntry, so the V1 path skips eligibility
exactly as upstream does, and no roster is injected on V1 at all.

One investigation finding is recorded as REJECTED rather than filed as a bug: a
subagent reported that sorting the roster by SPAWN_PRIORITY_FIELD instead of the
rewritten priority diverges from upstream's window. The divergence is real but is
the documented purpose of modelPickerOrder (#1649) - it is display-only and must
not change spawn candidate membership - and two existing tests pin that contract.
Comparing against upstream alone is not sufficient to call something a bug; local
design intent has to be checked first.

Includes the C1 implementation evidence doc (lidge: 12561 pass / 0 fail, tsc 0).
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 24d00e59-6095-4901-86ce-6ae206aa667b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 16, 2026

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a75124c19

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +202 to +205
`modelPickerOrder` (#1649) deliberately does **not** feed this window: it rewrites only the
Codex-visible `priority` while `SPAWN_PRIORITY_FIELD` preserves the natural priority the roster
sorts by, so a display reorder can never change candidate membership. That divergence from
upstream's own ordering is the feature's purpose, not a defect —

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Correct the modelPickerOrder window claim

When modelPickerOrder is configured for enough routed rows, this statement conflates OpenCodex's injected roster with Codex's native five-model tool-description window. buildCatalogEntriesFromObservedState rewrites the Codex-visible priority, and Codex sorts the catalog by that field before taking five; the private SPAWN_PRIORITY_FIELD is ignored by Codex and is consumed only by effectiveSubagentRoster. Consequently, picker ordering can change the models advertised by the native spawn_agent description even though the injected roster retains natural membership. Please distinguish those two windows instead of stating that the override cannot affect this one.

Useful? React with 👍 / 👎.

Four layers on origin/dev, each its own PR whose base is the layer below:
#1812 (docs, base dev) -> #1813 (roster) -> #1814 (daybreak) -> #1815 (five-cap).

DEV-STACK-03 requires each layer to build and pass its own tests at its own tip,
so every layer was checked out separately on ssh lidge and given a full run:

  L1 docs     bba4b96  tsc 0  12575 pass / 0 fail
  L2 roster   df83829  tsc 0  12575 pass / 0 fail
  L3 daybreak 926ca6c  tsc 0  12576 pass / 0 fail
  L4 five-cap 9a75124  tsc 0  12576 pass / 0 fail

The +1 at L3 is the new test proving the global Daybreak row exists without an
observation.

The original C1 commit mixed the roster and Daybreak changes, so the split was done
per file. Diffing the L4 tip against the previously verified branch shows the only
difference is the two new documents, which proves no code was lost or altered while
re-slicing.

Also records an unintended observation: one push carried a main -> main
fast-forward. It was a pre-existing maintenance commit by another author already in
the local main, not this work, but it changed remote state so it is logged.
Subsequent pushes used explicit refs/heads/<branch>:refs/heads/<branch>.
@lidge-jun
lidge-jun changed the base branch from codex/compat-v2-daybreak to dev August 16, 2026 05:43
@lidge-jun
lidge-jun merged commit 09bf1f1 into dev Aug 16, 2026
19 checks passed
ntdatt812 pushed a commit to ntdatt812/opencodex that referenced this pull request Aug 16, 2026
All four layers merged bottom-up into dev:

  lidge-jun#1812 docs     05:42:04Z  ead6327
  lidge-jun#1813 roster   05:42:32Z  e516912
  lidge-jun#1814 daybreak 05:42:52Z  aa585e7
  lidge-jun#1815 five-cap 05:43:12Z  09bf1f1

Each upper PR was retargeted to dev only after the layer below it landed, so the
stack order was never violated.

The dev ruleset requires one approving review plus code-owner review, and the PR
author is the code owner, so self-approval is impossible. Merges used the admin
bypass the ruleset already defines (bypass_actors: RepositoryRole 5, bypass_mode
pull_request) via gh pr merge --admin. No new bypass was created. Before merging,
all four PRs were MERGEABLE with zero failing or pending checks; lidge-jun#1812's BLOCKED
state was REVIEW_REQUIRED, not a CI failure.

Post-merge verification on ssh lidge against origin/dev:

  bun x tsc --noEmit                      -> exit 0
  OCX_TEST_NO_QUEUE=1 bun scripts/test.ts -> 12576 pass / 0 fail

The landed code was read back from dev directly: isEligibleV2SubagentEntry returns
entry.multi_agent_version !== "disabled", NATIVE_DAYBREAK_BLUE_MODEL is in the
allowlist, the devlog unit has 19 documents, and structure/03 carries the five-cap
section. All four layer tips are ancestors of origin/dev.

G1b, G2, G12 and G14 remain open as C2/C3.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant