microsoft / microsoft/amplifier
[amplifier-foundation/app-cli] Bundle-declared tool source overrides (modules.tools[].source) recorded in mount plan but not consumed at activation
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.1k
- Forks
- 261
- Avg merge
- 3h 28m
- Merged PRs (30d)
- 13
Description
Component: amplifier-foundation, amplifier-app-cli (Issues disabled on amplifier-foundation; filed here per ecosystem convention)
Summary
When a bundle-declared tool's source is overridden via modules.tools[].source in settings, the override is accepted at bundle preparation time, recorded in the mount plan as intent, and then silently discarded at session activation. For bundle-declared tools (like tool-skills), the source hint is never consulted — only for novel tools added post-prepare does lazy activation honor it.
Issue Details
Scope
This affects bundle-declared tools only — tools already present in the bundle. For novel tools added via settings (absent from the bundle), the lazy-activation path in foundation's PreparedResolver does consume the source hint. The restriction is specific: cannot override the source of a bundle-declared tool.
Root Cause (End-to-End Trace, Current Code)
Install time — where sources ARE consumed:
- app-cli
runtime/config.py:104-123builds override set:get_source_overrides()→overrides.<id>.source,get_module_sources()→sources.modules, provider extraction fromconfig.providers[].source - These feed
combined_sources = {**module_sources, **source_overrides, **provider_sources}(:123) - But
modules.tools[].sourceis absent from this set. Tool overrides are applied AFTER prepare viaget_tool_overrides()(:212) →_apply_tool_overrides(:608) →merge_module_items(lib/merge_utils.py:131), synced into mount plan (:288-296). - Installation is already complete. The source key reaches the mount plan but was never fed to any installer.
Session time — why the recorded source still doesn't run:
- core
_session_init.py:204passessource_hint=tool_config.get("source")to loader - The mounted
module-source-resolveris foundation'sPreparedResolver - Its sync
resolve()method (_prepared.py:222) discards the hint:_hint = ... # noqa: F841 - The async path (
_prepared.py:249-252) uses the hint ONLY on the lazy-activation path (whenmodule_id not in self._paths) - A bundle-declared tool is always in
self._paths, so the fast path returns the prepared (stock) path - Even after resolution,
loader.py:324tries the installed entry point first
Mechanical proof: Mount plans record intent, not code identity.
Working Workarounds
Both feed combined_sources at install time (safe):
sources.modules.<module-id>— documented override surface, settings.py:491 + config.py:104overrides.<id>.source— documented override surface, settings.py:1128 + config.py:104
Documentation Status
modules.tools[].source is not a documented override surface. Documented use of settings.yaml modules.tools is tool config only (foundation docs/BUNDLE_GUIDE.md:1307, allowed_write_paths example). The source-override design spec routes sources through resolver surfaces (app-cli docs/specs/2026-03-12-bundle-source-override-design.md:17).
However, the schema shape itself invites the expectation: settings modules.tools[] mirrors bundle tools[] where source IS consumed (foundation _dataclass.py:391-392).
Two-Path Reality (Previously Contradictory Observations Reconciled)
- Bundle-declared tool path: recorded, never consulted ← this issue
- Novel module lazy-activation: source hint IS honored ← both truths coexist
Scope Impact
Affects anyone expressing a tool-source pin/redirect via modules.tools[].source: private-mirror users (enterprise/gitea), fork pinning, local tool development copying bundle schema into settings. Hooks share the same post-prepare merge path (_apply_hook_overrides, runtime/config.py:540+) — same class; provisioning flows (profile.py:813 → engine.py:1018 → incus.file_push) also affected.
The provenance-integrity angle affects every consumer of mount plans as audit artifacts (evals, session forensics).
Reproduction
- Set
modules.tools[tool-skills].source: "https://custom-mirror/repo@custom-sha"in settings - Prepare a bundle with tool-skills included
- Check mount plan:
sourcekey is recorded for tool-skills - Run a session: the custom source is never consulted, stock tool-skills loads
- Evidence: mount-plan provenance integrity gate (during
20260728T114548Z validvs20260728T105215Z voidruns)
Fix Shape
Two complementary approaches, in order of implementability:
-
Extract at the :123 seam (app-cli install path): extend
combined_sourcesat runtime/config.py:123 with tool/hook source extraction (template exists at :112-120 for providers). -
Fail loud on mismatch (foundation activation path): when a mount-plan entry's
sourcediffers from the prepared source for that module id, raise at_prepared.py:222(the point where the hint is currently discarded). Identity check, no new mechanism required.
No new mechanism required in either case.
Related Issues
Same class as #342 and #344 (override-ladder members). Different from #26/#282 (both involve reproducibility but differ on URI pinning vs source shadowing).
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at runtime/config.py:104-123 and :212/:608 to trace how tool overrides enter the mount plan, then inspect _session_init.py:204 and foundation’s _prepared.py:222-252. Reproduce the bundle-declared tool case using the settings source override and compare it with a novel lazy-activated module. Done means the custom source is consumed for declared tools or a source mismatch fails loudly, with the mount-plan provenance behavior preserved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100