Copilot SDK Driver Specification

Version: 1.0.2
Status: Draft Specification
Latest Version: copilot-sdk-driver-specification
Editor: GitHub Agentic Workflows Team


This specification defines the normative behavior of a Copilot SDK driver that runs an agent session against a Copilot SDK endpoint and emits session telemetry. The specification is language agnostic and focuses on environment variable contracts, permission-checking policy, required logging behavior, and connection-token propagation between the harness-managed sidecar and the driver. Non-normative examples use TypeScript. Conforming implementations provide deterministic permission enforcement, auditable diagnostics, and interoperable runtime behavior across host environments.

This section describes the status of this document at the time of publication. This is a draft specification and may be updated, replaced, or made obsolete by other documents at any time.

This document is governed by the GitHub Agentic Workflows project specifications process.

  1. Introduction
  2. Conformance
  3. Driver Execution Model
  4. Configuration and Environment Variables
  5. Permission Checking Model
  6. Logging Requirements
  7. Error Handling and Exit Behavior
  8. Compliance Testing
  9. Appendices
  10. References
  11. Sync Notes
  12. Change Log

The Copilot SDK driver provides a host-independent contract for starting a Copilot SDK client session, sending a prompt, handling tool permissions, and producing structured operational logs.

This specification covers:

  • Driver configuration inputs and runtime environment variables
  • Permission request evaluation and deny/allow behavior
  • Required lifecycle and policy-denial logging
  • Required error handling and process exit semantics

This specification does NOT cover:

  • Copilot model quality or response content guarantees
  • Host workflow compiler internals
  • SDK transport protocol internals beyond driver-facing inputs

A conforming implementation MUST:

  • Remain language agnostic in externally visible behavior
  • Provide explicit and testable permission decisions
  • Produce consistent, audit-friendly logs for runtime and policy events
  • Fail fast on missing required configuration in standalone mode

A conforming driver implementation satisfies all MUST, REQUIRED, and SHALL requirements in this specification.

A partially conforming driver implementation satisfies all MUST requirements in Sections 4, 5, and 7 but MAY omit optional diagnostics in Section 6.

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Implementations MUST support:

  • Level 1 (Required): Required environment variables, session startup, prompt dispatch, and exit behavior
  • Level 2 (Standard): Permission checking model and denial diagnostics
  • Level 3 (Complete): Full lifecycle logging and structured event serialization interoperability

For extension integrations, a driver implementation MUST support standalone mode (executable entry point that reads configuration from environment variables). Embedded callable APIs are out of scope for this specification.

A conforming implementation MUST execute the following sequence:

  1. Resolve runtime configuration.
  2. Start SDK client connection.
  3. Create a session.
  4. Register event handlers.
  5. Send prompt and await completion.
  6. Return success/failure result.
  7. Perform best-effort cleanup of stream/session/client resources.

A complete implementation (Level 3) SHOULD serialize non-ephemeral session events to a JSON Lines stream compatible with downstream timeline rendering.

The built-in driver retains native event envelopes and payloads, including user messages, reasoning, errors, tool outcomes, and unknown non-ephemeral extensions. It also captures the ephemeral assistant.usage, assistant.message_delta, assistant.reasoning_delta, model.call_failure, and workflow lifecycle observations needed to recover accounting and interrupted conversations. Other ephemeral events are not persisted. Canonical conversion happens in the Copilot session adapter; missing timestamps, tool outcomes, and metrics are not synthesized by the driver. Default summaries continue to omit user prompts.

Bootstrap and collection retain independently parseable native observations from malformed JSONL and record an explicit collection warning. A usable partial native trace is not replayed together with fallback stdio. When fallback is needed, exact identified copies are deduplicated within their observed session; conflicting payloads and independent retry or child observations remain available.

When SDK mode is enabled (COPILOT_SDK_URI is set), the harness MUST generate a per-run COPILOT_CONNECTION_TOKEN and MUST pass the same token value to both:

  1. The harness-managed Copilot sidecar process
  2. The SDK driver subprocess environment

The SDK driver MUST treat COPILOT_CONNECTION_TOKEN as a required input and MUST fail fast with non-zero exit when it is missing.

The harness MUST NOT propagate platform authentication secrets such as GITHUB_TOKEN, COPILOT_GITHUB_TOKEN, or GH_TOKEN into the SDK driver subprocess environment. Driver processes run in a secret-isolated environment and MUST NOT rely on or attempt to read platform authentication tokens.


4. Configuration and Environment Variables

Section titled “4. Configuration and Environment Variables”

In standalone mode, the implementation MUST enforce the following contract:

