Skip to content

fix(server): select an available provider for delegated tasks - #11870

Merged
juliusmarminge merged 8 commits into
pingdotgg:t3code/codex-turn-mappingfrom
saphid:work/ov2-20260913-08
Sep 21, 2026
Merged

juliusmarminge merged 8 commits into
pingdotgg:t3code/codex-turn-mappingfrom
saphid:work/ov2-20260913-08

Conversation

@saphid

@saphid saphid commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A driver-only delegation target (target.driverKind without providerInstanceId) could fail on an unavailable inherited provider instance even when another instance of that driver was healthy: resolveTarget preferred the parent's instance whenever it was registered and orchestration-capable, and the constraint check then rejected it.

The fix requires the inherited candidate to pass the same providerConstraints availability check as any other candidate before it can win; otherwise the first constraint-free instance of the requested driver is selected. When no healthy candidate exists, resolution falls through to the parent instance id so the constraint check reports why the inherited instance cannot serve. Explicit providerInstanceId selection is unchanged and stays strict — an unavailable explicit target fails instead of silently rerouting.

Adapted from @kiiwee's earlier proposal in #11154 (closed unmerged) onto the live adapter-registry capability lookup from #11578. Credit to kiiwee for identifying the defect and the original fix shape.

Test plan

  • Focused regression: vp test run apps/server/src/mcp/OrchestratorMcpService.test.ts — the new table-driven test proves healthy inheritance (parent model selection including options preserved verbatim), unavailable parent + healthy peer fallback, no-available-peer rejection, explicit-unavailable rejection without rerouting, and explicit healthy selection. The fallback case failed before the fix (Provider codex cannot run a child task: Provider instance is disabled.).
  • vp test run apps/server/src/mcp/ — all 170 MCP tests pass, including the cross-provider delegation integration test.
  • Targeted vp lint, vp fmt --check, and tsc --noEmit clean for the changed scope.

Independent cross-provider review: attempted codex exec -m gpt-6-astra -c model_reasoning_effort="xhigh" --sandbox read-only on the frozen diff; the reviewer account was at its usage limit (reported retry Sep 19), so the review was skipped — disclosed, not substituted.

Targets Orchestrator V2 in #2829.

Engineering: SWE-2 Max through the Devin/T3 harness.
Coordination: T3 thread 3aa91588-f393-4df8-9d47-c8c769d27899, campaign saphid/t3code-personal#298.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Sep 15, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 46353a3

Macroscope's review found this PR approvable — This is a narrowly scoped provider-resolution bug fix: unavailable inherited instances now fall back to healthy peers, with explicit targets remaining strict. The production change is small and covered by focused regression tests, while the documentation is updated consistently.

You can add or adjust custom eligibility rules. Learn more.

@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from a5ebe9a to a62d7ad Compare September 15, 2026 17:25
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from b7e28ab to 792db8c Compare September 15, 2026 17:45
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 94be5b7 to 84c9498 Compare September 15, 2026 22:23
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from 792db8c to 9658bc7 Compare September 15, 2026 22:27
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 9a98db3 to 69bcbee Compare September 16, 2026 00:33
@saphid
saphid force-pushed the work/ov2-20260913-08 branch 2 times, most recently from 7cedd7b to c2ae308 Compare September 16, 2026 01:08
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 16, 2026
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 82d344d to 341b1ac Compare September 16, 2026 03:26
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from c2ae308 to ad59c2a Compare September 16, 2026 04:08
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 341b1ac to f63b335 Compare September 16, 2026 04:26
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from ad59c2a to 6ae1588 Compare September 16, 2026 05:10
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from f63b335 to 08a1b86 Compare September 16, 2026 07:02
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from 6ae1588 to cacc469 Compare September 16, 2026 07:19
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 0f32464 to 4ab2efc Compare September 16, 2026 21:15
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from cacc469 to 5966041 Compare September 16, 2026 22:03
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 43db598 to 6f6817f Compare September 17, 2026 06:24
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from 5966041 to 802b2fe Compare September 17, 2026 06:33
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from d61550b to e6cb2b5 Compare September 17, 2026 06:46
@saphid
saphid force-pushed the work/ov2-20260913-08 branch 2 times, most recently from c2d7665 to 2822470 Compare September 17, 2026 08:01
@macroscopeapp
macroscopeapp Bot dismissed their stale review September 17, 2026 08:48

Dismissing prior approval to re-evaluate bcf5ec8

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 17, 2026
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from bcf5ec8 to 8355a7f Compare September 17, 2026 18:08
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from a6b73de to 3d45b30 Compare September 17, 2026 19:51
@saphid
saphid force-pushed the work/ov2-20260913-08 branch 2 times, most recently from b3f5ee6 to 02a525d Compare September 17, 2026 20:40
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from b6933f8 to 642168a Compare September 18, 2026 21:38
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch 3 times, most recently from 5b1f6a2 to 9b4fd10 Compare September 19, 2026 00:46
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from 642168a to bbfc07d Compare September 19, 2026 01:43
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 5ff0a93 to fd8ea2f Compare September 19, 2026 04:23
@saphid
saphid force-pushed the work/ov2-20260913-08 branch from bbfc07d to c4dbf89 Compare September 19, 2026 04:31
@macroscopeapp

This comment has been minimized.

@saphid
saphid force-pushed the work/ov2-20260913-08 branch from 68475ad to eb1d74d Compare September 19, 2026 10:14
@macroscopeapp
macroscopeapp Bot dismissed their stale review September 19, 2026 11:14

