Skip to content

routing show: honour provider family aliases; warn when the hook isn't mounted in the active bundle - #321

Merged
Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/routing-show-alias-and-mount-warning
Sep 7, 2026
Merged

Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/routing-show-alias-and-mount-warning

Conversation

@bkrabach

Copy link
Copy Markdown
Collaborator

amplifier routing show misled two kinds of user. Both found 2026-09-07 while shipping routing-matrix#71; both fixed in commands/routing.py.

1. The alias-blind user

A ChatGPT-subscription-only user (openai-chatgpt module, no openai module) is routed fine at runtime — the hook's PROVIDER_FAMILY_ALIASES maps provider: openai onto that mount. But _get_configured_provider_types fed a bare set-membership test that never consulted the alias table, so the display told exactly that user "✗ not configured" for every openai role. Measured in a DTU.

Fix: _get_configured_provider_types also adds every canonical family a configured module satisfies, reading the table from amplifier_module_hooks_routing.resolver.PROVIDER_FAMILY_ALIASES itself — one home, so the display cannot disagree with the resolver. All four downstream in provider_types sites become alias-aware with no change. Guarded import: on a clean install before the bundle is first fetched it degrades to "no aliases" (pre-alias behaviour), never an error. Directional, as in the resolver.

2. The hook-less user — the one that actually bit

routing show reads matrix files straight from the bundle cache (it even lazy-fetches the bundle), so it renders a matrix as "★ active" on a host whose active bundle never mounts hooks-routing. Nothing then applies the matrix; every sub-agent silently inherits the parent's provider.

Measured on a project pinned to anchors-amp-dev — a lean base that does not include routing-matrix:

$ amplifier routing show
  general — …
    ★ openai / gpt-?.?-terra  [reasoning_effort: medium]  ← active   ← says this
delegate:agent_spawned … "provider_preferences": null              ← does this
llm:request … "model": "claude-opus-5", "provider": "anthropic"     ← explorer ran on the parent's model

Fix: _routing_hook_is_composed composes the session the way runtime/config.py does — active bundle + every app bundle, in order — but stops at to_mount_plan(): no prepare(), no module installs, no network beyond what's cached (~0.1s warm). If hooks-routing is absent, show prints:

⚠ Not applied in your sessions. The active bundle anchors-amp-dev (and your app
bundles) never mount the hooks-routing hook, so this matrix is only displayed
here -- sub-agents will inherit the parent session's provider instead.
  To apply it in every session regardless of active bundle:
    amplifier bundle add git+https://github.com/microsoft/amplifier-bundle-routing-matrix@main#subdirectory=behaviors/routing.yaml --app

