Role models¶
FrameworkConfig.role_models is a per-role ModelConfig override map.
Adopters who want to route different call sites in the framework to
different model SKUs (e.g. a cheap Haiku for summarisation, a
high-reasoning Sonnet for the brain, a tiny model for the critic)
set entries on this map.
Shape¶
from symfonic.agent.config import FrameworkConfig
from symfonic.core.config import AgentConfig, ModelConfig
fw = FrameworkConfig(
agent=AgentConfig(
model=ModelConfig(model_name="claude-sonnet-4-5"), # brain default
),
role_models={
"summary": ModelConfig(model_name="claude-haiku-4-5"),
"critic": ModelConfig(model_name="claude-haiku-4-5"),
},
)
Unset roles fall back to AgentConfig.model.
Role taxonomy¶
| Role | Site | Wired in |
|---|---|---|
action |
react node brain LLM (engine.py:1144-1158) |
v7.9.0 |
summary |
ContextWindowSummariser (core/nodes/context_window.py) |
v7.24.2 |
critic |
MetacognitiveCriticMiddleware (agent/middleware/metacognitive.py) |
v7.24.2 |
reflection |
self-reflection (engine.py:7233) |
v7.14.1 |
router |
CapabilityRouter (memory/layers/procedural/router.py) |
v7.9.1+ (backlog) |
consolidation |
Phase 12 promotion (core/learning/consolidation.py) |
v7.9.1+ (backlog) |
v7.24.2 update¶
role_models['summary'] and role_models['critic'] were documented at
agent/config.py:1050-1053 since v7.9.0 but the consuming call sites
never read them. Adopters who set the override silently got the
framework default Sonnet on both call sites.
v7.24.2 closes both gaps:
- summary --
create_context_window_nodegrows arole_modelskwarg;core/presets.pythreads it fromAgentConfig.role_models;engine.pypropagates the fullFrameworkConfig.role_modelsmap ontoAgentConfig.role_models(alongside the existing'action'propagation). The summariser usesrole_models['summary']as-is when set, otherwise builds the pre-v7.24.2 default fromCompactionConfig.compaction_model. - critic --
MetacognitiveCriticMiddlewareconsultsself._config.role_models.get('critic')before falling back to the bareModelConfig()default. No threading needed -- the middleware already held aFrameworkConfigreference viaself._config.
The default path (no override set) is byte-identical pre-vs-post
v7.24.2: adopters who never touched role_models see no change.
Resolver helper¶
The canonical resolver is SymfonicAgent.resolve_model_config(role)
(engine.py:2578). It returns the override when set, or
AgentConfig.model otherwise. Call sites that don't have a
SymfonicAgent handle (e.g. middleware constructed before the agent
is fully assembled) can replicate the same shape by reading
FrameworkConfig.role_models.get(role) directly.
Adopter pattern: cost vs quality¶
fw = FrameworkConfig(
agent=AgentConfig(
model=ModelConfig(model_name="claude-sonnet-4-5"), # brain
),
role_models={
# Summarisation is bulk-text -- cheap Haiku wins on cost.
"summary": ModelConfig(model_name="claude-haiku-4-5"),
# Critic is short-form -- cheap Haiku also wins on cost, but
# adopters who saw the critic miss subtle CorePreference
# violations should pin Sonnet here.
"critic": ModelConfig(model_name="claude-haiku-4-5"),
# Reflection sees the same prompt the brain saw -- usually
# the same SKU. Override only if you want a "second opinion"
# from a different model class.
"reflection": ModelConfig(model_name="claude-sonnet-4-5"),
},
)
The framework owns the orchestration; the adopter owns the model
assignment. This is the v7.8.3-era architectural principle that
role_models operationalises.