EQTY LabDocs

Run Agents

Start Claude Code, Codex, OpenCode, OpenClaw, or any other agent so its model traffic is signed and audited by the Agent Gateway.

View as Markdown

Once Viper is installed and your agent is registered, you run the agent through Viper instead of on its own. The agent works as usual. Its model requests go through the gateway, where the Guardian Platform verifies them, applies your policies, and records them.

Before you start, finish Install Viper and Register Agents for the app you plan to run.

Wrapper mode and sidecar mode

  • Wrapper mode. Viper launches the agent for you (viper claude, viper codex, and so on). It starts a local proxy, points the agent at it, and shuts down when the agent exits. Use this on your own machine.
  • Sidecar mode. You run viper proxy, which prints a local proxy URL, and you start the agent yourself with its base URL set to that address. Use this for servers, containers, Kubernetes, and any agent that is not a CLI you launch, and in OpenShell sandboxes, where wrapper commands refuse to run.

In both modes the agent keeps using its own provider credentials (your Anthropic or OpenAI key, or your subscription sign-in). The gateway does not hold or add provider keys; your credentials pass through to the provider unchanged.

In the sidecar examples below, replace https://gateway.example.com with your gateway address. --port fixes the local port; without it, Viper picks a free port and prints the URL. The gateway forwards the request path to the provider unchanged, so the base URL plus the path the agent’s SDK appends must form the provider’s real path. OpenAI SDK clients append /chat/completions and need /v1 on the base URL; Anthropic SDK clients add /v1 themselves and take the proxy address as is.

viper proxy prints its local address and nothing else, so a script can capture it:

console
$ viper proxy --app claude --port 8080 --gateway-endpoint https://gateway.example.com/v1/anthropic_api
http://localhost:8080

To watch requests pass through, add --stdout-traffic summary. Viper prints a line for each request and its response. Claude Code streams its responses, so each response line reads sse, with the request body size in bytes and the time until the stream started:

console
[viper proxy] -> POST /v1/messages
[viper proxy] <- POST /v1/messages 200 sse (1234 bytes in) 812ms

A response that does not stream shows both body sizes instead, such as 200 (1234 in, 5678 out) 812ms.

Claude Code

bash
viper claude                     # your usual session, through the gateway
viper claude --resume            # Claude Code arguments pass through unchanged

Sidecar mode:

bash
viper proxy --app claude --port 8080 \
  --gateway-endpoint https://gateway.example.com/v1/anthropic_api
ANTHROPIC_BASE_URL=http://127.0.0.1:8080 claude

Codex

bash
viper codex
viper codex --model o3          # Codex arguments pass through; `--` is optional

Viper adds its own endpoint setting after your Codex options, ahead of any --, so that its override always wins; your arguments still reach Codex. If Codex has an OpenAI API key (from OPENAI_API_KEY or its saved sign-in), Viper routes to the OpenAI API. Otherwise it routes to the ChatGPT backend that a ChatGPT sign-in uses.

Use a ChatGPT sign-in with Codex for now. With an API key, Codex sends its requests without /v1, so the OpenAI API answers 404.

Sidecar mode (ChatGPT sign-in):

bash
viper proxy --app codex --port 8080 \
  --gateway-endpoint https://gateway.example.com/v1/chatgpt_backend/codex
codex -c 'openai_base_url="http://127.0.0.1:8080"'

OpenCode

Run viper opencode, or in sidecar mode:

bash
viper proxy --app opencode --port 8080 \
  --gateway-endpoint https://gateway.example.com/v1/chatgpt_backend
OPENAI_BASE_URL=http://127.0.0.1:8080 opencode

OpenClaw

Run viper openclaw. OpenClaw can talk to four provider families at once, so the wrapper starts one proxy per family. In sidecar mode you must run all four yourself. Any family you leave out goes straight to the vendor, and nothing warns you:

bash
viper proxy --app openclaw --port 8080 --gateway-endpoint https://gateway.example.com/v1/anthropic_api &
viper proxy --app openclaw --port 8081 --gateway-endpoint https://gateway.example.com/v1/openai_api &
viper proxy --app openclaw --port 8082 --gateway-endpoint https://gateway.example.com/v1/chatgpt_backend &
viper proxy --app openclaw --port 8083 --gateway-endpoint https://gateway.example.com/v1/chatgpt_backend/codex &

