Skip to content

Harness matrix

The capability sets below are what halo harnesses prints (from each adapter’s Capabilities()); the mechanisms and paths come from internal/harness/FACTS.md, which cites the vendor page for every claim. Facts for the first four harnesses were last verified against vendor documentation on 2026-09-30; OpenCode and Cursor CLI on 2026-10-21 (see ADR-0019).

Legend: Rendered = the adapter writes the harness’s native enforcement. halod = the CLI cannot enforce it, so Halos’s agent installs the pinned version and reports drift. Root can stop halod, but on rings with posture: enforce the device then loses gateway access, and versionGate: enforce refuses CLI versions the ring does not pin (see gateway gates). No = not rendered; the adapter emits a warning in the release manifest rather than dropping the setting silently.

Terminal window
halo harnesses # table
halo harnesses --output json # machine-readable
Capability Claude Code Codex Gemini CLI Copilot CLI Cursor CLI OpenCode
Version pin Rendered (requiredMinimumVersion / requiredMaximumVersion; the CLI refuses to start outside the range) halod halod halod halod, but its installer cannot pin a version, so halod cannot install it (warns) halod; autoupdate: false is rendered so the CLI does not drift
Model lock Rendered (availableModels, enforceAvailableModels; with a gateway, modelOverrides maps the built-in fallback for a disallowed --model to an alias. The CLI drops a disallowed --model silently, with no setting to refuse it) Rendered Rendered (model.name) No: models.default only, users may override (warns) No: dashboard (warns) Rendered as a provider and model allowlist (enabled_providers, provider.halos.models)
MCP allowlist Rendered (allowManagedMcpServersOnly) Rendered (requirements.toml mcp_servers.<id>.identity; verified against OpenAI docs and schema) Rendered (mcp.allowed) Rendered (allowedMcpServers) No: dashboard (warns) No: OpenCode has no allowlist; mcp.denied disables by name and mcp.servers are rendered (warns)
Hooks lock Rendered (allowManagedHooksOnly) No No No No: system hooks.json is rendered but runs alongside user and project hooks (warns) No: hooks are plugins (warns)
Permissions Rendered (permissions.disableBypassPermissionsMode: "disable" plus rules) Rendered (allowed_approval_policies, allowed_sandbox_modes) Partial: security.disableYoloMode for disableBypass; not a declared capability in halo harnesses Rendered (permissions.disableBypassPermissionsMode: "disable"); permissions.mode and sandbox are not (warns) No: no system-wide location (warns) Rendered (permission per tool, deny last; a global allow is never rendered). disableBypass, mode and sandbox are not (warns)
Gateway Rendered (ANTHROPIC_BASE_URL / Bedrock env) Rendered (model_providers.<id>) Rendered as env via /etc/profile.d (login shells only) No: BYOK is env-only and managed settings cannot set env (warns) No: no base-URL override is documented (warns) Rendered (provider.halos.options.baseURL; anthropic-messages or openai-responses)
Request headers (ring stamps) Rendered (ANTHROPIC_CUSTOM_HEADERS) Rendered (http_headers) No No No Rendered (provider.halos.options.headers)
Telemetry (OTEL) Rendered Rendered ([otel]) Rendered Rendered (http/json, http/protobuf; grpc warns and is not rendered) No: dashboard (warns) No (warns)
Telemetry normalised into halo.* Metrics and log events Log events (native metrics unmapped) Metrics and log events Metrics only: gen_ai.client.token.usage, gen_ai.client.operation.duration, github.copilot.tool.call.count, github.copilot.code.lines_*. No OTel log records (spans and span events only), so no halo.api.request / halo.tool.call rows; github.copilot.code.lines_added/_removed map to halo.loc (type added/removed) None: no documented OTel export from the CLI. Per-request metrics come only from halo-proxy GenAI semconv (gen_ai.*, halo.source=gateway) when traffic goes through the gateway None: no OTLP setting; the exporter is env-only. Per-request metrics come only from halo-proxy GenAI semconv when traffic goes through the gateway
Instructions Managed CLAUDE.md No No No No: team rules are dashboard-side (warns) Rendered (instructions plus a managed file)
MCP denylist (mcp.denied) Rendered (deniedMcpServers; by URL, a name warns) No: requirements.toml has no denylist, so the allowlist is the control (warns) Rendered (mcp.excluded, names only; a URL warns and is skipped) Rendered (deniedMcpServers; by URL, a name warns) No (warns) Partial: each mcp.denied name is rendered as enabled: false; by name only, so a renamed server is not matched (warns)
MCP tool allow/deny (tools, ADR-0029) Deny rendered (permission rule mcp__<server>__<tool>); allow warns (deny always wins, no allowlist) Rendered (enabled_tools, disabled_tools; not on Windows, warns) Rendered (includeTools, excludeTools) No: managed settings have no per-tool key (warns) No (warns) Deny rendered (tools.<server>_<tool>: false); allow warns
MCP auth (auth.mode, ADR-0029) oauth: oauth.clientId/scopes; halos: headersHelper running halo mcp token; issuer, metadata document and enterprise warn No: auth keys not rendered (warns) oauth: oauth block with issuer; halos and enterprise warn No (warns) No (warns) oauth: oauth.clientId/scope; the rest warn
Plugin marketplace allowlist (extensions.marketplaces) Rendered (strictKnownMarketplaces) Rendered (requirements.toml [marketplaces]; github and git become git rules, hostPattern a host_pattern rule; a url source warns) No (warns) Rendered (strictKnownMarketplaces) No (warns) No (warns)
Blocked marketplaces Rendered (blockedMarketplaces) No (warns) No (warns) No (warns) No (warns) No (warns)
Plugin pins (extensions.plugins) Rendered (enabledPlugins; a managed false blocks install at every scope) Partial: plugins.<name>.enabled in managed_config.toml is a default a trusted project can override (warns) No (warns) Rendered (enabledPlugins) No (warns) No (warns)
Skills deny Rendered (permissions.deny Skill(skill:NAME)) No (warns) Rendered (skills.disabled) No (warns) No (warns) No (warns)
Subagents deny Rendered (permissions.deny Agent(NAME)) No (warns) Rendered (agents.overrides.<name>.enabled: false) No (warns) No (warns) No (warns)
Skills / subagents plugin-only Rendered (strictPluginOnlyCustomization) No (warns) No (warns) No (warns) No (warns) No (warns)
Output style, status line Rendered (outputStyle, statusLine) No (warns) No (warns) No (warns) No (warns) No (warns)
Login restriction (login) Rendered (forceLoginMethod: claudeai / console) Rendered (requirements.toml allowed_login_methods: chatgpt / api) Rendered (security.auth.enforcedType; subscription is not honored with a gateway, which needs gemini-api-key) No (warns) No (warns) No (warns)

