Skip to content

feat: host config layer + drop hostCapabilities surface#27

Merged
manojp99 merged 70 commits into
mainfrom
feat/host-config-and-drop-host-capabilities
Jun 2, 2026
Merged

feat: host config layer + drop hostCapabilities surface#27
manojp99 merged 70 commits into
mainfrom
feat/host-config-and-drop-host-capabilities

Conversation

@manojp99

@manojp99 manojp99 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Adds a persistent host-set config layer between the sealed bundle and per-turn argv,
and removes the hostCapabilities surface that has zero read sites today.

Implements two paired designs:

  • docs/designs/2026-06-01-drop-host-capabilities.md (Phase 1)
  • docs/designs/2026-06-01-host-config-layer-revisit.md (Phase 2)

Phase 1 — Drop hostCapabilities (BREAKING)

Removes the write-only hostCapabilities infrastructure across engine, wrappers, schemas, conformance fixtures, and tests. Both v1 capabilities (supports_steering, supports_structured_errors) resolved to "didn't need engine negotiation" — no behavior was gated on them.

Breaking changes (wrapper API):

  • wrappers/typescript/src/index.ts: drop SpawnAgentParams.host field; drop exported HostCapabilities + InitializeHostParams types
  • wrappers/python/src/amplifier_agent_client/: drop host/host_capabilities params from spawn_agent, assemble_argv, SessionHandleParams
  • --host-capabilities argv flag removed from amplifier-agent run — old callers see click UsageError, exit 2

Cross-repo follow-up: the nanoclaw adapter must drop host: { capabilities: ... } from its spawnAgent call. Tracked separately. ← Reviewer action required


Phase 2 — Host config layer

Adds --config <path> resolution (flag > $AMPLIFIER_AGENT_CONFIG env > no config tier → bundle defaults), JSON format with json.load, pass-through schema mirroring downstream module configs, layered merge over bundle declarations.

New surface:

  • --config <path> argv flag (wires the previously-stub at single_turn.py:406)
  • $AMPLIFIER_AGENT_CONFIG env var
  • 4 top-level config keys: mcp, approval, provider, allowProtocolSkew

Removed argv surface (BREAKING — subsumed by config):

  • --env-allowlist, --env-extra, --allow-protocol-skew
  • AMPLIFIER_AGENT_ALLOW_PROTOCOL_SKEW env var

Internal cleanup:

  • provider_detect.py deleted (vestigial — provider selection now from config or new bundle.md default_provider: field)
  • XDG resolution consolidated through persistence.py (was duplicated in bundle/cache.py and admin/doctor.py)
  • amplifier-agent config show extended to report resolved path, source tier, and parsed values — survives parse failures gracefully

New module: src/amplifier_agent_lib/config/{loader,merger}.py + tests/config/


Stats

Commits 65
Files changed 68 (+6,654 / −890)
New tests Config loader (13), merger (5), integration (2) — all passing
ruff check Clean
Phase 1 success-metric greps All zero (modulo intentional removal guardrails)
Phase 2 success-metric adversarials All hard-fail with expected error codes

Tests

  • All Phase 1 + Phase 2 tests pass
  • TypeScript wrapper: 62/62 pass
  • Python wrapper: 48/48 pass
  • Conformance (TS + Py): 5/5 each pass
  • Loud-failure verification: amplifier-agent run --host-capabilities '{}' "hi" → click UsageError, exit 2 ✓

Pre-existing failures NOT introduced by this work

17 engine-suite failures pre-date this PR and are out of scope. Verified by git stash + re-run against the baseline. Root causes:

  • 12 failures: protocol-0.2.0 drifttest_engine.py, test_engine_with_real_bundle.py, test_phase_2_1_exit_gate.py, test_protocol_gen.py hardcode protocolVersion: "0.1.0" but engine speaks 0.2.0 since commit ea51d05
  • 3 failures: --mcp-servers rename gaptest_mode_a_v2_envelope.py, test_single_turn_init_params.py use the renamed flag (renamed to --mcp-config-path in ea51d05)
  • 2 failures: pyproject.toml wheel-build duplicate-includetool.hatch.build.targets.wheel.force-include double-counts protocol/schemas/

Recommend a follow-up tracking issue: "protocol-0.2.0 rename drift + wheel-build cleanup" — independent of this PR's scope.

Design decision history

This PR locks decisions D1–D10 of the host config layer revisit. Key choices made during design conversation:

  • JSON over YAML — ecosystem consistency, no Norway problem, no safe_load mandate, no wrapper YAML dep, easier library extensibility
  • 2-tier resolution (flag > env), no XDG default — programs-first stance avoids silent shared-$HOME collision
  • Pass-through to module configs — amplifier-agent invents no vocabulary; mcp:, approval:, provider: mirror their module's schemas
  • Strict-by-default validation — unknown top-level keys hard-fail; forward-compat is the host's responsibility via --protocol-version

Supersedes

  • Mode A amendment §2.6 D12 (hostCapabilities field) → superseded by Phase 1
  • Mode A amendment §3 (argv surface for env-allowlist, env-extra, allow-protocol-skew) → superseded by Phase 2

Follow-up actions (out of scope)

  • File nanoclaw repo issue: "Drop host: { capabilities } from adapter spawnAgent call" — text drafted in docs/plans/2026-06-01-drop-host-capabilities-implementation.md H3
  • Tracking issue for 17 pre-existing test failures (protocol-0.2.0 rename drift + wheel-build)
  • pnpm-lock.yaml has a pnpm-version-drift marker (.: {}) — unrelated, left uncommitted in this PR

Manoj Prabhakar Paidiparthy and others added 30 commits June 1, 2026 21:00
Adds the locked design for a persistent host-set config layer between
sealed bundle.md and per-turn argv.

Key decisions:
- 2-tier resolution: --config flag > $AMPLIFIER_AGENT_CONFIG env. No XDG default.
- Hard error on missing path in either tier.
- YAML with yaml.safe_load only.
- 4 top-level keys (mcp, approval, provider, allowProtocolSkew) as pass-through
  to module configs (tool-mcp, hooks-approval, provider modules).
