Skip to content

Understanding the Copilot Parity Control Plane

What It Is

The Copilot parity control plane is the part of amplihack that makes GitHub Copilot CLI behave like a first-class amplihack runtime without pretending that Copilot and Claude Code expose the same hook surface.

It does three things:

  • stages amplihack behavior into Copilot's .github/ discovery model
  • preserves strict Rust-runner execution for recipes instead of silently falling back
  • keeps Bash security fail-closed through the canonical XPIA hook backed by xpia-defend

Why It Exists

Claude Code and GitHub Copilot CLI both support hooks, but they do not support the same hook capabilities.

  • Claude Code can inject context through hook output.
  • Copilot CLI can block tool use through pre-tool-use, but it does not support the same context-injection contract.
  • Recipe execution can launch nested agents, and Copilot does not accept every Claude-style prompt flag directly.

Without a parity control plane, the same amplihack feature would behave differently depending on which top-level CLI launched it.

How the Pieces Fit Together

1. The launcher stages a Copilot-native surface

src/amplihack/launcher/copilot.py generates .github/hooks/* wrappers and stages agents into .github/agents/. That keeps Copilot on its native discovery path instead of layering a second configuration mechanism on top.

2. The pre-tool wrapper emits exactly one decision

Copilot expects one JSON permission payload on stdout. The generated .github/hooks/pre-tool-use wrapper therefore captures:

  • the amplihack hook result
  • the XPIA hook result

It then applies a fixed precedence order and prints a single payload. This avoids the ambiguity that would come from multiple hook scripts printing independent JSON objects to the same stdout stream.

3. XPIA stays canonical for Bash security

The XPIA hook remains a Python entrypoint even when the amplihack hook engine is Rust-first. That is deliberate.

The canonical file, .claude/tools/xpia/hooks/pre_tool_use.py, is already the fail-closed bridge to xpia-defend. Keeping one canonical entrypoint means:

  • one place to log audit events
  • one place to enforce strict JSON parsing
  • one place to deny Bash when the defender is missing or invalid

pre_tool_use_rust.py exists only so older entrypoints still work.

4. The Rust recipe runner remains strict

The parity slice does not weaken recipe execution just because Copilot is selected.

If recipe-runner-rs is missing or too old, the bridge raises an explicit error. That is better than silently switching execution modes because it keeps failures visible and protects parity claims.

5. Nested Copilot launches normalize only what they must

The compatibility layer merges prompt fragments because Copilot does not accept the same prompt flag mix that Claude-oriented call sites can emit.

It injects --allow-all-tools and --allow-all-paths only when the caller did not already provide an explicit permission decision. This is the smallest safe normalization surface.

Security Model

Fail-closed by default

If xpia-defend is unavailable, malformed, or returns an ambiguous result, the canonical XPIA hook denies Bash.

Precedence favors the explicit security decision

The wrapper gives XPIA first priority because Bash policy evaluation is the security-critical decision in this slice.

No shell interpolation tricks

The Rust runner and compatibility wrappers build structured subprocess argument lists. They do not depend on shell interpolation to assemble nested commands.

Trade-offs

Choice Benefit Cost
One generated pre-tool-use wrapper Copilot sees one unambiguous permission payload Wrapper logic must preserve precedence correctly
Canonical Python XPIA bridge Single fail-closed security entrypoint XPIA is not fully Rust-native yet
Strict Rust-runner gating Visible failures and real parity Missing or outdated binaries stop execution instead of degrading silently
Narrow nested-arg normalization Preserves explicit permission flags Copilot-specific behavior lives in a dedicated compatibility layer

Common Misconceptions

"Parity means both CLIs behave identically"

Not exactly. Parity means amplihack delivers the same user-level feature outcome while respecting the actual contract of each host CLI.

"If Rust is unavailable, Python should just take over"

Not for recipe execution. Silent fallback would hide broken environments and make Python-vs-Rust parity impossible to trust.

"The nested Copilot wrapper is a general CLI adapter"

It is not. It exists only to bridge nested recipe-runner launches into the Copilot CLI contract.

When to Use This Architecture

Use the parity control plane when you need:

  • amplihack copilot to stage hooks and agents consistently
  • Bash security enforcement that remains active in Copilot sessions
  • recipe execution that keeps using Copilot in nested runs
  • one documented and testable decision path for Python and Rust control-plane behavior

When Not to Use It

Do not treat this as a generic wrapper for every Copilot invocation outside amplihack. It is specifically the control plane for staged amplihack launches and nested recipe execution.