(command printed with soft_wrap=True so Rich can't fold it mid-URI).

Scope guard: only runs when an active bundle is explicitly set. With none set the CLI starts its built-in default foundation, which includes routing-matrix — nothing to warn about, and skipping the composition keeps this out of every code path and test fixture that never chose a bundle. (An earlier draft composed the real foundation bundle inside the shadowing tests: 12s per invocation, and its side effects made two runs of show disagree. That's why the guard exists.) Any failure inside the check → None → prints nothing. A diagnostic must never break the command.

Verified live on the host that surfaced it

cwd active bundle routing show
the anchors-amp-dev project anchors-amp-dev warns, with the copy-pasteable command
/tmp default (foundation) no warning

Tests

tests/test_routing_family_alias_and_mount_warning.py (10): chatgpt alone makes openai configured · alias is directional · empty table is byte-identical to before · table is read from the hook, never copied · warning names bundle + hook + exact command · silent when composed · silent when it can't tell · detection failure degrades to None · no active bundle means no composition · remediation URI is the bundle's real behaviors file.

Full suite 1917 passed; ruff clean on touched files (the 9 pre-existing findings elsewhere are untouched).

… isn't mounted

`amplifier routing show` misled two kinds of user. Both found on 2026-09-07,
both fixed in commands/routing.py.

1. THE ALIAS-BLIND USER
-----------------------
A ChatGPT-subscription-only user (`openai-chatgpt` module, no `openai`
module) is routed fine at runtime: the routing hook's PROVIDER_FAMILY_ALIASES
maps `provider: openai` onto that mount. But `_get_configured_provider_types`
fed a bare set-membership test that never consulted the alias table, so the
display told exactly that user "not configured" for every openai role.
Measured in a DTU.

Fix: `_get_configured_provider_types` now also adds every canonical family a
configured module SATISFIES, reading the table from
`amplifier_module_hooks_routing.resolver.PROVIDER_FAMILY_ALIASES` itself --
one home, so the display cannot disagree with the resolver. All four
downstream `in provider_types` sites become alias-aware with no change. The
import is guarded: on a clean install before the bundle is first fetched it
degrades to "no aliases" (the pre-alias behaviour), never an error. The alias
is directional, as in the resolver: a plain `openai` mount does not make
`openai-chatgpt` look configured.

2. THE HOOK-LESS USER
---------------------
`routing show` reads matrix files straight from the bundle cache -- it even
lazy-fetches the bundle -- so it renders a matrix as "* active" on a host whose
ACTIVE BUNDLE never mounts `hooks-routing`. Then nothing applies the matrix
and every sub-agent silently inherits the parent session's provider.

Measured on a project pinned to `anchors-amp-dev` (a lean base that does not
include routing-matrix): `routing show` printed "balanced ... <- active" for
all 13 roles while the session's `delegate:agent_spawned` event carried
`provider_preferences: null` and the explorer ran on the parent's
claude-opus-5 instead of the matrix's gpt-5.6-terra.

Fix: `_routing_hook_is_composed` composes the session the way `runtime/
config.py` does -- active bundle + every app bundle, in order -- but stops at
`to_mount_plan()`: no `prepare()`, so no module installs and no network beyond
what is already cached (~0.1s on a warm host). If `hooks-routing` is absent,
`show` prints a warning naming the active bundle and the exact remediation:

    amplifier bundle add git+https://github.com/microsoft/amplifier-bundle-routing-matrix@main#subdirectory=behaviors/routing.yaml --app

Only runs when an active bundle is EXPLICITLY set: with none set the CLI
starts its built-in default, `foundation`, which includes routing-matrix, so
there is nothing to warn about -- and skipping the composition keeps this out
of every code path and test fixture that never chose a bundle (an earlier
draft composed the real `foundation` bundle inside the shadowing tests, 12s
per invocation, and its side effects made two runs of `show` disagree).
Any failure inside the check returns None and prints nothing: a diagnostic
must never break the command.

Verified live on the host that surfaced it: from the anchors-amp-dev project
the warning prints with the copy-pasteable command (soft_wrap, so Rich cannot
fold it); from a directory on the default bundle it prints nothing.

Tests: tests/test_routing_family_alias_and_mount_warning.py (10) -- chatgpt
alone makes openai configured; alias is directional; empty table is
byte-identical to before; table is read from the hook, never copied; warning
names the bundle, the hook and the exact command; silent when composed;
silent when it cannot tell; detection failure degrades to None; no active
bundle means no composition; remediation URI is the bundle's real behaviors
file. Full suite 1917 passed, ruff clean on touched files.

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
@bkrabach
Brian Krabach (bkrabach) merged commit a1c3183 into main Sep 7, 2026
9 checks passed
@bkrabach
Brian Krabach (bkrabach) deleted the fix/routing-show-alias-and-mount-warning branch September 7, 2026 18:25
Brian Krabach (bkrabach) added a commit that referenced this pull request Sep 7, 2026
…level policy (#322)

This CLI ships `amplifier routing show/use/create/manage` and `amplifier init`'s
routing panel. Until now the hook those commands describe was mounted only if
the base bundle happened to include routing-matrix -- and the DEFAULT bundle,
`anchors` (runtime/config.py:1415), did not. So every default user had the
routing UX and no routing.

Measured 2026-09-07: `routing show` printed "balanced ... <- active" for every
role while the session's `delegate:agent_spawned` carried
`provider_preferences: null` and the explorer ran on the parent's model. The
existing post-hoc injection in `_apply_config_overrides` (the
`routing_hook_override` block) only fires when `routing.matrix` is SET in
settings, so a user on defaults got nothing; and it appends a bare hook entry,
not the behavior -- no model_role contract in context, no role-definitions
skill.

Fix: `_build_routing_behaviors(app_settings)`, composed on every session next
to `_build_skills_behaviors` / `_build_wayfinder_behaviors`, same shape and
same reasoning as their docstrings ("regardless of which base bundle the user
selected"). Behavior file only -- never the bundle root bundle.md.

Swapping the implementation: `sources.bundles["routing-matrix"]` -- the
existing `amplifier source add` vocabulary -- is consulted first. A user who
prefers a fork composes THAT bundle's behaviors/routing.yaml instead of ours,
so exactly one routing hook is ever composed. Without an override the URI is
derived from WELL_KNOWN_BUNDLES["routing-matrix"], the same entry
`_routing_hook_source`, `amplifier routing` and `amplifier update` read: one
home for where the bundle lives. Composed BEFORE app bundles, so an app-bundle
fork composes later and wins by module id.

Bases that already include routing-matrix (`foundation`; `anchors` as of
amplifier-foundation#374) are unaffected -- compose dedupes hooks by module
id. Verified on this host: the workspace's `anchors-amp-dev` session, which
mounted 0 routing hooks before, now mounts exactly 1 through the real compose
path, with GitHub main's anchors (i.e. before #374 lands).

The existing `routing_hook_override` block is left as-is: with the hook now
always present its append-with-source path is a no-op and its config-merge
path is exactly what should happen when a user sets `routing.matrix`.

Retires #321's "displayed but not mounted" warning in `routing show`. It
composed active + app bundles but not the CLI's always-composed behaviors, so
after this change it would FALSE-ALARM on every anchors user -- and the
condition it guarded is now unreachable. Its helpers and tests are removed;
the alias fix from #321 stays (tests/test_routing_family_alias.py).

Tests: tests/test_runtime_routing_composition.py (7) -- default is the
well-known behavior file; never the bare root; one home with
_routing_hook_source; sources.bundles override swaps; override means exactly
one behavior (swap, not add); unrelated overrides don't touch it; the builder
is actually called at the compose site and before app bundles. Full suite
1918 passed, ruff clean on touched files.

Generated with Amplifier

Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
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.

2 participants