Skip to content

fix(hooks-routing): register model_role_resolver capability in mount()#27

Open
bkrabach wants to merge 1 commit into
mainfrom
fix/register-model-role-resolver-capability
Open

fix(hooks-routing): register model_role_resolver capability in mount()#27
bkrabach wants to merge 1 commit into
mainfrom
fix/register-model-role-resolver-capability

Conversation

@bkrabach
Copy link
Copy Markdown
Collaborator

What

Registers the model_role_resolver capability in hooks-routing mount() so that consumers calling coordinator.get_capability("model_role_resolver") actually receive the matrix-strategy resolver.

Why

The MatrixModelRoleResolver class shipped in resolver_class.py (introduced in commit 3f20ad3) was never wired into the capability registry. As a result:

  1. Every delegate(..., model_role=X, ...) call where the caller supplied a model_role triggered the stderr warning at amplifier-foundation/modules/tool-delegate/__init__.py:790-794:

    model_role 'X' specified but no model_role_resolver capability is registered (install a routing bundle)
    

    despite this bundle being mounted and the matrix loaded correctly.

  2. Silent functional regression: the caller's model_role override was discarded at tool-delegate/__init__.py:781-806. The spawn proceeded with the agent's pre-resolved default provider_preferences instead, ignoring caller intent.

Evidence

Reproduced end-to-end in a Digital Twin Universe environment running upstream main:

  • Before fix: amplifier tool invoke delegate agent=foundation:explorer model_role=writing instruction="..." emitted the warning above on every call (deterministic — confirmed with both writing and coding roles, byte-identical event sequences across runs, ruling out a race condition).
  • After fix: warning is gone. The replacement warning, when triggered by empty installed-provider matches, is model_role 'X' resolved to no candidates against installed providers (resolver=balanced) — emitted from the success branch of the capability check (tool-delegate/__init__.py:801-806), proving the resolver is now registered, looked up, and invoked. The matrix name balanced is correctly reported via MatrixModelRoleResolver.name.

Changes

  • modules/hooks-routing/amplifier_module_hooks_routing/__init__.py (+17 lines): instantiate MatrixModelRoleResolver after effective_matrix is composed, register via coordinator.register_capability("model_role_resolver", resolver). Guarded by effective_matrix truthy and hasattr(coordinator, "register_capability").
  • modules/hooks-routing/tests/test_init.py (+84 lines): new TestModelRoleResolverCapability test class asserting register_capability("model_role_resolver", ...) is called from mount() and the registered resolver's resolve("fast") returns a non-empty list of ProviderPreference.

All 8 tests in modules/hooks-routing/tests/test_init.py pass.

Cascade and fallback semantics

The fix preserves the existing five-step fallback cascade. Confirmed by code reading:

  1. Caller-supplied provider_preferences (delegate input) — highest priority
  2. Caller-supplied model_rolenow actually honored (was previously discarded)
  3. Agent's pre-resolved provider_preferences (from session:start hook, resolved from agent's frontmatter model_role chain)
  4. Agent's frontmatter provider_preferences: (hard-pinned, bundle-portable)
  5. provider_preferences=None → child spawn inherits parent's current provider

The role-level fallback chain (resolve_model_role iterating an ordered list) is unchanged. Agent-frontmatter model_role: [list] still honors chains.

Behavioral change to flag

Any caller that was incidentally relying on the override being silently discarded (i.e., expecting the agent's default model_role chain to win even when passing model_role=X to delegate) will see different behavior after this fix. I have no evidence such callers exist in the wild, but the bug-fix-shifts-behavior risk is non-zero — worth a heads-up.

Out of scope (intentionally)

  • The delegate-tool input schema accepts model_role as a string only, not a list. The resolver itself and the agent-frontmatter path both accept list-form chains. This asymmetry pre-dates the bug and is not addressed here.
  • Version bump in bundle.md: deferred. The matrix bundle has no established versioning discipline for hook-only behavior fixes. Maintainer call.

Verification commands

# In-tree unit tests
pytest modules/hooks-routing/tests/ -v

# End-to-end (any DTU or local install with at least one provider installed)
amplifier tool invoke delegate agent=foundation:explorer model_role=fast instruction="Say hello"
# Expected stderr: empty for "no model_role_resolver capability is registered"

MatrixModelRoleResolver was instantiated but never registered via
coordinator.register_capability(), leaving tool-delegate with no
resolver for model_role parameters.

Effect: every delegate(..., model_role=...) call emitted a spurious
WARNING on stderr and silently fell back to the agent's declared role,
discarding the caller's override.

Fix: after computing effective_matrix in mount(), instantiate a
MatrixModelRoleResolver and register it under 'model_role_resolver'.
Guard on effective_matrix being non-empty so the capability is
absent (not silent-empty) when the matrix file is missing.

Adds a regression test (TestModelRoleResolverCapability) that asserts
the capability is registered and that the resolver returns correct
ProviderPreference objects for a known role.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant