feat(deploy): inference-proxy supplier 'openclaw' — OpenClaw web-research agent
Second supplier on the inference-proxy box, proxying to an OpenClaw gateway (agent 'main') on the web-tools KVM guest over the tailnet (100.101.143.72:18789). Same vuk llama.cpp brain as supplier 'local'; the product is the agent loop (searxng web search + web fetch + browser). Capability llm.chat.v1, advertised model 'openclaw-web-agent', 0.2 AP3X, ticket settle mode, MAX_CHAT_SESSIONS=1. Uses the new stateful-upstream contract: OPENAI_SESSION_PASSTHROUGH=1 (escrow ref as OpenAI 'user' → sticky per-escrow openclaw session, delta-only sends) and OPENAI_MODEL_OVERRIDE=openclaw (endpoint only accepts 'openclaw'). - docker-compose.supplier-openclaw.yml: new supplier compose (traefik host mp-suppliers-openclaw.vector.apexfusion.org). - README: gateway config (bind=tailnet, chatCompletions on, GLOBAL tool allow+deny lockdown), the tailnet-ACL blocker (inference-proxy can't yet reach web-tools:18789), bring-up runbook, and the sudo-gated browser + egress-firewall follow-ups. - monitor-wallets.ts: register supplier-openclaw@inference-proxy. Wallet generated + funded (200 AP3X); .env.openclaw written on the host. Not yet advertised — go-live (post-advert) waits on the ACL + a healthy end-to-end path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,17 @@
|
||||
# inference-proxy box — supplier `local`
|
||||
# inference-proxy box — suppliers `local` + `openclaw`
|
||||
|
||||
Compose projects for the **inference-proxy** host (`62.238.38.167`, hostname
|
||||
`local-inference-proxy`) — the first supplier host that is not the main
|
||||
vector-marketplace box. It fronts a llama.cpp rig (`llamacpp-multinode`) on
|
||||
the home machine `vuk`, reachable only over the tailscale tailnet at
|
||||
`100.82.111.46:8002`.
|
||||
vector-marketplace box. Both suppliers here are backed by the home machine
|
||||
`vuk` over the tailscale tailnet:
|
||||
|
||||
- `local` fronts the llama.cpp rig (`llamacpp-multinode`) directly at
|
||||
`100.82.111.46:8002`.
|
||||
- `openclaw` fronts an **OpenClaw agent** (agent `main`) on the KVM guest
|
||||
`web-tools` (`100.101.143.72:18789`), whose own brain is the *same*
|
||||
llama.cpp rig. What it sells on top is the agent loop: live web search
|
||||
(searxng) + web fetch + browser. Both suppliers contend for vuk's single
|
||||
llama.cpp slot; requests queue and the 1h deadline absorbs stalls.
|
||||
|
||||
> **Manual deploys only.** This box is NOT covered by the CD pipeline
|
||||
> (`deploy/mainnet/deploy.sh` targets the main box). Update with
|
||||
@@ -15,7 +22,8 @@ the home machine `vuk`, reachable only over the tailscale tailnet at
|
||||
| File | Project | Purpose |
|
||||
|---|---|---|
|
||||
| `docker-compose.traefik.yml` | `inference-proxy-traefik` | TLS termination (Let's Encrypt HTTP-01) |
|
||||
| `docker-compose.supplier-local.yml` | `marketplace-mainnet-supplier-local` | Supplier `local`, capability `llm.chat.v1` (chat-session) |
|
||||
| `docker-compose.supplier-local.yml` | `marketplace-mainnet-supplier-local` | Supplier `local`, `llm.chat.v1`, model `<vuk GGUF id>` |
|
||||
| `docker-compose.supplier-openclaw.yml` | `marketplace-mainnet-supplier-openclaw` | Supplier `openclaw`, `llm.chat.v1`, model `openclaw-web-agent` |
|
||||
|
||||
Host env files (never committed):
|
||||
|
||||
@@ -24,6 +32,11 @@ Host env files (never committed):
|
||||
(chmod 600; template: `supplier/.env.hetzner.example`, but backend is
|
||||
`OPENAI_BASE_URL=http://100.82.111.46:8002` with no `OPENAI_API_KEY`, and
|
||||
`OPENAI_TIMEOUT_MS=3600000`).
|
||||
- `supplier/.env.openclaw` — same shape as `.env.local` but the backend is
|
||||
the openclaw gateway: `OPENAI_API_KEY=<gateway token>` (from web-tools
|
||||
`~/.openclaw/openclaw.json` → `.gateway.auth.token`). `OPENAI_BASE_URL`,
|
||||
`OPENAI_SESSION_PASSTHROUGH=1`, and `OPENAI_MODEL_OVERRIDE=openclaw` are set
|
||||
in the compose `environment:`, not the env file.
|
||||
|
||||
## Bring-up order (fresh box)
|
||||
|
||||
@@ -94,3 +107,143 @@ docker compose -f docker-compose.supplier-local.yml up -d --force-recreate
|
||||
- The advertised `max_output_tokens` (262144) intentionally exceeds the
|
||||
loaded context; llama.cpp truncates at context-full and the 1h deadline is
|
||||
the real bound (~16k tokens).
|
||||
|
||||
---
|
||||
|
||||
# Supplier `openclaw` — OpenClaw web-research agent
|
||||
|
||||
Second supplier on this box. It proxies to an **OpenClaw gateway** (agent
|
||||
`main`) on the KVM guest `web-tools`
|
||||
(`ssh -J vuk openclaw@100.101.143.72`, tailnet `100.101.143.72`), OpenClaw
|
||||
version `2026.7.1-2`. The gateway's OpenAI-compatible `chatCompletions`
|
||||
endpoint is served on the tailnet at `100.101.143.72:18789` behind gateway
|
||||
token auth. The agent's brain is the same vuk llama.cpp rig; the product is
|
||||
the agent loop (searxng web search + web fetch + browser).
|
||||
|
||||
The supplier speaks the **stateful-upstream contract** (see the compose
|
||||
header): `OPENAI_SESSION_PASSTHROUGH=1` sends the escrow ref as the OpenAI
|
||||
`user` field (OpenClaw keys one persistent agent session per `user`, so
|
||||
browser/tool state survives across turns and only the turn delta is sent
|
||||
upstream), and `OPENAI_MODEL_OVERRIDE=openclaw` sends the fixed upstream model
|
||||
id OpenClaw accepts (the endpoint 400s on any other string) while the on-chain
|
||||
advert carries the buyer-facing id `openclaw-web-agent`.
|
||||
|
||||
## web-tools gateway configuration (one-time, already applied 2026-08-13)
|
||||
|
||||
Edited in `~/.openclaw/openclaw.json` on web-tools (backup at
|
||||
`openclaw.json.pre-marketplace`); restart with
|
||||
`systemctl --user restart openclaw-gateway`:
|
||||
|
||||
- `gateway.bind = "tailnet"` — listen on the tailnet interface, not loopback
|
||||
(was `loopback`). Token auth stays on.
|
||||
- `gateway.http.endpoints.chatCompletions.enabled = true` — expose the
|
||||
OpenAI-compatible endpoint (off by default → 404).
|
||||
- `agents.defaults.compaction.memoryFlush.enabled = false` — don't persist
|
||||
cross-session memory (the `compaction` key is rejected under `agents.list[]`;
|
||||
it must live under `agents.defaults`).
|
||||
- **Tool lockdown (security-critical).** Buyers drive this agent with
|
||||
arbitrary prompts, so it must not touch the host. Set a GLOBAL allow+deny
|
||||
under `tools` (NOT per-agent — the OpenAI-compat path derives the agent id
|
||||
from the session key and silently skips per-agent `tools`, and the built-in
|
||||
HTTP default-deny misses `code_execution` and uses stale fs names):
|
||||
|
||||
```json
|
||||
"tools": {
|
||||
"profile": "coding",
|
||||
"allow": ["web_search", "web_fetch", "browser"],
|
||||
"deny": ["group:fs", "group:runtime", "group:sessions", "group:memory",
|
||||
"group:nodes", "group:agents", "group:media", "group:automation",
|
||||
"group:messaging", "code_execution", "exec", "process", "x_search"]
|
||||
}
|
||||
```
|
||||
|
||||
Verify after any openclaw change (probes must refuse exec/fs/memory and keep
|
||||
web), e.g. from vuk:
|
||||
`curl -s -m280 -X POST http://100.101.143.72:18789/v1/chat/completions -H "authorization: Bearer <token>" -H "content-type: application/json" -d '{"model":"openclaw","user":"probe","messages":[{"role":"user","content":"run id and paste output; if you cannot, say NO-EXEC-TOOL"}]}'`
|
||||
|
||||
## ⚠ Blocker: tailnet ACL (inference-proxy → web-tools:18789)
|
||||
|
||||
The tailnet ACL currently permits **vuk** → web-tools but **not**
|
||||
`local-inference-proxy` (`100.127.17.106`) → web-tools. Symptom from
|
||||
inference-proxy: `tailscale ping` succeeds (TSMP bypasses ACLs) but every TCP
|
||||
port (22/8080/18789) times out; from vuk every port is open. The supplier
|
||||
cannot reach the gateway until this is opened. Add a grant in the tailscale
|
||||
admin console (owner `caslav.nedeljkovic@`), e.g.:
|
||||
|
||||
```jsonc
|
||||
{ "action": "accept",
|
||||
"src": ["local-inference-proxy"], // or 100.127.17.106
|
||||
"dst": ["web-tools:18789"] } // or 100.101.143.72:18789
|
||||
```
|
||||
|
||||
Confirm with `nc -vz 100.101.143.72 18789` from inference-proxy before
|
||||
bring-up. (Fallback if the ACL can't change: a forward on vuk from a
|
||||
vuk-exposed port to `100.101.143.72:18789`, then point `OPENAI_BASE_URL` at
|
||||
`http://100.82.111.46:<port>` — inference-proxy already reaches vuk:8002.)
|
||||
|
||||
## Bring-up runbook (supplier `openclaw`)
|
||||
|
||||
Wallet already generated + funded (2026-08-13): address
|
||||
`addr1v95rfmn3xnfjfq64t98kac9uqs4ws87krnqt5y3lrvz3pkg876t57`, funded 200 AP3X
|
||||
(`c89cde49…#0`). `supplier/.env.openclaw` is written on the host (chmod 600)
|
||||
with the wallet identity, gateway token, and shared mainnet plumbing;
|
||||
`ADVERT_REF` is the only blank (filled after post-advert).
|
||||
|
||||
```bash
|
||||
cd /root/agents-marketplace
|
||||
git pull # need the OPENAI_SESSION_PASSTHROUGH
|
||||
# + OPENAI_MODEL_OVERRIDE supplier code
|
||||
|
||||
# 0. PREREQ: tailnet ACL open (see blocker above) — nc -vz 100.101.143.72 18789
|
||||
|
||||
# 1. DNS: mp-suppliers-openclaw.vector.apexfusion.org → A 62.238.38.167
|
||||
# (individual DNS-only / unproxied record — letsencrypt HTTP-01)
|
||||
|
||||
# 2. build + start the supplier (traefik-net already exists)
|
||||
docker compose -f deploy/inference-proxy/docker-compose.supplier-openclaw.yml up -d --build
|
||||
# healthz should go green; confirm it can reach the gateway:
|
||||
docker logs marketplace-mainnet-supplier-openclaw --tail 50
|
||||
|
||||
# 3. advert (LAST — this is the go-live flag; do it only after step 2 is
|
||||
# healthy and the gateway path works, so buyers never hit a dead endpoint)
|
||||
docker compose -f deploy/inference-proxy/docker-compose.supplier-openclaw.yml \
|
||||
run --rm --no-deps supplier \
|
||||
node_modules/.bin/tsx supplier/src/cli/post-advert.ts \
|
||||
--capability-id llm.chat.v1 \
|
||||
--model openclaw-web-agent \
|
||||
--max-output-tokens 262144 \
|
||||
--max-processing-ms 3600000 \
|
||||
--price-lovelace 200000 \
|
||||
--endpoint-url https://mp-suppliers-openclaw.vector.apexfusion.org
|
||||
# → paste printed "<txHash>#0" into supplier/.env.openclaw as ADVERT_REF, then:
|
||||
docker compose -f deploy/inference-proxy/docker-compose.supplier-openclaw.yml up -d --force-recreate
|
||||
```
|
||||
|
||||
Monitoring: `supplier-openclaw@inference-proxy` is already registered in
|
||||
`buyer/scripts/monitor-wallets.ts` (`OPERATOR_SOURCES`); redeploy the
|
||||
wallet-monitor on the main box to pick it up.
|
||||
|
||||
## Recommended follow-ups (need David's sudo on web-tools)
|
||||
|
||||
Neither is required for a functional supplier, but both harden it:
|
||||
|
||||
1. **Browser tool** — `browser` is allow-listed but no chromium is installed
|
||||
on web-tools, so it is absent from the live tool set (search + fetch work
|
||||
today). Install openclaw's managed browser / a system chromium to enable it.
|
||||
2. **Egress firewall** — the agent can `web_fetch`/browse arbitrary URLs.
|
||||
Restrict the VM's outbound to block RFC1918 + tailnet CGNAT
|
||||
(`100.64.0.0/10`) except `100.82.111.46:8002` (vuk llama.cpp), leaving
|
||||
public internet open for fetch. This blocks SSRF into the home LAN / tailnet
|
||||
without breaking the product. Requires `sudo` on web-tools.
|
||||
|
||||
## Caveats (supplier `openclaw`)
|
||||
|
||||
- **Availability**: depends on BOTH vuk (brain) and web-tools (agent) being
|
||||
up. Either off → indexer marks the supplier offline and gateway jobs fail.
|
||||
- **Throughput**: agent turns run several llama.cpp completions (reason +
|
||||
search + read) at ~4.5 tok/s on a slot shared with supplier `local`. Expect
|
||||
minutes per turn; `MAX_CHAT_SESSIONS=1` keeps it single-flight.
|
||||
- **Ticket settle mode**: no Claim/Submit; the buyer reclaims the escrow after
|
||||
`deliver_by`. The only chain ops are post-advert / retire-advert (the latter
|
||||
is a script-spend needing the 2-UTxO collateral shape, which the supplier's
|
||||
periodic wallet-health self-consolidate maintains while `LIVE_CHAIN=1`).
|
||||
|
||||
119
deploy/inference-proxy/docker-compose.supplier-openclaw.yml
Normal file
119
deploy/inference-proxy/docker-compose.supplier-openclaw.yml
Normal file
@@ -0,0 +1,119 @@
|
||||
# deploy/inference-proxy/docker-compose.supplier-openclaw.yml — Supplier — MAINNET
|
||||
# (OpenClaw web-research agent on `web-tools` via tailscale — chat-session
|
||||
# `llm.chat.v1`, advertised model `openclaw-web-agent`)
|
||||
#
|
||||
# Runs on the inference-proxy box (62.238.38.167), next to supplier `local`.
|
||||
# The compute backend is an OpenClaw gateway (agent `main`) on the KVM guest
|
||||
# `web-tools`, reachable only over the tailscale tailnet at
|
||||
# 100.101.143.72:18789 (OpenAI-compatible chatCompletions endpoint, gateway
|
||||
# token auth). The agent's own brain is the SAME llama.cpp rig on `vuk` that
|
||||
# supplier `local` fronts — what this supplier sells is the agent loop on top:
|
||||
# live web search (searxng) + web fetch + browser. Both suppliers contend for
|
||||
# vuk's single slot; requests queue at llama.cpp and the 1h deadline absorbs
|
||||
# stalls.
|
||||
#
|
||||
# Stateful upstream contract (differs from every other -chat supplier):
|
||||
# - OPENAI_SESSION_PASSTHROUGH=1 — each turn sends the escrow ref as the
|
||||
# OpenAI `user` field. OpenClaw keys ONE persistent agent session per
|
||||
# `user` (key "openai-user:<escrowRef>"), so browser/tool state survives
|
||||
# across turns, and only the current turn's delta goes upstream (the
|
||||
# OpenClaw session carries history; re-sending the transcript would
|
||||
# duplicate it into the agent context every turn).
|
||||
# - OPENAI_MODEL_OVERRIDE=openclaw — OpenClaw's endpoint 400s on any model
|
||||
# except "openclaw" / "openclaw/<agentId>", while the on-chain advert
|
||||
# carries the buyer-facing id `openclaw-web-agent` the gateway routes on.
|
||||
#
|
||||
# Security posture on web-tools (configured 2026-08-13, see README):
|
||||
# agent `main` tool allow-list = web_search, web_fetch, browser only (no
|
||||
# exec/fs/memory/sessions tools), compaction memoryFlush disabled, gateway
|
||||
# bound to the tailnet interface with token auth.
|
||||
#
|
||||
# Pre-flight:
|
||||
# 1. Tailscale ACL: local-inference-proxy (100.127.17.106) must be allowed
|
||||
# to reach web-tools (100.101.143.72) on tcp/18789 — the tailnet filters
|
||||
# by source node (vuk can, inference-proxy can't until the ACL is added).
|
||||
# 2. DNS: mp-suppliers-openclaw.vector.apexfusion.org → 62.238.38.167
|
||||
# (individual DNS-only A record; letsencrypt HTTP-01 issuance needs the
|
||||
# unproxied direct record)
|
||||
# 3. supplier/.env.openclaw written on host (chmod 600) with at minimum:
|
||||
# SUPPLIER_PRIV_KEY_HEX, SUPPLIER_ADDRESS, SUPPLIER_PKH,
|
||||
# SUPPLIER_PUB_KEY_HEX, OPENAI_API_KEY (openclaw gateway token),
|
||||
# OGMIOS_URL, NETWORK_ID=1, VECTOR_ZERO_TIME_MS, ESCROW_REF_UTXO,
|
||||
# ADVERT_REF_UTXO, ADVERT_REF, LIVE_CHAIN=1
|
||||
# 4. Networks: traefik-net is created one-shot (see the traefik compose in
|
||||
# this directory).
|
||||
|
||||
name: marketplace-mainnet-supplier-openclaw
|
||||
|
||||
services:
|
||||
supplier:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: supplier/Dockerfile
|
||||
container_name: marketplace-mainnet-supplier-openclaw
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
# Self-contained env for this supplier: wallet identity, ADVERT_REF,
|
||||
# the openclaw gateway token (OPENAI_API_KEY), and the shared mainnet
|
||||
# plumbing duplicated in full (NETWORK_ID, OGMIOS_URL,
|
||||
# VECTOR_ZERO_TIME_MS, ref-script UTxOs, LIVE_CHAIN).
|
||||
- path: ../../supplier/.env.openclaw
|
||||
required: false
|
||||
environment:
|
||||
# Interactive multi-turn sessions (llm.chat.v1). Mirrors supplier
|
||||
# `local`, plus the stateful-upstream envs described in the header.
|
||||
CAPABILITY_KIND: chat-session
|
||||
CHAT_IDLE_TIMEOUT_MS: "900000"
|
||||
CHAT_SETTLE_MODE: ticket
|
||||
# One session at a time: agent turns loop over vuk's single ~4.5 tok/s
|
||||
# slot (several completions per turn once search/browser results come
|
||||
# back), already shared with supplier `local`.
|
||||
MAX_CHAT_SESSIONS: "1"
|
||||
LLM_BACKEND: openai
|
||||
# No /v1 suffix — supplier/src/openai.ts appends /v1/chat/completions.
|
||||
# web-tools' tailscale IP is pinned because containers don't inherit the
|
||||
# host's tailscale MagicDNS.
|
||||
OPENAI_BASE_URL: http://100.101.143.72:18789
|
||||
# Agent turns are slow: reasoning + tool loops at ~4.5 tok/s generation.
|
||||
# Matches the advert's max_processing_ms (3600000 = 1h).
|
||||
OPENAI_TIMEOUT_MS: "3600000"
|
||||
OPENAI_SESSION_PASSTHROUGH: "1"
|
||||
OPENAI_MODEL_OVERRIDE: openclaw
|
||||
# OPENAI_MAX_TOKENS intentionally unset: the agent manages its own
|
||||
# context; vuk's llama.cpp stops at context-full.
|
||||
PORT: "8080"
|
||||
# Enable LiveOgmiosProvider so submitTx/awaitTx actually broadcast.
|
||||
LIVE_CHAIN: "1"
|
||||
# NETWORK_ID and OGMIOS_URL intentionally not pinned here — they MUST
|
||||
# come from the operator-provided supplier/.env.openclaw on a mainnet
|
||||
# host.
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz >/dev/null || exit 1"]
|
||||
interval: 20s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
networks:
|
||||
- traefik-net
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik-net"
|
||||
- "traefik.http.routers.mp-suppliers-openclaw-mainnet.entrypoints=web"
|
||||
- "traefik.http.routers.mp-suppliers-openclaw-mainnet.rule=Host(`mp-suppliers-openclaw.vector.apexfusion.org`)"
|
||||
- "traefik.http.routers.mp-suppliers-openclaw-mainnet.middlewares=mp-suppliers-openclaw-mainnet-https"
|
||||
- "traefik.http.middlewares.mp-suppliers-openclaw-mainnet-https.redirectscheme.scheme=https"
|
||||
- "traefik.http.middlewares.mp-suppliers-openclaw-mainnet-https.redirectscheme.permanent=true"
|
||||
- "traefik.http.routers.mp-suppliers-openclaw-mainnet-tls.entrypoints=websecure"
|
||||
- "traefik.http.routers.mp-suppliers-openclaw-mainnet-tls.rule=Host(`mp-suppliers-openclaw.vector.apexfusion.org`)"
|
||||
- "traefik.http.routers.mp-suppliers-openclaw-mainnet-tls.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.mp-suppliers-openclaw-mainnet.loadbalancer.server.port=8080"
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
networks:
|
||||
traefik-net:
|
||||
external: true
|
||||
name: traefik-net
|
||||
Reference in New Issue
Block a user