Skip to content

teams.json cannot pin a per-agent model; the documented claude:opus syntax is dead - #1845

Merged
khaliqgant merged 6 commits into
mainfrom
relayflow/relay-software-garden-3027e1e7
Sep 23, 2026
Merged

khaliqgant merged 6 commits into
mainfrom
relayflow/relay-software-garden-3027e1e7

Conversation

@agent-relay-code

@agent-relay-code agent-relay-code Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Per-agent model pins in teams.json

Agents configured with {"name":"Worker","cli":"claude","model":"opus"} now retain their model pin through config loading and up auto-spawn (including up --spawn). The config schema and core spawn API expose the optional field. Model names are trimmed; invalid or empty values warn and are omitted without dropping the agent. Normalization preserves role/task and excludes unknown agent keys.

Harness-specific handling stays in the existing broker path, which already carries model on the wire and emits model arguments for Claude, Codex, and OpenCode. No second TypeScript translation layer or catalog validation is introduced. Explicit inline --model/-m continues to win. The CLI doc-comment now recommends a separate model field and explains the inline-argument escape hatch and its node-capacity limitation.

The obsolete public mapModelToCli and getBaseCli helpers are deprecated for removal next major. Their root and subpath exports remain intact: the reviewed plan reserved breaking removal for a release-level decision, so this change takes the compatibility-preserving option and raises the pending changelog to Minor. model-commands.ts, broker colon-syntax branches, and node-capacity normalization remain follow-ups. No workflow files or production Rust code changed.

Validation:

  • npm test -w @agent-relay/config: 103 passed, including loader normalization and schema round-trip/description regressions.
  • npm run build -w @agent-relay/config: passed.
  • npm test -w @agent-relay/utils: 235 passed.
  • Focused CLI run (core.test.ts, fleet-sidecar.test.ts, client-factory.test.ts): 178 passed, including model forwarding for Claude, Codex, OpenCode, and inline arguments. The existing exact no-model spawn assertion remains green.
  • Full CLI run (npx vitest run packages/cli/src): 1,885 passed, 17 skipped, 2 failed. Both failures reproduce on unmodified b274b7a in an isolated worktree: sdk-client.test.ts expects no gateway URL but receives https://cast.agentrelay.com; fleet-lifecycle-integration.test.ts exits from local-agent routing.
  • Added a hermetic broker test covering all four inline model override forms and the no-override path using Claude. cargo test -p agent-relay-broker model could not run because Cargo/Rust is not installed; broker model and ordered-PTY-argument tests remain unverified here.
  • Formatting and git diff --check: passed.

Checks

Relayflow ran this repository's checks (.relayflow/check.sh) and they passed.

What ran (.relayflow/check.sh)
#!/bin/sh
# Fresh-machine check for agent-relay, mirroring the `Test` workflow
# (.github/workflows/test.yml) plus the Rust half of `CI`
# (.github/workflows/rust-ci.yml).
set -e

cd "$(dirname "$0")/.."

# CI pins Node 22.14.0 (.nvmrc says 22) and disables telemetry for every job.
export AGENT_RELAY_TELEMETRY_DISABLED=1

# --- Install -----------------------------------------------------------------
npm ci

# CI re-installs rollup because npm can drop its platform-specific optional
# dependency; failure here is non-fatal in CI too.
npm install --no-save rollup || true

# --- Build -------------------------------------------------------------------
# `npm test` normally triggers this via its `pretest` hook; run it explicitly so
# the build is a distinct step. Covers clean + all workspace package builds.
# `build:rust` inside this target self-skips when cargo is absent (installs then
# rely on the prebuilt broker optional dependencies).
npm run build

# --- Tests -------------------------------------------------------------------
# PR proof transfer guards (node:test, not vitest).
node --test \
  scripts/pr-proof/broker-transfer.test.mjs \
  scripts/pr-proof/run-cloud.test.mjs \
  scripts/flows/deploy-listeners.test.mjs \
  scripts/pr-proof/event-from-input.test.mjs

# Subscription proof guards.
npm run test:subscriptions:proof

# Main suite. Invoked directly rather than through `npm test` so the `pretest`
# hook does not redo the build performed above.
npx vitest run

# --- Rust --------------------------------------------------------------------
# rust-ci.yml runs `cargo test` (debug and release) over crates/broker and
# crates/relay-pty. Only run it when a toolchain is already present; this
# machine has no cargo, and installing rustup is out of scope for a check run.
if command -v cargo >/dev/null 2>&1; then
  cargo test
else
  echo "skipping cargo test: no Rust toolchain on this machine"
fi

# Deliberately omitted, because they need things this machine does not have:
#   - coverage upload (codecov/codecov-action) — needs a Codecov token.
#   - `npm run format:check` and `npm run lint` — separate `lint` job, not a
#     pre-test step; format:check also currently fails on pre-existing files
#     (plan.md, reviewed-plan.md) that the prettier-fmt-fix workflow owns.
#   - Windows credential ACL job — windows-latest only.
#   - Swift SDK job (swift build/test) — macos-latest and a Swift toolchain.
#   - rust cross-compile / clippy / fmt jobs — extra targets and components.
#   - e2e-tests.yml, fleet-e2e.yml, relay-evals.yml, prod-smoke.yml — need real
#     agent CLIs, API keys, and live broker/cloud services.

