Three corrections to PR #63 surfaced via live Telegram bot bring-up on Mac mini -> PI231 OLP: 1. headers.Authorization workaround does NOT work for openai-shape model ids. PI231 audit log showed key_id=__anonymous__ for every gpt-* model sent via olp-codex despite headers.Authorization being set. Canonical OpenClaw pattern is apiKey: dollar-brace VAR brace env-var reference (per docs.openclaw.ai). Verified end-to-end: this attributes both olp-claude/* and olp-codex/* traffic to the bot owner key. Root cause: two unresolved upstream openclaw bugs: - #41157 (Gemini openai-completions Authorization not sent) - #1669 (Ollama provider ignores apiKey) 2. claude-local renamed to olp-claude for naming symmetry with olp-codex. 3. /models is menu-only in OpenClaw. Typing /models olp-codex/gpt-5.5 does NOT directly switch -- only opens the picker. Changes: - Two-modes table: client-mode Auth row now recommends env-var-ref apiKey - Mode B: NEW step walks through env var setup per OS - Provider JSON examples use VAR ref pattern (no more headers.Authorization) - Gotchas rewritten with three failure modes documented - New gotcha for /models menu-only semantics - Troubleshooting table: updated 401 row + new rows for anonymous attribution + menu-only Authority: Live reproduction on Mac mini OpenClaw v2026.5.22 + PI231 OLP v0.5.1. PI231 audit log verification. OpenClaw issues #41157, #1669, #29095. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
19 KiB
OpenClaw + OLP
OpenClaw is a multi-bot gateway that exposes slash commands on Telegram, Discord, and other chat surfaces. OLP integrates with OpenClaw in two ways:
/olpslash commands via theolp-plugin/plugin (read-only parity to the localolpCLI).- LLM routing — OpenClaw's chat agent can route its model calls through your OLP server, giving you per-key audit + quota observability for every bot reply.
This doc covers both. Status: ✅ Supported.
Two deployment modes — pick yours
The OpenClaw config differs significantly depending on whether OpenClaw runs on the same host as the OLP server or on a separate client machine talking to a remote OLP. Pick the right section.
| Mode A: Server-co-located | Mode B: Client-mode (recommended for multi-machine setups) | |
|---|---|---|
| OpenClaw runs on | the OLP server host (loopback) | a different machine (Mac mini, laptop, etc.) |
| OLP server runs on | localhost (same host) | a remote host (e.g. PI231) |
olp-claude baseUrl |
http://127.0.0.1:4567/v1 |
http://<server-ip>:4567/v1 |
| Auth | authHeader: false (loopback trusted), OR anonymous-key if auth.allow_anonymous: true |
apiKey: "${OLP_OPENCLAW_BOT_TOKEN}" env-var reference (NOT raw string, NOT OPENAI_API_KEY — see § Gotchas) |
/olp slash plugin proxyUrl |
http://127.0.0.1:4567 |
http://<server-ip>:4567 |
/olp slash commands you get
| Slash command | Maps to | Tier |
|---|---|---|
/olp status |
GET /v0/management/status |
owner |
/olp health |
GET /health |
public |
/olp usage |
GET /v0/management/dashboard-data |
owner |
/olp models |
GET /v1/models |
public |
/olp cache |
GET /cache/stats |
owner |
/olp providers |
local registry view | public |
/olp chain show [model] |
local chain view | public |
/olp doctor |
informational (HTTP endpoint not yet shipped) | — |
/olp help |
usage text | — |
Mutating subcommands are deliberately not exposed via chat. keygen, revoke, restart, logs are SSH-only. See olp-plugin/README.md for the rationale.
Mode A — Server-co-located install
OpenClaw + OLP on the same host. Auth is simpler because everything is on loopback.
A1. Install the plugin
Two install paths — either works.
Option A — OpenClaw CLI:
openclaw plugins install /path/to/olp/olp-plugin/
Option B — symlink:
mkdir -p ~/.openclaw/extensions/
ln -s /path/to/olp/olp-plugin/ ~/.openclaw/extensions/olp
A2. Mint a bot owner key
npx olp-keys keygen --owner --name=openclaw-bot
Capture the printed plaintext token — shown exactly once.
A3. Configure (loopback recipe)
Edit ~/.openclaw/openclaw.json:
{
"plugins": {
"allow": ["...", "olp"],
"entries": {
"olp": {
"enabled": true,
"config": {
"proxyUrl": "http://127.0.0.1:4567",
"apiKey": "olp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}
}
For LLM routing through OLP, add (or update) the olp-claude provider so the bot's default agent goes through OLP-spawned claude -p:
{
"models": {
"providers": {
"olp-claude": {
"baseUrl": "http://127.0.0.1:4567/v1",
"api": "openai-completions",
"authHeader": false,
"models": [
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6", "input": ["text"],
"contextWindow": 200000, "maxTokens": 16384, "api": "openai-completions",
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }
]
}
}
}
}
authHeader: false is safe on loopback. If you set auth.allow_anonymous: true on the OLP server, the bot doesn't even need a key for slash commands (the apiKey field can be omitted). Owner-only subcommands (/olp status, /olp usage, /olp cache) still need an owner-tier key.
A4. Restart the gateway
openclaw gateway restart
Mode B — Client-mode install (OpenClaw on different host than OLP)
OpenClaw on machine X (e.g., Mac mini), OLP server on machine Y (e.g., a Raspberry Pi or any LAN host). This is the common family deployment shape.
B1. Install the plugin
Same as Mode A:
openclaw plugins install /path/to/olp/olp-plugin/
# OR
mkdir -p ~/.openclaw/extensions/
ln -s /path/to/olp/olp-plugin/ ~/.openclaw/extensions/olp
B2. Mint a bot owner key (on the OLP server, NOT on the OpenClaw host)
SSH to the OLP server:
ssh user@olp-server
cd ~/olp
node bin/olp-keys.mjs keygen --owner --name=openclaw-<hostname>-bot
Capture the plaintext — shown exactly once. This token will live in ~/.openclaw/openclaw.json on your OpenClaw host; pick a name that makes it independently revocable if that host is lost/compromised.
B3. Set the bot-token env var (OLP_OPENCLAW_BOT_TOKEN)
OpenClaw's canonical pattern for custom-provider auth is apiKey: "${VAR_NAME}" — an env-var reference, NOT a raw token. Choose a custom variable name (NOT OPENAI_API_KEY — OpenClaw service-manages that one and clobbers it with its own ChatGPT key on every restart). Convention: OLP_OPENCLAW_BOT_TOKEN.
macOS (gateway under launchd):
launchctl setenv OLP_OPENCLAW_BOT_TOKEN olp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Add the same export to ~/.zshrc so it survives reboot:
export OLP_OPENCLAW_BOT_TOKEN=olp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Linux (gateway under systemd-user): drop a file at ~/.config/environment.d/openclaw-olp.conf:
OLP_OPENCLAW_BOT_TOKEN=olp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Restart the gateway service so it picks up the new env.
B4. Configure ~/.openclaw/openclaw.json
Edit ~/.openclaw/openclaw.json on the OpenClaw host:
{
"plugins": {
"allow": ["...", "olp"],
"entries": {
"olp": {
"enabled": true,
"config": {
"proxyUrl": "http://<olp-server-ip>:4567",
"apiKey": "olp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
},
"models": {
"providers": {
"olp-claude": {
"baseUrl": "http://<olp-server-ip>:4567/v1",
"api": "openai-completions",
"apiKey": "${OLP_OPENCLAW_BOT_TOKEN}",
"models": [
{ "id": "claude-sonnet-4-6", "name": "Claude Sonnet 4.6 (via OLP)", "input": ["text"],
"contextWindow": 200000, "maxTokens": 16384, "api": "openai-completions",
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } },
{ "id": "claude-opus-4-7", "name": "Claude Opus 4.7 (via OLP)", "input": ["text"],
"contextWindow": 200000, "maxTokens": 16384, "api": "openai-completions",
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } },
{ "id": "claude-haiku-4-5", "name": "Claude Haiku 4.5 (via OLP)", "input": ["text"],
"contextWindow": 200000, "maxTokens": 16384, "api": "openai-completions",
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }
]
}
}
}
}
Note: the plugins.entries.olp.config.apiKey field (line 11) IS allowed to be a raw token — it's a separate code path that doesn't suffer the service-managed-env clobber problem. Only the models.providers.<id>.apiKey field needs the ${VAR} env-var-reference workaround.
B5. Confirm the default agent model is on olp-claude
Check agents.defaults.model.primary in openclaw.json. It should be something like:
{ "agents": { "defaults": { "model": { "primary": "olp-claude/claude-sonnet-4-6" } } } }
If it's pointing at one of OpenClaw's stock providers (openai/..., anthropic/..., github-copilot/...), free-text chat will bypass OLP entirely and hit your direct API account. You'll see no traffic in OLP's /dashboard and /olp usage will show no recent activity.
B5. Restart the gateway
openclaw gateway restart
B6. Verify routing
In Telegram or Discord, send a free-text message ("hello"). It should:
- Return a normal LLM reply (not "Something went wrong")
- Show up on the OLP dashboard's 24h-requests counter
- Show up in
/olp usageper-provider count
If you see "Something went wrong" — see § Troubleshooting below.
Using codex / OpenAI models through OLP
By default the olp-claude provider only knows about Claude models. To route OpenAI / codex models through OLP (so bot calls to gpt-5.5 etc. spawn codex exec --json on the OLP server and benefit from per-key audit + quota tracking), add a second provider:
{
"models": {
"providers": {
"olp-codex": {
"baseUrl": "http://<olp-server-ip>:4567/v1",
"api": "openai-completions",
"apiKey": "${OLP_OPENCLAW_BOT_TOKEN}",
"models": [
{ "id": "gpt-5.5", "name": "GPT 5.5 (via OLP→codex)", "input": ["text"],
"contextWindow": 200000, "maxTokens": 16384, "api": "openai-completions",
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } },
{ "id": "gpt-5.4-mini", "name": "GPT 5.4 mini (via OLP→codex)", "input": ["text"],
"contextWindow": 200000, "maxTokens": 16384, "api": "openai-completions",
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } },
{ "id": "gpt-5.3-codex", "name": "GPT 5.3 codex (via OLP→codex)", "input": ["text"],
"contextWindow": 200000, "maxTokens": 16384, "api": "openai-completions",
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 } }
]
}
}
},
"agents": {
"defaults": {
"models": {
"olp-codex/gpt-5.5": { "alias": "OLP GPT 5.5" },
"olp-codex/gpt-5.4-mini": { "alias": "OLP GPT 5.4 mini" },
"olp-codex/gpt-5.3-codex": { "alias": "OLP Codex" }
}
}
}
}
After restart, type /models in Telegram and pick olp-codex/gpt-5.5 from the menu that appears. /models is menu-driven — it does not accept inline model names; typing /models olp-codex/gpt-5.5 won't directly switch you. The available IDs are the ones OLP's /v1/models returns — typically gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.3-codex, gpt-5.3-codex-spark. Query your OLP server to see the live list:
curl -s -H "Authorization: Bearer olp_…" http://<olp-server-ip>:4567/v1/models | jq '.data[].id'
Why not use OpenClaw's stock openai provider? OpenClaw's built-in openai-codex provider uses the local ChatGPT account (via the sk-proj-… API key OpenClaw stores) and bypasses your OLP server entirely. You'd lose per-key audit + per-key quota visibility. olp-codex keeps everything routed through your central OLP for observability.
Gotchas
Auth must use apiKey: "${VAR}" env-var reference — three failure modes to avoid
Custom OpenAI-compatible providers in OpenClaw have a fragile auth path. Three patterns that don't work + the one that does:
❌ apiKey: "olp_<raw-token>" — raw string. Silently bypassed in some routing paths because OpenClaw treats OPENAI_API_KEY as service-managed (OPENCLAW_SERVICE_MANAGED_ENV_KEYS=DEEPSEEK_API_KEY,OPENAI_API_KEY), and certain model id patterns (notably gpt-*) fall back to that env var instead of using your explicit apiKey. Symptom: OLP audit shows the request as __anonymous__ instead of your owner key. Confirmed via openclaw#41157 (Gemini openai-completions Authorization not sent) and #1669 (Ollama provider ignores apiKey, hardcodes Bearer). Both unresolved upstream as of OpenClaw v2026.5.
❌ headers: { "Authorization": "Bearer olp_<raw-token>" } — works for SOME provider/model combinations (e.g., model id claude-sonnet-4-6) but breaks for openai-shape model ids (gpt-5.5 etc.) which take a different code path that ignores the headers field. Mixed behavior is worse than no behavior.
❌ Setting OPENAI_API_KEY=olp_… in the gateway env. OpenClaw service-manages that variable and overwrites your value with the user's ChatGPT key on every gateway start.
✅ apiKey: "${OLP_OPENCLAW_BOT_TOKEN}" — env-var reference with a custom variable name (NOT OPENAI_API_KEY). OpenClaw resolves the reference at request-construction time, before any service-managed-env logic runs. Both olp-claude/* (Claude models) and olp-codex/* (OpenAI models) auth correctly with this pattern. Verified end-to-end 2026-05-27: OLP audit shows requests attributed to the correct bot key for both provider blocks.
OpenClaw docs call out this as the canonical pattern: see docs.openclaw.ai/concepts/model-providers "API key or SecretRef/env reference".
Default agent model still points at a removed provider
If you've removed a provider (e.g., torn down a co-located OCP server) but the bot's default agent model still references that provider, free-text messages will fail with "Something went wrong while processing your request." Check agents.defaults.model.primary and update it to a provider that exists.
/new does not reset model selection — use /reset
OpenClaw's /new resets the conversation context but preserves the session's /models selection. If a session has been switched to a model that no longer works (revoked / removed), /new won't help — use /reset (resets both context and model selection).
/models is menu-only — does not accept inline model names
The OpenClaw /models command in Telegram is menu-driven: typing /models pops a model-picker menu where you tap the model name. Typing /models olp-codex/gpt-5.5 does NOT switch — it'll open the picker. The bot's own success-message after a pick may say "Use /model olp-codex/gpt-5.5 --runtime <runtime> to switch harnesses." — that command form is not actually accepted by the bot; ignore that line.
OpenClaw v2026.5+ requires openclaw.extensions in package.json
OpenClaw versions ≥ 2026.5.22 enforce a stricter plugin-manifest validation at openclaw plugins install time. If Option A fails with package.json missing openclaw.extensions despite recent OLP releases, your local olp-plugin/package.json may predate the v0.5.x fix that adds "extensions": ["./index.js"] to the openclaw block. Pull latest OLP main (git pull in your OLP clone) and retry, or fall through to symlink Option B which works against any plugin shape. (Original drift event: 2026-05-27, see commit history of olp-plugin/package.json.)
openclaw gateway restart is required after install
OpenClaw caches plugin discovery + model-provider config at gateway start. openclaw plugins reload does not guarantee a fresh import of the plugin module nor a fresh re-read of models.providers.*. Restart the gateway after every change to ~/.openclaw/openclaw.json.
Owner-key revocation kicks the plugin out immediately
If you revoke the bot's owner key (npx olp-keys revoke --id=<id>), the next /olp status will return 401 unauthorized. Mint a replacement key with a new name and edit ~/.openclaw/openclaw.json; do NOT reuse the revoked key's UUID.
Long responses are truncated
Telegram caps messages at ~4096 characters. The plugin truncates with a ... [truncated, use SSH for full] suffix when the rendered output would exceed ~3900 chars. Use SSH + the local olp CLI for full output.
OLP-specific notes
The plugin honours these env vars on the OpenClaw gateway process:
OLP_PROXY_URL— full URL, overrides plugin configproxyUrl.OLP_PORT— port only, localhost assumed; overridesproxyUrlwhenOLP_PROXY_URLis unset.
If you run the OpenClaw gateway under launchd or systemd with custom env vars, set OLP_PROXY_URL there rather than editing the plugin config — that way the same plugin install can serve multiple OLP hosts.
Per-bot vs maintainer key
Always create a dedicated bot key, never the maintainer's personal owner key. The bot key:
- Has its own
idso you can revoke it without affecting other clients. - Has its own audit-log entries so you can attribute
/v0/management/*traffic to the bot. - Can be rotated routinely (every 90 days etc.) without coordinating with the maintainer's daily-driver IDE configs.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
/olp status returns 401 |
bot key revoked / wrong / missing | Mint new key on OLP host; update plugins.entries.olp.config.apiKey; restart gateway |
/olp status returns 403 |
bot key is guest-tier, not owner-tier | Generate owner-tier key (olp-keys keygen --owner --name=...); update config |
OLP error: fetch failed |
proxyUrl unreachable from the gateway host |
curl http://<proxyUrl>/health from the gateway host to confirm reachability; check firewall / OLP server OLP_BIND=0.0.0.0 for LAN access |
Bot free-text chat returns "Something went wrong" but /olp ... works |
Default agent model points at a broken provider (e.g., a removed OCP install) | Check agents.defaults.model.primary in openclaw.json; update to olp-claude/claude-sonnet-4-6 or another working provider |
Free-text returns HTTP 401: OLP API key is invalid despite fresh key |
Raw-string apiKey: "olp_..." shadowed by service-managed env clobber; or headers.Authorization bypassed for gpt-* model ids |
Switch models.providers.<id>.apiKey to env-var reference: "${OLP_OPENCLAW_BOT_TOKEN}" (see § Gotchas: Auth) |
OLP audit shows key_id=__anonymous__ for traffic that should be owner-attributed |
Same root cause as 401 — raw-string apiKey or headers bypassed in some routing paths | Switch to env-var-reference apiKey: "${VAR}" pattern + verify launchctl getenv OLP_OPENCLAW_BOT_TOKEN returns the expected token |
| Bot routes to ChatGPT account directly, not through OLP | Provider config uses OpenClaw stock openai-codex instead of a custom OLP-pointing provider |
Add olp-codex provider per § Using codex / OpenAI models through OLP |
/models olp-codex/gpt-5.5 typed inline doesn't work |
OpenClaw /models is menu-only, doesn't accept inline names |
Type /models, tap the model from the picker menu that appears |
Cross-references
olp-plugin/README.md— full plugin docs.- ADR 0010 § Phase 4 D71-D73 — the plugin's charter.
- OCP
/ocpplugin — the OCP predecessor (includes mutating subcommands that OLP deliberately drops).