Observation
config.py:64 declares self.smolagent_model_id: str and assigns it a list of four model identifiers. The annotation and the value disagree, and every consumer has had to learn that the field may be either.
The consequence is a coercion repeated at roughly eight sinks: orchestrator.py:287, workflow_factory.py:322, single_agent_factory.py:56, precheck.py:372 and :421, config.py:203 and :219, pricing.py:275, evaluation_cli.py:676. Each independently rediscovers x[0] if isinstance(x, list) else x. Commit 91ef227 ("handle list smolagent_model_id at single-model sinks") fixed one instance of the pattern after it caused a pricing crash.
Why it matters
This is not currently a live bug. I checked the sinks: every functional one is guarded, and the only unguarded ones are display-only (onboard_cli.py:1113 passes the list where a string is expected; evaluation_cli.py:231 compares a string against the list, so no default is ever highlighted). Both are cosmetic.
The cost is that the invariant is duplicated rather than established. Each new sink must remember the coercion, and the one that forgets is a crash — which is exactly how 91ef227 came to be written. A widened type should be normalised once, not defended eight times.
Pathway
- Decide the field's real type. The natural one is
list[str]: a candidate list with the first entry as the default. Rename to make that explicit if it helps (smolagent_model_ids), or keep the name and fix the annotation.
- Normalise at load: in
Config.load/__init__, coerce a bare string to a one-element list once, so the rest of the codebase sees a single type.
- Expose the default explicitly — a small
default_smolagent_model property returning self.smolagent_model_id[0] — and replace the eight ad-hoc coercions with it.
- Fix the two display sinks while removing their neighbours.
The change is mechanical and can ship in one commit, since step 2 makes steps 3 and 4 pure deletions.
Observation
config.py:64declaresself.smolagent_model_id: strand assigns it a list of four model identifiers. The annotation and the value disagree, and every consumer has had to learn that the field may be either.The consequence is a coercion repeated at roughly eight sinks:
orchestrator.py:287,workflow_factory.py:322,single_agent_factory.py:56,precheck.py:372and:421,config.py:203and:219,pricing.py:275,evaluation_cli.py:676. Each independently rediscoversx[0] if isinstance(x, list) else x. Commit 91ef227 ("handle list smolagent_model_id at single-model sinks") fixed one instance of the pattern after it caused a pricing crash.Why it matters
This is not currently a live bug. I checked the sinks: every functional one is guarded, and the only unguarded ones are display-only (
onboard_cli.py:1113passes the list where a string is expected;evaluation_cli.py:231compares a string against the list, so no default is ever highlighted). Both are cosmetic.The cost is that the invariant is duplicated rather than established. Each new sink must remember the coercion, and the one that forgets is a crash — which is exactly how 91ef227 came to be written. A widened type should be normalised once, not defended eight times.
Pathway
list[str]: a candidate list with the first entry as the default. Rename to make that explicit if it helps (smolagent_model_ids), or keep the name and fix the annotation.Config.load/__init__, coerce a bare string to a one-element list once, so the rest of the codebase sees a single type.default_smolagent_modelproperty returningself.smolagent_model_id[0]— and replace the eight ad-hoc coercions with it.The change is mechanical and can ship in one commit, since step 2 makes steps 3 and 4 pure deletions.