Docker primer: OpenClaw + Talon (OpenAI only)
Straightforward setup: only your OpenAI API key is used. Talon runs as a gateway in front of OpenAI. OpenClaw sends every chat request to Talon; Talon checks policy, then forwards to OpenAI and records evidence.
What this primer does
- One key: You set
OPENAI_API_KEYin.env. Talon stores it and uses it when forwarding. OpenClaw never sees or stores the real key. - One provider: OpenAI only. No Anthropic or Ollama in this primer.
- Governance: Every request from OpenClaw goes through Talon. Talon resolves the agent from the presented key, scans for PII, enforces cost and model limits, then forwards to OpenAI and writes an audit record.
What OpenClaw must send (and what you configure)
OpenClaw talks to an “OpenAI-compatible” endpoint. To use Talon as that endpoint, you must configure OpenClaw so that all chat requests go to Talon, and Talon can resolve the agent identity.
| What OpenClaw sends | What you set in OpenClaw |
|---|---|
| Base URL (where requests go) | Set the OpenAI provider base URL to the Talon gateway URL with trailing /v1: http://localhost:8080/v1/proxy/openai/v1. So every request goes to Talon; the /v1 ensures paths like chat/completions become .../v1/chat/completions (required by OpenAI). |
API key (in Authorization: Bearer ...) | Set the OpenAI provider API key to the primer agent's Talon agent key — the value minted into the vault secret referenced by agent.key.secret_name in this primer's agent.talon.yaml (talon secrets set gateway-primer-talon-key "$(openssl rand -hex 24)"). Do not put your real OpenAI key here. Talon resolves the agent by this key; it then adds the real OpenAI key when forwarding. |
Two different keys (do not confuse):
- TALON_SECRETS_KEY (Docker: in
.envasTALON_SECRETS_KEY) — Used by Talon to encrypt/decrypt the vault where the real OpenAI key is stored. If it changes, Talon cannot decrypt the secret and you get "cipher: message authentication failed". Never put this in OpenClaw. - Agent key (the value in the
gateway-primer-talon-keyvault secret, and in OpenClaw'sapiKey) — The workload identity OpenClaw presents; it resolves to exactly one agent and its tenant. Not used for encryption. The vault value and OpenClaw'sopenclaw.jsonmust match. Rotate withtalon secrets set gateway-primer-talon-key <new>+ restart — one active key per agent, never two.
Important:
- If the base URL is wrong, OpenClaw will call OpenAI directly and Talon will not see or govern the traffic.
- If the API key in OpenClaw is your real OpenAI key, Talon rejects it as an unknown agent key (401). Use only the minted agent key in OpenClaw.
Prerequisites
- Docker and Docker Compose
- Talon repo clone (image is built from repo root)
- Your real OpenAI API key (only for
.env; never put it in OpenClaw)
Tip — non-Docker alternative: If you prefer a local binary over Docker, run
talon init(wizard, then choose OpenClaw) ortalon init --pack openclawto generate a gateway-ready project, then follow the local integration guide.
Quick start
1. Set your OpenAI key
cd docs/guides/openclaw-talon-primer
cp .env.example .env
# Edit .env and set:
# OPENAI_API_KEY=sk-your-openai-key
Only OPENAI_API_KEY is required. Talon stores it in the vault; OpenClaw never sees it.
2. Build and run Talon
chmod +x entrypoint.sh
docker compose build
docker compose up -d
Talon listens on port 8080. The gateway URL for OpenAI is:
http://localhost:8080/v1/proxy/openai/v1
The trailing /v1 is required so that when the client appends chat/completions, the path is .../v1/chat/completions. (If Talon runs on another host, use http://<host>:8080/v1/proxy/openai/v1.)
3. Configure OpenClaw
Edit ~/.openclaw/openclaw.json and add a top-level models block (sibling of agents, channels, etc.) with providers.openai that routes all OpenAI traffic through Talon. OpenClaw requires each provider to declare which models it serves using an array of objects with both id and name:
{
"models": {
"providers": {
"openai": {
"baseUrl": "http://localhost:8080/v1/proxy/openai/v1",
"apiKey": "<your-openclaw-agent-key>",
"api": "openai-responses",
"models": [
{ "id": "gpt-5.1-codex", "name": "gpt-5.1-codex" },
{ "id": "gpt-4o", "name": "gpt-4o" },
{ "id": "gpt-4o-mini", "name": "gpt-4o-mini" }
]
}
}
}
}
Then restart the OpenClaw gateway so it picks up the config:
openclaw gateway stop
openclaw gateway start
On SSH or headless servers, if you see "systemctl --user unavailable: Failed to connect to bus", stop the gateway process directly (e.g. pkill openclaw-gateway), then start OpenClaw again as you normally do.
Important:
apiKeyis the Talon agent key minted for this primer's agent — not your real OpenAI key.api: "openai-responses"is required for OpenAI-compatible proxy endpoints.- Each model must be an object with both
idandname(OpenClaw's schema requires both). - If Talon runs on another host, replace
localhost:8080with<talon-host>:8080. Use the trailing/v1inbaseUrlso paths are correct (avoids 404).
After this, every chat request from OpenClaw goes to Talon. Talon resolves the agent from the presented key, applies the agent's effective policy, then forwards to OpenAI with your real key and records evidence.
4. Verify (optional)
Send a test request through the gateway:
docker compose --profile verify up verify
List evidence for the agent (the agent.name from agent.talon.yaml is the evidence agent_id):
docker exec talon-gateway talon audit list --agent gateway-primer --limit 5
What Talon does with each request
- Resolve agent — Matches the presented key (
Authorization: Bearer <agent key>) against the identity registry (constant-time). Unknown or missing key:401 Invalid or missing agent key. Client must usebaseUrlending in/v1(e.g..../v1/proxy/openai/v1) so paths likechat/completionsbecome.../v1/chat/completionsand upstream does not return 404. - Scan — Extracts message text and scans for PII. Policy can block or redact (this primer uses
redact). - Policy — Evaluates the agent's effective policy (organization baseline → the agent's one override): cost caps, allowed models. Denies if over limit.
- Forward — Sends the request to
https://api.openai.comwith your real API key. OpenClaw never sees that key. - Evidence — Writes a signed audit record (agent, model, cost, PII detected, etc.).
Governance in Action
Beyond basic PII scanning and cost caps, Talon layers several runtime governance controls on every OpenClaw request. All of these are configured in talon.config.gateway.yaml and the agent .talon.yaml policy.
Tool-aware PII redaction
Talon applies per-tool, per-argument PII policies. Each tool can declare which arguments contain customer data and what to do when PII is detected:
| Action | Behaviour |
|---|---|
allow | Pass through without scanning |
audit | Log the PII finding to evidence but forward unchanged |
redact | Replace PII with [REDACTED] before the tool executes |
block | Reject the tool call entirely |
Example from the agent policy (see tool_policies in agent.talon.yaml):
tool_policies:
send_email:
arguments:
to: allow # email address is the whole point
body: redact # scrub customer PII from body
result: audit
_default:
argument_default: redact
result: redact
Destructive tool blocking
Talon detects destructive operations via configurable pattern matching in capabilities.destructive_patterns. The default patterns catch delete, drop, remove, bulk_*, truncate, purge, wipe, and destroy. Any tool call whose name matches a destructive pattern is denied by the OPA policy engine, even when the tool is in the allowed_tools list. This is a heuristic safety net for wildcard allowlists.
Per-agent rate limiting
Rate limits are enforced per agent identity. The per_agent_requests_per_min field in the gateway config caps how many requests a single agent (e.g. gateway-primer) can make per minute. The default is 60 RPM. Global limits (global_requests_per_min) apply across all agents.
Response PII scanning
Talon scans LLM responses before returning them to the client, including streaming (SSE) responses. Configure the baseline via defaults.response_pii_action in gateway.organization_policy; per agent, use the data_classification output booleans in the agent file (output_scan alone → warn; + redact_output → redact; + block_on_pii → block):
| Action | Behaviour |
|---|---|
allow | No scanning |
warn | Log PII detection to evidence, forward unchanged (default) |
redact | Replace PII in the response with [REDACTED] (works for both streaming and non-streaming) |
block | Reject the response with HTTP 451 and a policy violation error |
The default is warn because LLM-generated content is not company data — the primary compliance value is the audit trail. Escalate to redact or block if your environment requires active response filtering.
Test it with a prompt that asks the model to generate a German IBAN:
# Use /v1 in path so upstream gets .../v1/chat/completions
# ($OPENCLAW_KEY = the agent key minted into gateway-primer-talon-key)
curl -s http://localhost:8080/v1/proxy/openai/v1/chat/completions \
-H "Authorization: Bearer $OPENCLAW_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role":"user","content":"Generate a sample German IBAN for testing."}]
}' | jq '.choices[0].message.content'
# With response_pii_action: "warn" (default), the response passes through and PII is logged in evidence.
# With response_pii_action: "redact", the IBAN in the response is replaced with [REDACTED].
Attachment scanning
Talon scans base64-encoded file attachments embedded in LLM API requests (OpenAI file/image_url blocks, Anthropic document/image blocks). Text is extracted from supported formats (PDF, TXT, CSV, HTML), scanned for PII and prompt injection, then governed by attachment_policy:
| Action | Behaviour |
|---|---|
allow | No attachment scanning |
warn | Scan and log findings in evidence, forward unchanged (default) |
strip | Remove file content blocks from the request before forwarding |
block | Reject the entire request with HTTP 400 |
Configure injection_action separately for prompt injection detection (block, strip, or warn). Use allowed_types / blocked_types to control which file extensions are permitted, and max_file_size_mb for size limits.
Test it by sending a base64-encoded text file containing PII:
PII_FILE=$(echo -n "Customer IBAN: DE89370400440532013000" | base64)
curl -s http://localhost:8080/v1/proxy/openai/v1/chat/completions \
-H "Authorization: Bearer $OPENCLAW_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"gpt-4o-mini\",
\"messages\": [{\"role\":\"user\",\"content\":[
{\"type\":\"text\",\"text\":\"Summarize this file\"},
{\"type\":\"file\",\"file\":{\"file_data\":\"data:text/plain;base64,$PII_FILE\",\"filename\":\"data.txt\"}}
]}]
}"
# With attachment_policy.action: "warn" (default), the request passes through and PII is logged.
# With attachment_policy.action: "block", the request is rejected with HTTP 400.
# With attachment_policy.action: "strip", the file block is removed before forwarding.
Circuit breaker
When an agent accumulates repeated policy denials (e.g. hitting cost caps or forbidden tools), the circuit breaker trips and suspends the agent automatically. Configured via circuit_breaker_threshold (default 5 denials) and circuit_breaker_window (default 60 s). States:
- Closed — normal operation, requests flow through.
- Open — agent suspended, all requests denied immediately.
- Half-open — after the window elapses, one probe request is allowed. If it succeeds, the circuit closes; if it fails, the circuit reopens.
Manual reset (programmatic — CLI wrapper planned for a future release):
// In Go code: circuitBreaker.Reset(tenantID, agentID)
Kill switch
Running agents can be cancelled programmatically by correlation ID via ActiveRunTracker.Kill(correlationID), which cancels the agent's context and stops all in-flight LLM calls and tool executions.
// In Go code:
activeRunTracker.Kill(correlationID)
Note: CLI and HTTP API wrappers for the kill switch (
talon agent kill,DELETE /v1/agents/runs/<id>) are planned for a future release. Currently, the kill switch is available via the Go API.
Incident response
For a full runbook covering detection, triage, containment, and post-mortem, see Incident Response Playbook.
Diagnostics
If OpenClaw stops responding or you see gateway errors, see Troubleshooting and diagnostics in the integration guide. For Docker, you can run docker exec talon-gateway talon audit list --limit 5 to see if requests reached Talon, and check container logs with docker logs talon-gateway 2>&1 | tail -50.
Primer layout
openclaw-talon-primer/
├── docker-openclaw-talon-primer.md # This guide
├── docker-compose.yaml # Talon service + optional verify
├── .env.example # OPENAI_API_KEY only (copy to .env)
├── entrypoint.sh # Seeds vault with OpenAI key, starts gateway
├── talon.config.gateway.yaml # OpenAI provider + organization baseline
└── agent.talon.yaml # The primer agent: identity (key binding) + policy
Summary
| Step | Action |
|---|---|
| 1 | Copy .env.example to .env, set OPENAI_API_KEY (your real OpenAI key). |
| 2 | docker compose build && docker compose up -d. |
| 3 | In ~/.openclaw/openclaw.json: add top-level models.providers.openai with baseUrl, apiKey, api: "openai-responses", and models: [{ "id": "...", "name": "..." }, ...]. Then openclaw gateway stop and openclaw gateway start (or pkill openclaw-gateway if systemctl --user is unavailable). |
| 4 | Use OpenClaw as usual; all requests are governed and audited by Talon. |
Only the OpenAI key is used upstream. Configure OpenClaw so it sends every request to Talon and presents the agent key; Talon then forwards to OpenAI and records evidence. For more options (cost, PII, models), edit the agent file and talon.config.gateway.yaml, or see How to govern OpenClaw with Talon.
You're done
You now have OpenClaw + Talon running in Docker. Talon is the gateway in front of OpenAI; every request is logged, policy-checked, and recorded.
Next steps:
| I want to… | Doc |
|---|---|
| Cap cost or restrict models | How to cap daily spend per team or application |
| Run OpenClaw + Talon without Docker | How to govern OpenClaw with Talon |
| Export evidence for auditors | How to export evidence for auditors |
| Add another app through the gateway | Add Talon to your existing app |