Skip to content

feat(chat): send eligible Chat combo candidates on the native lane (PF-07) - #5815

Closed
lidge-jun wants to merge 9 commits into
feat/pf09-direct-encodersfrom
feat/pf07-native-chat-combos
Closed

lidge-jun wants to merge 9 commits into
feat/pf09-direct-encodersfrom
feat/pf07-native-chat-combos

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

PF-07 of the protocol-first-class unit (devlog/_plan/260924_protocol_first_class/020_engine_and_codecs.md#pf-07-native-chat-candidates-in-combos). Stacked on #5814 (PF-09).

Behind protocols.rollout.nativeChatCombos (default off): in a Chat Completions combo, each candidate that is native-Chat-eligible is sent on the native Chat lane from its own freshBody() copy of the source envelope, instead of through the internal Responses body. Other candidates keep the bridge. Failover, send budget, affinity and logging stay owned by the combo loop.

  • src/server/responses/core-combo-native.ts: settles the target route the way the Chat ingress does (key model scope, Chat static policy, wire override, OpenCode Go transport), checks isNativeChatRouteEligible, and runs runNativeChatAttempt on the attempt the combo opened, with the combo's send budget, abort signal and turn lease. Returns a Chat-wire response marked with markClientWire.
  • core-combo.ts dispatches eligible children there; a marked child skips preflightComboStreamResponse; non-OK native responses go through consumeComboFailure unchanged.
  • Send budget: a native child opens no spend tracker of its own (the parent's books every send); each physical send increments sendBudget.used, the first settling the hop reservation; transient/429 loops are capped by the remaining base sends.
  • No resend after partial output: a streamed native child commits its 200; a non-stream failure after any output is non-replayable.
  • The combo's reasoning-effort policy maps onto reasoning_effort so a native child keeps forced/default effort. Combos reached through an effort row stay on the bridge.
  • Under unrepresentable: "reject" a candidate whose path cannot carry a requested feature is skipped before any send with feature-unrepresentable on the entry trace; if all are skipped the combo returns the ingress refusal. n > 1 is never emulated.
  • Each native child marks its attempt path native; plan-snapshot.ts previews combo candidates the same way when the switch is on.
  • Chat ingress: a direct-encoder body (PF-09) still goes through the deferred request log (it reports its own facts); a native combo child's row is written by its terminal callbacks, so only its refusal is wrapped.

Recorded as not migrated (devlog 040): policy routes (the router resolves a policy to one candidate, so it never reaches the combo loop); combos with the switch off are not judged per candidate under reject; a streamed native child whose upstream fails in-band before any output does not hop (the bridge child would).

Verification

  • bun x tsc --noEmit: exit 0 on this head.

  • bun run structure:check, bun run privacy:scan: passed.

  • Tests (tests/responses/chat-native-combo.test.ts, 7 cases with fake upstreams; a planner case in protocol-plan-snapshot.test.ts) were written and registered but run in the full local suite below.

  • Full local run on the stack head (feat(protocols): protocol paths as a first-class concern — PF-01..PF-12 #5820, which contains this change): bun run test — the only failures are Lab CL-03/CL-07/CL-08/SEC-02 and release helper timeouts, which fail identically on a checkout without this stack (local environment), plus service/toggle cases that pass when run alone; cd gui && bun test --isolate tests — 2398 pass, 0 fail.

  • CI on this head: all required checks pass.

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.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 25, 2026 04:06
@coderabbitai

coderabbitai Bot commented Sep 25, 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: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 8b0ff35c-7027-4caa-85ac-c2ced3b384fe

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-25T04:11:12.219692Z 031af07 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the enhancement New feature or request label Sep 25, 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: 031af07204

ℹ️ 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".

// a candidate that can never go native costs no copy at all.
const nativeBody = (target: ComboTarget, route: RouteResult | undefined): Rec | undefined => {
if (!route || route.provider.adapter !== "openai-chat") return undefined;
const body = envelope.freshBody();

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 Reuse the eligibility body for native dispatch

When an eligible Chat combo body is roughly 11–16 MiB, this freshBody() is charged and then discarded after eligibility checking; nativeChild() calls nativeBody() again, and runNativeChatAttempt additionally retains the serialized request. Those three request-sized charges exceed the 32 MiB translator budget, so an otherwise admissible native request returns translation_buffer_limit as a 502 before any upstream send. Cache and reuse the candidate copy for dispatch, or perform eligibility against a non-copying read-only view.

Useful? React with 👍 / 👎.

Comment on lines +292 to +294
if (status < 400) {
callbacks.onTerminal("completed");
callbacks.onResponseComplete(plan.route.modelId);

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 Record the model returned by native Chat

When a successful non-streaming Chat JSON response reports a resolved model different from the configured target, such as a versioned model behind an alias, the response returned to the client contains that upstream model but this callback records plan.route.modelId. Consequently rememberComboForLane stores the wrong response model, and a subsequent request on the same session lane that echoes the model it actually received cannot be recalled to the combo. Pass the completion's reported model through the native completion callback, falling back to the route model only when it is absent.

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 63 / 80

채팅으로 들어온 콤보 요청에서, 원래 채팅 규격으로 바로 보내도 되는 후보만 그 길로 보낸다. 스위치는 protocols.rollout.nativeChatCombos이고 기본값은 꺼져 있다. 꺼져 있으면 후보 전부가 지금처럼 내부 Responses 몸통을 거쳐 간다.

켜져 있을 때의 흐름은 이렇다. 채팅 입구가 원본 요청 봉투를 콤보 루프에 넘긴다. 루프는 후보마다 경로를 다시 정한다. 어댑터가 openai-chat이고 네이티브 규칙을 통과하면, 원본 몸통을 복사해서 그 후보만 네이티브 채팅으로 보낸다. 나머지는 기존 다리를 탄다. 다음 후보로 넘어가기, 보낼 수 있는 횟수, 로그 한 줄은 콤보가 그대로 맡는다. n이 2 이상이거나 logprobs가 켜져 있어도 네이티브 후보는 그 값을 업스트림에 실어 보낸다.

unrepresentable이 reject이면, 요청한 기능을 실어 나를 수 없는 후보는 보내기 전에 건너뛴다. 켤 수 있는 후보가 전부 건너뛰어지면 400으로 거절한다. 노력(effort) 행으로 들어온 콤보와 정책(policy) 라우트는 이 루프의 네이티브 분기를 타지 않고 다리에 남는다.

스트림으로 나간 네이티브 후보는 본문을 미리 엿보지 않아서, 응답 상태 200을 먼저 확정한다. 글자가 하나도 나가기 전에 업스트림이 스트림 안에서 실패해도 다음 후보로 넘어가지 않는다. 글자가 나간 뒤의 실패는 다시 보내지 않는다. 이 한계는 devlog 040에 적혀 있다.

src/server/responses/core-combo-native.ts nativeBody - reject일 때 후보를 가르는 judge가 freshBody()로 채팅 본문 전체를 복사한다. 자격만 보고 그 복사는 버린다. 자격 판정은 본문을 고치지 않고 읽기만 한다. 실제로 보낼 때 nativeChild가 같은 후보를 한 번 더 복사한다. 복사는 번역 예산에 그대로 남는다. 본문이 크면, 한 번만 복사하면 통과할 요청이 413이 된다. 전부 건너뛰어 400을 돌려줘야 하는 요청도, 고르는 도중에 복사가 예산을 넘기면 413이 된다.

메인테이너의 판단이 필요한 지점

  • 스트림 네이티브 후보가 출력이 없는 채로 실패하면 다음 대상으로 넘어가지 않는다. 다리 후보는 넘어간다. 기본값이 꺼져 있으니, 이번 릴리스에 이 구멍을 남길지 정하면 된다.
  • 베이스는 feat/pf09-direct-encoders(feat(protocols): encode Chat and Messages responses directly from adapter events (PF-09) #5814)다. 이 브랜치만 먼저 dev에 넣으면 부모에 있는 프로토콜 코드가 같이 들어간다.
  • CI 샤드 2가 tests/cli/cli-headless-parity.test.ts에서 실패했다. 기대는 빈 목록인데 /api/protocols, /api/protocols/plan, /api/protocols/settings가 있다. 이 PR diff에는 그 테스트가 없다. 작성자는 새 콤보 테스트를 로컬에서 돌리지 않았다고 적었다.

너의 추천
자격 판정은 원본 본문을 읽기만 하고, freshBody()는 실제로 보내는 후보에서 한 번만 호출해라. 그 복사본으로 보내라. 스트림에서 글자 없이 실패한 경우는 040에 적은 대로 이번 범위 밖에 두면 된다. #5814의 패리티 실패를 먼저 맞추고, 그다음 이 PR의 chat-native-combo 테스트가 CI에서 통과한 뒤에 머지 순서를 정해라.

이 댓글은 grok-bot이 작성했습니다

@Ingwannu Ingwannu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed exact head 031af072041ca2db63668dfcf59ba98833f3a20e. Blocking budget bug: judge() calls nativeBody(), which allocates and charges envelope.freshBody() only to decide eligibility, then nativeChild() calls it again for the actual send. ProtocolEnvelope.freshBody() charges the full JSON size to request_copies on every call, so each native candidate is charged twice; a large request that fits one required copy can incorrectly fail 413 during candidate selection, and an all-skipped reject request can exhaust budget before returning its intended 400. Judge eligibility from the retained source or cached feature facts without allocating a send copy, then call freshBody() exactly once for the candidate actually dispatched. Add a near-budget regression that would fail on a second copy. The focused native-combo and plan suites pass 18/18 under CPUQuota=200%, MemoryMax=4G, MemorySwapMax=0, TasksMax=128, but exact-head parity still fails because /api/protocols, /api/protocols/plan, and /api/protocols/settings lack documented CLI coverage (98 pass, 1 fail in the combined focused run). #5814 remains a lower-stack blocker as well.

A combo child must spend the combo's per-target budget instead of opening a second
spend tracker, record its own first output and hold the parent's turn lease. All three
are optional; without them the attempt behaves exactly as before.
A combo candidate skipped as unrepresentable leaves no attempt behind, so the skip has
to be recorded on the request itself or the trace cannot say why it was passed over.
With a Chat protocol source on the options, a candidate whose settled route passes the
native Chat rule runs runNativeChatAttempt on the attempt the combo opened, with the
target budget and a fresh source body; under reject an unrepresentable one is never picked.
With the switch on, a combo route carries its source envelope and a native dispatcher
into the Responses combo loop, and a Chat-wire answer passes through without the
Responses-to-Chat conversion. Switch off leaves the request byte-for-byte unchanged.
Loopback upstreams show the native candidate receives the caller's own body (n and
logprobs intact), a failed one hops within the shared budget, output is never re-sent,
the switch-off path stays bridged, and reject skips or refuses without a send.
Records how a combo child is settled, judged and sent natively, how its sends and final
row are owned, and why a marked child skips the stream preflight.
Policy routes never reach the combo loop, effort-row combos keep the bridge, and a
streamed native child commits without the zero-output preflight.
…ends them

With nativeChatCombos on the combo loop judges each candidate as its concrete route, so
the plan snapshot does too; otherwise preview and observed trace would disagree.
The protocol lanes were created between the send-scope comment and the scope it
describes; move them above so each comment sits on the statement it explains.
@lidge-jun
lidge-jun force-pushed the feat/pf09-direct-encoders branch from c4a3159 to b783a2e Compare September 25, 2026 04:32
@lidge-jun
lidge-jun force-pushed the feat/pf07-native-chat-combos branch from 031af07 to 6dc5f8c Compare September 25, 2026 04:32
@devin-ai-integration devin-ai-integration Bot added the priority: P3 Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/ro label Sep 25, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

Maintainer triage: priority: P3 — protocol-first-class series PF-07 (default off).

Criteria (P3): Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/roadmap, or long-stale branch.

Related / overlapping PRs:

lidge-jun added a commit that referenced this pull request Sep 25, 2026
…12 (#5820)

Squash of the protocol-first-class stack #5808, #5809, #5810, #5811, #5812, #5813, #5814, #5815, #5816, #5817, #5819 and #5820. Every new lane sits behind a protocols.rollout switch that defaults off.
@lidge-jun

Copy link
Copy Markdown
Owner Author

Landed in dev as part of the single squash of the protocol-first-class stack: #5820 (0f4c8d4).

@lidge-jun lidge-jun closed this Sep 25, 2026
@lidge-jun
lidge-jun deleted the feat/pf07-native-chat-combos branch September 26, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority: P3 Low: new provider/client integration, large or experimental feature (>2000 LOC or >50 files), RFC/ro

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants