feat: host-config skills: block (D11/D12/D13) + tool-skills bundle composition#30
Conversation
Adds design amendments and implementation plan for skills-block extension to the host_config layer. ## New Sections - **D11**: `skills:` block added as fifth top-level key - **D12**: List-merge semantics for cross-layer composition - **D13**: Environment variable preservation and xor-override semantics - **D14**: What is NOT changed (host-level config responsibility boundaries) ## Implementation Plan Corresponding implementation plan added with 24 tasks across 4 phases: - Phase 1 (Engineering groundwork): Stubs and host_config subcommand refactor - Phase 2 (Provider integration): Capability snapshots and schema validation - Phase 3 (Sequencer + Rules): Skills appliance merge + rule evaluation engine - Phase 4 (Integration + E2E): Agent callsites + integration testing Parent design doc: docs/designs/2026-06-01-host-config-layer-revisit.md 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add 'skills' to _VALID_TOP_LEVEL_KEYS so JSON host_config documents may
include a {"skills": {...}} block without tripping the closed-schema
guard (config_unknown_key).
The loader returns the raw parsed dict (no ParsedConfig dataclass), so
the change is the one-line allowlist addition plus a regression test
that mirrors test_load_config_accepts_all_four_known_keys for the new
key.
Implements D11 — skills is the fifth recognized top-level host_config key.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add _validate_skills_block helper that enforces the skills.skills key (when present) is a JSON array of string source URIs. Non-list values and non-string members raise ConfigError(code='config_invalid_type', classification='protocol') at parse time, mirroring the existing approval.patterns validation. Catching the violation in the loader gives the operator a clear parse-time error with the offending key path and type, rather than an opaque failure deep in the downstream skills loader. Implements D11 (skills as a recognized top-level block) + D7 (closed schema with per-block type guards). Tests: - test_loader_rejects_skills_skills_non_list - test_loader_rejects_skills_skills_non_string_member - test_loader_accepts_skills_skills_valid_list (29/29 config tests pass; no regressions.) 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Per D11 + D7, enforce skills.visibility is a JSON object (dict) shape in the loader. Non-dict values (e.g. lists, strings, numbers) raise ConfigError(code='config_invalid_type', classification='protocol') at parse time so the operator gets a clear error rather than an opaque failure deep in the downstream skills module. Per the D11 pass-through rule, the loader does NOT iterate or validate inner keys under skills.visibility. The downstream skills module owns the semantics of enabled / inject_role / max_skills_visible / ephemeral / priority and any future inner keys it chooses to recognize. This keeps loader responsibility narrow (shape only) and lets the skills module evolve its accepted keys independently of the loader's release cadence. Adds three tests: * test_loader_rejects_skills_visibility_non_dict * test_loader_accepts_skills_visibility_valid_dict * test_loader_passes_through_unknown_visibility_inner_keys Mirrors the existing _validate_skills_block / _validate_approval_patterns guard style. Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Per D11, the skills.* inner shape is closed against {skills, visibility}.
Any other sub-key (e.g. 'sources', perhaps from a confused operator or an
older schema) now raises ConfigError(code='config_invalid_type',
classification='protocol') at parse time rather than being silently
dropped before the skills module sees the config.
Distinction from D7: this is config_invalid_type (a closed inner shape
violation), NOT config_unknown_key, which D7 reserves for the top-level
mapping. D7 pass-through applies one level deeper — inside
skills.visibility — not at skills.* itself. The boundary keeps the
loader's responsibility narrow (shape only) while preserving the
skills module's authority over visibility's inner keys.
Adds frozenset _ALLOWED_SKILLS_SUBKEYS = {'skills', 'visibility'} as a
module-level constant alongside the existing _VALID_TOP_LEVEL_KEYS and
_VALID_PROVIDER_MODULES guards.
Adds tests/config/test_loader.py::test_loader_rejects_unknown_skills_subkey
covering the reject path with the documented {'skills': {'sources': ['x']}}
example.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Append regression-anchor test test_loader_end_to_end_skills_block to
tests/config/test_loader.py covering the full canonical skills block:
* skills.skills containing all three documented source-URI shapes
(remote git URI, workspace-relative path, user-home path)
* skills.visibility containing the full set of currently-documented
inner keys (enabled, inject_role, max_skills_visible, ephemeral,
priority)
The test writes JSON to a tmp file, parses via load_config, and asserts
that parsed['skills'] round-trips verbatim against the input block.
Per D11 the loader does not interpret skills.visibility inner keys; the
downstream skills module owns their semantics. The whole-block equality
assertion proves no field is dropped, reordered, or coerced.
This test passes already given Tasks 1.1-1.4 -- no new implementation
needed -- and serves as a regression anchor for D11 inner-shape validation.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
D12. Bundle's curated sources come first; host_config additions
are appended. Bundle is the floor, host extends, host cannot
silently erase. The _concat_list_pass_through helper generalizes
to any future list-shaped pass-through value per D12's closing
paragraph.
Implements the skills branch of the host-config merger:
- _concat_list_pass_through(bundle_value, host_value): named helper
encoding the D12 list-concat semantic for any list-shaped
pass-through sub-key.
- _merge_skills(merged, skills_block): dispatches host.skills.skills
through the helper onto the tool-skills module config.
- merge_config wires host_config.get('skills') to _merge_skills
alongside the existing mcp / approval / provider blocks.
Tests cover the three cases mandated by D12:
- bundle list + host list -> concat in bundle-first order
- empty host list -> bundle list preserved verbatim
- bundle without skills key -> host list passes through alone
skills.visibility (dict-overlay per D5) lands in a follow-up
task; this commit is scoped strictly to the list-concat semantic.
Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
D5: skills.visibility is a dict-shaped sub-key whose host keys overlay
shallow per-key on top of the bundle's declared visibility block. Mirrors
the inline overlay applied to host.mcp, host.approval, and
host.provider.config -- bundle keys come through unless the host
overrides them; the host's keys win; bundle-declared keys never silently
disappear.
The regression anchor pins the contract:
- bundle visibility = {enabled: True, priority: 20, inject_role: 'user'}
- host visibility = {priority: 10}
- merged visibility = {enabled: True, inject_role: 'user', priority: 10}
The visibility branch of _merge_skills was previously documented as
'handled in a follow-up task'. This commit adds the inline overlay
alongside the regression test so the dict-overlay semantics are now
mechanically enforced.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
D7: when host_config declares a skills: block but the bundle has no tool-skills mount entry, the merger now raises AaaError with code='config_no_matching_module' and classification='protocol' instead of silently fabricating a tool-skills config dict that no module will consume. The empty-block-plus-missing-mount boundary remains a no-op (D7): an empty skills: block has nothing to push, so the absence of a target mount is harmless; merged is left untouched. Tests: - test_merge_skills_block_without_tool_skills_mount_raises - test_merge_empty_skills_block_without_tool_skills_is_noop 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Canonical D11/D12/D5/D7 round-trip test exercising both skills sub-key merge semantics in a single block: list-concat for skills.skills (D12) and shallow per-key dict overlay for skills.visibility (D5). Bundle declares a git URI source plus a 5-key visibility block; host appends two local skill paths and overrides priority only. The merged result must compose both semantics: bundle URI floor with host paths appended in order, plus bundle visibility keys passed through untouched except for the host's priority override. Anchors the canonical D11 example so future _merge_skills changes that break the composition of the two sub-key semantics are caught even when per-sub-key tests still pass. Per D7, an empty host block + no tool-skills mount remains a no-op boundary; a non-empty block with no mount still raises config_no_matching_module — both covered by sibling tests in this file. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add regression-anchor test that asserts `run --help` does not advertise
a `--skills-dir` option. The per-turn argv surface for skill directories
is closed the same way --env-allowlist, --env-extra, and --allow-protocol-skew
were closed under D10.
The deletion-side of this task is structurally already true on this branch
(`feat/host-config-skills-block` never added the flag), so this commit
codifies the invariant via a help-text inspection test rather than
re-deleting code that does not exist.
Red-green cycle verified by temporarily injecting
`@click.option('--skills-dir', ...)` into single_turn.py and confirming
the test fails; reverting and confirming green.
Grep evidence:
grep -n 'skills-dir|skill_dirs|skill_sources|inject_skill' \
src/amplifier_agent_cli/modes/single_turn.py
-> ZERO matches.
Test evidence:
pytest tests/cli/test_single_turn.py -v
-> 19/19 PASS, including
test_run_help_text_no_longer_documents_skills_dir.
BREAKING CHANGE: --skills-dir is no longer a recognised CLI option for
`run`. Migration paths:
- host_config `skills:` block (D11)
- $AMPLIFIER_SKILLS_DIR environment variable (D13)
Refs: D10 amendment.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…removal Add three regression-anchor tests in tests/cli/test_package_imports.py asserting that skill_sources.py and inject_skill_dirs() are absent from the CLI package: import-level, attribute-level, and filesystem-level. The deletion-side of this task is structurally already true on this branch (`feat/host-config-skills-block` never landed skill_sources.py), so this commit codifies the invariant via anchor tests rather than re-deleting code that does not exist — same pattern as bf8d2a6 ("drop --skills-dir argv flag"). Grep evidence: grep -rn 'skill_sources\|inject_skill_dirs' src/ tests/ --include='*.py' -> ZERO matches. Red-green cycle verified by temporarily writing src/amplifier_agent_cli/skill_sources.py with a stub inject_skill_dirs() and confirming test_cli_skill_sources_module_absent and test_cli_source_tree_has_no_skill_sources_file both fail; removing the stub and confirming all three anchors return to green. Test evidence: pytest tests/cli/test_package_imports.py -v -> 6/6 PASS (3 new anchors + 3 pre-existing). pytest tests/cli/ -q -> 113 passed, 1 skipped (Linux-only), 3 failed. The 3 failures are in tests/cli/test_mode_a_v2_envelope.py (mcp_servers JSON parsing); they pre-date this commit and are unrelated to skill_sources — verified by reproducing them at the parent SHA via `git stash && pytest tests/cli/test_mode_a_v2_envelope.py`. BREAKING CHANGE: inject_skill_dirs() is removed. With --skills-dir gone (Task 3.2), the helper has no caller; its responsibilities (extending the tool-skills mount entry's skill source list) move to the host_config merger per D12. Migration paths: - host_config `skills:` block (D11) - $AMPLIFIER_SKILLS_DIR environment variable (D13) Refs: D12 amendment, Task 3.3. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add tests/bundle/test_tool_skills_declared.py asserting that the vendored bundle.md declares tool-skills sourced from the amplifier-bundle-skills subdir, and that the entry ships sensible defaults under config.skills (three sources) and config.visibility (enabled=True, inject_role=user, max_skills_visible=50, ephemeral=True, priority=20). These tests are intentional regression anchors written ahead of the bundle.md re-edit (parent plan Phase 4.1). In the current state they fail (4 FAILs: one membership assertion + three StopIteration on next(...) lookups) and will turn GREEN once the bundle.md edit lands. References: - D11 (decision: bundle ships tool-skills with defaults out of the box) - Parent plan Phase 4.1 (re-land bundle.md tool-skills edit after the prior session's rollback) Task: R1.1 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add tool-skills as the fourth entry in bundle.md's tools: block, sourced
from the upstream amplifier-bundle-skills repo. Configure the module with
project-default skills sources and visibility, baking the convention into
the wheel so hosts get sensible defaults out of the box (D11/D12).
Defaults shipped:
- skills sources (list-concat with host config skills.skills):
git+.../amplifier-bundle-skills#subdirectory=skills (upstream catalog)
.amplifier/skills (per-project)
~/.amplifier/skills (per-user)
- visibility (dict-overlay with host config skills.visibility):
enabled=true, inject_role=user, max_skills_visible=50,
ephemeral=true, priority=20
D11 (bundle ships tool-skills with defaults) and D12 (host-config merge
semantics: skills.skills list-concatenated bundle-first, skills.visibility
dict-overlaid host-on-top) — see docs/plans/2026-06-02-skills-block-recovery-plan.md.
Cache invalidation: this change rewrites the bundle.md frontmatter, which
flips the bundle's prepared-source hash. Existing prepared-bundle caches
will be invalidated and rebuilt on the next cold start.
Verified:
- tests/bundle/test_tool_skills_declared.py: 4/4 PASS (was 0/4 RED)
- tests/test_bundle_packaging.py + tests/test_bundle_loader.py: 14/14 PASS
- ruff check src/ tests/: clean
- pyright src/ tests/: no new errors (14 pre-existing, unrelated to bundle.md)
Refs: R1.1 (regression-anchor tests), R1.2 (this commit)
Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Append two failing regression tests to tests/cli/test_config_show.py
that anchor the missing 'skills' field in the config show JSON payload
(R2.1 of the skills-block-recovery plan).
Test 1 — test_config_show_reports_merged_skills_block:
Writes a host config with a skills block (one extra skill path +
visibility.max_skills_visible=10) and invokes 'config show --config'.
Asserts the emitted payload contains a top-level 'skills' field with:
- host append at the tail of the merged skills list,
- bundle-default skill roots ('.amplifier/skills',
'~/.amplifier/skills') preserved,
- host visibility override (max_skills_visible=10),
- bundle visibility defaults preserved (enabled=true,
inject_role='user').
Test 2 — test_config_show_reports_bundle_skills_when_host_block_absent:
Invokes 'config show' with no host config. Asserts the emitted payload
contains a top-level 'skills' field reflecting bundle defaults
verbatim ('.amplifier/skills', '~/.amplifier/skills', visibility.enabled=true,
visibility.max_skills_visible=50).
Both tests currently fail because the config_show payload dict in
src/amplifier_agent_cli/admin/config_show.py does not include any
'skills' key. Implementation of the merged-skills surface in a follow-up
commit will turn these red anchors green.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
config show now emits a top-level 'skills' field that reflects the
post-merge view operators care about: bundle defaults from bundle.md's
tool-skills static config, with host_config 'skills:' overlays applied
using the runtime's merge semantics:
- skills.skills is list-concatenated (bundle-first, host-appended)
so operator-supplied skill roots appear at the tail of the list.
- skills.visibility is dict-overlaid (host wins per-key) so an
operator can override only the visibility fields they care about
without losing bundle defaults for the rest.
Behavior is diagnostic-only: parse failures on the host config surface
as 'parse_error' while bundle defaults remain reported, so the operator
can see what would compose if the broken host file were fixed. Imports
are kept local to the helper to avoid pulling lib.config onto the
no-config startup path.
This closes D8 for the skills block (D11/D12) so 'config show' matches
what the engine will actually see at mount time.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Add [Unreleased] section to CHANGELOG.md documenting the breaking change
and new skills block per D11/D12/D13.
Added:
- Engine: skills: block as fifth top-level key in host_config (D11),
pass-through to tool-skills module. Supports skills.skills (list-concatenated
with bundle-declared sources per D12, bundle-first, host-appended) and
skills.visibility (dict-overlaid on bundle defaults per D11).
- Bundle: tool-skills module declared in bundle.md with three default skill
sources (curated bundle, .amplifier/skills, ~/.amplifier/skills) and default
visibility config. Cache key invalidates on upgrade.
- CLI: config show reports the post-merge skills block (D8) so operators can
confirm both host additions and bundle defaults.
Changed (BREAKING):
- CLI: --skills-dir argv flag removed from amplifier-agent run. Migration
paths per D13:
1. Preferred: set $AMPLIFIER_SKILLS_DIR (preserved as adapter-bridge surface)
2. Or: add a skills: block to host_config JSON and pass via --config or
$AMPLIFIER_AGENT_CONFIG.
Removed:
- Engine: src/amplifier_agent_cli/skill_sources.py (the inject_skill_dirs()
helper). Unreachable after --skills-dir removal.
Design references:
- docs/designs/2026-06-01-host-config-layer-revisit.md (D11/D12/D13)
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
The regression-anchor test added in bf8d2a6 had a multi-line parenthesized assertion message that ruff format collapses to a single line. The TDD pipeline missed running ruff format --check before the implementer's commit; this fix lands it. No functional change; 19/19 tests in this file still pass after the format. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This is the focused recovery plan used to close gaps left by the first SDD pass — R1 (bundle.md tool-skills add), R2 (config_show.py skills reporting), R3 (CHANGELOG entry), R4 (cold-prepare smoke), R5 (e2e discovery, deferred). Committing it as a process record alongside the implementation it drove. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
DTU validation — full evidence (V1–V6) 🟢Branch Results summary
V1 evidence —
|
Bump the wire protocol version to reflect accumulated backward-incompatible changes shipped under the 0.4.0 release window: - metadata.hostCapabilities removed from response envelope (#27) - InitializeParams.host removed (#27) - InitializeParams.mcpServers renamed to mcpConfigPath (#24) - skills field added (host config skills: block, #30) Old wrappers pinned to '0.2.0' should hard-fail handshake with a typed protocol_version_mismatch error and exit 2 — verified manually: $ uv run amplifier-agent run --protocol-version 0.2.0 --session-id sm 'hi' {... "code": "protocol_version_mismatch" ...} EXIT=2 $ uv run amplifier-agent run --protocol-version 0.3.0 --session-id sm 'hi' {... "reply": "..." ...} EXIT=0 Updated sites (audited via 'git grep "0.2.0" src/ tests/'): - src/amplifier_agent_lib/protocol/methods.py — PROTOCOL_VERSION constant - src/amplifier_agent_lib/protocol/spec.md — regenerated artifact - 9 conformance fixtures' protocolVersion: literals (setup + initialize params) - version_skew.yaml — serverVersion in expected error.data - 6 test files pinning protocolVersion in wrapper-side InitializeParams Left as historical references (per release-issue guidance): - loader.py docstring example (illustrative fixture shape) - test_protocol_conformance_fixtures.py's self-contained _VALID_FIXTURE (loader smoke test, not engine-compat) - serverInfo.version: "0.2.0" in fixture script result blocks (not asserted by conformance harness — engine emits __version__ at runtime; fixture scripts are descriptive, only the explicit assertions: block is verified) BREAKING CHANGE: Wire protocol 0.2.0 -> 0.3.0. Old wrappers pinned to 0.2.0 will hard-fail handshake with protocol_version_mismatch (exit 2). Reinstall both engine and wrapper, or set allowProtocolSkew: true in host config.
Engine version bump for the host-config-layer release window. Pairs with the wire PROTOCOL_VERSION bump (0.2.0 -> 0.3.0) and consolidates argv/wire surface removals shipped in PRs #27, #29, #30, #31. Verified: $ uv run amplifier-agent --version amplifier-agent, version 0.4.0 BREAKING CHANGE: Engine version 0.3.0 -> 0.4.0. Wire protocol 0.2.0 -> 0.3.0 shipped in the same release. Old wrappers fail handshake. See CHANGELOG.md for the full argv/wire/API removal list.
The TS wrapper jumps minor (0.4.0 -> 0.5.0) rather than tracking the
engine's major.minor (0.4.0) because of release-window history:
- 0.4.0 was already published to npm (verified: 'npm view
amplifier-agent-ts versions' lists 0.3.0, 0.3.1, 0.4.0)
- 0.4.0 was published by PR #17 (path-based MCP config delivery,
pre-#27/#29/#30/#31)
- Since the 0.4.0 publish, PRs #27, #29, #30, #31 have all landed,
removing breaking surface from the TS wrapper API:
* SpawnAgentParams.host / HostCapabilities type (#27)
* mcpConfigPath field + argv emission (#29)
* envAllowlist / envExtra / allowProtocolSkew fields (#31)
We cannot republish 0.4.0 with different code, and the accumulated changes
are breaking (not a patch). Bumping to 0.5.0 puts the next npm release on
a fresh, unpublished version. If you have context I'm missing about a
prior decision to keep TS major.minor tied to engine major.minor, override
with a follow-up bump.
BREAKING CHANGE: TS wrapper API removed SpawnAgentParams.host,
HostCapabilities type, InitializeHostParams type (#27); mcpConfigPath field
+ argv emission (#29); envAllowlist, envExtra, allowProtocolSkew fields
(#31). Callers must migrate to AMPLIFIER_MCP_CONFIG env var and a
host_config JSON file passed via --config.
, #32) Replace the [Unreleased] section with a full [0.4.0] - 2026-06-03 entry that consolidates the host-config-layer release window: PR #27 - Host config layer + drop hostCapabilities surface PR #29 - Drop --mcp-config-path argv (subsumed by host config + env var) PR #30 - host-config skills: block + tool-skills bundle composition PR #31 - Drop env-allowlist, env-extra, allow-protocol-skew from wrappers PR #32 - Restore conformance suite Highlights: - 4 argv flags + 1 env var removed (subsumed by host config layer) - hostCapabilities fully removed from envelope, initialize, and wrapper API - 5th host-config block 'skills:' (D11/D12/D13) - Wire protocol 0.2.0 -> 0.3.0 (BREAKING) - Engine 0.3.0 -> 0.4.0, Python wrapper 0.3.0 -> 0.4.0, TS wrapper 0.4.0 -> 0.5.0 Also documents the cross-repo follow-ups that downstream consumers (notably amplifier-module-provider-nc) must catch up on but are NOT part of this release.
…ce restored (#33) * chore(protocol)!: bump wire PROTOCOL_VERSION 0.2.0 -> 0.3.0 Bump the wire protocol version to reflect accumulated backward-incompatible changes shipped under the 0.4.0 release window: - metadata.hostCapabilities removed from response envelope (#27) - InitializeParams.host removed (#27) - InitializeParams.mcpServers renamed to mcpConfigPath (#24) - skills field added (host config skills: block, #30) Old wrappers pinned to '0.2.0' should hard-fail handshake with a typed protocol_version_mismatch error and exit 2 — verified manually: $ uv run amplifier-agent run --protocol-version 0.2.0 --session-id sm 'hi' {... "code": "protocol_version_mismatch" ...} EXIT=2 $ uv run amplifier-agent run --protocol-version 0.3.0 --session-id sm 'hi' {... "reply": "..." ...} EXIT=0 Updated sites (audited via 'git grep "0.2.0" src/ tests/'): - src/amplifier_agent_lib/protocol/methods.py — PROTOCOL_VERSION constant - src/amplifier_agent_lib/protocol/spec.md — regenerated artifact - 9 conformance fixtures' protocolVersion: literals (setup + initialize params) - version_skew.yaml — serverVersion in expected error.data - 6 test files pinning protocolVersion in wrapper-side InitializeParams Left as historical references (per release-issue guidance): - loader.py docstring example (illustrative fixture shape) - test_protocol_conformance_fixtures.py's self-contained _VALID_FIXTURE (loader smoke test, not engine-compat) - serverInfo.version: "0.2.0" in fixture script result blocks (not asserted by conformance harness — engine emits __version__ at runtime; fixture scripts are descriptive, only the explicit assertions: block is verified) BREAKING CHANGE: Wire protocol 0.2.0 -> 0.3.0. Old wrappers pinned to 0.2.0 will hard-fail handshake with protocol_version_mismatch (exit 2). Reinstall both engine and wrapper, or set allowProtocolSkew: true in host config. * chore(engine)!: bump amplifier-agent version 0.3.0 -> 0.4.0 Engine version bump for the host-config-layer release window. Pairs with the wire PROTOCOL_VERSION bump (0.2.0 -> 0.3.0) and consolidates argv/wire surface removals shipped in PRs #27, #29, #30, #31. Verified: $ uv run amplifier-agent --version amplifier-agent, version 0.4.0 BREAKING CHANGE: Engine version 0.3.0 -> 0.4.0. Wire protocol 0.2.0 -> 0.3.0 shipped in the same release. Old wrappers fail handshake. See CHANGELOG.md for the full argv/wire/API removal list. * chore(wrapper-py)!: bump Python wrapper version 0.3.0 -> 0.4.0 Python wrapper version bump to pair with the engine 0.4.0 release. Same major.minor as engine — the two move together. The wrapper's compiled PROTOCOL_VERSION (sourced from amplifier_agent_lib) follows the engine bump 0.2.0 -> 0.3.0 transitively. BREAKING CHANGE: SpawnAgentParams API removed envAllowlist, envExtra, allowProtocolSkew, host, and mcpConfigPath fields across PRs #27, #29, #31. Callers must migrate to host_config (JSON file passed via --config or $AMPLIFIER_AGENT_CONFIG) or env var injection (AMPLIFIER_MCP_CONFIG). * chore(wrapper-ts)!: bump amplifier-agent-ts version 0.4.0 -> 0.5.0 The TS wrapper jumps minor (0.4.0 -> 0.5.0) rather than tracking the engine's major.minor (0.4.0) because of release-window history: - 0.4.0 was already published to npm (verified: 'npm view amplifier-agent-ts versions' lists 0.3.0, 0.3.1, 0.4.0) - 0.4.0 was published by PR #17 (path-based MCP config delivery, pre-#27/#29/#30/#31) - Since the 0.4.0 publish, PRs #27, #29, #30, #31 have all landed, removing breaking surface from the TS wrapper API: * SpawnAgentParams.host / HostCapabilities type (#27) * mcpConfigPath field + argv emission (#29) * envAllowlist / envExtra / allowProtocolSkew fields (#31) We cannot republish 0.4.0 with different code, and the accumulated changes are breaking (not a patch). Bumping to 0.5.0 puts the next npm release on a fresh, unpublished version. If you have context I'm missing about a prior decision to keep TS major.minor tied to engine major.minor, override with a follow-up bump. BREAKING CHANGE: TS wrapper API removed SpawnAgentParams.host, HostCapabilities type, InitializeHostParams type (#27); mcpConfigPath field + argv emission (#29); envAllowlist, envExtra, allowProtocolSkew fields (#31). Callers must migrate to AMPLIFIER_MCP_CONFIG env var and a host_config JSON file passed via --config. * docs(changelog): consolidate 0.4.0 release notes (PRs #27, #29, #30, #31, #32) Replace the [Unreleased] section with a full [0.4.0] - 2026-06-03 entry that consolidates the host-config-layer release window: PR #27 - Host config layer + drop hostCapabilities surface PR #29 - Drop --mcp-config-path argv (subsumed by host config + env var) PR #30 - host-config skills: block + tool-skills bundle composition PR #31 - Drop env-allowlist, env-extra, allow-protocol-skew from wrappers PR #32 - Restore conformance suite Highlights: - 4 argv flags + 1 env var removed (subsumed by host config layer) - hostCapabilities fully removed from envelope, initialize, and wrapper API - 5th host-config block 'skills:' (D11/D12/D13) - Wire protocol 0.2.0 -> 0.3.0 (BREAKING) - Engine 0.3.0 -> 0.4.0, Python wrapper 0.3.0 -> 0.4.0, TS wrapper 0.4.0 -> 0.5.0 Also documents the cross-repo follow-ups that downstream consumers (notably amplifier-module-provider-nc) must catch up on but are NOT part of this release. * fix(ci): commit uv.lock for deterministic dependency resolution The CI workflow uses astral-sh/setup-uv@v3 with enable-cache: true, which defaults to globbing **/uv.lock to compute the cache key. With uv.lock gitignored, every CI run failed at the install step with: No matches found for glob **/uv.lock Committing uv.lock fixes CI and aligns with Astral's recommended practice for reproducible builds. Catches reproducibility drift between contributors and CI/DTU. The lock is ~150KB. This was a pre-existing CI break on main (every recent CI run failing) that this version-bump release is unblocking as part of release-readiness. * fix(ci): install Node + pnpm for conformance parity tests tests/test_conformance_parity.py::test_ts_and_py_runners_agree[*] shells out to 'pnpm exec tsx runner_ts.ts' to cross-validate the TS runner against the Python runner. The Python CI job had no Node.js or pnpm installed, so all 10 parametrized cases failed with: FileNotFoundError: [Errno 2] No such file or directory: 'pnpm' Adds setup-node@v4, pnpm/action-setup@v3, and a pnpm-install step in wrappers/conformance/ before pytest runs. Pre-existing CI gap that this release branch is closing as part of release-readiness. * fix(ci): make XDG test hermetic + delete obsolete --mcp-servers tests Closes the last 4 Python CI failures on this release branch. (1) tests/cli/test_config_show.py::test_config_show_reports_default_when_env_absent Click's CliRunner.invoke(env=...) MERGES the env dict with os.environ instead of replacing it. GitHub Actions runners set XDG_CONFIG_HOME by default, which leaked into the test and made source='env:XDG_CONFIG_HOME' instead of the 'default' the test was asserting. Now uses monkeypatch.delenv() to explicitly remove XDG_CONFIG_HOME / XDG_CACHE_HOME / XDG_STATE_HOME before invoking. (2) tests/cli/test_mode_a_v2_envelope.py The three test_mcp_servers_* tests (inline_json_parsed, at_path_form, malformed_json_yields_argv_envelope) target the --mcp-servers argv flag. That flag was renamed to --mcp-config-path by PR #24 and then fully removed by PR #29. The tests have been failing as 'pre-existing baseline' through PRs #27, #29, #31, #32, and #33's earlier baselines. They test removed surface and should never have been kept. Replaced with an inline comment naming the removal context and pointing at the removal guardrail at tests/cli/test_drop_mcp_config_path_flag.py. Local: 532 passed, 3 skipped, 0 failed (full pytest tests/). --------- Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
Summary
Adds the
skills:block as the fifth top-level key in the host_config layer (D11/D12/D13 amendments to2026-06-01-host-config-layer-revisit.md), and lands the bundle composition fortool-skillsso the new config block actually has something to merge into at runtime.Design references
docs/designs/2026-06-01-host-config-layer-revisit.md— new D11 (skills:block as fifth top-level key), D12 (list-concatenation merge forskills.skills), D13 ($AMPLIFIER_SKILLS_DIRenv var preserved as the adapter bridge pattern). Amends D4/D5/D7/D8/D10.docs/plans/2026-06-02-skills-block-host-config-implementation.md— original implementation plan.docs/plans/2026-06-02-skills-block-recovery-plan.md— recovery plan used to close gaps left by the first SDD pass.What landed
src/amplifier_agent_lib/config/loader.py(+76)skillsadded to top-level allowlist;skills.skillsvalidated aslist[str];skills.visibilityvalidated asdict; unknown sub-keys rejected; nested keys invisibilitypass throughsrc/amplifier_agent_lib/config/merger.py(+84)skills.skills(bundle-first, host-appended); dict-overlay forskills.visibility;config_no_matching_moduleraised for orphanskills:blockssrc/amplifier_agent_lib/bundle/bundle.md(+17)tool-skillsmodule added (sourceamplifier-bundle-skills@main#subdirectory=modules/tool-skills) with default sources (curated bundle,.amplifier/skills,~/.amplifier/skills) and default visibility configsrc/amplifier_agent_cli/admin/config_show.py(+64)config showreports mergedskillsblock per D8CHANGELOG.md(+30)[Unreleased]entry covering breaking change + new feature + design refstests/config/test_loader.py(+279),tests/config/test_merger.py(+240),tests/cli/test_single_turn.py(+29),tests/cli/test_package_imports.py(+46),tests/bundle/test_tool_skills_declared.py(+77),tests/cli/test_config_show.py(+73)Breaking change
--skills-dirargv flag is removed fromamplifier-agent run. Migration paths (per D13):$AMPLIFIER_SKILLS_DIR(preserved as the adapter-bridge surface). Thetool-skillsmodule continues to honor it.skills:block to your host_config JSON and pass via--config <path>or$AMPLIFIER_AGENT_CONFIG:{ "skills": { "skills": ["/path/to/extra/skills"], "visibility": {"max_skills_visible": 20} } }Verification evidence
tests/config/,tests/cli/test_single_turn.py,tests/cli/test_package_imports.py,tests/bundle/,tests/cli/test_config_show.py)git diff origin/main..HEADon the failing test files (test_mode_a_v2_envelope.py,test_phase_2_1_exit_gate.py,test_conformance_parity.py) is empty; failures are MCP-related or caused by missingtsxtoolchain (environment issue)amplifier-agent prepareexits 0 against fresh cache; pickle inspection confirmstool-skillsmounts with correct source URL and config block including all three default skill sources andvisibility.enabled: trueruff checkruff format --checkpyright(changed surface)What's NOT done (intentionally deferred)
amplifier-agent runagainst a host_config that appends a skill source and confirm the binary loads both curated and appended skills. Requires provider API key + network access; not blocking ship per the design's own framing (structural correctness is proven at unit + integration + cold-prepare layers).model_roledeclarations in skills will silently no-op untilamplifier-bundle-routing-matrixis composed (per the design, this is safe — confirmed by the regression testtest_fork_skill_model_role_falls_through_when_no_resolverin the upstream tool-skills module). Separate follow-on.Commit history
20 commits (after the final 2 in this PR):
Co-author
Generated with Amplifier.