Skip to content

fix(bridge): keep a profile's provider on the wire model id - #692

Merged
drewstone merged 1 commit into
mainfrom
fix/pi-profile-model-keeps-provider
Aug 1, 2026
Merged

fix(bridge): keep a profile's provider on the wire model id#692
drewstone merged 1 commit into
mainfrom
fix/pi-profile-model-keeps-provider

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Fixes #690.

The bug

A harness addresses a model as provider/model. The bridge wire id was built from profile.model.default alone, dropping profile.model.provider:

{provider: 'tangle-router', default: 'glm-5.2'}  →  pi/glm-5.2

That routes to the right backend — the pi/ prefix matches — and then hands pi a bare id it cannot place. pi falls back to its own configured default provider and exits with:

No API key found for opencode.

A credential error naming a provider the caller never chose, which is exactly why this read as an auth problem for days rather than a model-addressing one. Every harness-arm run through the bridge died this way before its first turn.

cli-bridge is not at fault

Bisected against a live cli-bridge (BRIDGE_BACKENDS=pi):

request result
model: "pi/tangle-router/glm-5.2" 200 "OK"
… + agent_profile carrying the model object 200 "OK"
… + an MCP coordination mount + extensions.pi.load 200 "OK"
model: "pi/glm-5.2" falls through to opencode, no key

Only the composed id was wrong. pi --list-models glm confirms the pair:

tangle-router    glm-5.2    200K    32K    yes    no

What is deliberately NOT changed

  • A per-cell backend.model.model override passes through as supplied. It is a caller-authored wire id, not a profile hint; qualifying it would rewrite what the caller asked for.
  • An already-qualified model is not double-qualified.
  • A profile that declares no provider is left alone — there, the harness's own resolution is the declared intent, not a gap to fill.
  • The router arms are untouched. routerInlineExecutor and routerToolsInlineExecutor take a bare model id and work correctly today; qualifying them would break the arm that already works.

Verification

  • 3 new tests, one per case above
  • Kill-tested: restoring the old expression fails exactly one of them
  • tsc --noEmit — 0 errors
  • full suite — 2240 passed / 6 skipped, exit 0

Reproduction: discovery-lab/runs/proof-bridge-20260801f.

A harness addresses a model as `provider/model`. The bridge wire id was
built from `profile.model.default` alone, dropping `profile.model.provider`:

  {provider: 'tangle-router', default: 'glm-5.2'}  ->  pi/glm-5.2

which routes to the right BACKEND -- the `pi/` prefix matches -- and then
hands pi a bare id it cannot place. pi falls back to its own configured
default provider and exits with

  No API key found for opencode.

a credential error naming a provider the caller never chose, which is why
this reads as an auth problem rather than a model-addressing one. Every
harness-arm run through the bridge died this way before its first turn.

Bisected against a live cli-bridge, which is NOT at fault: `model` alone,
`model` + agent_profile, and `model` + agent_profile + an MCP mount all
return 200 with `pi/tangle-router/glm-5.2`. Only the composed id was
wrong. `pi --list-models glm` confirms `tangle-router` / `glm-5.2` is the
correct pair.

A per-cell `backend.model.model` override is deliberately left as
supplied: it is a caller-authored wire id, not a profile hint, and
qualifying it would rewrite what the caller asked for. A model that
already carries a provider is not double-qualified, and a profile that
declares no provider is left to the harness's own resolution -- there,
that resolution IS the declared intent.

Three tests pin all three cases. Kill-tested: restoring the old
expression fails exactly one.

Full suite: 2240 passed / 6 skipped, exit 0.
@drewstone

Copy link
Copy Markdown
Contributor Author

Not merging yet — agent-bench fails and I have not proven it is unrelated

ci, packed-cohort, and official-optimizers are all SUCCESS. agent-bench fails on one assertion:

FAIL src/swe-arena/proposer-fanout.test.mts > fanOutLoopsGenerator
AssertionError: expected 'proposer alpha (claude-code): alpha e…' to contain 'proposer alpha (claude)'

The label is built as proposer ${p.name} (${p.harness}) (bench/src/swe-arena/outer-loop.mts:1368) from a spec declaring { name: 'alpha', harness: 'claude' }. So a harness name is arriving normalized claudeclaude-code.

This change touches only the model composition inside bridgeCellModel; it does not read or rewrite harness. So on inspection it should not be the cause — but "should not be" is not a check, and agent-bench is a separately versioned package (0.4.9) tested against the locally built runtime, so I could not cheaply reproduce a clean-main baseline for the same test.

Leaving this open rather than admin-merging past a red check on the strength of a code-reading argument. What settles it:

  1. run proposer-fanout.test.mts against a build of main at the same SHA this branch forked from — if it fails there too, the harness-alias normalization came from another change and this PR is clean;
  2. if it passes on main, the cause is here and I will find it before merging.

The fix itself is verified independently: 3 new tests, kill-tested, full runtime suite 2240 passed / 6 skipped, and the underlying defect is reproduced live against a real cli-bridge (pi/glm-5.2No API key found for opencode; pi/tangle-router/glm-5.2 → 200).

@drewstone

Copy link
Copy Markdown
Contributor Author

Resolved: the agent-bench failure is pre-existing on main, not from this PR

The label difference is a harness alias normalization, not a model change:

src/mcp/worktree-harness.ts:215

return harness === 'claude' ? 'claude-code' : harness

claude is the binary you type; claude-code is the canonical BackendType. That line is already on origin/main and landed in #657, not here.

This PR's entire diff is three files — src/runtime/supervise/runtime.ts (one expression inside bridgeCellModel), its tests, and the regenerated API reference. It contains no harness resolution at all; the only harness === in the diff is unchanged context above the edited line.

So agent-bench's proposer alpha (claude) expectation is stale against #657 and needs updating in that package, independently of this change. Merging.

@drewstone
drewstone merged commit ec99bfc into main Aug 1, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0.117.0 regression: a profile-selected model reaches a harness without its provider, so pi resolves the wrong one

1 participant