Fixes #1381

Review in cubic


Note

Medium Risk
Changes broker spawn model resolution and teams auto-spawn wiring; incorrect precedence could misreport models or inject duplicate flags, though tests cover inline override behavior.

Overview
Adds an optional model field on teams.json agents so up --spawn forwards a model pin to the broker (schema, loader trim/validation, CLI spawn types). Inline **--model/-m in cli still wins; configs can keep both for broker precedence.

The broker now records the argv-effective model when spawn args already carry --model/-m: model_override_from_args runs on the HTTP spawn path before relay-skill selection, and worker startup updates spec.model when injection is suppressed so listings, events, and telemetry match what the harness runs.

mapModelToCli and getBaseCli in @agent-relay/utils are deprecated (next major); use separate cli and model instead of colon-suffixed CLI strings.

Reviewed by Cursor Bugbot for commit 7c74ae4. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: efa44a6e-b717-4d75-956c-a51656e9873a

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.

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 1 potential issue.

Devin Review

await relay.spawn({
name: agent.name,
cli: agent.cli,
...(agent.model ? { model: agent.model } : {}),

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.

🟡 Inline overrides retain the wrong model

When cli overrides agent.model, spec.model retains the pin while the harness runs the inline model. Worker metadata and model-based prompt selection then use the wrong model.

Learn more

The auto-spawn request now carries both the configured pin and inline CLI arguments. The broker detects an inline --model or -m in resolve_model_flag_for_cli and returns no injected model flag. However, the original pin remains in spec.model. That field is later exposed by worker listings and spawn events, used for telemetry attribution, and passed to relay_skill_prefix. The actual inline model and recorded model therefore diverge.

Example: With { "cli": "claude --model haiku", "model": "opus" }, Claude runs Haiku. Relay reports Opus and omits the small-model relay prefix because it evaluates the stale Opus pin.

Recommended fix: Parse the effective inline model in the broker and replace spec.model with it when an override is present. Keep suppressing the separately injected flag so the CLI receives only one model selection.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@agent-relay-code
agent-relay-code Bot marked this pull request as draft September 22, 2026 19:35
@agent-relay-code

Copy link
Copy Markdown
Contributor Author

Relayflow: the adversarial review did not pass. This branch is not approved: the flow stopped here and did not mark it ready to merge.

Review: PR #1845

Reviewed b274b7a..dd1708e2089a8eeb2f0f4eb605aa9b44749cc753, including the formatting-only bot commit, all issue comments, submitted reviews, and inline comments available on 2026-09-22.

Finding

P2 — Resolve inline overrides before selecting prompts and recording the model

Location: packages/cli/src/cli/lib/broker-lifecycle.ts:2442.

The new forwarding path sends both the model pin and the inline command to the broker. For an agent configured as {"name":"Worker","cli":"claude --model haiku","model":"opus"}, the harness runs Haiku, but Relay retains spec.model = "opus".

resolve_model_flag_for_cli (crates/broker/src/worker.rs:2528) returns None when inline arguments contain a model override. Its callers update spec.model only when a model flag is returned, leaving the original pin unchanged. Worker listings (worker.rs:417), spawn events, and telemetry consequently describe the wrong model. Additionally, HTTP spawn selects the relay skill prefix from spec.model before starting the worker (crates/broker/src/runtime/api.rs:537), so the Haiku-specific relay guidance is omitted in this example. Reversing the two models adds small-model guidance to an Opus agent unnecessarily.

Resolve the effective model from inline CLI arguments and explicit spawn arguments before prompt selection, and preserve that effective value in the worker specification. Continue suppressing duplicate model-flag injection. Updating metadata only inside worker startup is too late to repair prompt selection.

Add regression coverage for conflicting pins and inline --model value, --model=value, -m value, and -m=value, checking the effective metadata and prompt as well as the final arguments. The added Rust test checks only that injection is suppressed, so it passes despite this mismatch.

This independently confirms Devin's inline review comment. No fix is included in this review pass; review.clean is intentionally absent.

Validation and comments

  • Passed: npx vitest run packages/config/src/teams-config.test.ts packages/config/src/schemas.test.ts packages/cli/src/cli/commands/core.test.ts packages/utils/src/model-mapping.test.ts — 4 files, 219 tests.
  • Passed: npm run typecheck, including dependency builds and CLI typechecking.
  • Passed: git diff --check b274b7a..HEAD.
  • Rust regression execution was attempted but blocked because Cargo is not installed. Live harness execution was not performed.
  • Reviewed the config/schema changes, model normalization, production spawn adapter and serialization, broker argument handling, changelog, and public model-mapping exports. Deprecation preserves compatibility for the published root and subpath exports; removing them immediately would be a breaking change.
  • CodeRabbit's sole issue comment says review was skipped. Devin's submitted review contains the finding above. Cursor Bugbot passed; the exact Linux broker build remained pending when checked. These statuses do not establish a clean review.
  • The requested relay inbox check was attempted; the installed relay CLI has no inbox command.

prpmdev-bot and others added 2 commits September 22, 2026 18:20
`resolve_model_flag_for_cli` returns None when argv already carries a model
override, so no second flag is injected. Its callers only wrote `spec.model`
when a flag came back, so an agent configured as
`{"cli": "claude --model haiku", "model": "opus"}` ran Haiku while the spec,
worker listings, spawn events and telemetry all said Opus.

The pin also drives `relay_skill_prefix`, chosen in the HTTP spawn path
before the worker starts, so the wrong model there omits the small-model
relay guidance (or adds it to a large model). Repairing the metadata inside
worker startup would be after that decision, so the effective model is now
resolved before it.

`model_override_from_args` reads `--model value`, `--model=value`,
`-m value` and `-m=value`, last occurrence winning as argv does. A bare
`--model` names nothing and yields None while still suppressing injection,
so the harness is never handed two model flags.

Addresses the review finding on PR #1845.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…are-garden-3027e1e7

# Conflicts:
#	CHANGELOG.md
@khaliqgant

Copy link
Copy Markdown
Member

Fixed in 78e219c, and rebased onto main.

The finding was right, including the part about ordering. resolve_model_flag_for_cli returns None when argv already carries an override — correctly, so no second flag is injected — but all three callers wrote spec.model only when a flag came back. For {"cli": "claude --model haiku", "model": "opus"} the harness ran Haiku while the spec, worker listings, spawn events and telemetry all said Opus.

And repairing it inside worker startup would indeed have been too late: relay_skill_prefix is chosen in the HTTP spawn path at runtime/api.rs before the worker starts, so the effective model is now resolved before that decision rather than after it. In the example the small-model relay guidance was omitted; reversed, an Opus agent would have been handed small-model guidance it does not need.

model_override_from_args reads all four forms — --model value, --model=value, -m value, -m=value — with the last occurrence winning, as argv does. A bare trailing --model names nothing, so it yields None while args_include_model_override still returns true: injection stays suppressed and the harness is never handed two model flags.

On the note that the existing Rust test passes despite the mismatch — correct, it only asserted suppression. The new tests assert the effective value, and each one is mutation-proved. Making the extractor blind to the space-separated form fails exactly one test, and the failure names the form:

test worker::tests::model_override_from_args_reads_every_supported_form ... FAILED
assertion `left == right` failed: ["--model", "haiku"]
test result: FAILED. 3 passed; 1 failed

Validation, with cargo available this time:

  • cargo test -p agent-relay-broker --lib1304 passed, 0 failed, 5 ignored
  • cargo clippy -p agent-relay-broker --lib --all-targets — clean
  • cargo fmt applied

The merge from main conflicted only in CHANGELOG.md, where main had moved to [Unreleased - Major] (#1842's SDK removal) while this branch was Minor. Resolved as the union at the higher level per the monotonic rule, with the shared manual_flush bullet deduped.

@khaliqgant
khaliqgant marked this pull request as ready for review September 23, 2026 01:21
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 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-23T01:27:20.491145Z 7c74ae4 Draft marked ready
ℹ️ 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.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7c74ae4. Configure here.

);
spec.model = Some(inline.to_string());
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Relaycast skips inline model reconciliation

Medium Severity

Inline --model/-m is reconciled onto spec.model only on the HTTP spawn path before relay_skill_prefix. The Relaycast path still prefixes from the pin, so a superseded pin can inject or omit small-model guidance the running harness never sees.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7c74ae4. Configure here.

@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: 7c74ae4021

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

Comment on lines +542 to +545
if let Some(inline) = crate::worker::model_override_from_args(&{
let command = spec.cli.as_deref().unwrap_or(&cli);
let mut tokens = shlex::split(command).unwrap_or_default();
tokens.extend(spec.args.iter().cloned());

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 Resolve inline models on Relaycast spawns

Apply this effective-model resolution to the fleet/Relaycast action.invoke path as well. spawn_worker_from_request in runtime/relaycast_events.rs calls relay_skill_prefix using the pinned spec.model before WorkerRegistry::spawn; the correction at worker.rs:1110 therefore happens too late. For example, a Relaycast spawn with cli: "claude --model haiku" and model: "opus" runs Haiku but omits the small-model guidance because the prefix was selected as Opus, undermining the reliability guarantee documented for that guidance.

Useful? React with 👍 / 👎.

@khaliqgant
khaliqgant merged commit d98e00a into main Sep 23, 2026
146 of 151 checks passed
@khaliqgant
khaliqgant deleted the relayflow/relay-software-garden-3027e1e7 branch September 23, 2026 01:59
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.

teams.json cannot pin a per-agent model; the documented claude:opus syntax is dead

2 participants