- Layered merge over bundle defaults.
- Bundle gains default_provider: field; provider_detect.detect_provider() is
  removed as vestigial.
- Strict-by-default validation: no escape hatch on unknown keys.
- amplifier-agent config show extended to report resolved path + source.
- XDG resolution consolidated through persistence.py.
- Drops argv flags: --env-allowlist, --env-extra, --allow-protocol-skew.
- Drops env var: AMPLIFIER_AGENT_ALLOW_PROTOCOL_SKEW.

Supersedes Mode A amendment §3 surface.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…ion)

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Remove the --host-capabilities click option, function parameter, and
parser invocation from single_turn.py. Insert a transitional
host_capabilities = None binding so downstream call sites (engine wiring,
mount preset, fingerprint) keep compiling until A3/A4/A5 land.

The removal-guardrail test (test_host_capabilities_flag_not_in_help) now
PASSES.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…velope)

Remove the host_capabilities keyword-only argument from _build_envelope
and the conditional that wrote it into metadata. Update the success-path
call site in run() to stop passing host_capabilities. Append a guardrail
test (test_success_envelope_metadata_excludes_host_capabilities) that
verifies hostCapabilities never appears in success envelope metadata.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…r_envelope)

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…sitional shim

Remove host_capabilities from _write_audit signature, audit dict body,
and all three call sites (AaaError, generic Exception, success paths).
Delete the transitional 'host_capabilities = None' shim placed in Task A2.

Adds test_audit_dict_excludes_host_capabilities which patches
session_state_dir to tmp_path, calls _write_audit without the removed
kwarg, reads the audit JSON, and asserts hostCapabilities is absent.

grep host_capabilities src/amplifier_agent_cli/modes/single_turn.py
returns zero hits. Module imports cleanly. All 4 removal-guardrail
tests pass.

🤖 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>
…w-up)

The InitializeHostParams TypedDict was deleted in 26906d7 but the
InitializeParams.host field still referenced it, causing NameError
when _gen.main ran. This was a missed step in Task B2.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Removes the two now-obsolete protocol schema files and prunes their
entries from test_protocol_gen.py expected set. The schema generator
no longer emits them since the underlying TypedDicts were dropped
in 26906d7.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…lope

The --host-capabilities CLI flag was removed in prior commits; this test
exercised that removed surface and now fails with click exit code 2
('No such option: --host-capabilities'). Deleting the obsolete test.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…+ update consumer sets

🤖 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>
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…v emission

Removes the hostCapabilities field from the AssembleArgvInput interface
and the corresponding emission block from assembleArgv(). Adds a
regression test asserting that argv emitted for a minimal session
contains no 'host'-prefixed flag.

Note: session.ts (lines 117, 251) and index.ts (line 163) still
reference hostCapabilities; tsc --noEmit will fail until task F3
removes the host? field from SpawnAgentParams and the SessionHandle
plumbing.
Removes the HostCapabilities import/re-export, the `host?` field from
SpawnAgentParams, and the spread that forwarded `host.capabilities` to
SessionHandle. Also cleans up the trailing references in session.ts:
the HostCapabilities import, the `hostCapabilities?` field on
SessionHandleParams, and the now-unrecognized `hostCapabilities` key
passed to assembleArgv (a0e02f0 already dropped that field).

BREAKING: callers passing `params.host` now get a TS error and silent
runtime drop.

Tests: tsc --noEmit clean; vitest run 62/62 pass.

Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Remove the HostCapabilities interface, the InitializeHostParams envelope,
and the host? field on InitializeParams. Also strip orphaned JSDoc
blocks left behind by the deletions.

Task: F4

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…with ts)

Aligns Python wrapper with TS wrapper changes (a0e02f0, 86291bf, 193492f).
Removes the host parameter from spawn_agent signature, docstring,
conditional host_capabilities extraction block, and SessionHandleParams
constructor call. argv_builder still accepts host_capabilities directly;
that path is cleaned in G2.

Tests: 48/48 pass.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Remove the host_capabilities keyword parameter and its conditional
emission from assemble_argv(). Invert the corresponding test to assert
the --host-capabilities flag is not present in the produced argv.

Part of drop-host-capabilities sequence. session.py still passes
host_capabilities= to assemble_argv — that call site is broken at
runtime until the follow-up session.py task lands. No tests currently
exercise the session.py->assemble_argv path, so the full Python wrapper
test suite passes.

🤖 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>
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
….2.0 rename)

The audit-trail test was still using the pre-protocol-0.2.0 --mcp-servers
inline JSON flag, which was renamed to --mcp-config-path (path-based) in
commit ea51d05. This rename was missed when that PR landed and was caught
by Phase 1 H2 full-suite verification.

Replaces inline JSON with a tmpfile path; updates the audit assertion
from mcpServersDigest to mcpConfigPathDigest (the path digest the engine
now emits).
…1 follow-up)

Phase 1 missed deletion: G2 dropped host_capabilities from assemble_argv,
but SessionHandleParams still held the field and threaded it into the
assemble_argv call. Remove both the dataclass field and the caller arg.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
… (B2 follow-up)

Three tests in test_wire_types_v01.py asserted shapes that no longer exist:

- test_host_capabilities_typed_dict_exists: HostCapabilities TypedDict was
  removed by the drop-host-capabilities work (B2).
- test_initialize_params_has_mcp_servers_field: the 'mcpServers' field was
  superseded by 'mcpConfigPath' in the protocol-0.2.0 rename.
- test_initialize_params_has_host_field: the 'host' field was removed by B2
  together with HostCapabilities.

Delete those three tests. test_mcp_server_config_typed_dict_exists remains
valid (McpServerConfig is still part of the protocol surface) and is kept.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…lizeParams (C1 follow-up)

InitializeParams.schema.json still carried a $ref to InitializeHostParams.schema.json
even though that schema file was deleted in C1, leaving a dangling reference.

