Skip to main content

Manual Install over SSH

This walkthrough provisions a Venice provider on an Intern from a developer SSH session. You pick the Venice base URL, transport, and model id yourself (from venice.ai docs or by calling api.venice.ai/api/v1/models directly), write the venice block into the active runtime's config, persist the key in config.json, and restart the gateway.

Prerequisites

  • SSH access to the device. See SSH Access on Developer Edition. Standard Interns don't ship openssh-server; you need a Dev-Edition image or one with SSH installed by hand.
  • The device is already paired and has a working llm_base_url / llm_api_key / llm_model in /root/config/config.json. Venice is layered on top, not a replacement.
  • A Venice API key from your venice.ai account.
  • (Optional) A specific Venice model id to promote to runtime default. If you don't pass one, you can still call Venice explicitly (/model venice/<id>) but the runtime default stays untouched.
  • Most commands need sudo — both runtime config files are mode 600 and owned by root.

0. Open a session and check current state

ssh system@172.168.20.145

# Which runtime is the device on?
sudo jq .active_agent /root/config/config.json
# → "openclaw" (default) or "hermes"

# Is Venice already provisioned?
sudo jq .venice_inference_api_key /root/config/config.json
sudo jq .venice_inference_model /root/config/config.json

If a key is already there and you only want to change the model, you can re-run the install with the same key and a new model — the merge is idempotent.

1. Decide what you want to write

Three pieces of info come from venice.ai:

ValueWhere to get itTypical value
baseUrlvenice.ai API docshttps://api.venice.ai/api/v1
transport (a.k.a. api)venice.ai API docsopenai-completions (OpenClaw) / openai_completions (Hermes)
model id(s)GET https://api.venice.ai/api/v1/models (Bearer your Venice key)e.g. deepseek-v3.2, qwen3.6-max

You can list the available models directly from venice.ai:

VENICE_KEY='vn-...'
curl -fsSL -H "Authorization: Bearer ${VENICE_KEY}" \
https://api.venice.ai/api/v1/models | jq '.data | map(.id)'

Pick what you want and set shell variables for the rest of the walkthrough:

VENICE_KEY='vn-...'
VENICE_BASE_URL='https://api.venice.ai/api/v1'
VENICE_MODEL='deepseek-v3.2' # optional — leave empty to skip primary promotion

# Models you want to register on the device. Add a context_length you can
# look up in venice.ai's docs (or pick a sensible default like 200000).
read -r -d '' VENICE_MODELS <<'JSON'
[
{"id": "deepseek-v3.2", "contextLength": 200000},
{"id": "qwen3.6-max", "contextLength": 131072}
]
JSON

2a. Write the venice block into openclaw.json (openclaw runtime)

Skip to 2b if active_agent is hermes.

The OpenClaw transport name uses hyphens (openai-completions). The models block is an array.

CONFIG=/root/.openclaw/openclaw.json
sudo cp "$CONFIG" "${CONFIG}.bak"

