From 9a75124c19781cfb27f28b0f4c818ff74b0df208 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 16 Aug 2026 13:52:40 +0900 Subject: [PATCH 1/2] docs(agents): document the five-model spawn_agent window and its V1/V2 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). --- .../012_c1_implementation_evidence.md | 88 +++++++++++++++++ .../013_five_cap_v1_vs_v2.md | 97 +++++++++++++++++++ structure/03_catalog-and-subagents.md | 55 +++++++++++ 3 files changed, 240 insertions(+) create mode 100644 devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/012_c1_implementation_evidence.md create mode 100644 devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/013_five_cap_v1_vs_v2.md diff --git a/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/012_c1_implementation_evidence.md b/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/012_c1_implementation_evidence.md new file mode 100644 index 0000000000..c30357be58 --- /dev/null +++ b/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/012_c1_implementation_evidence.md @@ -0,0 +1,88 @@ +# 012 — C1-D: 구현 완료와 검증 증거 + +브랜치 `codex/compat-multiagent-v2-catalog` @ `d7bac3476` (origin/dev `8a0de6c44` 위). +상류 codex-rs `49db349ff`. 계획: `011` 4판. + +## 구현한 것 + +### 1. 로스터 자격 (G1a) — `src/codex/catalog/sync.ts:105-108` + +```ts +// before +return pinned === "v2" || pinned === null || pinned === undefined; +// after +return entry.multi_agent_version !== "disabled"; +``` + +상류 `6d4d9442c` 와 정합. `v1` 핀은 이제 **자격 있는 리프 워커**이며, 이것이 upstream이 +`gpt-5.6-luna` 에 붙인 핀이다. 주석에 3분류(eligible-recursive / eligible-LEAF / excluded)와 +`keepNativeChatGptOnV1`(#1728) 정책과의 관계를 명시했다. + +### 2. `gpt-daybreak-blue-latest` 전역 편입 — `src/codex/catalog/native-models.ts` + +소유자 결정. `NATIVE_OPENAI_MODELS` 에 추가. 선언부 주석에 **수용된 트레이드오프**를 기록: +권한 없는 계정도 행을 보게 되며, 선택 시 백엔드 400이 릴레이된다(bare 풀 라우트는 인식된 +400 본문에 한해 다른 계정 1회 재시도; 셀렉터 한정 라우트는 고정 계정이라 즉시 릴레이). +`disabledModels` 는 가시성 hatch이지 런타임 거부가 아니다. + +`NATIVE_OPENAI_CAPABILITY_ALIAS_MODELS` 주석도 정정 — 두 목록 겸속이 이제 정상이며, +소비 맵이 `Map` 이라 중복이 접힌다는 점을 명시. + +## 검증 (lidge, Ubuntu 16-core, `~/ocx-c1-test` @ `d7bac3476`) + +| 게이트 | 결과 | +| --- | --- | +| `bun x tsc --noEmit` | **exit 0** | +| `OCX_TEST_NO_QUEUE=1 bun scripts/test.ts` | **12561 pass / 13 skip / 0 fail**, `TEST_EXIT=0` | +| 규모 | `Ran 12574 tests across 813 files. [453.83s]` | + +SHA 일치 확인: 로컬 `d7bac347604bbd2cafcc7653a030f4aa506e577b` = 원격 +`refs/heads/codex/compat-multiagent-v2-catalog` = lidge 체크아웃. + +첫 실행에서 12 fail / 7 errors 였다. 7 errors는 `gui/` 의존성 미설치(환경)였고, +`cd gui && bun install` 후 사라졌다. 12 fail은 전부 **의미가 뒤집힌 픽스처**로, 감사가 +사전에 지목한 목록과 일치했다. + +## 뒤집힌 테스트와 처리 + +| 테스트 | 낡은 전제 | 새 단언 | +| --- | --- | --- | +| `multi-agent-compat` (3건) | `v1` 핀은 로스터에서 제외 | Luna가 `candidates`/`advertised` 에 등장; `disabled` 가 `surface_incompatible` 역할 | +| `native-model-toggle` (2건) | daybreak = 미지의 관측 id | `gpt-future-unlisted` 로 교체 + **관측 없이 전역 행 존재 + sol의 `v2` 상속** 신규 케이스 | +| `codex-catalog` | bare 행 없음 | bare 1개 + 셀렉터당 1개 (**중복 없음** 증명) | +| `codex-models-cache-invalidate` | daybreak = 미지 관측 | `gpt-future-unlisted` 로 교체 | +| `codex-convergence-account-selectors` | 셀렉터 1개만 투영 | **모든 가시 셀렉터** + bare, 각 정확히 1개 | +| `codex-catalog-sync-hardening` (2건) | bare 행 없음 | bare 정확히 1개; 명시적 Codex-forward 커스텀 행은 별개 정체성 유지; API-key alias는 여전히 Codex 표면에 없음 | +| `claude-models-discovery` | 계정한정 행으로 발견 | 관리 API는 **전역 bare 정체성**; Anthropic 표면에는 둘 다 없음(전역 행이 `hide` 로 합성됨) — **실측으로 확인 후** 단언 | + +모든 변경 단언은 `some(...)` 가 아니라 **`toHaveLength(1)`** 로 바꿔, 중복 행 회귀가 여기서 +잡히도록 했다. + +## 예상 밖 발견 + +Anthropic 디스커버리에서 daybreak가 **양쪽 정체성 모두 사라졌다.** 전역 합성 행은 +`visibility: "hide"` 이고, 계정한정 투영은 전역 편입으로 더는 생성되지 않기 때문이다. +추측으로 단언하지 않고 실제 응답을 출력해 확인했다(필터된 id 목록에 `gpt-5.5` 만 존재). +이는 **의도된 결과는 아니지만 무해**하다 — Claude 표면은 Codex 카탈로그에서 가시적인 행만 +광고하며, daybreak는 네이티브 OpenAI 경로로 라우팅되기 때문이다. 별도 노출이 필요하면 +후속 사이클에서 `visibility` 정책을 다뤄야 한다. + +## 열린 채로 남은 것 + +| 갭 | 사이클 | +| --- | --- | +| G1b — `default` 모드 blanket `v2` 스탬프 (`parsing.ts:409+`, `8a0de6c44` 가 안 고침) | C2 | +| G2 — capability creation path / bridge | C3 | +| G12 — fallback capability class (암호화 NEW_TASK fallback과 얽힘) | C3 | +| G14 — `model_messages.multi_agent` (#38619) | 별도 | + +## 프로세스 기록 + +A 게이트: 리뷰어1과 3라운드(블로커 4+4+5, 전부 소스로 검증 후 반영, 반박 0), +이후 신선한 리뷰어 2명(NEAR-PASS → **PASS**). 리뷰어1이 잡은 결정적 두 건: +(1) `origin/dev` 가 4커밋 앞서 있었고 `8a0de6c44` 가 이 범위와 겹친다는 것 — 리베이스로 해결, +(2) 2판의 "`8a0de6c44` 가 default 스탬프를 재정의했다"는 **내 사실 오류**. + +`cxc review-round` 판정 훅은 이 환경에서 리뷰어 종료 시 기록되지 않았다(직전 사이클과 동일). +감사는 실제로 수행되었고 판정 원문은 `orchestrate` attest와 이 문서에 보존한다. + diff --git a/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/013_five_cap_v1_vs_v2.md b/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/013_five_cap_v1_vs_v2.md new file mode 100644 index 0000000000..88c572f794 --- /dev/null +++ b/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/013_five_cap_v1_vs_v2.md @@ -0,0 +1,97 @@ +# 013 — "다섯 개가 v2에서도 동일하게 노출되는가" 확정 답변 + +사용자 질문에 대한 P-단계 조사. 상류 `49db349ff`, opencodex `bf97f4bd0` 기준. +원자료: `.tmp/research3/J_five_cap_v2.md` (gitignored). 서브에이전트 조사 + 메인 직접 검증. + +## 한 줄 답 + +**상한 숫자 5는 V1/V2 동일하지만, 그 5개를 고르는 창(window)의 내용은 다르다.** + +## 왜 다른가 — 필터가 cap보다 먼저 적용된다 + +`spawn_agent_models_description` (`multi_agents_spec.rs:785-790`) 순서: +`show_in_picker` 필터 → `model_supports_multi_agent_backend` 필터 → `.take(5)`. + +두 번째 필터가 표면에 따라 다르게 동작한다 (`multi_agents_common.rs:36-42`): + +```rust +multi_agent_version != MultiAgentVersion::V2 + || model.multi_agent_version != Some(MultiAgentVersion::Disabled) +``` + +- **V1 호출**: 좌변이 참이라 **모든 행이 통과**한다 — `Disabled` 조차 포함된다. +- **V2 호출**: `Some(Disabled)` 만 제외된다. + +제외된 행이 있으면 **뒤 행이 5칸 안으로 밀려 들어온다.** 그래서 같은 카탈로그가 두 표면에서 +다른 목록을 만든다. + +### 구체 예 (모두 picker-visible, 이 순서) + +| # | 모델 | `multi_agent_version` | +| ---: | --- | --- | +| 1 | `v2-a` | `V2` | +| 2 | `disabled-a` | `Disabled` | +| 3 | `v1-a` | `V1` | +| 4 | `null-a` | `None` | +| 5 | `v2-b` | `V2` | +| 6 | `disabled-b` | `Disabled` | +| 7 | `null-b` | `None` | + +- **V1 광고**: `v2-a, disabled-a, v1-a, null-a, v2-b` +- **V2 광고**: `v2-a, v1-a, null-a, v2-b, null-b` + +## 그 외 V1/V2 차이 + +| 항목 | V1 | V2 | 근거 | +| --- | --- | --- | --- | +| 목록 게이트 | `hide_agent_type_model_reasoning` — 실제 등록이 `false` 로 고정하므로 **항상 광고** | `expose_spawn_agent_model_overrides` — 이것이 진짜 게이트 | `multi_agents_spec.rs:67-70` / `:102-105`; `spec_plan.rs:1200-1207` / `:1150-1160` | +| 그 플래그 기본값 | (V1에선 불활성) | **`true`** (`MultiAgentV2Config` 기본) | `config/mod.rs:1229-1255`, `:2650-2655` | +| 게이트 off일 때 | 해당 없음 | 목록 생략 + `model`/`reasoning_effort` 스키마에서 제거 | `multi_agents_spec.rs:103-119` | +| `service_tier` | 노출 | `hide_spawn_agent_metadata` 기본 true → 제거 | `config/mod.rs:1242-1243` | +| 성공 경로 검증 | 전체 카탈로그, cap/visibility 없음 | 동일 + `Disabled` 만 거부 | `multi_agents_common.rs:431-442` | + +**5는 여전히 "광고 + 실패 에러 메시지"에만 쓰인다** (`multi_agents_spec.rs:789`, +`multi_agents_common.rs:448`). 5위 밖 모델도 이름만 맞으면 수락된다. 동시 실행 수(V1 6, +V2 총 4 → 자식 3)와는 **무관**하다. + +## opencodex 패리티: 이미 맞다 + +`effectiveSubagentRoster` (`src/codex/catalog/sync.ts:157-188`)의 필터가 +`surface !== "v2" || isEligibleV2SubagentEntry(entry)` 이므로 **V1은 자격 필터를 건너뛴다** — +상류 V1이 모든 행을 통과시키는 것과 정확히 같다. C1에서 `isEligibleV2SubagentEntry` 를 +`!== "disabled"` 로 고친 결과, V2 쪽도 상류와 일치한다. + +또한 opencodex는 **V1 표면에 로스터를 주입하지 않는다** (`collaboration.ts:368-372` — 최상위 +effort에서 proactive 텍스트만). 즉 V1 목록의 권위는 상류 툴 설명문이고, 우리가 개입하지 않는다. + +## 기각한 주장: `modelPickerOrder` "실버그" + +서브에이전트가 `effectiveSubagentRoster` 가 `SPAWN_PRIORITY_FIELD`(자연 우선순위)로 정렬해 +상류의 재작성된 `priority` 창과 달라진다며 **실버그**라고 보고했다. **기각한다.** + +이는 **이슈 #1649의 의도된 설계**다: + +- `sync.ts:70`: "modelPickerOrder is a **DISPLAY-ONLY**" +- `sync.ts:622-624`: "modelPickerOrder is a DISPLAY-ONLY override. Record the natural priority + spawn_agent must keep using" +- `sync.ts:174-176`: "so a modelPickerOrder display reorder (#1649) **can never change candidate + membership**" +- `sync.ts:477`: "Independent of the 5-slot spawn_agent cap" + +그리고 **기존 테스트가 이 동작을 계약으로 고정**하고 있다 +(`tests/codex-catalog-model-picker-order.test.ts`): +`"picker-order-only rows do not displace default-tier spawn_agent candidates"` (`:113`), +`"candidate set is unchanged when all routed rows are listed in reverse order"` (`:176`). + +서브에이전트가 관찰한 "발산"은 실재하지만 **그것이 이 기능의 목적**이다: 사용자가 피커 표시 +순서를 바꿔도 스폰 후보 집합은 흔들리지 않게 하는 것. 상류 창과 다른 것은 알려진 트레이드오프이지 +회귀가 아니다. 커밋 `088997364` 참조. + +**교훈:** 상류 대조만으로 "버그"를 판정하면 안 된다. 로컬 설계 의도(주석·이슈·테스트)를 먼저 +확인해야 한다. + +## 결론: 코드 변경 없음 (문서화 레이어) + +이 질문에 대해 opencodex가 고칠 것은 **없다.** V1/V2 창 차이는 상류 의미이며 opencodex는 이미 +표면별로 올바르게 동작한다. 산출물은 `structure/03_catalog-and-subagents.md` 문서화다. + diff --git a/structure/03_catalog-and-subagents.md b/structure/03_catalog-and-subagents.md index d3821cd3c1..8de3610b33 100644 --- a/structure/03_catalog-and-subagents.md +++ b/structure/03_catalog-and-subagents.md @@ -152,6 +152,61 @@ The `multi_agent_v2` feature flag and the logical maximum thread count are separ `multiAgentMode` (`src/codex/features.ts`): the mode decides which surface Codex advertises, while the flag and thread count decide what the native runtime allows. +### What the five-model `spawn_agent` window is, and how V1 differs from V2 + +`MAX_SPAWN_AGENT_MODEL_OVERRIDES = 5` (mirrored in `src/codex/catalog/sync.ts`) is **not** a +subagent concurrency limit and **not** an eligibility limit. Upstream uses it in exactly two +places: the model list rendered into the `spawn_agent` tool description +(`multi_agents_spec.rs:789`) and the "Available models:" suggestions in an unknown-model error +(`multi_agents_common.rs:448`, inside the `ok_or_else` closure that runs only *after* the lookup +already failed). The success path `find_spawn_agent_model_name` (`:431-442`) 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. + +Three different numbers, often conflated: + +| Quantity | Value | Source | +| --- | --- | --- | +| Models **advertised** as overrides | `min(5, picker-visible eligible rows)` | `multi_agents_spec.rs:785-790` | +| Models **eligible** as targets | no numeric cap (only `"disabled"` is excluded, and only on V2) | `multi_agents_common.rs:36-42` | +| **Concurrent** subagents | V1 6 children (root excluded); V2 total 4 including root → 3 children | `config/mod.rs:211-212`, `:1497-1506` | + +**The cap is the same 5 on both surfaces, but the window's contents are not.** The eligibility +filter runs *before* `.take(5)`, and it behaves differently per surface: on a V1 call +`model_supports_multi_agent_backend` short-circuits true for every row (including `disabled` +ones), while a V2 call drops `Some(Disabled)` first — which lets a later row move into the five. +Same catalog, different advertised list: + +| # | Model | pin | V1 advertises | V2 advertises | +| ---: | --- | --- | :---: | :---: | +| 1 | `v2-a` | `v2` | ✅ | ✅ | +| 2 | `disabled-a` | `disabled` | ✅ | — | +| 3 | `v1-a` | `v1` | ✅ | ✅ | +| 4 | `null-a` | absent | ✅ | ✅ | +| 5 | `v2-b` | `v2` | ✅ | ✅ | +| 6 | `disabled-b` | `disabled` | — | — | +| 7 | `null-b` | absent | — | ✅ | + +opencodex already matches this: `effectiveSubagentRoster` filters with +`surface !== "v2" || isEligibleV2SubagentEntry(entry)`, so the V1 path skips the eligibility +filter exactly as upstream does. opencodex also injects no roster on V1 +(`src/server/responses/collaboration.ts` emits only proactive text at the top effort tier), so +the upstream tool description remains the authority there. + +Two further V1/V2 differences worth knowing: 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). And V2's +`hide_spawn_agent_metadata` defaults true, which removes `service_tier`. + +`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 — +`tests/codex-catalog-model-picker-order.test.ts` pins it. + +Full derivation with per-line citations: `devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/013_five_cap_v1_vs_v2.md`. + ## Routed tool discovery and hosted search Non-Cursor routed catalog rows advertise `supports_search_tool: true` together with From 86042ab5dd159af3a9791b25e6ad476781633f63 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sun, 16 Aug 2026 14:34:12 +0900 Subject: [PATCH 2/2] docs(devlog): record the stacked-PR split and per-layer verification 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 bba4b9669 tsc 0 12575 pass / 0 fail L2 roster df83829cd tsc 0 12575 pass / 0 fail L3 daybreak 926ca6cdd tsc 0 12576 pass / 0 fail L4 five-cap 9a75124c1 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/:refs/heads/. --- .../014_stacked_pr_evidence.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/014_stacked_pr_evidence.md diff --git a/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/014_stacked_pr_evidence.md b/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/014_stacked_pr_evidence.md new file mode 100644 index 0000000000..f700731225 --- /dev/null +++ b/devlog/_plan/260816_codexrs_multiagent_v2_and_history_perf/014_stacked_pr_evidence.md @@ -0,0 +1,68 @@ +# 014 — 스택 PR 개설과 레이어별 검증 증거 + +`origin/dev` 위에 4층 스택을 구성하고 PR 4개를 열었다. DEV-STACK-01/02/03 준수. + +## 스택 구조 (bottom-up 병합) + +| # | PR | 브랜치 | base | 커밋 | 논지 | +| ---: | --- | --- | --- | ---: | --- | +| 4 | [#1815](https://github.com/lidge-jun/opencodex/pull/1815) | `codex/compat-v2-five-cap` | `codex/compat-v2-daybreak` | 5 | 5-모델 창의 V1/V2 차이 문서화 + SoT 동기화 | +| 3 | [#1814](https://github.com/lidge-jun/opencodex/pull/1814) | `codex/compat-v2-daybreak` | `codex/compat-v2-roster` | 4 | daybreak 전역 네이티브 행 (소유자 결정) | +| 2 | [#1813](https://github.com/lidge-jun/opencodex/pull/1813) | `codex/compat-v2-roster` | `codex/compat-v2-docs` | 3 | 로스터 술어: `v1` 핀 = 자격 있는 리프 | +| 1 | [#1812](https://github.com/lidge-jun/opencodex/pull/1812) | `codex/compat-v2-docs` | **`dev`** | 2 | 상류 분석 문서만, 코드 없음 | + +각 레이어가 자기 커밋만 담는지 확인 (`git log ..`): + +``` +L2 over L1: df83829cd feat(agents): treat a v1 pin as an eligible leaf subagent +L3 over L2: 926ca6cdd feat(catalog): ship gpt-daybreak-blue-latest as a global native row +L4 over L3: 9a75124c1 docs(agents): document the five-model spawn_agent window... +``` + +base ref는 `gh pr list --json baseRefName` 로 되읽어 확인했다. + +## 레이어별 독립 검증 (ssh lidge, Ubuntu 16-core) + +DEV-STACK-03 "각 레이어는 자기 tip에서 빌드·테스트를 통과해야 한다"를 만족한다. +레이어마다 체크아웃 → `tsc` → 전체 스위트를 각각 돌렸다: + +| 레이어 | SHA | `tsc --noEmit` | 전체 스위트 | +| --- | --- | --- | --- | +| L1 docs | `bba4b9669` | **0** | **12575 pass / 0 fail** | +| L2 roster | `df83829cd` | **0** | **12575 pass / 0 fail** | +| L3 daybreak | `926ca6cdd` | **0** | **12576 pass / 0 fail** | +| L4 five-cap | `9a75124c1` | **0** | **12576 pass / 0 fail** | + +L3에서 pass가 1 늘어난 것은 daybreak 전역 행을 증명하는 신규 테스트 +("ships as a global native row without an observation")가 추가되었기 때문이다. + +L2 단독 포커스 검증도 별도로 수행: `multi-agent-compat` + `multi-agent-keep-native-v1` + +`codex-catalog-model-picker-order` = **69 pass / 0 fail**. + +## 분할 방식 + +`729e2e4a2` 는 로스터 변경과 daybreak 변경을 한 커밋에 담고 있었으므로 파일 단위로 갈랐다: + +- L2 ← `src/codex/catalog/sync.ts`, `tests/multi-agent-compat.test.ts`, 관련 devlog +- L3 ← `src/codex/catalog/native-models.ts`, daybreak 픽스처 4개 + `codex-catalog`/`native-model-toggle` + +L4 tip을 원래 검증된 브랜치(`codex/compat-multiagent-v2-catalog`)와 diff한 결과, 차이는 +`013_five_cap_v1_vs_v2.md` 와 `structure/03` 신규 섹션뿐이다 — 즉 **분할 과정에서 코드가 +유실되거나 변형되지 않았다.** + +## 주의: `main` 푸시 관찰 + +`git push` 중 `6abcd2631..e664647de main -> main` 이 함께 실행되었다. 확인 결과 로컬 +`main` 에 이미 있던 **기존 유지보수 커밋**(`bitkyc08-arch` 작성, "Merge dev into main: bind +the retained usage window")이 fast-forward된 것으로, **이번 작업 커밋은 포함되지 않았다.** +그럼에도 의도치 않은 원격 상태 변경이므로 기록해 둔다. 이후 push는 전부 +`refs/heads/:refs/heads/` 형태로 명시했다. + +## 다음 + +병합은 bottom-up: #1812 → #1813 → #1814 → #1815. 하위 레이어가 수정되면 상위를 +`git rebase --update-refs` 로 캐스케이드하고 `--force-with-lease` 로 재푸시해야 한다 +(DEV-STACK-02). 병합은 사용자 결정 사항이며 이 작업에서는 수행하지 않았다. + +여전히 열린 갭: G1b(default 모드 blanket 스탬프), G2, G12, G14 — `000_plan.md` 의 C2/C3 참조. +