ANTHROPIC_BASE_URL=http://127.0.0.1:8080 \
OPENAI_BASE_URL=http://127.0.0.1:8081 \
CHATGPT_BACKEND_BASE_URL=http://127.0.0.1:8082 \
OPENAI_CODEX_BASE_URL=http://127.0.0.1:8083 \
  openclaw

Any other agent

For an agent Viper does not wrap, run a proxy with --app proxy and point the agent’s model client at it. No SDK is needed beyond the base URL.

bash
viper proxy --app proxy --port 8080 \
  --gateway-endpoint https://gateway.example.com/v1/openai_api &
python agent.py
python
model = ChatOpenAI(
    model="gpt-4o",
    base_url="http://127.0.0.1:8080/v1",     # the Viper proxy; OpenAI clients need /v1
    api_key=os.environ["OPENAI_API_KEY"],    # your own key; it passes through the gateway
)

For an Anthropic client, use /v1/anthropic_api as the gateway endpoint and give the client the proxy address with no /v1; the Anthropic SDK adds it.

Requests sent this way are signed as the proxy itself. To have the Guardian Platform tell your agents apart, run each as a named agent with its own key, as the LangGraph and Deep Agents quickstarts do. The Guardian Agent SDK does that for any Python agent, and its Proxy API reference describes it for agents in other languages.

Which providers each agent routes

AgentProvider families routed through the gateway
Claude CodeAnthropic
CodexOpenAI API or ChatGPT backend, depending on how Codex signs in
OpenCodeOpenAI only
OpenClawAnthropic, OpenAI API, ChatGPT backend, and ChatGPT Codex backend
Any other agentWhichever endpoint you give viper proxy

When Viper refuses to start OpenCode or OpenClaw

A session that looks governed while some of its traffic goes directly to a vendor is worse than no session. So before launching OpenCode or OpenClaw, Viper reads the agent’s configuration and refuses to start if it would route around the gateway:

Refused configurationOpenCodeOpenClaw
A provider family Viper does not route (for example a non-OpenAI provider in OpenCode, or ollama in OpenClaw)YesYes
A routed provider with its own pinned base URLYesYes
A --model on the command line that selects an unrouted familyYesYes
A configuration file that cannot be parsedYesYes

The error lists each offending entry. Remove or disable those entries (OpenClaw ignores providers set to "enabled": false), or run the agent without Viper and accept that its traffic is neither signed nor audited. viper proxy runs the same configuration-file checks when you start it with --app opencode or --app openclaw; the one check it cannot do is the command-line --model one, because it does not see the launch.

Capture the agent’s other traffic

Wrapper commands accept --egress-capture, which also routes the agent’s non-model HTTP and HTTPS traffic (package downloads, web fetches, git, and so on) through the gateway. To read HTTPS, Viper decrypts it locally with its own root certificate, and it tells Node, Python, curl, and git to trust that certificate for the session.

bash
viper claude --egress-capture

On macOS, some tools (notably Go programs such as gh) check only the system keychain and fail with x509: certificate is not trusted. Run viper ca trust once to add Viper’s root to your login keychain for SSL only. viper ca trust --check reports the status, and viper ca untrust removes it.

Check that it worked

  • viper doctor passes, with a line for the app reading did:key:… is registered, and the gateway accepts its signature. (viper did list lists the keys on this machine but does not show whether they are registered.)
  • Each session writes its own log file in ~/.config/viper/logs/, named viper-<date>-<time>-<pid>.log. In wrapper mode, the newest file has an LLM proxy servers started line listing one local proxy per provider family.
  • In the Agent Catalog, the agent’s Requests / 12h and Last Active update, and its requests appear in the audit trail.
  • If Viper cannot sign a request, the agent gets a 502 error and the request is not sent. Unsigned traffic never falls back to the provider.
  • Every error Viper makes itself carries an X-Viper-Error: 1 header, which it never passes on from the gateway or the provider, so a client can tell them apart. For a request that names an agent, the error has the agent API’s shape, {"error": {"code", "message", "hint"}}.

The Overview page for Governed Coding Agent, with the agent's pages listed beside the sidebar: status Active with its checks, a summary row with the agent ID, projects, registration time, and when it was last active, a chart of Requests and a preview of Request Logs, then the start of its Keys section.

Open View All Request Logs to see each request with its status and decision:

