|
| 1 | +# 260816 — codex-rs multi-agent v2 + history performance: opencodex response |
| 2 | + |
| 3 | +## Objective |
| 4 | + |
| 5 | +Upstream codex-rs shipped two changes that opencodex must answer: multi-agent v2 |
| 6 | +delegation now targets **every** catalog model that is not explicitly disabled |
| 7 | +(Luna included), and the local conversation-history stack was redesigned around |
| 8 | +paginated rollouts, ordinals, and a SQLite projection. This unit determines what |
| 9 | +opencodex must change, and stages it as dependency-ordered implementation phases. |
| 10 | + |
| 11 | +> **Revision 2 (2026-08-16), after an adversarial A-phase audit.** An independent |
| 12 | +> reviewer returned FAIL with 11 blockers against revision 1; all were verified against |
| 13 | +> real code and folded in. The substantive corrections: the roster eligibility predicate |
| 14 | +> (`sync.ts:105`) was the actual defect and revision 1 left it untouched; the subagent |
| 15 | +> fallback chain can silently downgrade a v2 child; G5's stated impact was unreachable |
| 16 | +> and is now conditional; three phase dependencies were not real; and the research docs |
| 17 | +> carried prescriptive roadmaps that belong here. Details in each decade doc's amendment |
| 18 | +> note and in `006_audit_round1.md`. |
| 19 | +
|
| 20 | +## Evidence baseline |
| 21 | + |
| 22 | +| Repo | Commit | Verified | |
| 23 | +| --- | --- | --- | |
| 24 | +| upstream codex-rs (조사 시점) | `9dd22890f5ff47e4af128c20e32b9758a61d78d2` | `git log -1`, 2026-08-12 | |
| 25 | +| upstream codex-rs (재검증 후, ff 완료) | `49db349ff` | 2026-08-15, +181 커밋. 두 P0 모두 유효함을 재확인 — `008`/`009` | |
| 26 | +| opencodex | `7612e4c4f81544a250c3eea9fe8ca85d8022e765` | `git log -1`, `fix(routing): source capability evidence from explicit catalog provenance (#1799)` | |
| 27 | + |
| 28 | +Research documents (evidence only — no prescriptions; see LEXICO-SPLIT-01): |
| 29 | + |
| 30 | +- `001_upstream_multiagent_v2_evidence.md` — v2 wire surface, catalog contract, tool schemas. |
| 31 | +- `002_upstream_history_perf_evidence.md` — rollout format, migration, pagination, proxy-visibility verdict. |
| 32 | +- `003_opencodex_subagent_catalog_inventory.md` — current opencodex catalog/subagent surface. |
| 33 | +- `004_opencodex_history_responses_inventory.md` — current opencodex rollout/Responses surface. |
| 34 | +- `005_public_web_evidence.md` — public claim ledger. |
| 35 | +- `006_audit_round1.md` — the A-phase reviewer's blockers and their disposition. |
| 36 | + |
| 37 | +## The two findings that drive this unit |
| 38 | + |
| 39 | +### 1. `multi_agent_version` no longer means "may I be a delegation target" |
| 40 | + |
| 41 | +Before `6d4d9442c` (2026-08-04), a v2 parent could only spawn a model whose catalog |
| 42 | +value equalled `v2`. Now `model_supports_multi_agent_backend` |
| 43 | +(`codex-rs/core/src/tools/handlers/multi_agents_common.rs:36-42`) admits every model |
| 44 | +*except* explicit `Disabled`. The value instead decides whether the **child** gets |
| 45 | +collaboration tools: `collab_tools_enabled` |
| 46 | +(`codex-rs/core/src/tools/spec_plan.rs:599-610`) gives a child recursive tools only |
| 47 | +when its own catalog value is exactly `Some(V2)`. |
| 48 | + |
| 49 | +| Value | Offered to a v2 parent | Child gets collab tools | Meaning | |
| 50 | +| --- | --- | --- | --- | |
| 51 | +| `"v2"` | yes | yes | recursive delegator | |
| 52 | +| `"v1"` | yes | no | **leaf worker** | |
| 53 | +| absent/null | yes | no | **leaf worker** | |
| 54 | +| `"disabled"` | no | no | ineligible | |
| 55 | + |
| 56 | +opencodex encodes the OLD rule in two places, not one: |
| 57 | + |
| 58 | +1. `isEligibleV2SubagentEntry` (`src/codex/catalog/sync.ts:105-108`) returns true only |
| 59 | + for `v2`/null/undefined — so an explicit `v1` pin like Luna's is **excluded from the |
| 60 | + roster entirely**. This is the load-bearing defect. |
| 61 | +2. `applyMultiAgentMode` (`src/codex/catalog/parsing.ts:382-388`) stamps every unpinned |
| 62 | + row `"v2"` when the feature flag is on, claiming every routed third-party model is a |
| 63 | + recursive delegator. |
| 64 | + |
| 65 | +Both must change together: fixing only the stamp leaves Luna excluded, and fixing only |
| 66 | +the predicate leaves routed models over-claiming recursion. |
| 67 | + |
| 68 | +Classification: **silent degradation** — nothing errors; the roster is simply wrong. |
| 69 | + |
| 70 | +### 2. Paginated rollouts reject ordinal-less appended records |
| 71 | + |
| 72 | +`6bb6e9045` + `4bb7ee347` introduce paginated rollouts: each JSONL line carries a |
| 73 | +monotonically increasing `ordinal`, `SessionMeta.history_mode` becomes `"paginated"`, |
| 74 | +and a SQLite projection materializes turns/items. `read_projection_steps` |
| 75 | +(`thread_history_materialization.rs:170-186`) returns a hard `Internal` error for a |
| 76 | +paginated line missing an ordinal. |
| 77 | + |
| 78 | +opencodex's `updateSessionMeta` (`src/codex/history-provider.ts:523+`) **always** |
| 79 | +appends an ordinal-less `session_meta` line, and every thread SELECT |
| 80 | +(`history-provider.ts:589,714,722`) omits `history_mode`. Upstream's equivalent |
| 81 | +(`codex-rs/thread-store/src/local/update_thread_metadata.rs:74`) branches on `paginated` and updates only the threads table. |
| 82 | + |
| 83 | +Classification: **compat-break** — corrupts a paginated thread's projection. |
| 84 | + |
| 85 | +### What is explicitly NOT our problem |
| 86 | + |
| 87 | +The "~98% fewer requests" figure is an N+1 elimination in *local SQLite* summary paging |
| 88 | +(`332eac4b8`): ~749 queries to ~8 for a 741-turn thread at the 100-turn page cap. It is |
| 89 | +not a reduction in `/v1/responses` calls, and `ResponsesApiRequest` |
| 90 | +(`codex-rs/codex-api/src/common.rs:252`) is unchanged by the entire history series. |
| 91 | + |
| 92 | +The `27.6s → 1.7s` and 741-turn figures come from an **official OpenAI announcement** |
| 93 | +(user-confirmed). They do not appear in any public PR body — `gh api search/issues` returns |
| 94 | +`total_count: 0` for both `27.6s` and `"98% fewer"` — so treat them as an internal benchmark |
| 95 | +whose *code* is public, not as a fabricated claim. `009_gh_pr_review.md` maps the figures to |
| 96 | +the PRs that produced them (#36384 N+1 removal, #32234/#33364 pagination, #36948-36951 TUI |
| 97 | +bounded hydration, #38604 resume round-trip removal, #34361 clone avoidance). What remains |
| 98 | +opencodex-relevant is unchanged: those requests are local SQLite/app-server calls, not |
| 99 | +`/v1/responses` calls. |
| 100 | + |
| 101 | +opencodex is a provider proxy, not Codex's app-server. It must **not** implement |
| 102 | +`thread/turns/list`, `thread/items/list`, or `includeTurns`. |
| 103 | + |
| 104 | +## Gap matrix |
| 105 | + |
| 106 | +| # | Gap | Class | Phase | |
| 107 | +| --- | --- | --- | --- | |
| 108 | +| G1 | `default` mode stamps unpinned rows `v2`, erasing leaf semantics | silent-degradation | 010 | |
| 109 | +| G2 | No typed per-model multi-agent capability, and no creation path for one | missed-opportunity | 010 | |
| 110 | +| G3 | `updateSessionMeta` appends ordinal-less lines to paginated rollouts | compat-break | 020 | |
| 111 | +| G4 | Thread SELECTs omit `history_mode`; bulk updates cannot skip unknown rows | compat-break | 020 | |
| 112 | +| G5 | `extractUserMessagePreview` misses canonical `ItemCompleted` records | **conditional** — no reachable consumer found; see 030 gate | 030 | |
| 113 | +| G6 | Rollout filename assumed to carry thread id; `4ef836f88` separates them | compat-break (latent) | 030 | |
| 114 | +| G7 | `hasAgentsMaxThreads` claims a boot refusal upstream reverted in `1f304dd1f` | stale-guard | 040 | |
| 115 | +| G8 | `/v1/responses/compact` full-field forwarding unverified | missed-opportunity | 050 | |
| 116 | +| G9 | `structure/03_catalog-and-subagents.md:133-153` documents the superseded rule | docs-drift | 010 | |
| 117 | +| **G12** | Quota fallback can rewrite a v2 child to a v1/disabled model, keeping collab tools | **compat-break** | 010 | |
| 118 | +| G13 | `collaboration.ts:349` fork-override guidance | **no-action (optional polish)** — upstream's own hint (`config/mod.rs:253`) says forks "do not accept overrides", so our text mirrors it; the implementation honors overrides but the guidance is not wrong | 010 | |
| 119 | +| **G14** | `model_messages.multi_agent` (role/mode 지시문)이 카탈로그에서 공급됨 (#38619); opencodex는 `model_messages` 를 `metadata.ts:300-308` 에서 변형하고 `upstream-models.json` 스냅샷으로 공급하는데 이 서브트리를 모름 | missed-opportunity → 잠재 silent-degradation | 060 (신규, 미작성) | |
| 120 | +| G10 | Storage cleanup refuses paginated history | no-action (keep refusal) | — | |
| 121 | +| G11 | App-server pagination protocol | no-action (out of proxy scope) | — | |
| 122 | + |
| 123 | +G12 and G13 were found by the A-phase reviewer, not by the research swarm. |
| 124 | + |
| 125 | +## Phase map |
| 126 | + |
| 127 | +Ordering follows build order where a real dependency exists. Revision 1 asserted three |
| 128 | +dependencies that the reviewer disproved; the corrected map is mostly **parallel**, which |
| 129 | +is itself a useful finding — these are independent defects, not a chain. |
| 130 | + |
| 131 | +| Phase | Doc | Outcome | Depends on | |
| 132 | +| --- | --- | --- | --- | |
| 133 | +| 1 | `010_phase1_catalog_capability_contract.md` | Leaf semantics restored end-to-end: eligibility, stamp, creation path, fallback capability class | — | |
| 134 | +| 2 | `020_phase2_history_mode_awareness.md` | Never write ordinal-less records to a paginated rollout | — | |
| 135 | +| 3 | `030_phase3_rollout_identity_and_previews.md` | Rollout-id/thread-id separation locked; G5 gated | — | |
| 136 | +| 4 | `040_phase4_config_alias_and_docs.md` | `max_threads` alias truth through its full consumer chain | — | |
| 137 | +| 5 | `050_phase5_compact_wire_verification.md` | `/v1/responses/compact` field-fidelity regression | — | |
| 138 | + |
| 139 | +Recommended execution order (risk-first, not dependency-forced): 1 → 2 → 4 → 3 → 5. |
| 140 | +Phase 1 carries two compat-breaks (G12) and the headline defect; Phase 2 carries the |
| 141 | +data-corruption risk. Phases 3 and 5 may both close as NOOP with recorded evidence. |
| 142 | + |
| 143 | +Each phase is one full PABCD cycle and closes with an independently verifiable gate. |
| 144 | + |
| 145 | +## Environment precondition (applies to every phase) |
| 146 | + |
| 147 | +This worktree has **no `node_modules/`**. Every verifier command therefore exits 1 for an |
| 148 | +environmental reason (`Cannot find module 'zod/v4'`; `TS2688: bun-types`). Each phase's B |
| 149 | +must run `bun install` first and re-record real exit codes beside its verifier table. |
| 150 | +Note `package.json:41` defines `"test": "bun scripts/test.ts"` — use `bun run test` for a |
| 151 | +full run, `bun test <file>` for focused iteration. |
| 152 | + |
| 153 | +## Scope boundary |
| 154 | + |
| 155 | +IN, by phase: |
| 156 | + |
| 157 | +- **catalog/capability (010):** `src/codex/catalog/parsing.ts`, `src/codex/catalog/sync.ts`, |
| 158 | + `src/codex/catalog/provider-fetch.ts`, `src/codex/catalog/effort.ts` (the |
| 159 | + `applyCatalogModelMetadata` bridge), `src/codex/subagent-model-fallback.ts`, |
| 160 | + `src/server/responses/core.ts` (both fallback call sites), |
| 161 | + `src/server/responses/collaboration.ts`, `src/server/management/model-rows.ts`, |
| 162 | + `gui/src/pages/models-shared.ts`. |
| 163 | +- **history (020/030):** `src/codex/history-provider.ts`, `src/codex/sqlite-columns.ts` (new), |
| 164 | + `src/codex/history-worker.ts`, `src/codex/history-job.ts`, `src/codex/history-transition.ts`, |
| 165 | + `src/codex/history-migration-guardian.ts`, `src/codex/inject.ts`, |
| 166 | + `src/codex/convergence-types.ts` and `src/codex/transition-state.ts` (the durable |
| 167 | + reason vocabulary + its schema migration, Phase 2 Change 6), |
| 168 | + `src/storage/cleanup.ts` (read paths + the `columnExists` re-export). |
| 169 | +- **config/docs (040):** `src/codex/features.ts`, `src/cli/v2.ts`, |
| 170 | + `src/server/management/agent-settings-routes.ts` (GET and PUT), `gui/src/pages/Models.tsx` |
| 171 | + and its locale strings. |
| 172 | +- **compact (050):** `src/server/responses/compact.ts`, `src/adapters/openai-responses.ts`. |
| 173 | +- **docs:** `structure/03_catalog-and-subagents.md`, `structure/05_gui-and-management-api.md`. |
| 174 | +- matching `tests/` for each. |
| 175 | + |
| 176 | +OUT: the codex-rs checkout (read-only), app-server protocol reimplementation, **other** |
| 177 | +provider adapters (`src/adapters/openai-responses.ts` is explicitly IN for Phase 5's |
| 178 | +passthrough regression; no other adapter is), GUI redesign beyond the copy/type changes |
| 179 | +listed above, release actions, any `git push`. |
| 180 | + |
| 181 | +## Terminal outcome for this docs cycle |
| 182 | + |
| 183 | +`DONE` when 000-007 plus every decade doc exist at diff-level precision, every audit-round |
| 184 | +blocker is folded or explicitly rebutted, and the unit is committed locally. `006` and `007` |
| 185 | +are audit history: the decade docs are canonical, and where an amendment corrected an |
| 186 | +earlier instruction the canonical text was rewritten rather than appended to. |
0 commit comments