Start with the README or the guide for setup. This page keeps the detailed notes.
Clients send OpenAI Responses or Anthropic Messages requests. Both interfaces use the same model aliases and provider routes. A provider adapter handles authentication and inference. The client executes tools and returns their results to the gateway. Claude Code can use GPT, Claude, and local model routes through its native Messages interface; see Claude Code setup and Messages compatibility.
The optional Claude Desktop client shows truthful account and model labels for Claude, separately registered GPT accounts, and local models. Its isolated compatibility adapter preserves the existing Codex client and shared gateway configuration.
Claude runs the unmodified Claude Code binary using its subscription login. A private virtual MCP server declares the client's tools. The gateway returns Claude's native tool calls to the client, which owns execution and approvals. Claude's built-in tools and user customizations are disabled.
Codex uses a separate Sign in with ChatGPT registration and the public Responses endpoint. Each account has its own provider ID, model aliases, and credentials. Routing never switches accounts or billing modes after a failure.
Copilot uses the installed GitHub Copilot CLI's SDK protocol 3 and subscription
login. Sessions expose only caller tools and disable local instruction
discovery. A one-shot session whose turn ended with a complete text answer is deleted
after inference; any other one-shot end kills its CLI. Live sessions (the
default) last until they park out, are evicted or are closed, and their CLI is
then killed and its private folder removed (see
live Copilot sessions). Inherited BYOK settings and
GitHub token overrides are removed. The adapter verifies the reported subscription
route and exact model before completing a response. The SDK's allowed-model list
also restricts each session to the requested model. It adds no SDK dependency.
By default the gateway keeps one live Copilot session per Codex thread, so Copilot's
prompt cache stays warm across a tool loop; the same history sent as a new session on
every request rewrites most of it to the cache each time, which cost about 9.5 times
as much as the same work in a native Copilot session. Set copilotSessions.mode: "oneshot" on
the provider to turn that off, which is also what a request falls back to when a live
session cannot serve it (see live Copilot sessions).
The Apple Silicon installer bundles a verified Node runtime. Subscription inference hosts also need the native CLI for each configured provider. Desktop clients need the signed Codex app and an adapter on the selected execution device. The native connected-machine adapter uses the existing Codex connection. An SSH forward is required only when that device uses the remote-gateway setup described in remote setup.
VERSION=1.7.8
gh release download "v$VERSION" -R edihasaj/gatefox \
--pattern '*macos-arm64.tar.gz' --pattern SHA256SUMS
shasum -a 256 -c SHA256SUMS
tar -xzf "harness-gateway-$VERSION-macos-arm64.tar.gz"
"./harness-gateway-$VERSION-macos-arm64/install"
gateway --version
The installer needs no administrator access or repository checkout. It installs
versioned files under the user's local installation prefix. It refuses an
unrelated gateway command and preserves provider registrations during upgrades.
An earlier npm installation of this package is redirected to the installed command.
For a new inference host:
gateway init
gateway login claude
gateway login codex
gateway discover claude --load
gateway discover codex --load
gateway doctor
gateway service install
gateway desktop register
For a secondary Codex account, use gateway login codex --account-email EMAIL.
The gateway verifies that email in the signed identity token before saving its
separate registration. A different account is rejected. Browser sign-in waits
up to fifteen minutes; an expired callback requires a fresh sign-in.
For an existing development setup, gateway setup --from-config PATH copies only
gateway-owned registrations, the client token, and private preferences into the
installed data directory. It never copies native Codex authentication or settings.
Desktop registration applies to future normal app launches and is restored at
login. Quit the app normally once after initial registration. Run gateway desktop
to launch immediately with the adapter. The launcher refuses to stop a running app.
gateway desktop status checks registration and reports legacyForceCli: true while an old
CODEX_APP_SERVER_FORCE_CLI=1 is ours to clear, forceCliSet: true while the variable holds anything
left after the gateway strips the single newline launchctl getenv prints (an empty value reads as not
set, and a read that fails or times out is an error, not forceCliSet: false), and
staleWrapper: true while the session names a gateway wrapper the receipt no longer names. gateway desktop register clears the legacy value when a registration receipt proves the gateway set it (see
the adapter setup). gateway desktop unregister removes the registration, and
gateway uninstall reads the same answer first, without writing, so it can stop before it removes
anything.
For an optional Codex CLI installation, generate settings in a separate home:
gateway_client_root="$HOME/.config/harness-gateway/clients"
gateway_codex_home="$gateway_client_root/codex"
mkdir -p "$gateway_codex_home"
gateway codex-config --model codex/gpt-6.1-sol > "$gateway_codex_home/config.toml"
CODEX_HOME="$gateway_codex_home" codex
Keep the native account’s base configuration unchanged. The generated file is a complete configuration, not a named profile. This separate CLI home has its own history and settings. The generated catalog exposes account labels and reasoning controls. It also configures a credential helper so the gateway token does not need to appear in the TOML file. The daemon reloads saved model routes on the next request. A model name alone cannot switch an existing native ChatGPT thread to the gateway. The experimental app-server adapter adds gateway models after native models and routes new chats. Its rendered Claude/Codex launch passed on the test Mac; see the remaining limits in verification.
Subscription providers support account-specific discovery:
gateway discover claude --load
gateway discover codex --load
gateway codex-config --model codex/gpt-6.1-sol > gateway.codex.toml
Claude discovery reads the official CLI initialization catalog without sending an
inference prompt. It resolves model aliases and omits the duplicate Default row.
Codex discovery reads the signed-in account’s public model catalog. Omit --select
to load every offered model, or pass comma-separated IDs to choose a subset.
Refresh retains existing aliases for models that are still offered.
To configure Copilot, add a provider with type: "copilot-subscription", a unique
id, label: "Copilot", and an initial models alias map. Then run
gateway login ID, gateway discover ID --load, and gateway doctor.
Discovery keeps selectable models allowed by the account policy and omits Auto.
An optional authDirectory selects the CLI's Copilot home; credentials remain
under the native CLI's control. The gateway does not copy them. The metadata
uses the default context tier rather than promising the larger context tier.
Provider label controls the name before the middle dot, such as Claude · Opus 5.5 or Codex 2 · GPT-6.1-Sol. Providers appear in configuration order; metadata
priority sorts models within each provider. Use authDirectory to isolate
another Claude account. There is no recent-use tracker.
Standard remains the default speed. Codex 2 discovery retains offered Fast tiers for local and connected-machine model lists. Claude models that support fast mode (Opus) offer Fast too, billed to Claude usage credits. Speed is independent of reasoning. The current Copilot CLI has no selected-model Fast tier; unsupported requests fail before inference. See speed controls.
On macOS, install an explicit user service to keep the daemon alive independently of terminals and the Codex app:
gateway service install --config /absolute/path/gateway.local.json
gateway service status
gateway service restart
gateway service uninstall
Uninstall moves the managed LaunchAgent to Trash and preserves credentials. Changing the listener port or state directory requires a daemon restart.
Use gateway service consolidate --next-login to keep one gateway startup entry
without stopping current chats. Existing services keep running until logout; the
single entry restores their duties at the next login. The command preserves the
native account and saves the original startup files for recovery. See
service consolidation for activation checks.
Use gateway service consolidate to preview replacing the gateway's individual
jobs with one supervisor. Apply with --apply after local and remote chats finish.
The migration preserves endpoints and saves rollback copies. See
service ownership.
Development gate: npm run build && npm run check && npm test. npm test runs the suite under a private
temporary folder and a private home folder (scripts/run-tests.mjs, HOME is <folder>/home) that it removes
afterwards, so the real ones are left as they were, including ~/.local/state/gw-exec, where execution hosts keep their
folders, receipts and socket links; name test files
after -- to run only those. The runner fails a test that sets no timeout after 120 s
(GATEWAY_TEST_TIMEOUT_MS), lets a test file exit once its tests have ended, and runs the suite in its own
process group, so a signal to the runner stops every test process and it exits with 128 plus the signal. Each test
process exits on the signal, so the Copilot CLIs it started (they run in groups of their own) stop with it, and the runner
removes the stale sockets its run linked to in /tmp/codex-daemon-<uid>, the one place the pinned SDK fixes outside both folders.
Test processes refuse the real per-user roots outside a temporary folder, and the runner fails a run that left a test
receipt in the real gw-native or gw-exec (CI).
Hosted macOS native CI is manual-only: dispatch from production with
Run paid macOS native checks enabled (default off); ordinary development pushes and PRs stay on Ubuntu. The local
guard is node --test scripts/tests/ci-native-policy.test.mjs; see CI.
Adapters also exist for ordinary APIs; the current delivery
focus is Claude, Codex, and Copilot subscriptions.
Grok/xAI, OpenCode Console and Go, OpenRouter, and custom compatible APIs can serve models behind the same gateway. See API provider connections for the appropriate transport, endpoint and credentials.
chat-completions-api supports local vLLM servers and compatible APIs through
their native streaming endpoint. API providers accept either credentialFile
(owner-only) or apiKeyEnv. Plain HTTP endpoints must use loopback, except that a
chat-completions-api provider may set "tailnet": true to use http: (or https:)
toward a Tailscale host, for example the DGX Spark routers at
http://dgx.example:4000/v1. The gateway resolves the host on every connection and
dials only addresses in 100.64.0.0/10 or fd7a:115c:a1e0::/48; any other answer is
dropped before a connection is opened, so the key is never sent outside the tailnet. If
the name resolves to several tailnet addresses (one per router), a connection that cannot be
made in 3 seconds falls over to the next address; a request that reached a router is
never replayed on another. If none can be reached the request fails with the retryable
upstream_unavailable. When no address is on the tailnet it fails with
provider_unreachable and the gateway records provider_address_refused. Model
discovery (gateway discover and the periodic metadata refresh) uses the same checked
transport, so the key is never sent anywhere else. Changing
tailnet needs a restart, like changing baseUrl. Without tailnet, a DGX Spark
can still be reached through gateway tunnel install. Qwen 3.8 Flash Next passed real Codex
streaming, file reads, patches, follow-ups, reasoning, interruption, and recovery.
Its current gateway route supports text only.
A codex-subscription provider may set "toolSearch": "bridge" to let Codex's tool search
work through the gateway's login, which refuses the tool_search tool type. The default
"off" drops it. gateway tool-search on, off and status change or show the setting without a
restart. See tool search.
The gateway translates subscription and API inference into the caller's Responses or Messages protocol. The client owns agent roles, delegation, permissions, tool execution, and chat names. Ordinary aliases and explicit model choices keep their exact bindings, and the gateway adds no role instructions, default worker, or agent-only tools. Opt-in managed worker aliases are the one deliberate exception: when a caller selects one, Gateway code chooses among that route's configured candidates before inference, while task scope, permissions, tool execution and context stay with the client. Saved gateway chats remain readable before model discovery and resume through the registered provider.
gateway uninstall moves the installed files to Trash, removes managed services
and desktop registration, and preserves the data directory and credentials. On
macOS it first runs a read-only desktop preflight, which reads the desktop state
and stops before anything is removed. A launchctl getenv that fails stops it the
same way, and so does a gateway login promotion staged for the next login. A
unified service plan does not stop it, because the service step moves that file to
Trash with the service. Elsewhere there is no desktop registration to plan, so the
service step answers with its own platform message. If a desktop step fails once
removal has started, the error names what is already gone: resolve that cause,
then rerun gateway uninstall.
Inline image attachments and image tool results use native provider image inputs. The verified formats are PNG, JPEG, GIF, and WebP. See Responses support for payload limits and provider preprocessing. Stop/continue and saved-chat image history have passed through the real Codex engine on the test Mac.
See architecture, Responses support, Messages support, verification, desktop adapter, remote clients.
Local inference farms can opt into automatic capacity discovery so worker concurrency changes update admission without editing every Gateway.