Skip to content

feat: host-config skills: block (D11/D12/D13) + tool-skills bundle composition#30

Merged
manojp99 merged 20 commits into
mainfrom
feat/host-config-skills-block
Jun 3, 2026
Merged

feat: host-config skills: block (D11/D12/D13) + tool-skills bundle composition#30
manojp99 merged 20 commits into
mainfrom
feat/host-config-skills-block

Conversation

@manojp99

@manojp99 manojp99 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds the skills: block as the fifth top-level key in the host_config layer (D11/D12/D13 amendments to 2026-06-01-host-config-layer-revisit.md), and lands the bundle composition for tool-skills so 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 for skills.skills), D13 ($AMPLIFIER_SKILLS_DIR env 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

Area Files Behavior
Config schema src/amplifier_agent_lib/config/loader.py (+76) skills added to top-level allowlist; skills.skills validated as list[str]; skills.visibility validated as dict; unknown sub-keys rejected; nested keys in visibility pass through
Merger src/amplifier_agent_lib/config/merger.py (+84) List-concatenation for skills.skills (bundle-first, host-appended); dict-overlay for skills.visibility; config_no_matching_module raised for orphan skills: blocks
Bundle composition src/amplifier_agent_lib/bundle/bundle.md (+17) tool-skills module added (source amplifier-bundle-skills@main#subdirectory=modules/tool-skills) with default sources (curated bundle, .amplifier/skills, ~/.amplifier/skills) and default visibility config
CLI surfacing src/amplifier_agent_cli/admin/config_show.py (+64) config show reports merged skills block per D8
CHANGELOG CHANGELOG.md (+30) [Unreleased] entry covering breaking change + new feature + design refs
Tests tests/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) 79 tests added/changed; all green

Breaking change

--skills-dir argv flag is removed from amplifier-agent run. Migration paths (per D13):

  1. Preferred — env var: set $AMPLIFIER_SKILLS_DIR (preserved as the adapter-bridge surface). The tool-skills module continues to honor it.
  2. Or — host_config: add a 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

Check Result
Skills-specific tests (tests/config/, tests/cli/test_single_turn.py, tests/cli/test_package_imports.py, tests/bundle/, tests/cli/test_config_show.py) 79 passed, 0 failed
Full suite 500 passed, 3 skipped, 13 failed
Failures pre-existing? YES — git diff origin/main..HEAD on 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 missing tsx toolchain (environment issue)
Cold-prepare smoke amplifier-agent prepare exits 0 against fresh cache; pickle inspection confirms tool-skills mounts with correct source URL and config block including all three default skill sources and visibility.enabled: true
ruff check clean
ruff format --check clean (after this PR's final commit)
pyright (changed surface) 0 errors, 0 warnings, 0 informations

What's NOT done (intentionally deferred)

  • End-to-end LLM-driven skills discovery smoke (R5.2) — would spawn a real amplifier-agent run against 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).
  • Routing matrix supportmodel_role declarations in skills will silently no-op until amplifier-bundle-routing-matrix is composed (per the design, this is safe — confirmed by the regression test test_fork_skill_model_role_falls_through_when_no_resolver in the upstream tool-skills module). Separate follow-on.

Commit history

20 commits (after the final 2 in this PR):

  • 1 initial: design+plan checkpoint
  • 12 implementation (Phase 1-3 of original plan)
  • 5 recovery (R1-R3 from recovery plan)
  • 2 final (this PR's format fix + recovery plan record)

Co-author

Generated with Amplifier.

Manoj Prabhakar Paidiparthy and others added 20 commits June 2, 2026 17:55
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>
@manojp99

manojp99 commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

DTU validation — full evidence (V1–V6) 🟢

Branch feat/host-config-skills-block was launched in a DTU and validated end-to-end. All six goals PASS, including the host_config append/overlay path (the R5.2 gap noted in the PR description).

Results summary

Goal Surface validated Result
V1 tool-skills mounts + config show reports bundle defaults
V2 load_skill discovers + loads a curated skill (cli-packaging-patterns)
V3 Agent uses loaded skill in reply
V4 skills.skills list-concat (D12) — bundle-first / host-appended
V5 skills.visibility dict-overlay (D5 amended) — host wins on collision, bundle keys preserved
V6 Appended skill discoverable + loadable + usable end-to-end

V1 evidence — config show (bundle defaults only)

"skills": {
  "skills": [
    "git+https://github.com/microsoft/amplifier-bundle-skills@main#subdirectory=skills",
    ".amplifier/skills",
    "~/.amplifier/skills"
  ],
  "visibility": {
    "enabled": true,
    "inject_role": "user",
    "max_skills_visible": 50,
    "ephemeral": true,
    "priority": 20
  }
}

V2/V3 evidence — cli-packaging-patterns round-trip

amplifier-agent run --yes "load cli-packaging-patterns and summarize" produced:

Based on the loaded skill content, here are the three main install/entry patterns covered:

  • uv tool install git+https://... — The primary one-line install method for pure Python CLIs...
  • [project.scripts] entry point — Declares the CLI command in pyproject.toml...
  • __main__.py dual entry fallback — Provides python -m my_tool as a reliable fallback...

These three patterns are literally what cli-packaging-patterns/SKILL.md documents (GitHub fetch → tool-skills load → agent context → reply, full stack traversed).

V4 evidence — list-concat with host_config.json: {"skills": {"skills": ["/root/test-skills"]}}

config show --config /root/host-config-v4.json:

"skills": {
  "skills": [
    "git+https://github.com/microsoft/amplifier-bundle-skills@main#subdirectory=skills",
    ".amplifier/skills",
    "~/.amplifier/skills",
    "/root/test-skills"
  ]
}

Order matches D12: 3 bundle defaults first, host append last.

V5 evidence — dict-overlay with visibility: {"max_skills_visible": 20}

"visibility": {
  "enabled": true,
  "inject_role": "user",
  "max_skills_visible": 20,
  "ephemeral": true,
  "priority": 20
}

max_skills_visible: 20 — host won. enabled, inject_role, ephemeral, priority — bundle defaults preserved. Dict-overlay behaving exactly as D5 (amended) specifies.

V6 evidence — probe skill loaded + executed

A test skill was created at /root/test-skills/dtu-probe-skill/SKILL.md containing description: ... Returns the string "DTU PROBE OK".

amplifier-agent run --config /root/host-config-v4.json --yes "Load the dtu-probe-skill skill and tell me what it says" stream:

[usage] in=10 out=103
[tool/started] load_skill
[tool/completed] load_skill (0ms)
[usage] in=4200 out=63
[result/delta] The skill says to respond with "DTU PROBE OK".

Discovery → load → execution all clean. The host_config append path is fully proven end-to-end.

Minor CLI surface note (worth recording)

--config is on the run and config show subcommands, not the top level. Pattern: amplifier-agent config show --config <path> and amplifier-agent run --config <path> ....

What this closes

The "R5.2 LLM-driven runtime smoke" gap flagged in the PR description as deferred. The append path (D12), the partial visibility overlay (D5 amended), and the full discovery→load→execute round-trip on an appended source are all now empirically verified.

The branch is ready for merge from a validation standpoint.


🤖 Generated with Amplifier

@manojp99 manojp99 merged commit 275a50a into main Jun 3, 2026
2 of 3 checks passed
manojp99 pushed a commit that referenced this pull request Jun 3, 2026
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.
manojp99 pushed a commit that referenced this pull request Jun 3, 2026
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.
manojp99 pushed a commit that referenced this pull request Jun 3, 2026
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.
manojp99 pushed a commit that referenced this pull request Jun 3, 2026
, #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.
manojp99 added a commit that referenced this pull request Jun 3, 2026
…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>
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