Files
openclaw-openclaw/docs/plugins/copilot.md
Peter Steinberger f7d7148cf0 docs: rewrite published docs grounded in current source (#100142)
Source-grounded rewrite of 529 published docs pages with per-unit information-loss verification: 1,713 factual corrections cited to src/**, generated surfaces regenerated, frontmatter titles preserved for i18n, release notes pages untouched. All docs gates green.

Closes #100141
2026-07-05 00:32:47 -04:00

19 KiB
Executable File

summary, title, read_when
summary title read_when
Run OpenClaw embedded agent turns through the external GitHub Copilot SDK harness Copilot SDK harness
You want to use the GitHub Copilot SDK harness for an agent
You need configuration examples for the `copilot` runtime
You are wiring an agent to subscription Copilot (github / openclaw / copilot) and want it to run through the Copilot CLI

The external @openclaw/copilot plugin runs embedded subscription Copilot agent turns through the GitHub Copilot CLI (@github/copilot-sdk) instead of OpenClaw's built-in PI harness. The Copilot CLI session owns the low-level agent loop: native tool execution, native compaction (infiniteSessions), and CLI-managed thread state under copilotHome. OpenClaw still owns chat channels, session files, model selection, dynamic tools (bridged), approvals, media delivery, the visible transcript mirror, /btw side questions (see Side questions (/btw)), and openclaw doctor.

For the broader model/provider/runtime split, start with Agent runtimes.

Requirements

  • OpenClaw with the @openclaw/copilot plugin installed.
  • If your config uses plugins.allow, include copilot (the manifest id the plugin declares). An allowlist entry for the npm package name @openclaw/copilot will not match and leaves the plugin blocked, even with agentRuntime.id: "copilot" set.
  • A GitHub Copilot subscription that can drive the Copilot CLI, or a gitHubToken env var / auth-profile entry for headless or cron runs.
  • A writable copilotHome directory. Defaults to <agentDir>/copilot when OpenClaw provides an agent directory, otherwise ~/.openclaw/agents/<agentId>/copilot.

openclaw doctor runs the plugin's doctor contract for session-state ownership and future config migrations. It does not probe the Copilot CLI environment.

Install

The Copilot runtime ships as an external plugin so the core openclaw package does not carry @github/copilot-sdk or its platform-specific @github/copilot-<platform>-<arch> CLI binary (roughly 260 MB together). Install it only for agents that opt into this runtime:

openclaw plugins install @openclaw/copilot

The setup wizard installs the plugin automatically the first time you select a github-copilot/* model and your config routes that model (or its provider) to the Copilot runtime via agentRuntime: { id: "copilot" }; see Quickstart. Without that opt-in, OpenClaw uses its built-in GitHub Copilot provider and never installs this plugin.

The runtime resolves the SDK in this order:

  1. import("@github/copilot-sdk") from the installed @openclaw/copilot package.
  2. The fallback dir ~/.openclaw/npm-runtime/copilot/ (legacy on-demand install target).

A missing SDK surfaces one error with code COPILOT_SDK_MISSING and the reinstall command above.

Quickstart

Pin one model (or one provider) to the harness:

{
  agents: {
    defaults: {
      model: "github-copilot/auto",
      models: {
        "github-copilot/auto": {
          agentRuntime: { id: "copilot" },
        },
      },
    },
  },
}

Set agentRuntime.id on a single model entry to route only that model through the harness, or on a provider to route every model under that provider.

github-copilot/auto is the portable starting point. Named Copilot models are account- and organization-policy-dependent; confirm your authenticated Copilot CLI actually exposes a model before pinning it.

Supported providers

The harness supports the canonical github-copilot provider (owned by extensions/github-copilot), plus custom models.providers entries when the model has a non-empty baseUrl and one of these api shapes:

  • anthropic-messages
  • azure-openai-responses
  • ollama (OpenAI-compatible completions)
  • openai-completions
  • openai-responses

Native provider ids (openai, anthropic, google, ollama) stay owned by their native runtimes. Use a distinct custom provider id to route an endpoint through Copilot BYOK instead.

Copilot BYOK endpoints must be public HTTPS URLs. The harness gives the Copilot SDK a per-attempt loopback proxy, then forwards provider traffic through OpenClaw's guarded fetch path so DNS pinning and SSRF policy stay owned by OpenClaw. Use the native OpenClaw runtime for local Ollama, LM Studio, or LAN model servers.

BYOK

Copilot BYOK uses the SDK's session-level custom provider contract. OpenClaw passes the resolved model endpoint, API key, bearer-token mode, headers, model id, and context/output limits; provider transport logic stays in the SDK, not core.

{
  agents: {
    defaults: {
      model: "custom-proxy/llama-3.1-8b",
      models: {
        "custom-proxy/llama-3.1-8b": {
          agentRuntime: { id: "copilot" },
        },
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      "custom-proxy": {
        baseUrl: "https://api.example.com/v1",
        apiKey: "${CUSTOM_PROXY_API_KEY}",
        api: "openai-responses",
        authHeader: true,
        models: [{ id: "llama-3.1-8b", name: "Llama 3.1 8B" }],
      },
    },
  },
}

BYOK sessions are keyed separately from subscription sessions and from other BYOK endpoints or credentials. Rotating the key, headers, model, or endpoint starts a fresh Copilot SDK session instead of resuming incompatible state.

Auth

Precedence, applied per agent during runCopilotAttempt:

  1. Explicit useLoggedInUser: true on the attempt input — uses the Copilot CLI's logged-in user under the agent's copilotHome.

  2. Explicit gitHubToken on the attempt input (requires profileId + profileVersion). For direct CLI invocations and tests that need to bypass auth-profile resolution.

  3. Contract-resolved resolvedApiKey + authProfileId — the production main path. Core resolves the agent's configured github-copilot auth profile (src/infra/provider-usage.auth.ts:resolveProviderAuths) before invoking the harness, so a github-copilot:<profile> auth profile works end-to-end for headless, cron, or multi-profile setups without env vars.

  4. Env-var fallback, checked in this order (first non-empty value wins, empty strings count as absent; mirrors the shipped github-copilot provider precedence in extensions/github-copilot/auth.ts):

    1. OPENCLAW_GITHUB_TOKEN — harness-specific override; lets you pin a token for the OpenClaw harness without disturbing system-wide gh / Copilot CLI config.
    2. COPILOT_GITHUB_TOKEN — standard Copilot SDK / CLI env var.
    3. GH_TOKEN — standard gh CLI env var.
    4. GITHUB_TOKEN — generic GitHub token fallback.

    The synthesized pool profile id is env:<NAME>; the profile version is a non-reversible sha256 fingerprint of the token, so rotating the env value busts the client pool cleanly.

  5. Default useLoggedInUser when no token signal is available.

Each agent gets its own copilotHome so Copilot CLI tokens, sessions, and config never leak between agents on the same machine. Default: <agentDir>/copilot (keeps SDK state out of the same directory as OpenClaw's models.json / auth-profiles.json), or ~/.openclaw/agents/<agentId>/copilot when no agent directory is supplied. Override with copilotHome: <path> on the attempt input for a custom location (for example, a shared mount for migration).

Live harness tests use OPENCLAW_COPILOT_AGENT_LIVE_TOKEN for a direct token. The shared live-test setup scrubs COPILOT_GITHUB_TOKEN, GH_TOKEN, and GITHUB_TOKEN after staging real auth profiles into the isolated test home, so a gh auth token value passed through the dedicated variable avoids false skips without leaking into unrelated suites.

Configuration surface

The harness reads config from per-attempt input (runCopilotAttempt({...})) plus a small set of env defaults inside extensions/copilot/src/:

Field Purpose
copilotHome Per-agent CLI state directory (defaults above).
model String or { provider, id, api?, baseUrl?, headers?, authHeader? }. Omit to use the agent's normal model selection; the harness verifies the resolved provider is supported.
reasoningEffort "low" | "medium" | "high" | "xhigh". Maps from OpenClaw's ThinkLevel / ReasoningLevel resolution in auto-reply/thinking.ts.
infiniteSessionConfig Optional override for the SDK infiniteSessions block driven by harness.compact. Safe to leave as-is.
hooksConfig Optional native Copilot SDK SessionHooks config for tool/MCP, user-prompt, session, and error callbacks. Separate from OpenClaw's portable lifecycle hooks.
permissionPolicy Optional override for the SDK's onPermissionRequest handler for built-in SDK tool kinds (shell, write, read, url, mcp, memory, hook). Defaults to rejectAllPolicy as a safety net; see Permissions and ask_user for why it never actually fires.
enableSessionTelemetry Optional SDK session telemetry flag.

OpenClaw plugin hooks need no Copilot-specific attempt configuration. The harness runs before_prompt_build (and the legacy before_agent_start compatibility hook), llm_input, llm_output, and agent_end through the standard harness helpers. Successful SDK compactions also run before_compaction and after_compaction. Bridged OpenClaw tools run before_tool_call and report after_tool_call; hooksConfig remains for native SDK-only callbacks with no portable equivalent.

Nothing else in OpenClaw needs to know about these fields. Other plugins, channels, and core code see only the standard AgentHarnessAttemptParams / AgentHarnessAttemptResult shape.

Compaction

When harness.compact runs, the Copilot SDK harness:

  1. Resumes the tracked SDK session without continuing pending work.
  2. Calls the SDK's session-scoped history compaction RPC.
  3. Returns the SDK compaction outcome without writing compatibility marker files under the workspace.

The OpenClaw-side transcript mirror (below) keeps receiving post-compaction messages, so user-facing chat history stays consistent.

Transcript mirroring

runCopilotAttempt dual-writes each turn's mirrorable messages into the OpenClaw audit transcript via extensions/copilot/src/dual-write-transcripts.ts. The mirror is scoped per session (copilot:${sessionId}) and keyed per message (${role}:${sha256_16(role,content)}), so re-emitted prior-turn entries collide with existing on-disk keys instead of duplicating.

Two layers of failure containment wrap the mirror so a transcript write failure never fails the attempt: an internal best-effort wrapper, plus a defense-in-depth .catch(...) at the attempt level. Failures are logged, not surfaced.

Side questions (/btw)

/btw is not native on this harness. createCopilotAgentHarness() deliberately leaves harness.runSideQuestion undefined (asserted in extensions/copilot/harness.test.ts, describe("runSideQuestion")), so OpenClaw's /btw dispatcher (src/agents/btw.ts) falls through to the same path it uses for every non-Codex runtime: the configured model provider is called directly with a short side-question prompt and streamed back via streamSimple (no CLI session, no extra pool slot).

This keeps Copilot CLI sessions reserved for the agent's main turn loop, and keeps /btw behavior identical to other non-Codex runtimes.

Doctor

extensions/copilot/doctor-contract-api.ts is auto-loaded by src/plugins/doctor-contract-registry.ts. It contributes:

  • An empty legacyConfigRules (no retired fields yet).
  • A no-op normalizeCompatibilityConfig (kept so future field retirements have a stable in-tree home).
  • One sessionRouteStateOwners entry: provider github-copilot, runtime copilot, CLI session key copilot, auth profile prefix github-copilot:.

Limitations

  • The harness claims github-copilot plus unowned custom BYOK provider ids. Manifest-owned native provider ids stay on their owning runtime even when agentRuntime.id is forced to copilot.
  • No TUI surface; PI's TUI remains the fallback for runtimes without a peer surface.
  • PI session state does not migrate when an agent switches to copilot. Selection is per attempt; existing PI sessions remain valid.
  • ask_user uses the same OpenClaw prompt-and-reply path as the Codex harness: when the Copilot SDK asks for user input, OpenClaw posts a blocking prompt to the active channel/TUI, and the next queued user message resolves the SDK request.

Permissions and ask_user

Permission enforcement for bridged OpenClaw tools happens inside the tool wrapper, not via the SDK's onPermissionRequest callback. The same wrapToolWithBeforeToolCallHook that PI uses (src/agents/agent-tools.before-tool-call.ts) is applied by createOpenClawCodingTools to every coding tool: loop detection, trusted plugin policies, before-tool-call hooks, and two-phase plugin approvals via the gateway (plugin.approval.request) all run through the exact same code path as native PI attempts.

The SDK Tool returned by convertOpenClawToolToSdkTool is marked with:

  • overridesBuiltInTool: true — replaces the Copilot CLI's built-in tool of the same name (edit, read, write, bash, ...) so every tool call routes back to OpenClaw.
  • skipPermission: true — tells the SDK not to fire onPermissionRequest({kind: "custom-tool"}) before invoking the tool. The wrapped execute() already performs the richer OpenClaw policy check; an SDK-level prompt would either short-circuit OpenClaw's enforcement (allow-all) or block every tool call (reject-all) — neither matches PI parity.

The in-tree Codex harness uses the same split: bridged OpenClaw tools are wrapped (extensions/codex/src/app-server/dynamic-tools.ts) and the codex-app-server's own native approval kinds (item/commandExecution/requestApproval, item/fileChange/requestApproval, item/permissions/requestApproval) route through plugin.approval.request (extensions/codex/src/app-server/approval-bridge.ts). The Copilot SDK equivalent — fail-closed rejectAllPolicy for any non-custom-tool kind that ever reaches onPermissionRequest — is the same safety net, and it never fires in practice because overridesBuiltInTool: true displaces every built-in.

For the wrapped-tool layer to make policy decisions equivalent to PI, the harness forwards the full PI attempt-tool context to createOpenClawCodingTools: identity (senderIsOwner, memberRoleIds, ownerOnlyToolAllowlist, ...), channel/routing (groupId, currentChannelId, replyToMode, message-tool toggles), auth (authProfileStore), run identity (sessionKey / runSessionKey derived from sandboxSessionKey, runId), model context (modelApi, modelContextWindowTokens, modelCompat, modelHasVision), and run hooks (onToolOutcome, onYield). Without those fields, owner-only allowlists silently deny by default, plugin-trust policies cannot resolve to the right scope, and session_status: "current" resolves to a stale sandbox key. The bridge builder is extensions/copilot/src/tool-bridge.ts, mirroring the PI authoritative call at src/agents/embedded-agent-runner/run/attempt.ts:1262. runAttempt resolves sandbox context through the shared resolveSandboxContext seam, passes the SDK an effective working directory, and forwards sandbox plus the subagent-spawn workspace into the tool bridge. The bridge also forwards the bounded tool-construction controls it can enforce at the SDK boundary: includeCoreTools, the runtime tool allowlist, and toolConstructionPlan.

The bridge also uses the shared harness tool-surface helper from openclaw/plugin-sdk/agent-harness-tool-runtime for PI parity. When tool-search is enabled, the SDK sees compact control tools plus a hidden catalog executor instead of every OpenClaw tool schema. When code mode is enabled, the helper builds the same code-mode control surface and catalog lifecycle used by other agent harnesses. Local-model lean defaults, runtime-compatible schema filtering, directory hydration, and catalog cleanup all stay in the shared helper so Copilot and Codex-adjacent harnesses do not drift.

Session-level GitHub token

The Copilot SDK contract distinguishes the client-level GitHub token (CopilotClientOptions.gitHubToken, authenticates the CLI process itself) from the session-level token (SessionConfig.gitHubToken, determines content exclusion, model routing, and quota for that session; honored on both createSession and resumeSession). The harness resolves auth once via resolveCopilotAuth and sets both fields when the auth mode is gitHubToken (an explicit auth.gitHubToken or a contract-resolved resolvedApiKey from a configured github-copilot auth profile). When the resolved mode is useLoggedInUser, the session-level field is omitted so the SDK keeps deriving identity from the logged-in identity.

ask_user uses SessionConfig.onUserInputRequest. The bridge accepts choice indexes or labels for fixed-choice requests, accepts free-form answers when the SDK request allows them, and cancels a pending request when the OpenClaw attempt is aborted.