Problem
There's no way today to override a workflow's provider/model for a single invocation. If a workflow YAML declares provider: claude, model: sonnet, every run uses Sonnet — even when the user wants to:
- run cheaper on Pi/Minimax for a one-off cost experiment,
- run on Opus instead of Sonnet to compare quality on a hard PR,
- fall back to Codex when Claude is rate-limited,
- isolate a model-specific bug.
Today's escape hatch is editing the YAML before each run and reverting after. That doesn't survive parallel runs and clutters git status.
Proposed primitives
CLI
archon workflow run <name> [--provider <p>] [--model <m>] "<args>"
--provider <p> alone — uses that provider's default model (e.g. --provider claude → sonnet per .archon/config.yaml).
--model <m> alone — interpreted under the resolved provider (e.g. --model haiku on a Claude workflow). Loader rejects incompatible combinations (haiku on a Codex workflow).
- Both — full explicit override.
- Neither — current behavior (no behavior change for existing users).
HTTP API parity
POST /api/workflows/{name}/runs
{
...,
"provider": "pi",
"model": "minimax/MiniMax-M2.7"
}
Same semantics as the CLI flags. Keeps Web UI parity easy to add later.
Resolution pipeline
CLI flag (or HTTP body field) set?
YES → that value applies to every AI node in the run, period.
Workflow-level AND per-node provider/model in YAML are IGNORED.
Provider-specific fields that don't apply on the new provider
(effort/thinking/betas/sandbox/hooks/mcp/skills on Pi, etc.)
emit the existing loader warnings — same behavior as today.
NO → existing pipeline:
per-node YAML > workflow YAML > .archon/config.yaml > SDK default
That's it. One pipeline, one priority order. No --force- flavor. The flag name (--provider, --model) already conveys force-semantics; a second flavor would introduce a bikeshed without solving a real problem.
The deliberate tradeoff
Per-node provider: / model: fields in YAML are ignored when the CLI/API override is set. Workflow authors lose the ability to force a specific model for a specific node against an explicit override.
Cost: can't express "this node MUST be Opus" via YAML and still respect a user-issued override.
Benefit: --provider pi actually does what it says on the tin. The runtime user issued an explicit override; honoring it is the principle of least surprise.
If a real use case for "lock a node against override" emerges, it can be added later as a single schema field (provider_locked: true on a node) and one priority-rule branch. Keeping it out of v1 to avoid premature optimization.
Validation
Single reusable function in the workflow loader:
validateProviderModelCombo(provider: string, model: string, workflow: WorkflowDefinition): ValidationResult
Called after CLI/API overrides are merged into the effective config. Surfaces the same errors users see today on workflow load:
- Claude models:
sonnet | opus | haiku | claude-* | inherit
- Codex models: any non-Claude-alias ID
- Pi models:
<vendor>/<model> format (e.g. anthropic/claude-haiku-4-5, openrouter/qwen/qwen3-coder, minimax/MiniMax-M2.7)
Compatibility with SDK-specific options (e.g. betas: ['context-1m-2025-08-07'] required for opus[1m]) follows the existing loader rules — CLI override of a model that requires a beta the workflow doesn't declare is a hard error at run start, not at runtime.
Logging
Extend the existing workflow_provider_resolved event:
{
"msg": "workflow_provider_resolved",
"workflowName": "...",
"provider": "pi",
"model": "minimax/MiniMax-M2.7",
- "providerSource": "workflow definition",
+ "providerSource": "cli" | "http" | "workflow" | "config" | "sdk",
...
}
Same shape; just one more enum value. Lets users answer "why did this run resolve to that model?" from logs alone.
Edge cases
| Case |
Behavior |
Workflow has worktree.enabled: false and CLI passes --branch |
Existing error; unchanged |
Workflow has per-node effort:/thinking: (Claude-only) and CLI overrides to --provider pi |
Loader emits the existing "field ignored on this provider" warnings; run proceeds |
--model opus[1m] and the workflow declares no betas: |
Hard error at run start (existing rule, just applied to CLI-resolved values) |
.archon/config.yaml has assistants.claude.model: opus and CLI passes --provider codex --model gpt-5.3 |
CLI wins. Config never reads. |
CLI passes only --provider codex for a workflow that hardcodes Claude features (betas, sandbox) |
Loader warns the Claude-specific fields are ignored on Codex; run proceeds |
Implementation sketch
Rough scope, ~150-200 LOC + tests:
packages/cli/src/cli.ts (workflow-run command) — add --provider / --model flag definitions; pass through to executor invocation.
packages/workflows/src/executor.ts — accept runtimeOverrides: { provider?: string, model?: string }; apply during resolution before workflow-level defaults; log new providerSource.
packages/workflows/src/loader.ts — extract the existing provider/model validator into a reusable function (likely already mostly factored out); call it on the merged config.
packages/server/src/routes/api.ts — accept provider / model in the POST runs body; pass through to the same executor entry point as CLI.
- Tests:
- unit on the resolver: each priority level + override combinations
- CLI integration:
archon workflow run --provider pi --model minimax/MiniMax-M2.7 e2e-claude-smoke "" and assert the smoke ran on Pi via session log (matches the pattern in e2e-minimax-smoke.yaml).
- HTTP integration: POST with override body fields; verify resolution.
Open question
Should CLI/HTTP override beat .archon/config.yaml's assistants.* block too? My read: yes, same principle (runtime intent > config). The pipeline above reflects this. Calling it out so it's explicit before someone implements.
Out of scope (future)
provider_locked: true per-node opt-out.
- Environment-variable equivalents (
ARCHON_PROVIDER_OVERRIDE=...) for shell scripting.
- Web UI run-page picker for provider/model.
Each is a small, independent follow-up if real use cases show up.
Related
- The
e2e-minimax-smoke workflow already exercises the resolution pipeline end-to-end, so a CLI override test can lean on the same session-log assertion pattern.
Problem
There's no way today to override a workflow's provider/model for a single invocation. If a workflow YAML declares
provider: claude, model: sonnet, every run uses Sonnet — even when the user wants to:Today's escape hatch is editing the YAML before each run and reverting after. That doesn't survive parallel runs and clutters git status.
Proposed primitives
CLI
--provider <p>alone — uses that provider's default model (e.g.--provider claude→sonnetper.archon/config.yaml).--model <m>alone — interpreted under the resolved provider (e.g.--model haikuon a Claude workflow). Loader rejects incompatible combinations (haikuon a Codex workflow).HTTP API parity
Same semantics as the CLI flags. Keeps Web UI parity easy to add later.
Resolution pipeline
That's it. One pipeline, one priority order. No
--force-flavor. The flag name (--provider,--model) already conveys force-semantics; a second flavor would introduce a bikeshed without solving a real problem.The deliberate tradeoff
Per-node
provider:/model:fields in YAML are ignored when the CLI/API override is set. Workflow authors lose the ability to force a specific model for a specific node against an explicit override.Cost: can't express "this node MUST be Opus" via YAML and still respect a user-issued override.
Benefit:
--provider piactually does what it says on the tin. The runtime user issued an explicit override; honoring it is the principle of least surprise.If a real use case for "lock a node against override" emerges, it can be added later as a single schema field (
provider_locked: trueon a node) and one priority-rule branch. Keeping it out of v1 to avoid premature optimization.Validation
Single reusable function in the workflow loader:
Called after CLI/API overrides are merged into the effective config. Surfaces the same errors users see today on workflow load:
sonnet|opus|haiku|claude-*|inherit<vendor>/<model>format (e.g.anthropic/claude-haiku-4-5,openrouter/qwen/qwen3-coder,minimax/MiniMax-M2.7)Compatibility with SDK-specific options (e.g.
betas: ['context-1m-2025-08-07']required foropus[1m]) follows the existing loader rules — CLI override of a model that requires a beta the workflow doesn't declare is a hard error at run start, not at runtime.Logging
Extend the existing
workflow_provider_resolvedevent:{ "msg": "workflow_provider_resolved", "workflowName": "...", "provider": "pi", "model": "minimax/MiniMax-M2.7", - "providerSource": "workflow definition", + "providerSource": "cli" | "http" | "workflow" | "config" | "sdk", ... }Same shape; just one more enum value. Lets users answer "why did this run resolve to that model?" from logs alone.
Edge cases
worktree.enabled: falseand CLI passes--brancheffort:/thinking:(Claude-only) and CLI overrides to--provider pi--model opus[1m]and the workflow declares nobetas:.archon/config.yamlhasassistants.claude.model: opusand CLI passes--provider codex --model gpt-5.3--provider codexfor a workflow that hardcodes Claude features (betas,sandbox)Implementation sketch
Rough scope, ~150-200 LOC + tests:
packages/cli/src/cli.ts(workflow-run command) — add--provider/--modelflag definitions; pass through to executor invocation.packages/workflows/src/executor.ts— acceptruntimeOverrides: { provider?: string, model?: string }; apply during resolution before workflow-level defaults; log newproviderSource.packages/workflows/src/loader.ts— extract the existing provider/model validator into a reusable function (likely already mostly factored out); call it on the merged config.packages/server/src/routes/api.ts— acceptprovider/modelin the POST runs body; pass through to the same executor entry point as CLI.archon workflow run --provider pi --model minimax/MiniMax-M2.7 e2e-claude-smoke ""and assert the smoke ran on Pi via session log (matches the pattern ine2e-minimax-smoke.yaml).Open question
Should CLI/HTTP override beat
.archon/config.yaml'sassistants.*block too? My read: yes, same principle (runtime intent > config). The pipeline above reflects this. Calling it out so it's explicit before someone implements.Out of scope (future)
provider_locked: trueper-node opt-out.ARCHON_PROVIDER_OVERRIDE=...) for shell scripting.Each is a small, independent follow-up if real use cases show up.
Related
e2e-minimax-smokeworkflow already exercises the resolution pipeline end-to-end, so a CLI override test can lean on the same session-log assertion pattern.