VariableRequiredDescriptionDefault / Validation
GH_AW_PROMPTYesPath to prompt fileMUST exist and be readable
COPILOT_SDK_URIYesSDK endpoint URIMUST be non-empty
COPILOT_CONNECTION_TOKENYesPer-run shared token generated by the harness in SDK modeMUST be non-empty in the driver environment
COPILOT_MODELYesModel to use (e.g. gpt-4o, claude-sonnet-4)MUST be non-empty; implementations MUST fail fast when unset
COPILOT_SDK_SEND_TIMEOUT_MSNoSend timeout in millisecondsInput SHOULD be a positive integer; gh-aw typically sets this from workflow timeout-minutes; default 600000; implementations MUST fall back on invalid values
GH_AW_MAX_TOOL_DENIALSNoMaximum repeated tool denials before aborting inferenceInput SHOULD be a positive integer; default 5; implementations MUST fall back on invalid values
COPILOT_SDK_LOG_LEVELNoSDK client log levelgh-aw may set this for driver runtime logging; valid values: none, error, warning, info, debug, all; invalid values MUST fall back to warning
GITHUB_WORKSPACENoWorking directory hintSHOULD be used when present

Note: Platform authentication tokens (GITHUB_TOKEN, COPILOT_GITHUB_TOKEN, GH_TOKEN) are NOT available in the SDK driver subprocess environment. Driver implementations MUST NOT reference or depend on these variables.

4.2 Connection Token Requirement and Token Isolation Policy

Section titled “4.2 Connection Token Requirement and Token Isolation Policy”

COPILOT_CONNECTION_TOKEN is a harness-generated per-run secret used by the SDK driver to authenticate to the harness-managed sidecar session.

In SDK mode, a conforming implementation:

  • MUST generate a token value with sufficient entropy for local authentication.
  • MUST propagate the same token to sidecar and driver processes for a given run.
  • MUST require the token in the driver process environment before creating RuntimeConnection.
  • MUST NOT log the raw token value.

The SDK driver subprocess runs in a secret-isolated environment. As a result:

  • COPILOT_CONNECTION_TOKEN is the only authentication token a driver MUST use.
  • A driver SHOULD NOT attempt to read GITHUB_TOKEN, COPILOT_GITHUB_TOKEN, or GH_TOKEN from its environment.
  • A driver MUST NOT use a GitHub platform token as a substitute or supplement for COPILOT_CONNECTION_TOKEN.
  • If a driver encounters an absent GITHUB_TOKEN or COPILOT_GITHUB_TOKEN, it MUST NOT treat this as an error condition. The absence of these variables is expected and normal.

COPILOT_SDK_SEND_TIMEOUT_MS controls maximum send wait duration for prompt completion in standalone mode.

When gh-aw hosts the driver, it generally derives and injects this variable from workflow timeout-minutes (exposed to the harness as GH_AW_TIMEOUT_MINUTES) so SDK completion stays below the step timeout budget.

A conforming driver implementation MUST treat COPILOT_SDK_SEND_TIMEOUT_MS as milliseconds and MUST apply the default value (600000) when unset, non-numeric, or non-positive.

COPILOT_SDK_LOG_LEVEL is the host-provided SDK client log level control. A conforming driver implementation MUST use the provided value when it is one of none, error, warning, info, debug, or all; otherwise it MUST fall back to warning.

4.5 Tool-Denials Guardrail Environment Variable

Section titled “4.5 Tool-Denials Guardrail Environment Variable”

GH_AW_MAX_TOOL_DENIALS controls the catastrophic tool-denials guardrail in SDK mode. A conforming driver implementation MUST count repeated tool refusals (permission denials), and MUST stop inference once the configured threshold is reached. When unset, non-numeric, or non-positive, implementations MUST apply the default value (5).

This counting requirement tightens the specification to match the existing gh-aw reference implementation in actions/setup/js/copilot_sdk_session.cjs and the current copilot_sdk_driver.test.cjs guardrail coverage. It does not change gh-aw runtime behavior, but implementations that previously treated denial counting as optional would need to align with this clarified conformance requirement.

Prerequisite: install @github/copilot-sdk in the runtime where this example executes.

import { readFile } from "node:fs/promises";
import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk";
const promptPath = process.env.GH_AW_PROMPT;
const sdkUri = process.env.COPILOT_SDK_URI;
const connectionToken = process.env.COPILOT_CONNECTION_TOKEN;
const model = process.env.COPILOT_MODEL;
if (!promptPath || !sdkUri || !connectionToken || !model) {
throw new Error("Missing required standalone environment variables.");
}
const rawTimeoutValue = process.env.COPILOT_SDK_SEND_TIMEOUT_MS;
const sendTimeoutMs = rawTimeoutValue && /^[1-9]\d*$/.test(rawTimeoutValue) ? Number(rawTimeoutValue) : 600000;
const allowedLogLevels = new Set(["none", "error", "warning", "info", "debug", "all"]);
const rawLogLevel = process.env.COPILOT_SDK_LOG_LEVEL || "warning";
const logLevel = allowedLogLevels.has(rawLogLevel) ? rawLogLevel : "warning";
const workingDirectory = process.env.GITHUB_WORKSPACE || process.cwd();
const prompt = await readFile(promptPath, "utf8");
const client = new CopilotClient({
connection: RuntimeConnection.forUri(sdkUri, {
connectionToken,
}),
workingDirectory,
logLevel,
});
await client.start();
try {
const session = await client.createSession({ model });
const response = await session.sendAndWait({ prompt }, { timeoutMs: sendTimeoutMs });
console.log(response);
await session.disconnect();
} finally {
await client.stop();
}