The schemas/ tree is generator output — regenerate it from the authoritative
TypedDicts via 'python -m amplifier_agent_lib.protocol._gen'. This:

- removes the 'host' property (dangling InitializeHostParams $ref) — the C1
  Phase 1 miss we're fixing
- removes the now-renamed 'mcpServers' property, replacing it with
  'mcpConfigPath' (protocol-0.2.0 rename, source already updated in
  methods.py — generator output was just stale)
- bumps the protocol version banner in spec.md from 0.1.0 to 0.2.0 to match
  PROTOCOL_VERSION in methods.py

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…llow-up)

The committed dist tree still contained old code referencing hostCapabilities,
HostCapabilities, and InitializeHostParams from before Phase 1's drop-
host-capabilities work. Running 'npm run build' regenerates:

- src/types.ts via the prebuild step (gen-types from the now-clean JSON
  schemas — see C1 follow-up)
- dist/ via tsc

After the rebuild, dist/ no longer references any of the removed symbols.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Manoj Prabhakar Paidiparthy and others added 10 commits June 2, 2026 08:15
Add test_config_show_succeeds_when_config_malformed: write a host
config with truncated JSON, invoke 'config show --config <path>',
and assert exit_code == 0 with parsed=None and
parse_error.code == 'config_malformed_json'.

Operators must be able to locate the offending config file before
they can debug its contents, so config show stays exit-0 even when
the loader raises ConfigError and surfaces the loader's error code
under host_config.parse_error. Implementation already lives in F3;
this test locks the contract.

Implements D8.

🤖 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>
🤖 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>
Auto-fixed by `ruff check --fix` as part of G4 Step 3 verification.
No functional change.
…s, not phantom keys

The previous code passed dict(prepared.mount_plan) to merge_config() as
bundle_modules, treating mount_plan's top-level section names ("tools",
"hooks", "providers") as if they were module IDs. The merger expects
{module_id: config_dict}, so merged.get("tool-mcp", {}) always returned {}.
The .update() then wrote merged results into phantom top-level keys
(mount_plan["tool-mcp"]) that the kernel ignored, since the kernel reads
tool/hook/provider configs from the list entries at
mount_plan["tools"][n]["config"] etc. Net effect: every mcp:, approval:,
and provider.config: override in the host config was silently dropped.

The fix builds a proper {module_id: config_dict} view from the list
entries, runs merge_config(), then writes merged values back into the
SAME list entries in-place. The kernel now sees host overrides where
foundation's mount logic reads them.

Tests:
  - Replace the masked _FakePrepared fixture in test_runtime_config_merge.py
    with one that mirrors Bundle.to_mount_plan()'s real shape (lists of
    {module, config, source} dicts at tools/hooks/providers).
  - Add per-section assertions that merged values land in the list entries
    and NOT in phantom top-level keys.
  - Add end-to-end regression test in tests/config/test_integration.py.

Diagnosed by bug-hunter; the unit test masked the bug because it used the
wrong mount_plan shape (module IDs at the top level — the shape the merger
expects, but not the shape PreparedBundle.mount_plan actually has).
…NFIG (D4)

The CLI --mcp-config-path flag has been forwarded to tool-mcp via the
AMPLIFIER_MCP_CONFIG env var since the wire protocol introduced it, but
the equivalent host-config path (mcp.configPath in YAML/JSON) was never
wired. configPath is an engine-level convenience key, not a tool-mcp
config key — tool-mcp's own config.py picks it up from the env var via
its standard priority chain.

When both CLI and host config provide a path, CLI wins (matches the
documented precedence: explicit invocation flags > config file).

Tests:
  - test_mcp_config_path_in_host_sets_env_var: host config → env var.
  - test_cli_mcp_config_path_takes_precedence_over_host: CLI > host.

Both tests use monkeypatch.delenv to start from a known state and let
monkeypatch's teardown restore the prior env.
…roke wheel build

The packages = ["src/amplifier_agent_lib"] entry already ships everything under
protocol/schemas/ and protocol/conformance/fixtures/. The additional force-include
entries for those directories caused hatchling to reject the build with:

  ValueError: A second file is being added to the wheel archive at the same path:
  amplifier_agent_lib/protocol/schemas/AgentShutdownParams.schema.json.

Surfaced when reinstalling the host-config branch into a Digital Twin Universe.

Closes 2 of the 17 pre-existing failures previously noted as out of scope (test_bundle_packaging,
test_phase_2_1_packaging).
Profile generated by amplifier-tester:setup-digital-twin during PR
validation. Mirrors this branch to a local Gitea instance and provisions
amplifier-agent 0.3.0 in an Incus container so reviewers can verify the
host config layer + hostCapabilities removal end-to-end.

Launch:
  amplifier-digital-twin launch \\
    .amplifier/digital-twin-universe/profiles/amplifier-agent-host-config-pr27.yaml \\
    --var GITEA_URL=<your-gitea-url> --var GITEA_TOKEN=<your-token>
…vitest bump)

Resolves 10 conflicts between this PR's hostCapabilities removal + host
config layer work and main's protocol-0.2.0 rename pass (#24).

Strategy: combine both changes — our hostCapabilities deletions are
preserved; main's mcpServers → mcpConfigPath renames are preserved.
The deleted initialize-with-host-capabilities.yaml fixture stays deleted
(modify/delete resolved in favor of our deletion).

