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-123 builds override set: get_source_overrides() → overrides.<id>.source, get_module_sources() → sources.modules, provider extraction from config.providers[].source
- These feed
combined_sources = {**module_sources, **source_overrides, **provider_sources} (:123)
- But
modules.tools[].source is absent from this set. Tool overrides are applied AFTER prepare via get_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:204 passes source_hint=tool_config.get("source") to loader
- The mounted
module-source-resolver is foundation's PreparedResolver
- 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 (when module_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:324 tries 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:104
overrides.<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:
source key 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 valid vs 20260728T105215Z void runs)
Fix Shape
Two complementary approaches, in order of implementability:
-
Extract at the :123 seam (app-cli install path): extend combined_sources at 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 source differs 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).
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[].sourcein settings, the override is accepted at bundle preparation time, recorded in the mount plan asintent, 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
PreparedResolverdoes 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:
runtime/config.py:104-123builds override set:get_source_overrides()→overrides.<id>.source,get_module_sources()→sources.modules, provider extraction fromconfig.providers[].sourcecombined_sources = {**module_sources, **source_overrides, **provider_sources}(:123)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).Session time — why the recorded source still doesn't run:
_session_init.py:204passessource_hint=tool_config.get("source")to loadermodule-source-resolveris foundation'sPreparedResolverresolve()method (_prepared.py:222) discards the hint:_hint = ... # noqa: F841_prepared.py:249-252) uses the hint ONLY on the lazy-activation path (whenmodule_id not in self._paths)self._paths, so the fast path returns the prepared (stock) pathloader.py:324tries the installed entry point firstMechanical proof: Mount plans record intent, not code identity.
Working Workarounds
Both feed
combined_sourcesat 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:104Documentation Status
modules.tools[].sourceis not a documented override surface. Documented use ofsettings.yaml modules.toolsis tool config only (foundation docs/BUNDLE_GUIDE.md:1307,allowed_write_pathsexample). 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 bundletools[]wheresourceIS consumed (foundation _dataclass.py:391-392).Two-Path Reality (Previously Contradictory Observations Reconciled)
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
modules.tools[tool-skills].source: "https://custom-mirror/repo@custom-sha"in settingssourcekey is recorded for tool-skills20260728T114548Z 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).