Fields the adapters cannot render are listed by halo render and halo release build with warnings such as gemini-cli: profile field "hooks.items" is not supported and was not rendered.

Harness macOS Linux Windows
Claude Code /Library/Application Support/ClaudeCode/ /etc/claude-code/ C:\Program Files\ClaudeCode\
Codex /etc/codex/{requirements,managed_config}.toml /etc/codex/ C:\ProgramData\OpenAI\Codex\requirements.toml
Gemini CLI /Library/Application Support/GeminiCli/settings.json /etc/gemini-cli/settings.json C:\ProgramData\gemini-cli\settings.json
Copilot CLI /Library/Application Support/GitHubCopilot/managed-settings.json /etc/github-copilot/managed-settings.json C:\Program Files\GitHubCopilot\managed-settings.json
Cursor CLI /Library/Application Support/Cursor/hooks.json /etc/cursor/hooks.json C:\ProgramData\Cursor\hooks.json
OpenCode /Library/Application Support/opencode/{opencode.json,halos-instructions.md} /etc/opencode/ C:\ProgramData\opencode\

halod writes only under these directories per harness (see delivery). Copilot CLI is in the allowlist too (from its adapter’s harness.Meta). On Linux, codex, gemini-cli and copilot-cli each also own one /etc/profile.d/halos-<harness>.sh. The first four were installed and applied by halod against the real CLIs in make uat-clis; Cursor CLI and OpenCode are not part of that run yet.