The agent's Request Logs: a timeline of requests, then two results — a 200 verified request to anthropic claude-sonnet-5, and a 403 policy_denied request to openai gpt-5.2.

Troubleshooting

Start with viper doctor. It checks the config, that the gateway answers, that each agent and app key is registered and accepted, and the local CA, and prints a fix for each problem. It changes no configuration, keys or credentials, and exits nonzero if a check fails, so it can gate a script. A check it cannot make, such as a YubiKey key with no PIN to hand, is skipped and does not fail the run. What doctor reports explains each result.

SymptomWhat to do
no DID configured for <agent>Create and assign a key: viper did add --assign <app>.
<agent> executable not found in PATHInstall the agent CLI, or add its directory to PATH.
openclaw executable not foundSet OPENCLAW_BIN to the OpenClaw binary, or install OpenClaw on PATH.
configuration cannot be fully mediated by viperRemove the providers or base URLs named in the error (see the refusal table above).
cannot be fully mediated by viper inside an OpenShell sandboxWrapper commands refuse to run in an OpenShell sandbox, because the agent would share Viper’s network access. Run the agent beside viper proxy; see the OpenShell quickstart.
no llm_gateway_api_url configuredSet llm_gateway_api_url in Viper’s configuration file, or set VIPER_GATEWAY_URL. See Viper Configuration.
llm_gateway_api_url path must be `/v1` or emptyThe address in config.toml has an extra path, usually /v1/v1 from entering a gateway URL ending in /v1 at the first-run prompt. Set llm_gateway_api_url to the gateway’s origin followed by /v1, such as https://gateway.example.com/v1.
could not reach the gateway at <url> (502)The gateway is down or unreachable from this machine, or llm_gateway_api_url names the wrong address. Nothing was sent. If the error says the TLS certificate was not accepted, check the address matches the certificate’s host and that this machine trusts the authority that issued it. If it says the TLS connection failed, check the address uses https only if the gateway does.
Upstream request failed: could not reach the gateway endpoint <url> (502, or upstream_unreachable)The request was signed, but the proxy’s gateway endpoint did not answer. Check the gateway is up, and that viper proxy --gateway-endpoint names it. Wrapper commands and relay mode use llm_gateway_api_url instead. Viper marks it x-should-retry: true, so OpenAI’s and Anthropic’s clients retry it on their own.
upstream_response_lost (502, or 500)The request reached the proxy’s gateway endpoint, but no complete answer came back: the connection dropped, timed out or the answer could not be passed on. The model may have run, so check before sending the request again; Viper’s SDK doesn’t retry it. Viper marks it x-should-retry: false, so OpenAI’s and Anthropic’s clients don’t retry it on their own.
request_unreadable (400, or 413)The proxy could not take in the request’s body: it broke off on the way in, so send it again, or it was over relay mode’s 32 MiB limit, so send less. Viper marks it x-should-retry: false, so OpenAI’s and Anthropic’s clients don’t resend it themselves: resend it from your code.
Gateway rejected traffic: signing key is not registered (502)The Guardian Platform does not know the key. Register it: viper agent register <name> for a named agent, viper did register --app <app> otherwise. Nothing was sent.
Gateway rejected traffic: Agent access has been revoked (502)An Organization Owner can restore the agent from its detail page in Guardian. Nothing was sent.
Failed to collect identity attestation data (500, or signing_failed)The key is on a YubiKey that could not be read. Check it is plugged in and, on Linux, that the smart card service (pcscd) is running. Nothing was sent.
Any other 502 or signing_failedViper could not sign the request, so it did not send it. The session log says why, and the response’s hint says what to do when there is one. Unsigned traffic never falls back to the provider.
agent_not_enrolled (409)The agent the request names has no key on this machine. Run viper agent add <name>, register it, and restart the proxy.
gateway endpoint ... is not an http(s) URLPass viper proxy --gateway-endpoint the full URL of the gateway’s provider API, for example https://gateway.example.com/v1/openai_api.
unsupported_endpoint (404)The request reached a gateway route that does not exist. Check the proxy’s --gateway-endpoint matches the provider the agent calls, for example /v1/openai_api for an OpenAI client.
sig_missing_headers (401) from the gatewayThe request bypassed Viper and went straight to the gateway unsigned, which the gateway refuses. Point the agent’s base URL at the Viper proxy.
x509: certificate is not trusted with --egress-capture on macOSRun viper ca trust.