sudo jq \
--arg key "$VENICE_KEY" \
--arg baseUrl "$VENICE_BASE_URL" \
--arg api "openai-completions" \
--arg primary "${VENICE_MODEL:+venice/${VENICE_MODEL}}" \
--argjson newModels "$VENICE_MODELS" '
.models.providers.venice = (
(.models.providers.venice // {}) +
{apiKey: $key, baseUrl: $baseUrl, api: $api} +
{models: (
((.models.providers.venice.models // []) | map({(.id): .}) | add // {}) as $byId
| $byId + ($newModels | map({(.id): .}) | add)
| to_entries | map(.value)
)}
)
| .agents = (
.agents // {defaults: {}}
| with_entries(
.value.models = (
(.value.models // {}) +
($newModels | map({("venice/" + .id): {}}) | add // {})
)
| if $primary != "" then .value.model = ((.value.model // {}) + {primary: $primary}) else . end
)
)
' "$CONFIG" | sudo tee "${CONFIG}.tmp" > /dev/null

sudo mv "${CONFIG}.tmp" "$CONFIG"
sudo chmod 600 "$CONFIG"
sudo chown openclaw:openclaw "$CONFIG" # matches openclawRuntimeUser

What this writes:

  • models.providers.venice = {apiKey, baseUrl, api, models[]} — replaces any existing block but merges the models array by id (existing entries kept, new ids appended).
  • agents.*.models["venice/<id>"]: {} — adds an entry under every agent for each new model id.
  • agents.*.model.primary = "venice/<model>" — set under every agent only when $VENICE_MODEL is non-empty. If empty, the existing primary is left alone.

Then restart:

sudo systemctl restart openclaw

2b. Write the venice block into /root/.hermes/config.yaml (hermes runtime)

Skip to 3 if active_agent is openclaw.

Hermes uses a different layout — providers.venice lives at the top level of config.yaml, models is a map keyed by model id (not an array), and the transport name uses underscores (openai_completions) instead of hyphens.

CONFIG=/root/.hermes/config.yaml
sudo cp "$CONFIG" "${CONFIG}.bak"

sudo VENICE_KEY="$VENICE_KEY" \
VENICE_MODEL="$VENICE_MODEL" \
VENICE_BASE_URL="$VENICE_BASE_URL" \
VENICE_MODELS="$VENICE_MODELS" \
CONFIG="$CONFIG" \
python3 - <<'PY'
import os, yaml, json, pathlib
config_path = pathlib.Path(os.environ["CONFIG"])
doc = yaml.safe_load(config_path.read_text()) or {}

providers = doc.setdefault("providers", {})
venice = providers.setdefault("venice", {})
venice["name"] = "Venice"
venice["base_url"] = os.environ["VENICE_BASE_URL"]
venice["transport"] = "openai_completions"
venice["api_key"] = os.environ["VENICE_KEY"]
venice["discover_models"] = False

models = venice.setdefault("models", {})
for m in json.loads(os.environ["VENICE_MODELS"]):
mid = m["id"]
if mid in models:
continue
models[mid] = {"context_length": int(m.get("contextLength") or 200000)}

primary = os.environ["VENICE_MODEL"]
if primary:
model_block = doc.setdefault("model", {})
model_block["default"] = primary
model_block["provider"] = "venice"

config_path.write_text(yaml.safe_dump(doc, sort_keys=False))
PY

sudo chmod 600 "$CONFIG"
sudo systemctl restart hermes-gateway

What this writes:

  • providers.venice.{name, base_url, transport, api_key, discover_models} at the top level — discover_models: false is important; Venice doesn't expose an OpenAI-style /models route the way Hermes expects to consume it from this code path.
  • providers.venice.models[<id>] = {context_length: N} for each model.
  • model.default + model.provider — only when $VENICE_MODEL is non-empty.

The Python helper preserves the YAML roundtrip behavior the runtime itself uses (Go code uses goccy/go-yaml; pyyaml is close enough for hand-editing).

3. Persist key + model into config.json

This is what makes a later agent swap auto-replay Venice. Do this regardless of which runtime you're on:

sudo jq \
--arg key "$VENICE_KEY" \
--arg model "${VENICE_MODEL:-}" '
.venice_inference_api_key = $key |
.venice_inference_model = $model
' /root/config/config.json | \
sudo tee /root/config/config.json.tmp > /dev/null && \
sudo mv /root/config/config.json.tmp /root/config/config.json

Skipping this step is "fine" — the runtime is already serving Venice — but when the device is later switched to Hermes (or back to OpenClaw), the venice block will not auto-apply on the new runtime. You'd need to re-run the install by hand.

4. Verify end-to-end

# Active runtime saw a restart and is healthy.
systemctl is-active openclaw # or hermes-gateway
journalctl -u openclaw -n 30 --no-pager # or hermes-gateway

# Venice block present in the right config file.
sudo jq '.models.providers.venice | {has_key: (.apiKey != ""), baseUrl, model_count: (.models | length)}' \
/root/.openclaw/openclaw.json 2>/dev/null
# OR
sudo grep -A2 '^providers:' /root/.hermes/config.yaml

# Persistence keys set in config.json.
sudo jq '{key_len: (.venice_inference_api_key | length), model: .venice_inference_model}' /root/config/config.json
# → {"key_len": N, "model": "deepseek-v3.2"}

Send a chat on your configured channel; if $VENICE_MODEL was set, the reply should come back from the Venice model. If you didn't pass a model, the runtime default is unchanged — to actually call a Venice model you'd select it explicitly (e.g. /model venice/<id> in the Hermes chat).

Common failures

SymptomLikely cause
401 calling api.venice.aiThe Venice API key is wrong, revoked, or doesn't have model-list scope.
Reply still comes from the old default model$VENICE_MODEL was empty so the primary wasn't promoted, or the gateway didn't restart cleanly.
read hermes config: ... (file not found)You're editing config.yaml but /root/.hermes/config.yaml is missing. Run Hermes installation first.
Gateway restart hangsCheck systemctl status for the active runtime — the venice block has likely been written to disk already; only the restart is stuck. journalctl -u <unit> -n 200 is the source of truth.

Re-applying after an agent swap

If venice_inference_api_key is set in config.json when you switch the device to Hermes via the Hermes install, intern-server automatically replays the venice provisioning against the freshly-written ~/.hermes/config.yaml using the saved key + model. You don't need to re-run this walkthrough.

If that replay fails (best-effort, agent swap commits anyway), the maintainer chat gets ⚠️ Venice re-apply on hermes failed (agent swap committed OK). Re-running the SSH install (steps 1–3 above) is the retry.

Removing Venice

To deprovision by hand:

# 1. Clear runtime config
# openclaw:
sudo jq 'del(.models.providers.venice)' /root/.openclaw/openclaw.json | sudo tee /root/.openclaw/openclaw.json.tmp > /dev/null && \
sudo mv /root/.openclaw/openclaw.json.tmp /root/.openclaw/openclaw.json && \
sudo systemctl restart openclaw

# hermes:
sudo yq -i 'del(.providers.venice)' /root/.hermes/config.yaml && \
sudo systemctl restart hermes-gateway

# 2. Clear persistence so agent swap doesn't re-add it
sudo jq '.venice_inference_api_key = "" | .venice_inference_model = ""' /root/config/config.json | \
sudo tee /root/config/config.json.tmp > /dev/null && \
sudo mv /root/config/config.json.tmp /root/config/config.json

Step 2 is the part most people forget. Without it, the next agent swap re-applies Venice from the saved key.