Server-managed settings Not fetched with Bedrock or a custom ANTHROPIC_BASE_URL. Hence file, MDM or environment delivery
Files rendered managed-settings.json, managed-mcp.json, CLAUDE.md
MDM com.anthropic.claudecode plist, HKLM\SOFTWARE\Policies\ClaudeCode (used by halo export jamf and intune)
Sandbox Does not run on native Windows; the adapter warns that sandbox and egress settings are not enforced there (use WSL2). read-only has no equivalent and renders as workspace-write; off is overridden when egress.allowedDomains is set
Telemetry Eight OTEL metrics (for example claude_code.cost.usage, claude_code.token.usage, claude_code.code_edit_tool.decision) plus events; OTEL_RESOURCE_ATTRIBUTES carries halo.ring, halo.release, halo.harness
Headless (evals) claude -p --restricted --output-format stream-json --max-budget-usd
Also mcp.denied is matched by name only, which a user can bypass by renaming the server: deny by URL (warns)
Requirements /etc/codex/requirements.toml (Linux, macOS), %ProgramData%\OpenAI\Codex\requirements.toml (Windows). Keys: allowed_approval_policies, allowed_sandbox_modes, mcp_servers.<id>.identity.{command|url} (name and identity must both match; others are disabled)
Managed defaults /etc/codex/managed_config.toml on Unix. Windows has no system-wide path (only ~/.codex), so the adapter renders only requirements.toml there and warns that model, gateway, telemetry and MCP definitions are not rendered
Gateway model_providers.<id> with base_url, env_key, http_headers. Codex speaks only the Responses wire API, so the gateway must too
Permissions approval_policy: on-request, never, or granular; untrusted is unsupported and on-failure deprecated. sandbox_mode never renders danger-full-access; off maps to workspace-write (warns). A permissions.mode with no Codex equivalent maps to on-request (warns)
Telemetry [otel] with otlp-grpc or otlp-http (protocol required for http; logs go to <endpoint>/v1/logs). No resource-attribute key, so on Linux /etc/profile.d/halos-codex.sh wraps codex with OTEL_RESOURCE_ATTRIBUTES (halo.ring, halo.release, halo.harness; login shells)
MDM macOS domain com.openai.codex (requirements_toml_base64); not rendered by Halos
Headless (evals) codex exec --json (JSONL events)
Version pin None in the CLI; halod enforces (warns at render). requirements.toml is enforced from 0.77.0, and codex exec runs under it from 0.99.0 (warns below)
Settings System settings.json; override path with GEMINI_CLI_SYSTEM_SETTINGS_PATH
Keys model.name, mcp.allowed, telemetry.{enabled,target,otlpEndpoint,otlpProtocol,logPrompts}, security.disableYoloMode (refuses YOLO), security.auth.enforcedType: gemini-api-key with a gateway. admin.secureModeEnabled in the system file is ignored (the admin block is remote-only)
Gateway GOOGLE_GEMINI_BASE_URL, honored only with gemini-api-key auth and only over HTTPS or localhost. Settings cannot set env, so the adapter writes /etc/profile.d/halos-gemini.sh (login shells only: GOOGLE_GEMINI_BASE_URL, GEMINI_API_KEY from gateway.auth.helperCommand, and a gemini wrapper setting OTEL_RESOURCE_ATTRIBUTES) and warns
Headers Not supported (warns)
Headless (evals) gemini -p with --output-format json
Version pin None in the CLI; halod enforces
File managed-settings.json (regular, root-owned, not group or world writable, non-symlink on macOS and Linux). Precedence: MDM over server-managed over file over user
Keys rendered model (default only), permissions.disableBypassPermissionsMode, allowedMcpServers, telemetry. On Linux /etc/profile.d/halos-copilot-cli.sh wraps copilot with OTEL_RESOURCE_ATTRIBUTES
Gateway Not rendered: BYOK (COPILOT_PROVIDER_BASE_URL, COPILOT_PROVIDER_API_KEY, COPILOT_MODEL) is env-only. Traffic is not routed through Halos
Verified (make uat-clis, 1.0.90) telemetry endpoint and allowedMcpServers are honored by the CLI. UNVERIFIED: whether disableBypassPermissionsMode changes tool approval (--allow-all still starts); model; sandbox sub-keys are not mapped
halod delivery Verified against the real Copilot CLI in make uat-clis
File opencode.json in the system managed directory, loaded at the highest precedence (above project config and OPENCODE_CONFIG_CONTENT). macOS MDM (ai.opencode.managed) is not rendered
Keys rendered provider.halos (npm, options.{baseURL,apiKey,headers}, models), enabled_providers: ["halos"], model, permission (bash, read, edit, glob, grep, task, external_directory), mcp (servers, and enabled: false for mcp.denied), instructions, autoupdate: false when the profile pins a version
Gateway @ai-sdk/anthropic (/v1/messages) or @ai-sdk/openai (/v1/responses), chosen by gateway.protocols.opencode (default anthropic-messages); bedrock-invoke and gemini are refused. The credential is {env:HALO_GATEWAY_TOKEN}
Permissions bash and edit start from ask once they have a rule; order is allow, ask, deny, last match wins. Home and absolute paths go to external_directory. A global allow is never rendered, and the release check rejects it
Not enforceable OPENCODE_PERMISSION (an environment variable) overrides the managed permission block, and --auto approves every ask (explicit deny still holds). OpenCode has no MCP allowlist: user servers still load. Both warn
User-Agent opencode/<version> ai-sdk/...: the gateway’s UA map and version gate recognise it
Headless (verify) opencode run "<message>"
File System hooks.json (the CLI’s enterprise hooks path) with preToolUse, postToolUse, sessionStart, stop and beforeSubmitPrompt hooks from hooks.items. Claude-style tool matchers map to Cursor’s (Bash to Shell, Edit to Write); a tool with no Cursor type skips that hook and warns
Not rendered Permissions (~/.cursor/cli-config.json, project .cursor/cli.json), MCP (mcp.json), models, rules, telemetry: per-user files or dashboard settings with no root-owned location. Gateway: the CLI documents no base-URL override. All warn
Install https://cursor.com/install takes no version argument, so halod cannot install a pinned version; halo doctor still detects cursor-agent and its version
User-Agent None documented, so the gateway cannot map it
Headless (verify) cursor-agent -p "<message>" (never --force, --yolo or --trust)

The org-wide cloud console of each vendor, driven by halo admin from the GA ring’s profile (guide, ADR-0025). These settings are separate from the machine files above, and rings do not reach them.

Vendor Console Write Read Otherwise
Claude Code Server-managed settings none none export managed-settings.json without machine keys (credential helpers, gateway env, hooks). Replaces the file for sessions that fetch it. Gateway (apiKeyHelper) sessions do not fetch it
Codex ChatGPT Agent Security none documented none export requirements.toml and config.toml
Gemini Code Assist logging, release channel cloudaicompanion v1 cloudaicompanion v1 logging covers IDE use, not Gemini CLI
Copilot content exclusion PUT /orgs/{org}/copilot/content_exclusion GET of the same path not applied by Copilot CLI or agent mode
Copilot CLI policy none GET /orgs/{org}/copilot/billing set by hand
Cursor model allowlist preview only, not used not used export the alias list
  • ai-proxy supports llm_format: anthropic passthrough.
  • ai-proxy-advanced (load balancing) is Enterprise-only.
  • OSS Kong has no request-mirroring plugin; halo-shadow fills the gap.