The effective permission configuration supports:

  • allowAllTools (boolean)
  • allowedTools (string array)

In scoped allowlist mode, read is treated as a configured permission and MUST be denied unless explicitly allowed.

The driver MUST always configure an onPermissionRequest handler when creating an SDK session. The handler MUST consume the effective permission configuration input and resolve as follows:

  1. If allowAllTools is true, the driver MUST approve all permission requests.
  2. If effective permission configuration is absent, the driver MUST treat the session as unrestricted and approve all permission requests.
  3. If allowedTools is empty after normalization, the driver MUST treat the session as unrestricted and approve all permission requests.
  4. Otherwise, the driver MUST enforce the scoped allow rules below.

When scoped rules are active, the implementation MUST evaluate requests as follows:

  • read: MUST be denied unless allowedTools contains read.
  • write: MUST be approved only when allowedTools contains write.
  • url: MUST be approved only when allowedTools contains web_fetch.
  • custom-tool: MUST be approved only when allowedTools contains the request tool name.
  • mcp: MUST be approved when either:
    • allowedTools contains <serverName>, or
    • allowedTools contains <serverName>(<toolName>).
  • shell: MUST be approved when at least one condition is true:
    • allowedTools contains shell.
    • A shell(<rule>) entry matches the request command identifier.
    • A shell(<full command text>) entry exactly matches full command text.
  • Unknown kinds MUST be rejected.

For shell(<rule>) entries:

  • Rules ending with :* MUST perform prefix matching against command identifiers.
  • Rules without spaces SHOULD be treated as identifier matches.
  • Rules containing spaces MUST be treated as exact full-command matches.

On rejection, the handler MUST return a reject decision with feedback indicating that invocation is not allowed by workflow tool permissions.

type PermissionConfig = {
allowAllTools?: boolean;
allowedTools?: string[];
};
export function canUseWriteTool(config: PermissionConfig): boolean {
if (config.allowAllTools) return true;
return (config.allowedTools ?? []).includes("write");
}

A conforming implementation MUST support:

  • Driver logger output for runtime lifecycle events.
  • Permission-denial diagnostics for policy rejections.

The driver logger MUST emit log entries for:

  1. Connection attempt start
  2. Client start confirmation
  3. Session creation with session identifier
  4. Prompt dispatch start
  5. Completion summary (including output presence and duration)
  6. Runtime error summary on failure

When a permission request is denied, the implementation MUST:

  1. Log a denial entry with a compact request summary.
  2. Emit denial diagnostics to optional secondary loggers when configured.

In standalone mode, missing required environment variables or unreadable prompt input MUST be reported to standard error with a driver-specific prefix.

All structured log entries emitted by a conforming implementation MUST conform to the following field schema. Implementations MUST include all required fields and MAY include additional fields.

FieldTypeRequiredDescription
eventstringMUSTEvent type identifier (e.g., "connection.start", "session.created", "prompt.dispatched", "session.completed", "error")
timestampstring (ISO 8601)MUSTUTC timestamp of the event
sessionIdstringMUST when availableSession identifier assigned at session creation; MUST be omitted before session is established
durationMsnumberMUST for completion eventsElapsed time in milliseconds from prompt dispatch to completion
hasOutputbooleanMUST for completion eventsIndicates whether the session produced a non-empty output
levelstringMUSTLog severity level: one of "info", "warning", "error"
messagestringMUSTHuman-readable description of the event
FieldTypeRequiredDescription
eventstringMUSTMUST be "permission.denied"
timestampstring (ISO 8601)MUSTUTC timestamp of the denial
sessionIdstringMUSTSession identifier in which the denial occurred
requestKindstringMUSTPermission request kind: one of "read", "write", "url", "shell", "mcp", "custom-tool"
requestSummarystringMUSTCompact human-readable summary of the denied request (MUST NOT include secret values or raw token content)
levelstringMUSTMUST be "warning" or "error"

In standalone mode, the implementation MUST exit with a non-zero status when:

  • Any required environment variable is missing
  • Prompt file read fails
  • Unhandled runtime exception occurs

The session result object SHOULD include:

  • Exit code
  • Output text
  • Output-presence indicator
  • Duration in milliseconds

The implementation MUST perform best-effort cleanup of event streams, session handles, and client handles regardless of success or failure.


Implementations MUST provide automated tests for all Level 1 and Level 2 requirements.