Skip to content

ModelResolveRequest carries no model pin, so a definition's exact model id cannot reach the resolver #89

Description

@senamakel

What is missing

AgentDefinition has a model: Option<String> documented as "preferred model id, when the agent pins one". ModelResolveRequest has three fields — agent_id, role, is_team_lead — and no model field.

So there is no defined route for a definition's exact pin (claude-3-5-sonnet, a BYOK id, a local model name) to reach ModelResolver::resolve. The two seams are adjacent and never connected.

Why it matters now rather than later

Nothing in the crate constructs a ModelResolveRequest today, so nothing is currently broken. The risk is what a wiring author does when they need a pin honoured: role is the only string field available, so the natural move is to put the model id there.

That misroutes, and quietly. A host resolver reasonably treats role as a workload-role vocabulary, so an arbitrary model id becomes an unrecognised role and falls back to a default. In OpenHuman's adapter the id would additionally have been mistaken for a tier if it ended in -v1 (role_for_model_tier answers "chat" for anything it does not recognise, with no diagnostic). I tightened that suffix heuristic in tinyhumansai/openhuman#5396, but the tightening only converts a silent misroute into a warned one — the pin is still dropped, because there is nowhere for it to go.

Two possible shapes

A. Carry the pin on the request.

pub struct ModelResolveRequest {
    pub agent_id: String,
    pub role: Option<String>,
    pub is_team_lead: bool,
    /// Exact model id the definition pinned, if any. Distinct from `role`:
    /// a role is a host taxonomy, this is a concrete model.
    pub model_pin: Option<String>,
}

Keeps one resolution site and lets the host decide whether it can honour the pin (credentials, provider availability) rather than the runner deciding for it.

B. Document that the runner consults definition.model first and only calls the resolver when it is None.

Simpler, but it splits model selection across two places and means a host cannot apply its own policy to a pin — OpenHuman would want to validate the id against configured providers rather than pass it through blind.

I would favour A, but either is fine as long as it is written down. The thing to avoid is leaving role as the only channel, because "role may also be a model id" is exactly the ambiguity that produces the bug.

Impact

Phase 4 repointing blocker on the OpenHuman side (docs/specs/plan-agents.md). Per-agent model pins in agent.toml and user-authored custom agents would silently resolve to the workload default until this is settled.

Found by review on tinyhumansai/openhuman#5396.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions