How Amplifier bundles compose without collisions
Amplifier projects all need the same providers, tools, hooks, and behaviors under their own domain logic. Plain configuration doesn't compose: you copy-paste, and keys collide.
So what if the unit itself knew how to compose?
The composable unit is a single Bundle dataclass with one method: compose(self, *others) -> Bundle. It's not a proposal — it's shipped in amplifier-foundation 1.0.0.
But merging a whole bundle correctly is harder than merging one field.
The hard part isn't merging one field. Different sections of a bundle need different rules, and a naive merge lets context files silently overwrite each other.
compose() answers this with one rule per section family.
compose() applies exactly five documented merge strategies, one per section family, so every part merges the right way instead of clobbering.
The context rule is the one that guarantees no collisions.
Each bundle's context keys are prefixed with its own name during compose. Bundles accumulate files instead of overwriting — the prefix exists to avoid collisions.
Now the ordering rule makes inheriting a whole stack safe.
The registry loads every includes: in parallel with per-chain cycle detection, then composes your bundle last. Inheriting the stack still lets your domain logic override.
That's what makes one line safe to inherit an entire foundation.
One declarative line pulls in an entire foundation stack: loaded in parallel, composed with your bundle last, with zero context collisions — verified by a passing test suite.
And the pattern beneath it is what generalizes.
Make the unit composable and give it one mechanism — includes: (declarative) plus compose() (imperative). Layering foundations under domain logic stops being copy-paste.
Data as of: 2026-04-09 (amplifier-foundation HEAD 0965a34)
Feature status: Shipped and tested in microsoft/amplifier-foundation, version 1.0.0
Research performed:
grep -n 'def compose' amplifier-foundation/amplifier_foundation/bundle/_dataclass.py and sed -n '96,101p' .../bundle/_dataclass.pysed -n '116,192p' .../bundle/_dataclass.pysed -n '77,129p' amplifier-foundation/amplifier_foundation/dicts/merge.pysed -n '680,735p' amplifier-foundation/amplifier_foundation/registry.pygrep -n 'cycle\|_loading_chain' .../registry.pyuv run python -m pytest tests/test_bundle.py -k compose -q (7 passed) and uv run python -m pytest tests/test_dicts.py tests/test_bundle.py -q (59 passed)git remote -v; grep -n 'version' amplifier-foundation/pyproject.tomlgit log --format='%an' -- .../bundle.py .../bundle/_dataclass.py .../dicts/merge.py | sort | uniq -c | sort -rnGaps: "Zero collisions" summarizes the namespace-prefix mechanism (code says prefixes are added "to avoid collisions"); no absolute guarantee is asserted in code. Contributor counts (48/1/1) cover only the three composition files, not repo-wide totals.
Primary contributors: Brian Krabach — author of compose() (48 commits to composition files); Diego Colombo (1 commit, relative-path fix #40); Marc Goodner (1 commit, UTF-8 encoding fix).