Skip to content
Merged
6 changes: 3 additions & 3 deletions .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5563,12 +5563,12 @@ jobs:
}

emit_known_missing_string_finding \
"STRIX_MODEL: contextual-orchestrator/orchestrator/free" \
"Strix PR scans must use contextual-orchestrator/orchestrator/free" \
"STRIX_MODEL: contextual-orchestrator/orchestrator/auto" \
"Strix PR scans must use contextual-orchestrator/orchestrator/auto" \
".github/workflows/strix.yml" \
"scripts/ci/strix_required_workflow_smoke.sh"
emit_known_missing_string_finding \
"Strix model overrides are limited to contextual-orchestrator/orchestrator/free." \
"Strix model overrides are limited to contextual-orchestrator/orchestrator/auto." \
"Strix model overrides must remain gateway-only" \
".github/workflows/strix.yml" \
"scripts/ci/strix_required_workflow_smoke.sh"
Expand Down
15 changes: 8 additions & 7 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -549,14 +549,14 @@ jobs:
- name: Gate Strix secrets
id: gate
env:
STRIX_MODEL: contextual-orchestrator/orchestrator/free
STRIX_MODEL: contextual-orchestrator/orchestrator/auto
STRIX_MODEL_REQUESTED: ${{ github.event.client_payload.strix_llm || '' }}
run: |
requested_model="$(printf '%s' "$STRIX_MODEL_REQUESTED" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
case "$requested_model" in
""|orchestrator/free|contextual-orchestrator/orchestrator/free) ;;
""|orchestrator/auto|contextual-orchestrator/orchestrator/auto) ;;
*)
echo '::error::Strix model overrides are limited to contextual-orchestrator/orchestrator/free.'
echo '::error::Strix model overrides are limited to contextual-orchestrator/orchestrator/auto.'
Comment thread
seonghobae marked this conversation as resolved.
exit 1
;;
esac
Expand All @@ -574,6 +574,7 @@ jobs:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CONTEXTUAL_ORCHESTRATOR_REQUIRE_ZDR: ${{ steps.target_visibility.outputs.is_private }}
CONTEXTUAL_ORCHESTRATOR_POOL: auto
Comment thread
seonghobae marked this conversation as resolved.
run: |
set -euo pipefail
bash "$TRUSTED_STRIX_SOURCE/scripts/ci/contextual_orchestrator_review_sidecar.sh"
Expand Down Expand Up @@ -712,11 +713,11 @@ jobs:
strix_llm_file="$RUNNER_TEMP/strix_llm.txt"
strix_model="$(printf '%s' "$STRIX_MODEL" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
case "$strix_model" in
orchestrator/free | contextual-orchestrator/orchestrator/free)
printf '%s' 'orchestrator/free' > "$strix_llm_file"
orchestrator/auto | contextual-orchestrator/orchestrator/auto)
printf '%s' 'orchestrator/auto' > "$strix_llm_file"
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
;;
*)
echo '::error::STRIX_LLM must select contextual-orchestrator/orchestrator/free.'
echo '::error::STRIX_LLM must select contextual-orchestrator/orchestrator/auto.'
exit 1
;;
esac
Expand All @@ -739,7 +740,7 @@ jobs:
LLM_API_KEY_FILE: ${{ env.LLM_API_KEY_FILE }}
STRIX_TARGET_PATH: ${{ (github.event_name == 'pull_request_target' || github.event.client_payload.pr_number != '') && '__PR_SCOPE__' || './' }}
STRIX_SOURCE_DIRS: ". backend frontend"
# The gateway free pool is capability-diverse. Strix function tools
# The gateway auto pool is provider-diverse. Strix function tools
# must not send a provider-specific reasoning setting to every route.
STRIX_REASONING_EFFORT: none
STRIX_LLM_MAX_RETRIES: 1
Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ provider secrets (`BYTEZ_API_KEY`, `NVIDIA_NIM_API_KEY`,
as bootstrap transport in the same process that discovers models and serves;
OpenCode and Noema use the fail-closed zero-cost pool `orchestrator/free`.
Authoritative Strix analysis uses the correctness-first `orchestrator/auto`
pool. Private targets require ZDR-compliant routes in either pool, enforced by
provider-diverse pool. Non-free Strix routes
are admitted only with complete published prompt/completion price and currency
evidence, and private targets still require ZDR-compliant routes under
[`scripts/ci/zdr_policy.py`](scripts/ci/zdr_policy.py).
See [`docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md`](docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md).

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- Route required Strix scans through the contextual-orchestrator
`orchestrator/auto` pool so the five configured provider credentials form
real cross-provider failover. Priced routes require finite, nonnegative
published prompt/completion prices and an explicit currency; unknown pricing
fails closed. Private-target ZDR enforcement and the no-external-fallback
contract remain unchanged.
- Allow the protected Strix required-workflow smoke to recognize only the
existing `orchestrator/free` route or the provider-diverse
`orchestrator/auto` route. This provides a fail-closed two-phase migration
Expand Down
26 changes: 17 additions & 9 deletions docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ all five, and auto-optimize routing by cost.
transport only; request-time credential reads go through the KV.
2. **Auto model discovery + governed virtual pools**: discovery runs with the
orchestrator's own `discover_all_models()` against the KV credentials.
OpenCode and Noema admit only zero-priced routes. The gateway's
OpenCode and Noema admit only zero-priced routes. Strix admits provider-
diverse priced routes only when discovery supplies finite, nonnegative
prompt and completion prices plus an explicit currency; missing or malformed
price evidence fails closed. The gateway's
`orchestrator/free` virtual id fails closed (`400 invalid_model`) unless an
enabled zero-cost agent exists. Strix uses `orchestrator/auto`; its catalog
may admit priced routes only through the same evidence-bearing policy, never
Expand All @@ -48,8 +51,8 @@ all five, and auto-optimize routing by cost.
fetched when egress allows it and is authoritative for the `openrouter`
scope; otherwise the dated static attestation table is used, never a
fabricated policy.
`scripts/ci/contextual_orchestrator_review_policy.py` turns the free-tier
discovery report into a ZDR-prioritized, provider-family-diverse agents
`scripts/ci/contextual_orchestrator_review_policy.py` turns the price-
evidenced discovery report into a ZDR-prioritized, provider-family-diverse agents
catalog (primary/secondary NVIDIA keys share one outage-domain family),
capped in size, in the orchestrator's own `ModelAgent` schema.
4. **Wiring**: `pr-review-autofix.yml` and the Required OpenCode dispatch
Expand All @@ -59,9 +62,13 @@ all five, and auto-optimize routing by cost.
`opencode.jsonc` default `model`/`small_model` is the same gateway route.
`noema-review.yml` retains `orchestrator/free`. `strix.yml` provisions the
same sidecar and uses the loopback chat-completions/API-compatible URL with
virtual model `orchestrator/auto`, delegating discovery, provider diversity,
and provider cost evidence to the gateway rather than embedding a second
routing policy in GitHub Actions. Strix has no external fallback and private targets pass
`orchestrator/auto`: the 2026-08-29 exact-head DiskSage scan proved that four
discovered free routes all shared the OpenRouter outage domain, which the
gateway correctly collapsed to one provider attempt. Strix therefore uses
the provider-diverse pool supplied by all five configured credentials.
Provider diversity and provider cost evidence remain delegated to the
gateway rather than embedding a second routing policy in GitHub Actions.
Strix has no external fallback and private targets pass
visibility through to the gateway's ZDR requirement. Noema reviewer identity
remains `NOEMA_REVIEW_TOKEN` / GitHub App / OIDC and is still never
`github.token`; Autofix mutation still requires `PR_REVIEW_MERGE_TOKEN` /
Expand All @@ -83,10 +90,11 @@ all five, and auto-optimize routing by cost.

- The autofix/OpenCode review paths no longer hard-code any provider base URL
or model id; upstream model selection is delegated to the orchestrator's
discovery + cost routing, under the zero-cost pool, with ZDR routes first.
discovery under the zero-cost pool. Strix uses the separately governed auto
pool without treating absent price metadata as paid-route evidence.
- Strix delegates selection to `orchestrator/auto`. Its correctness-first pool
is distinct from the zero-cost OpenCode/Noema pool, while private-target ZDR
admission remains fail-closed.
remains distinct from the zero-cost OpenCode/Noema pool, while private-target
ZDR admission remains fail-closed.
- Workers need egress to the five provider model-list hosts and, when reachable,
`https://openrouter.ai/api/v1/endpoints/zdr`; the feed failure path is
graceful (static table).
Expand Down
45 changes: 34 additions & 11 deletions scripts/ci/contextual_orchestrator_review_launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,19 @@ def _has_text_output(model: object) -> bool:
return not modalities or "text" in {str(modality).casefold() for modality in modalities}


def _free_report_rows(discovered: list[object]) -> list[dict[str, object]]:
"""Convert in-process discovered models into free-only report rows.
def _route_identity(model: object) -> tuple[str, str]:
"""Return the provider/model identity used to bind price evidence."""

return (
str(getattr(model, "provider_name", None) or ""),
str(getattr(model, "model_id", None) or ""),
)


def _report_rows(
discovered: list[object], free_route_identities: frozenset[tuple[str, str]]
) -> list[dict[str, object]]:
"""Convert in-process discovered models into price-evidenced report rows.

Only routes the orchestrator itself marks zero-priced (whole-prompt and
whole-completion published price equal to zero; never name-implied) are
Expand All @@ -73,10 +84,11 @@ def _free_report_rows(discovered: list[object]) -> list[dict[str, object]]:
org ZDR policy table (``scripts/ci/zdr_policy.py``).

Args:
discovered: ``discover_all_models()`` result (the free subset).
discovered: Selected ``discover_all_models()`` result.
free_route_identities: Routes the orchestrator attested as zero-priced.

Returns:
Free-only rows shaped for
Price-evidenced rows shaped for
``contextual_orchestrator_review_policy.parse_discovery_report``.
"""
from scripts.ci import zdr_policy
Expand All @@ -99,7 +111,10 @@ def _free_report_rows(discovered: list[object]) -> list[dict[str, object]]:
"provider": provider,
"model": model_id,
"agent_id": str(getattr(model, "agent_id", None) or f"{provider}_{model_id}"),
"is_free": True,
"is_free": (provider, model_id) in free_route_identities,
"prompt_price_per_1k": getattr(model, "prompt_price_per_1k", None),
"completion_price_per_1k": getattr(model, "completion_price_per_1k", None),
"currency_code": getattr(model, "currency_code", None),
"base_url": base_url,
"credential_key": credential_key,
"auth_scheme": auth_scheme,
Expand Down Expand Up @@ -241,6 +256,7 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument("--preflight-out", required=True, help="Path to write sanitized runtime preflight JSON")
parser.add_argument("--zdr-endpoints", default=None, help="Optional OpenRouter /api/v1/endpoints/zdr JSON path")
parser.add_argument("--require-zdr", action="store_true")
parser.add_argument("--pool", choices=("free", "auto"), default="free")
args = parser.parse_args(argv)

from contextual_orchestrator.credentials import get_credential
Expand Down Expand Up @@ -272,16 +288,22 @@ def main(argv: list[str] | None = None) -> int:
discovered, _ = discover_all_models()
except Exception as exc: # pragma: no cover - provider/networking failure is runtime-only
raise SystemExit(f"review sidecar discovery failed: {exc}") from exc
free_models = []
for model in free_discovered_models(discovered) if discovered else []:
free_models = list(free_discovered_models(discovered)) if discovered else []
free_route_identities = frozenset(_route_identity(model) for model in free_models)
selected_models = []
for model in discovered or []:
model_id = getattr(model, "model_id", "")
if not is_general_chat_agent_model_id(model_id) or not _has_text_output(model):
continue
free_models.append(model)
if not free_models:
raise SystemExit("review sidecar discovered no zero-cost models; orchestrator/free would fail closed")
if args.pool == "free" and _route_identity(model) not in free_route_identities:
continue
selected_models.append(model)
if not selected_models:
raise SystemExit(
f"review sidecar discovered no eligible models; orchestrator/{args.pool} would fail closed"
)

rows = _free_report_rows(free_models)
rows = _report_rows(selected_models, free_route_identities)
_write_json(args.discovery_out, {"models": rows})
zdr_endpoints = _load_zdr_endpoints(args.zdr_endpoints)
result = build_zdr_prioritized_catalog(
Expand All @@ -290,6 +312,7 @@ def main(argv: list[str] | None = None) -> int:
family_cap=int(os.environ.get("ORCHESTRATOR_CATALOG_FAMILY_CAP", "4")),
zdr_endpoints=zdr_endpoints,
require_zdr=args.require_zdr,
pool=args.pool,
)
Path(args.catalog_out).write_text(
json.dumps({"agents": result["agents"]}, indent=2, sort_keys=True) + "\n",
Expand Down
Loading
Loading