You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This binds us to the Copilot CLI's own MCP trust model. #1462 is a direct
symptom: to make the compiler-generated SafeOutputs MCP usable under --allow-all-tools, we had to (a) mark generated MCPG entries isDefaultServer: true via a jq step and (b) add an explicit --allow-tool safeoutputs. Every future quirk of the CLI's flag/trust surface
lands on us the same way.
How gh-aw does it (current main)
gh-aw does not run Copilot as a one-shot command in its SDK path. Instead:
Copilot CLI runs as a long-lived headless sidecar, started/stopped by a
harness:
A Node "driver" drives one session over the @github/copilot-sdk npm
package.copilot_sdk_driver.cjs → copilot_sdk_session.cjs connects to
the sidecar's COPILOT_SDK_URI with a per-run COPILOT_CONNECTION_TOKEN,
reads the prompt file, runs a single programmatic session, and streams JSONL
events. Prompt/tools/model are set through SDK API calls, not CLI flags.
Tool permissions are enforced by a JS onPermissionRequest callback, not
the CLI trust model. The engine serializes the allow list to GH_AW_COPILOT_SDK_SERVER_ARGS (JSON); copilot_sdk_permissions.cjs builds
the handler. --allow-all-tools → { allowAllTools: true } → approveAll,
which approves every request including MCP server calls. No isDefaultServer and no explicit safeoutputs grant are required — the
concept simply does not appear anywhere in gh-aw source.
The harness adds retry/reliability logic around the session (transient
CAPIError 400, auth-failure token refresh, --continue resume in CLI mode,
MCP-policy-blocked detection, permission-denial counting, etc.).
Relevant gh-aw files:
actions/setup/js/copilot_sdk_sidecar.cjs — spawns headless CLI server
actions/setup/js/copilot_sdk_driver.cjs — SDK-driven session entry point
gh-aw pins Copilot CLI 1.0.68 and @github/copilot-sdk1.0.5
(pkg/constants/version_constants.go)
Why this is worth doing for ado-aw
Removes CLI-trust coupling. Permission decisions move into our own JS
callback, so quirks like fix(compile): trust generated SafeOutputs MCP for Copilot #1462 (isDefaultServer, explicit safeoutputs grant
under --allow-all-tools) disappear — we approve MCP calls programmatically.
Programmatic control of the session. Structured JSONL events instead of
scraping CLI stdout; cleaner hooks for audit/telemetry, permission-denied
accounting, and missing-tool reporting.
Reliability. A harness gives us a natural home for retries (transient
400s, auth refresh, MCP-policy-blocked detection) that are awkward around a
one-shot invocation.
Convergence with gh-aw. We already mirror gh-aw's security model; aligning
the Copilot execution mechanism reduces future drift and lets us borrow their
hardening.
Constraints / open questions specific to ado-aw
The naive port is non-trivial because our runtime shape differs from gh-aw:
AWF sandbox boundary. Today the agent's copilot command is the only
thing wrapped by AWF (awf … -- '<engine_run>' in agentic_pipeline.rs::run_agent_step). A sidecar model means we run a headless Copilot server + a Node driver. We need to decide what lives
inside the AWF L7 allowlist boundary vs. outside it, and how the driver
reaches the sidecar (localhost port inside the sandbox?). This must not widen
the network egress surface.
Node/SDK availability in the sandbox. We'd need @github/copilot-sdk
(pinned) plus a Node driver bundle available at runtime. Natural fit for the
existing scripts/ado-script/ ncc-bundle mechanism (a new copilot-sdk-driver
bundle shipped in ado-script.zip), mirroring gh-aw's actions/setup/js.
SafeOutputs MCP wiring. Our SafeOutputs MCP already runs as a host
process reached via host.docker.internal:8100. Under the SDK model the
driver's permission callback replaces --allow-tool safeoutputs / isDefaultServer, but the MCP config handed to the headless server still
needs the MCPG/host URLs. Confirm the headless server consumes the same mcp-config.json (or an SDK-registered equivalent).
BYOK / multi-provider. gh-aw's driver treats multi-provider BYOK as the
only supported mode (GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON). We need to map
our engine: model/provider config onto the SDK's provider shape.
Scope of change. Decide whether to (a) fully replace the one-shot CLI
path, or (b) add SDK mode behind a flag and keep CLI mode as fallback (gh-aw
keeps both). Option (b) is lower risk.
Proposed plan (incremental)
Spike: stand up a copilot --headless --port … sidecar + a minimal Node
driver bundle in one target (standalone) behind an opt-in engine.copilot-sdk: true flag; keep the existing one-shot path as default.
Permission callback: port onPermissionRequest semantics so --allow-all-tools and per-tool allow-lists are enforced in JS; retire the isDefaultServerjq mutation and the forced --allow-tool safeoutputs
from fix(compile): trust generated SafeOutputs MCP for Copilot #1462 in the SDK path.
Reliability harness: add retry/auth-refresh/MCP-policy-blocked handling.
Audit/telemetry: consume the JSONL event stream in place of stdout
scraping.
Roll out across targets (1es/job/stage) and flip the default once stable.
Acceptance criteria
A SafeOutputs workflow runs end-to-end via the sidecar/SDK model with no isDefaultServer mutation and no forced --allow-tool safeoutputs.
AWF network isolation is preserved (no new egress; documented boundary for the
sidecar + driver).
Existing one-shot CLI path remains available (at least during rollout).
Version pins for Copilot CLI + @github/copilot-sdk are centralized and
documented.
Summary
Adopt gh-aw's Copilot "sidecar" (SDK-driver) model for running the Copilot
engine, replacing (or augmenting) the current one-shot
copilot --prompt … --allow-tool … --additional-mcp-config …CLI invocation.Today ado-aw invokes the Copilot CLI as a single-shot command inside the AWF
sandbox:
This binds us to the Copilot CLI's own MCP trust model. #1462 is a direct
symptom: to make the compiler-generated SafeOutputs MCP usable under
--allow-all-tools, we had to (a) mark generated MCPG entriesisDefaultServer: truevia ajqstep and (b) add an explicit--allow-tool safeoutputs. Every future quirk of the CLI's flag/trust surfacelands on us the same way.
How gh-aw does it (current
main)gh-aw does not run Copilot as a one-shot command in its SDK path. Instead:
Copilot CLI runs as a long-lived headless sidecar, started/stopped by a
harness:
(
actions/setup/js/copilot_sdk_sidecar.cjs→buildCopilotSDKServerArgs,startCopilotSDKServer.)A Node "driver" drives one session over the
@github/copilot-sdknpmpackage.
copilot_sdk_driver.cjs→copilot_sdk_session.cjsconnects tothe sidecar's
COPILOT_SDK_URIwith a per-runCOPILOT_CONNECTION_TOKEN,reads the prompt file, runs a single programmatic session, and streams JSONL
events. Prompt/tools/model are set through SDK API calls, not CLI flags.
Tool permissions are enforced by a JS
onPermissionRequestcallback, notthe CLI trust model. The engine serializes the allow list to
GH_AW_COPILOT_SDK_SERVER_ARGS(JSON);copilot_sdk_permissions.cjsbuildsthe handler.
--allow-all-tools→{ allowAllTools: true }→approveAll,which approves every request including MCP server calls. No
isDefaultServerand no explicit safeoutputs grant are required — theconcept simply does not appear anywhere in gh-aw source.
The harness adds retry/reliability logic around the session (transient
CAPIError 400, auth-failure token refresh,
--continueresume in CLI mode,MCP-policy-blocked detection, permission-denial counting, etc.).
Relevant gh-aw files:
actions/setup/js/copilot_sdk_sidecar.cjs— spawns headless CLI serveractions/setup/js/copilot_sdk_driver.cjs— SDK-driven session entry pointactions/setup/js/copilot_sdk_session.cjs— session runner + JSONL eventsactions/setup/js/copilot_sdk_permissions.cjs—onPermissionRequesthandleractions/setup/js/copilot_harness.cjs— retry / reliability wrapperpkg/workflow/copilot_engine_tools.go— allow-list computation (CLI mode)1.0.68and@github/copilot-sdk1.0.5(
pkg/constants/version_constants.go)Why this is worth doing for ado-aw
callback, so quirks like fix(compile): trust generated SafeOutputs MCP for Copilot #1462 (
isDefaultServer, explicit safeoutputs grantunder
--allow-all-tools) disappear — we approve MCP calls programmatically.scraping CLI stdout; cleaner hooks for audit/telemetry, permission-denied
accounting, and missing-tool reporting.
400s, auth refresh, MCP-policy-blocked detection) that are awkward around a
one-shot invocation.
the Copilot execution mechanism reduces future drift and lets us borrow their
hardening.
Constraints / open questions specific to ado-aw
The naive port is non-trivial because our runtime shape differs from gh-aw:
thing wrapped by AWF (
awf … -- '<engine_run>'inagentic_pipeline.rs::run_agent_step). A sidecar model means we run aheadless Copilot server + a Node driver. We need to decide what lives
inside the AWF L7 allowlist boundary vs. outside it, and how the driver
reaches the sidecar (localhost port inside the sandbox?). This must not widen
the network egress surface.
@github/copilot-sdk(pinned) plus a Node driver bundle available at runtime. Natural fit for the
existing
scripts/ado-script/ncc-bundle mechanism (a newcopilot-sdk-driverbundle shipped in
ado-script.zip), mirroring gh-aw'sactions/setup/js.process reached via
host.docker.internal:8100. Under the SDK model thedriver's permission callback replaces
--allow-tool safeoutputs/isDefaultServer, but the MCP config handed to the headless server stillneeds the MCPG/host URLs. Confirm the headless server consumes the same
mcp-config.json(or an SDK-registered equivalent).only supported mode (
GH_AW_COPILOT_SDK_MULTI_PROVIDER_JSON). We need to mapour
engine:model/provider config onto the SDK's provider shape.path, or (b) add SDK mode behind a flag and keep CLI mode as fallback (gh-aw
keeps both). Option (b) is lower risk.
Proposed plan (incremental)
copilot --headless --port …sidecar + a minimal Nodedriver bundle in one target (standalone) behind an opt-in
engine.copilot-sdk: trueflag; keep the existing one-shot path as default.onPermissionRequestsemantics so--allow-all-toolsand per-tool allow-lists are enforced in JS; retire theisDefaultServerjqmutation and the forced--allow-tool safeoutputsfrom fix(compile): trust generated SafeOutputs MCP for Copilot #1462 in the SDK path.
scraping.
Acceptance criteria
isDefaultServermutation and no forced--allow-tool safeoutputs.sidecar + driver).
@github/copilot-sdkare centralized anddocumented.
References
fix(compile): trust generated SafeOutputs MCP for Copilot(the workaround this proposal would obviate)
actions/setup/js/copilot_sdk_*.cjs,copilot_harness.cjspkg/constants/version_constants.go(Copilot CLI1.0.68,@github/copilot-sdk1.0.5)