Side effect: PR #24's protocol-0.2.0 fixes close most of the pre-existing
tech debt that this PR's description previously documented as out of scope.
@manojp99 manojp99 merged commit d587306 into main Jun 2, 2026
2 of 3 checks passed
manojp99 added a commit that referenced this pull request Jun 3, 2026
…g + env var) (#29)

* test(engine): add removal guardrail for --mcp-config-path argv flag

Pure intent-capture commit (RED): the assertions in this test file FAIL
against the current source. The matching implementation drops in the next
commit (chore(engine)!: drop --mcp-config-path argv flag).

Mirrors the Phase 1 cleanup pattern from tests/cli/test_drop_host_capabilities.py:
each guardrail asserts an absence at a specific seam — click --help output,
click usage rejection, _TurnSpec dataclass fields, make_turn_handler signature,
and _runtime.py source — so future refactors that re-introduce the flag fail
loudly at this layer instead of via downstream regressions.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

* chore(engine)!: drop --mcp-config-path argv flag

Pure removal, no flag tolerance — click rejects with `Error: No such
option '--mcp-config-path'` (exit 2). Mirrors Phase 1's hostCapabilities
removal posture.

After PR #27 merged the host config layer (commit 70121c3) and PR #24
renamed `--mcp-servers` to `--mcp-config-path`, three equivalent paths
set AMPLIFIER_MCP_CONFIG for tool-mcp:
  (1) host_config["mcp"]["configPath"]   — host policy via config file
  (2) AMPLIFIER_MCP_CONFIG env var       — read natively by tool-mcp
  (3) --mcp-config-path argv flag        — redundant survivor

Per Mode A amendment §2.5 D9 + host config D10, per-invocation flags stay
argv and host policy moves to config. `mcpConfigPath` is host policy.
This commit removes path (3); paths (1) and (2) remain.

Engine changes (src/amplifier_agent_cli/modes/single_turn.py):
 - Drop @click.option("--mcp-config-path") declaration and the matching
   `mcp_config_path` parameter on run().
 - Drop the §(5b) validation block that gated the flag value as a real
   file (mcp_config_path_invalid).
 - Drop `mcp_config_path` from the _TurnSpec dataclass and from the
   make_turn_handler kwargs in _execute_turn.
 - Drop `mcp_config_path` parameter on _write_audit and the
   `mcpConfigPathDigest` audit field. The field was only populated by
   the argv flag; no remaining surface feeds it. The envDigest
   precedent of preserving stable digests with empty placeholders
   does not apply here — the field carried no information once the
   source was gone, so dropping it cleanly is preferable.

Engine changes (src/amplifier_agent_lib/_runtime.py):
 - Drop the `mcp_config_path` parameter on make_turn_handler.
 - Drop the CLI-flag → AMPLIFIER_MCP_CONFIG translation block.
 - The host_config["mcp"]["configPath"] → AMPLIFIER_MCP_CONFIG block
   (added by 70121c3) remains; the `not mcp_config_path` precedence
   guard is dropped because the CLI path is gone.
 - The wire-side `handle_initialize` path (`params["mcpConfigPath"]` →
   `_wire_mcp_config_path` → AMPLIFIER_MCP_CONFIG) is untouched; it is
   protocol-0.2.0 surface, not argv surface.

Test updates:
 - tests/cli/test_drop_mcp_config_path_flag.py (added in the previous
   commit) now passes — all five guardrails green.
 - tests/cli/test_drop_host_capabilities.py: drop the obsolete
   mcp_config_path=None kwarg from the _write_audit call.
 - tests/cli/test_mode_a_audit_trail.py: rewrite to invoke `run`
   without the removed flag and assert mcpConfigPathDigest is absent
   from the audit (negative guardrail).
 - tests/test_runtime_config_merge.py: drop
   test_cli_mcp_config_path_takes_precedence_over_host (precedence
   semantics are gone with the CLI path). The host-config translation
   test stays.
 - tests/test_runtime_mcp_threading.py: drop the two make_turn_handler
   CLI-path tests; rename the surviving "no source" test to reflect
   host_config being the only engine-side translation site. The
   wire-side handle_initialize tests are untouched.

Cross-repo follow-up: amplifier-nc-provider's adapter currently passes
`--mcp-config-path` to the engine subprocess (this was set up by the
PR #24 rename). It will break against this engine. Same shape as
Phase 1's hostCapabilities NC issue — file a tracking issue in the
adapter repo to migrate the spilled path forwarding from the argv
flag to AMPLIFIER_MCP_CONFIG via the subprocess environment.

BREAKING CHANGE: The `--mcp-config-path` argv flag was removed from
`amplifier-agent run`. Callers must migrate to one of:
  (1) host config: `mcp.configPath: /path/to/mcp.json` in the file
      consumed by --config; the engine translates it to
      AMPLIFIER_MCP_CONFIG at make_turn_handler boot.
  (2) ambient env: set AMPLIFIER_MCP_CONFIG=/path in the engine's
      subprocess environment; tool-mcp reads it natively via its
      config-discovery priority chain.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

* chore(wrapper-py)!: drop mcp_config_path from assemble_argv + spawn_agent

Lockstep with the engine drop (chore(engine)!: drop --mcp-config-path
argv flag). The Python wrapper still spills the supplied mcp_servers
dict to a 0600 tmpfile (CR-A); the forwarding path changes from argv
flag to subprocess env var.

argv_builder.py:
 - Drop `mcp_config_path` parameter from `assemble_argv` and the
   `--mcp-config-path` emission block. Note the new path in the
   docstring (AMPLIFIER_MCP_CONFIG injected into subprocess env by
   SessionHandle._make_iterable, or set by the host directly).

session.py:
 - Drop the `mcp_config_path=spill["config_path"]` kwarg from the
   `assemble_argv` call in `_make_iterable`.
 - Build a fresh subprocess env dict per submit; when the spill
   produced a path, inject `AMPLIFIER_MCP_CONFIG=<path>` into it
   and pass that to `asyncio.create_subprocess_exec`. The
   SessionHandle's stored `subprocess_env` is never mutated.
 - Update the step-numbered comment block to reflect the env-var
   forwarding mechanism.

mcp_spill.py:
 - Docstring updates only — replace references to "engine receives
   the plain file path via --mcp-config-path" with the new "caller
   injects the spilled path into the engine's subprocess environment
   as AMPLIFIER_MCP_CONFIG" description.

__init__.py:
 - Update the spawn_agent() docstring for `mcp_servers` to describe
   the env-var forwarding mechanism.

tests/test_argv_builder.py:
 - Rename case (iv) from "threaded as bare path when caller
   pre-spilled" to "flag is not emitted".
 - Add case (v): asserts `assemble_argv(..., mcp_config_path=...)`
   raises TypeError so callers that still pass the obsolete kwarg
   fail loudly instead of silently no-opping.

BREAKING CHANGE: assemble_argv() no longer accepts the
`mcp_config_path` keyword argument. Callers using the public spawn_agent
API are unaffected — the wrapper handles the env-var injection
internally. Callers depending on the lower-level argv_builder must
remove the kwarg and inject AMPLIFIER_MCP_CONFIG into their
subprocess environment themselves.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

* chore(wrapper-ts)!: drop mcpConfigPath from AssembleArgvInput + argv emission

Lockstep with the engine drop (chore(engine)!: drop --mcp-config-path
argv flag) and the Python wrapper drop (chore(wrapper-py)!: drop
mcp_config_path from assemble_argv + spawn_agent). The TypeScript
wrapper still spills the supplied mcpServers map to a 0600 tmpfile
(CR-A); the forwarding path changes from argv flag to subprocess env
var.

src/argv-builder.ts:
 - Drop `mcpConfigPath` field from `AssembleArgvInput`.
 - Drop the `--mcp-config-path` emission block in `assembleArgv`.
 - Document the new forwarding mechanism (AMPLIFIER_MCP_CONFIG injected
   into subprocess env by SessionHandle.makeIterable, or set by the
   host directly).

src/session.ts:
 - Drop the `mcpConfigPath: spill.configPath ?? undefined` field from
   the `assembleArgv` call in `makeIterable`.
 - Build a fresh `subprocessEnv` per submit; when the spill produced a
   path, inject `AMPLIFIER_MCP_CONFIG=<path>` into it and pass that
   object to `childSpawn`. The SessionHandle's stored
   `params.subprocessEnv` is never mutated.
 - Update the step-numbered JSDoc to reflect the env-var forwarding
   mechanism and the dropped argv flag.
 - Update the `mcpServers` field's JSDoc on `SessionHandleParams`.

src/mcp-spill.ts:
 - Docstring updates only — replace references to "engine receives the
   plain file path via --mcp-config-path" with the new description.

src/index.ts:
 - Update the JSDoc on `SpawnAgentParams.mcpServers` to describe
   env-var forwarding.

test/argv-builder.test.ts:
 - Rename case (iv) from "threaded as plain path" to "is not emitted
   (removed surface)".
 - Add case (v): a compile-time guardrail using `@ts-expect-error` to
   assert `AssembleArgvInput` no longer accepts `mcpConfigPath`; if a
   future refactor re-adds the field, the directive becomes a build
   error.

dist/*: regenerated by `npm run build` — these are committed artifacts
in this wrapper. Source-of-truth changes live in src/ and test/; the
dist/ delta is mechanical.

BREAKING CHANGE: `AssembleArgvInput` no longer exposes the
`mcpConfigPath` field. Callers using the public `spawnAgent` API are
unaffected — the wrapper handles env-var injection internally. Callers
depending on the lower-level `assembleArgv` must remove the field and
inject `AMPLIFIER_MCP_CONFIG` into their subprocess environment
themselves.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

---------

Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
manojp99 added a commit that referenced this pull request Jun 3, 2026
…from wrappers (sync with engine) (#31)

* chore(wrapper-ts)!: drop envAllowlist, envExtra, allowProtocolSkew from AssembleArgvInput + argv emission

The engine removed the --env-allowlist, --env-extra, and
--allow-protocol-skew argv flags in PR #27 (host config layer). The
TypeScript wrapper was still emitting them when the corresponding
AssembleArgvInput / SessionHandleParams / SpawnAgentParams fields
were set, which made engine click reject the invocation with UsageError
(exit 2) for any wrapper consumer that supplied them.

This commit removes:
- envAllowlist, envExtra, allowProtocolSkew from AssembleArgvInput
  and the three argv.push(...) blocks in assembleArgv()
- envAllowlist, envExtra, allowProtocolSkew from SessionHandleParams
  and the threading through to assembleArgv()
- allowProtocolSkew from SpawnAgentParams and the threading through to
  new SessionHandle(...)

SpawnAgentParams.env = { allowlist, extra } is preserved unchanged - the
wrapper still composes the subprocess environment via buildEnv(). Only
the FORWARDING of those values as argv flags to the engine is removed.

argv-builder tests are extended with @ts-expect-error compile-time
guardrails and runtime negative assertions for all three flags, mirroring
PR #29's removal pattern for --mcp-config-path.

NC follow-up: the NC adapter (amplifier-module-provider-nc) may pass
envAllowlist / envExtra / allowProtocolSkew to spawnAgent(); a follow-up
adapter PR will catch it up.

BREAKING CHANGE: SpawnAgentParams.allowProtocolSkew,
SessionHandleParams.envAllowlist / envExtra / allowProtocolSkew, and the
corresponding AssembleArgvInput fields are removed. Hosts that need the
skew override now set host_config.allowProtocolSkew: true in the JSON
config file. Hosts that need custom env composition either set
$AMPLIFIER_AGENT_CONFIG in the subprocess env or pass --config <path>
per turn.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

* chore(wrapper-py)!: drop env_allowlist, env_extra, allow_protocol_skew from spawn_agent + assemble_argv

The engine removed the --env-allowlist, --env-extra, and
--allow-protocol-skew argv flags in PR #27 (host config layer). The
Python wrapper was still emitting them when the corresponding kwargs were
set, which made engine click reject the invocation with UsageError
(exit 2) for any wrapper consumer that supplied them.

This commit removes:
- env_allowlist, env_extra, allow_protocol_skew kwargs from assemble_argv
  and the three argv.extend(...) / argv.append(...) blocks
- env_allowlist, env_extra, allow_protocol_skew fields from
  SessionHandleParams (and the unused `field` import that they required)
  plus the threading into assemble_argv
- allow_protocol_skew kwarg from spawn_agent and the threading into
  SessionHandleParams

spawn_agent(env={"allowlist": ..., "extra": ...}) is preserved unchanged
- the wrapper still composes the subprocess environment via build_env().
Only the FORWARDING of those values as argv flags to the engine is
removed.

test_argv_builder is extended with three TypeError-rejected kwarg tests
and three "flag not emitted" negative assertions, mirroring PR #29's
removal pattern for --mcp-config-path.

NC follow-up: the NC adapter (amplifier-module-provider-nc) may pass
env_allowlist / env_extra / allow_protocol_skew to spawn_agent(); a
follow-up adapter PR will catch it up.

BREAKING CHANGE: spawn_agent.allow_protocol_skew,
SessionHandleParams.env_allowlist / env_extra / allow_protocol_skew, and
the corresponding assemble_argv kwargs are removed. Hosts that need the
skew override now set host_config.allowProtocolSkew: true in the JSON
config file. Hosts that need custom env composition either set
$AMPLIFIER_AGENT_CONFIG in the subprocess env or pass --config <path>
per turn.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

* chore(wrapper-ts): rebuild dist after wrapper API removals

Regenerates wrappers/typescript/dist/ to reflect the source removals in
796f10d (drop envAllowlist, envExtra, allowProtocolSkew from
AssembleArgvInput / SessionHandleParams / SpawnAgentParams + argv
emission).

Mirrors PR #29's pattern of committing the rebuilt dist alongside the
source change so consumers using the published package see the API
narrowing immediately. No additional source changes.

🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>

---------

Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
manojp99 added a commit that referenced this pull request Jun 3, 2026
…#27, #29, #31) (#32)

* fix(conformance): rename runner refs to initialize-with-mcp-config-path (post-#24)

PR #24 renamed the wire-shape fixture from
``initialize-with-mcpservers.yaml`` to
``initialize-with-mcp-config-path.yaml`` but left three hardcoded
references to the old name behind:

  * wrappers/conformance/test/runner-ts.test.ts (TS conformance suite)
  * wrappers/conformance/tests/test_runner_py.py (Py conformance suite)
  * tests/test_phase_2_1_exit_gate.py (engine-side fixture-set guard)

All three have been failing since PR #24. The conformance suites
showed up as "1 pre-existing failure each" in PRs #27, #29, and
#31, so wrapper/engine drift could land unseen during the entire
window in which we did most of the v0.3.0 argv cleanup -- the
exact class of bug the conformance suite is supposed to catch.

This commit restores the baseline to green by pointing each
reference at the surviving wire-protocol fixture name. No fixture
data changes, no engine logic changes.

Verification (this commit, host machine):
  * wrappers/conformance/ (TS): 5 passed (was 4 passed, 1 failed)
  * wrappers/conformance/ (Py): 5 passed (was 4 passed, 1 failed)
  * tests/test_phase_2_1_exit_gate.py: passes (was failing)

Out of scope (separate cleanup, noted for the PR body):
  * tests/cli/test_mode_a_v2_envelope.py still has 3 pre-existing
    failures referencing the removed ``--mcp-servers`` flag.
    Not touched here per the conformance-restore PR scope.

* chore(conformance): align version_skew remediation text with post-#27 host-config surface

PR #27 dropped the --allow-protocol-skew argv flag and moved protocol-skew opt-in into the host config file (allowProtocolSkew: true, surfaced through --config). The engine's real protocol_version_mismatch remediation string was updated in lockstep (src/amplifier_agent_lib/engine.py:160-163).

The version_skew wire-shape fixture's scripted server response still carried the old 'pass --allow-protocol-skew' remediation text, drifting it from current engine behavior even though no assertion in the fixture exercises the field directly. Update the scripted response to match the engine's current text so the fixture remains an honest exemplar of the wire-protocol surface post-#27/#29/#31.

Companion to PR #31 fixture sweep. No engine logic changes, no assertions changed, no test outcome changes.

* test(conformance): add baseline + protocol-skew-override wire-shape fixtures

Extend the conformance suite with two wire-protocol fixtures that lock in the post-cleanup surface and the previously-uncovered override branch of the version-skew contract:

* initialize-baseline.yaml -- canonical minimum init + turn/submit + result/final flow with only protocol-required params (no mcpConfigPath, no allowProtocolSkew, no hostCapabilities, no envAllowlist, no envExtra). Locks in the wire shape that survives PR #27 (host config + drop hostCapabilities), PR #29 (drop --mcp-config-path argv), and PR #31 (drop env-allowlist / env-extra / allow-protocol-skew from wrappers). Any future re-introduction of those legacy fields on the wire must be an intentional, reviewed addition rather than silent drift.

* initialize-with-protocol-skew-override.yaml -- the 'permits the handshake' branch of design §8 D6, companion to version_skew.yaml which covers only the strict-refuse branch. Client sends allowProtocolSkew=true (now sourced from the host config file, not from a --allow-protocol-skew argv flag) and a mismatched protocolVersion; server returns a successful sessionState instead of protocol_version_mismatch.

Both new fixtures are wired into the TS and Python conformance runners and acknowledged in the two fixture-set guard tests (tests/test_protocol_conformance_fixtures.py and tests/test_phase_2_1_exit_gate.py).

Scope note: the conformance runners use scripted JSON-RPC wire replay (ScriptedTransport) -- they do NOT spawn the engine subprocess. Argv-level 'loud failure' invariants for the dropped flags (--host-capabilities, --env-allowlist, --env-extra, --allow-protocol-skew, --mcp-config-path) are already locked in by the engine-side click-CliRunner tests at tests/cli/test_drop_host_capabilities.py and tests/cli/test_drop_mcp_config_path_flag.py. Extending the conformance runner to also exercise argv-level invariants would require subprocess invocation and changes to the loader at src/amplifier_agent_lib/protocol/conformance/loader.py (engine-internal, off limits per the PR scope) and is intentionally left for a future PR.

Verification (host machine):

* wrappers/conformance/ (TS): 7 passed (was 5 after rename commit)

* wrappers/conformance/ (Py): 7 passed (was 5 after rename commit)

* tests/test_protocol_conformance_fixtures.py: 17 passed (was 16; the new fixtures load and validate)

* tests/test_phase_2_1_exit_gate.py: passes (now acknowledges the new fixtures in the sorted-name guard)

* Engine suite (uv run pytest -q --ignore=wrappers): 519 passed, 3 pre-existing failures in tests/cli/test_mode_a_v2_envelope.py (unchanged from PR #31's pre-existing baseline -- they reference the long-removed --mcp-servers flag and are tracked for a separate cleanup).

* Wrappers/python: 48 passed; wrappers/typescript: 63 passed; manual smoke confirms --host-capabilities / --env-allowlist / --env-extra / --allow-protocol-skew / --mcp-config-path all reject with click UsageError ('no such option').

---------

Co-authored-by: Manoj Prabhakar Paidiparthy <mpaidiparthy@microsoft.com>
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
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).
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 pushed a commit that referenced this pull request Jun 3, 2026
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/).
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>
manojp99 added a commit that referenced this pull request Jun 3, 2026
* fix(packaging): drop non-existent workspace members from pyproject.toml

The [tool.uv.workspace] block declared three members but only
wrappers/python actually exists in the tree. The two packages/...
entries have never existed at any commit. Most uv versions tolerate
this (warn or silently skip), but specific uv-version + config
combinations would resolve the workspace install to an ancestor
commit where pre-PR-#27 wheel-build duplicates were still present,
producing confusing hatchling errors at uv tool install time.

Declare only the real member: wrappers/python.

* chore(release): bump engine to 0.4.1 + CHANGELOG entry

---------

Co-authored-by: release <mpaidiparthy@microsoft.com>
manojp99 pushed a commit that referenced this pull request Jun 3, 2026
…th (#1)

Engine PR #27 / v0.4.0 added the --config <path> flag and the
host_config layer (approval mode, MCP servers, provider defaults,
allowProtocolSkew, etc.). The wrapper had no surface to forward this,
so callers had to fall back to AMPLIFIER_AGENT_CONFIG in env.extra.

This change:

  - Adds SpawnAgentParams.configPath?: string (public, @public TSDoc).
  - Adds AssembleArgvInput.configPath?: string.
  - assembleArgv emits --config <path> when configPath is set.
  - Threads configPath through SessionHandleParams to the per-submit
    argv assembly.

Also drive-by adds approvalMode field to AssembleArgvInput (used by
#10's commit). The argv-builder now reads input.approvalMode and emits
-y / -n / nothing accordingly. Default remains -y for backward compat
with callers that haven't opted into the approval API.

Closes #1.
manojp99 added a commit that referenced this pull request Jun 3, 2026
…, approval, getEngineInfo, +5 more) (#36)

* feat(wrapper-ts): re-export internal helpers from index.ts (#5)

Adds named re-exports from the package entry point so consumers can
import internal helpers without reaching into private deep paths:

  assembleArgv, AssembleArgvInput
  resolveMcpConfigPath, cleanupSpillFile, McpSpillResult
  buildEnv, resolveBinaryPath, probeEngineVersion,
    DEFAULT_ALLOWLIST, BLOCKED_ENV_KEYS,
    ResolveBinaryPathOptions, BuildEnvOptions
  Transport, TransportOptions, ExitInfo
  checkProtocolVersion, VersionCheckResult, VersionCheckOk,
    VersionCheckFail, CheckProtocolVersionOptions
  parseRunOutput, STDERR_TAIL_BYTES, SubprocessOutcome
  makeApprovalHandler, ApprovalAdapter, ApprovalRequest,
    ApprovalHandler

Each export is annotated @public.

Closes #5.

* feat(wrapper-ts): wire checkProtocolVersion() into init path (#9)

spawnAgent() now probes the engine's protocol version once during
initialization (via amplifier-agent version --json) and runs
checkProtocolVersion() against PROTOCOL_VERSION_REQUIRED_BY_WRAPPER
BEFORE constructing a SessionHandle. Mismatch fails fast wrapper-side
with AaaError(protocol_version_mismatch), saving a full subprocess
roundtrip later.

Adds two new SpawnAgentParams fields:
  - allowProtocolSkew?: boolean — bypass the check (mirrors engine's
    host_config.allowProtocolSkew)
  - _engineVersionProbe?: () => Promise<EngineVersionPayload> —
    test-only injection point for the probe

Also bumps PROTOCOL_VERSION_REQUIRED_BY_WRAPPER from "0.2.0" to
"0.3.0" to match the engine's current wire protocol
(amplifier_agent_lib.protocol.methods.PROTOCOL_VERSION). The wrapper
was shipping with a stale pin; the new check would have surfaced this
at startup.

Closes #9.

* feat(wrapper-ts): add runChildProcess injection point (#3)

Adds SpawnAgentParams.runChildProcess?: ChildProcessFactory — a public
seam to substitute the subprocess factory used inside SessionHandle.
When set, the wrapper invokes the factory in place of
child_process.spawn, preserving the same options shape (detached, stdio,
env, optional cwd).

Useful for:
  - Sandboxing (e.g. wrapping the child in a container or namespace)
  - Test doubles (e.g. EventEmitter fakes that drive scripted outputs)
  - Harness wrapping (e.g. observing the subprocess from outside)

ChildProcessFactory is exported as a @public type from index.ts.

Closes #3.

* feat(wrapper-ts)!: wire Transport NDJSON pipeline + dispatch to display.onEvent (#2, #4, #6)

The engine emits one JSON object per line on the child subprocess's
stderr stream for each wire-protocol notification (progress,
result/delta, result/final, thinking/delta, thinking/final,
tool/started, tool/completed, approval/request, approval/timeout,
plus wire-level error). Before this change the wrapper buffered
stderr as raw text and silently dropped every event — the existing
Transport class implemented NDJSON parsing but was never wired
anywhere (dead code).

This change:

  - Adds parseNdjsonStream(stream, {onJson, onNonJson?}) — a
    standalone helper extracted from the parsing logic Transport
    already had. Resolves when the stream emits 'close'. Exported
    @public.

  - Wires parseNdjsonStream onto child.stderr inside
    SessionHandle.makeIterable(). JSON lines are parsed into
    'notification' DisplayEvents and dispatched to
    params.display?.onEvent. Non-JSON lines (and JSON lines, for
    completeness) are still accumulated into stderrBuf so the
    stderrTail surface on parseRunOutput remains diagnostically
    useful.

  - Extends the DisplayEvent discriminated union with a new
    {type: 'notification', method: string, params: unknown}
    variant. **BREAKING**: existing exhaustive switch statements
    on event.type will no longer be exhaustive without a
    notification branch.

  - Threads SpawnAgentParams.display through to SessionHandle so
    the callback that was previously silently dropped is now
    actually fired (Issue #4).

Closes #2, #4, #6.

BREAKING CHANGE: display.onEvent callbacks are now actually invoked
with wire-event notifications. Callers that registered onEvent
expecting it to be a no-op may observe new event flow. The
DisplayEvent union has a new 'notification' variant; exhaustive
switch statements need a corresponding branch.

* feat(wrapper-ts): surface --config flag via SpawnAgentParams.configPath (#1)

Engine PR #27 / v0.4.0 added the --config <path> flag and the
host_config layer (approval mode, MCP servers, provider defaults,
allowProtocolSkew, etc.). The wrapper had no surface to forward this,
so callers had to fall back to AMPLIFIER_AGENT_CONFIG in env.extra.

This change:

  - Adds SpawnAgentParams.configPath?: string (public, @public TSDoc).
  - Adds AssembleArgvInput.configPath?: string.
  - assembleArgv emits --config <path> when configPath is set.
  - Threads configPath through SessionHandleParams to the per-submit
    argv assembly.

Also drive-by adds approvalMode field to AssembleArgvInput (used by
#10's commit). The argv-builder now reads input.approvalMode and emits
-y / -n / nothing accordingly. Default remains -y for backward compat
with callers that haven't opted into the approval API.

Closes #1.

* feat(wrapper-ts)!: wire approval API to engine -y/-n + approval.mode (#10)

Previously, SpawnAgentParams.approval threw AaaError(
approval_not_supported_in_v1) whenever set because it required the
mid-turn onRequest callback that v1 doesn't support.

This change extends SpawnAgentParams.approval to also accept the
static-policy shape { mode: 'yes' | 'no' | 'prompt' }, which maps to
engine argv:

  - 'yes'    -> -y (auto-allow every tool call)
  - 'no'     -> -n (auto-deny every tool call)
  - 'prompt' -> emit no flag; engine falls back to
                host_config.approval.mode or the bundle's TTY-based
                default. This is how a host hands policy resolution
                back to the engine.

The legacy { onRequest, timeoutMs } form still throws
approval_not_supported_in_v1 — the Mode A wire has no mid-turn
channel. Mid-turn callbacks will return when WG-4 lands.

Engine compatibility: { mode: 'prompt' } requires
amplifier-agent >= 0.4.0 (PR #34 added host_config.approval.mode).

Closes #10.

BREAKING CHANGE: SpawnAgentParams.approval is now a union shape;
callers passing { mode } no longer hit approval_not_supported_in_v1.
Callers that defensively catch that error need to remove the try/catch
when migrating to the mode shape.

* feat(wrapper-ts): implement getEngineInfo() — engineVersion + bundleDigest (#7)

Closes the Task-9 TODO: getEngineInfo() now returns the values
captured during the engine version probe that spawnAgent() runs at
init (Issue #9). Previously both fields were hardcoded empty strings.

  - engineVersion populated from `amplifier-agent version --json`
    payload's `version` field.
  - bundleDigest populated from the probe payload's optional
    `bundleDigest` field. The engine's current `version --json`
    output (from admin/version_info.py) only emits {version,
    protocolVersion} — bundleDigest will be empty string until a
    future engine release exposes it. Forward-compatible: when the
    engine adds it, the wrapper picks it up automatically with no
    further changes.

DONE_WITH_CONCERNS for the bundleDigest follow-up: filed as an
engine-side ask for a future PR. The wrapper does what it can with
the data the engine surface exposes today; the contract is wired
so the field will populate the moment the engine emits it.

Closes #7.

* chore(wrapper-ts): rebuild dist after hardening release changes

Mirrors PR #29 / #31 pattern: dist/ is tracked so consumers installing
from the git tarball get the compiled artifacts without a build step.

Regenerated from npm run build after issues #1, #2, #3, #4, #5, #6, #7,
#9, #10 landed.

* chore(release): bump amplifier-agent-ts to 0.6.0 + CHANGELOG

Wrapper hardening release closing 8 consumer-reported gaps at 0.5.0:
  #1  configPath surface
  #2  stderr NDJSON parsing
  #3  runChildProcess injection
  #4  display.onEvent dispatch
  #5  public re-exports
  #6  Transport dead code (root cause of #2/#4)
  #7  getEngineInfo() implementation
  #9  checkProtocolVersion() wired into init path
  #10 approval API mapped to engine -y/-n + approval.mode

Issue #8 in the consumer report was a misread — InitializeParams.
mcpConfigPath is intentionally retained in protocol-0.3.0. No
type change needed; the schema is canonical and correct.

This is a minor bump per 0.x convention even though some changes
are BREAKING — the wrapper hasn't shipped a 1.0 yet, so breaking
changes ride minor bumps. See CHANGELOG for the BREAKING list.

Engine compatibility: requires amplifier-agent >= 0.4.0.
Pinned protocol: 0.3.0.

---------

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.

1 participant