Dismissing prior approval to re-evaluate f13b647

Comment on lines +868 to +878
if (instanceId === undefined) {
const reasons = candidates.flatMap((candidate) =>
providerConstraints(candidate, true).map(
(constraint) => `${candidate.instanceId}: ${constraint}`,
),
);
return yield* failure(
"provider_unavailable",
`No available V2 provider instance for driver ${requestedDriver}: ${reasons.join(" ")}`,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This returned MCP failure now concatenates every provider constraint, including raw provider.unavailableReason / provider.message, without any length bound. Those strings may contain arbitrary provider diagnostics or sensitive data. Keep the caller-visible error normalized and bounded; the provider details can remain available through a separately sanitized diagnostic path.

Suggested change
if (instanceId === undefined) {
const reasons = candidates.flatMap((candidate) =>
providerConstraints(candidate, true).map(
(constraint) => `${candidate.instanceId}: ${constraint}`,
),
);
return yield* failure(
"provider_unavailable",
`No available V2 provider instance for driver ${requestedDriver}: ${reasons.join(" ")}`,
);
}
if (instanceId === undefined) {
return yield* failure(
"provider_unavailable",
`No available V2 provider instance for driver ${requestedDriver}.`,
);
}

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

This comment has been minimized.

1 similar comment
@macroscopeapp

macroscopeapp Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Effect Service Conventions found 1 issue: the new provider-unavailable failure exposes unbounded, potentially sensitive provider diagnostics. See the inline review comment for the suggested fix.

Posted via Macroscope — Effect Service Conventions

@saphid
saphid force-pushed the work/ov2-20260913-08 branch from 10d3c83 to ab79e2f Compare September 19, 2026 13:31
@macroscopeapp

macroscopeapp Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

All clear

Posted via Macroscope — Effect Service Conventions

@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a1f8051 to 0337dd6 Compare September 21, 2026 05:40
github-actions Bot and others added 8 commits September 21, 2026 13:48
A driver-only delegation target could fail on an unavailable inherited
provider instance even when another instance of that driver was healthy.
Prefer the inherited instance only when it passes the availability checks,
then fall back to a healthy peer. Explicit instance selection remains
strict.

Adapted from the proposal in pingdotgg#11154 onto the
adapter-registry capability lookup.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Upstream 1ee1d04 rejects v2 file-restore rollback in shared workspaces;
fixture threads had worktreePath: null so every thread_rollback replay was
treated as shared and rejected. Each fixture workspace is a private temp
checkout, so declare it as the thread's worktreePath — an accurate model
that keeps the file-restore path covered.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Two consecutive CI runs failed queued_cancelled_while_active/codex on
release_replay_gate:turn/completed (budget exhausted in <200ms) while the
base stayed green. Declaring every fixture workspace as a private worktree
enables checkpoint capture git work per turn, adding latency unrelated
fixtures never need. Only thread_rollback exercises the file-restore path
that requires a non-shared worktree, so gate the declaration on rollback
steps.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A driver-only delegation request whose registered candidates were all
unavailable fell through to the parent's instance, which belongs to a
different driver, and failed with invalid_request. The request is valid;
the instances are not. Return provider_unavailable listing each
candidate's constraints instead.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Keep the parent snapshot on the codex driver and make only the peer
candidate claudeAgent, so the pre-fix fallback would hit the real
driver-mismatch invalid_request rather than passing on the shared
provider_unavailable code. Also assert provider_unavailable through the
shared create_threads path.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The provider_unavailable failure for an exhausted driver candidate set
concatenated raw provider diagnostics (unavailableReason, status
message) without a length bound. Return the normalized driver-scoped
message instead; provider detail stays visible through provider state.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…Records

v2 already avoids the shared-workspace rollback rejection with
restoreFiles: false, so the fixture worktreePath option and its
integration-test branch are dead weight. Also point the new delegation
test's ThreadManagementService mock at getThreadRecords, which is what
OrchestratorMcpService calls on v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge

Copy link
Copy Markdown
Member

Rebased onto t3code/codex-turn-mapping at 9d6feae1ff. All seven commits applied cleanly with no conflicts.

One follow-up commit on top (46353a3d7c):

  • Dropped the replay-fixture worktreePath plumbing. The three fixture commits (declare replay fixture workspaces as thread worktrees, the exactOptionalPropertyTypes fix, and declare fixture worktrees only for rollback scenarios) worked around a rollback rejection that v2 has since fixed at the source with restoreFiles: false in testkit/fixtures/shared.ts. OrchestratorReplayFixtures.integration.test.ts and fixtures/shared.ts are back to their v2 versions; the net diff is now the 13-line change in OrchestratorMcpService.ts, its test, and the doc paragraph.
  • Test fix. The new delegation test mocked ThreadManagementService.getThreadProjection, but OrchestratorMcpService.loadProjection on v2 calls getThreadRecords, so it failed with UnimplementedError. Pointed the mock at the right method, matching the other cases in that file.
  • The test was already one table-driven loop over six scenarios, so there was nothing to fold.

Verified in apps/server: vp test run src/mcp/OrchestratorMcpService.test.ts (9 passed), vp test run src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts (73 passed, no hangs without the fixture worktree branch), and vpr typecheck (clean).

Rebased and touched up by a maintainer's agent; a human will re-review.

@juliusmarminge
juliusmarminge merged commit fc66b9f into pingdotgg:t3code/codex-turn-mapping Sep 21, 2026
23 checks passed
juliusmarminge added a commit that referenced this pull request Sep 23, 2026
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants