Summary
Let each orchestrator instance define its own LLM model. Today the model is a single global default applied to every Agent's orchestrator. With the pluggable LLM provider work merged (#289, #292), the next step is making the model a per-instance choice so one Agent can run on a different model (or provider) than another.
Current behaviour
The orchestrator is the @omadia/n plugin (middleware/packages/harness-orchestrator), and it is instantiated per Agent (per-Agent plugin-scoped registration). The model is resolved in plugin.ts:
const model =
(ctx.config.get<string>('orchestrator_model') ?? '').trim() ||
DEFAULT_ORCHESTRATOR_MODEL;
ctx.config is plugin-scoped, so the storage layer can already hold a distinct value per Agent. In practice every instance resolves to the same value: ORCHESTRATOR_MODEL (default claude-opus-4-8, middleware/src/config.ts) is seeded into the orchestrator plugin config once at first boot, and there is no per-Agent control to override it afterwards. Per-turn routing (orchestrator_model_routing + model_routing_{classifier,simple,complex}_model) exists, but it is also a global toggle and assumes the Claude family.
So the limitation is not the config plumbing. It is that the model is set globally and not surfaced as a per-instance setting.
Motivation
- Cost and latency differ per Agent. A high-volume support Agent can run on a cheaper model while a reasoning-heavy Agent stays on the top model, instead of forcing one global choice.
- The provider abstraction only pays off when instances can actually pick different models. Right now every Agent is locked to one model regardless of how many providers are registered.
- It removes a deploy-time decision (
ORCHESTRATOR_MODEL env) from something that should be an operator runtime choice per Agent.
Proposed change
- Surface
orchestrator_model as a per-Agent setting in the Admin UI (Agent configuration), reading from and writing to the per-Agent orchestrator plugin config that already backs it.
- Resolution order per instance: per-Agent setting, then the global seeded default (
ORCHESTRATOR_MODEL), then DEFAULT_ORCHESTRATOR_MODEL. Keep ORCHESTRATOR_MODEL as the fallback so existing deployments are unchanged.
- Populate the model picker from the
@omadia/llm-provider registry (the same source the builder model registry already uses), so only registered models are selectable and the list stays correct as providers are added.
- Make per-turn routing config (
model_routing_*) resolvable per instance as well, so an Agent that opts into routing can pin its own simple/complex models. Cross-provider per-turn routing stays out of scope (the model router still assumes one family per modelRouting block).
- Hot-apply on config change the same way the orchestrator already re-registers per Agent, so changing an Agent's model does not need a restart.
Out of scope
- Cross-provider per-turn routing inside a single
modelRouting block (the classifier and fallbacks assume one model family). Tracked separately if needed.
- Sub-agent model selection (
SUB_AGENT_MODEL) and the Agent Builder model choice (separate issue).
Acceptance criteria
- An operator can set a different orchestrator model per Agent in the Admin UI and the change takes effect without a restart.
- Agents with no per-Agent model set keep running on
ORCHESTRATOR_MODEL; behaviour for existing deployments is unchanged.
- The model picker lists only models registered in
@omadia/llm-provider.
- The resolved model per instance is visible in logs or Admin so an operator can confirm which model an Agent is on.
References
Summary
Let each orchestrator instance define its own LLM model. Today the model is a single global default applied to every Agent's orchestrator. With the pluggable LLM provider work merged (#289, #292), the next step is making the model a per-instance choice so one Agent can run on a different model (or provider) than another.
Current behaviour
The orchestrator is the
@omadia/nplugin (middleware/packages/harness-orchestrator), and it is instantiated per Agent (per-Agent plugin-scoped registration). The model is resolved inplugin.ts:ctx.configis plugin-scoped, so the storage layer can already hold a distinct value per Agent. In practice every instance resolves to the same value:ORCHESTRATOR_MODEL(defaultclaude-opus-4-8,middleware/src/config.ts) is seeded into the orchestrator plugin config once at first boot, and there is no per-Agent control to override it afterwards. Per-turn routing (orchestrator_model_routing+model_routing_{classifier,simple,complex}_model) exists, but it is also a global toggle and assumes the Claude family.So the limitation is not the config plumbing. It is that the model is set globally and not surfaced as a per-instance setting.
Motivation
ORCHESTRATOR_MODELenv) from something that should be an operator runtime choice per Agent.Proposed change
orchestrator_modelas a per-Agent setting in the Admin UI (Agent configuration), reading from and writing to the per-Agent orchestrator plugin config that already backs it.ORCHESTRATOR_MODEL), thenDEFAULT_ORCHESTRATOR_MODEL. KeepORCHESTRATOR_MODELas the fallback so existing deployments are unchanged.@omadia/llm-providerregistry (the same source the builder model registry already uses), so only registered models are selectable and the list stays correct as providers are added.model_routing_*) resolvable per instance as well, so an Agent that opts into routing can pin its own simple/complex models. Cross-provider per-turn routing stays out of scope (the model router still assumes one family permodelRoutingblock).Out of scope
modelRoutingblock (the classifier and fallbacks assume one model family). Tracked separately if needed.SUB_AGENT_MODEL) and the Agent Builder model choice (separate issue).Acceptance criteria
ORCHESTRATOR_MODEL; behaviour for existing deployments is unchanged.@omadia/llm-provider.References
middleware/packages/harness-orchestrator/src/plugin.tsmiddleware/src/config.ts(ORCHESTRATOR_MODEL)middleware/src/plugins/builder/modelRegistry.ts(slugs resolved through@omadia/llm-provider)