The tailnet account changed (caslav.nedeljkovic@ to teamhaleight@, taild99fee.ts.net) and all device IPs were reassigned, breaking both inference-proxy suppliers' hardcoded backend addresses. - supplier-local: vuk llama.cpp 100.82.111.46 to 100.77.146.49. - supplier-openclaw: the openclaw gateway is now exposed via Tailscale Serve (David's reconfigure set gateway.tailscale.mode=serve, which forces bind=loopback). Switch OPENAI_BASE_URL from the old raw http endpoint to https://web-tools.taild99fee.ts.net, and pin that MagicDNS name to web-tools' tailnet IP (100.105.36.0) via extra_hosts (Serve's TLS cert is for the hostname, and containers don't inherit the host tailscale MagicDNS). - README: new account/IP table, Serve config (replaces the tailnet-bind section), reachability check (the old ACL blocker is gone), updated probe/runbook. Verified: authenticated turn to the Serve endpoint from inference-proxy returns a completion; vuk:8002 reachable from inference-proxy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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. Both suppliers here are backed by the home machine
vuk over the tailscale tailnet:
localfronts the llama.cpp rig (llamacpp-multinode) directly at100.77.146.49:8002.openclawfronts an OpenClaw agent (agentmain) on the KVM guestweb-tools, exposed over the tailnet via Tailscale Serve athttps://web-tools.taild99fee.ts.net, 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.
Tailnet (2026-08-13): account
teamhaleight@, tailnettaild99fee.ts.net. Node IPs: inference-proxy100.110.165.124, vuk100.77.146.49, web-tools100.105.36.0. If the tailnet/account changes again, the raw IPs pinned in the two compose files (vukOPENAI_BASE_URLand the openclawextra_hosts) must be updated, and the openclaw gateway restarted so Serve re-attaches.
Manual deploys only. This box is NOT covered by the CD pipeline (
deploy/mainnet/deploy.shtargets the main box). Update withgit pull && docker compose ... up -d --build.
Layout
| 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, 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):
deploy/inference-proxy/.env—ACME_EMAIL=<operator email>for traefik.supplier/.env.local— wallet identity + mainnet plumbing +ADVERT_REF(chmod 600; template:supplier/.env.hetzner.example, but backend isOPENAI_BASE_URL=http://100.77.146.49:8002with noOPENAI_API_KEY, andOPENAI_TIMEOUT_MS=3600000).supplier/.env.openclaw— same shape as.env.localbut 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, andOPENAI_MODEL_OVERRIDE=openclaware set in the composeenvironment:, not the env file.
Bring-up order (fresh box)
# 1. docker + network (one-shot)
curl -fsSL https://get.docker.com | sh
docker network create traefik-net
# 2. repo
git clone https://github.com/Apex-Fusion/agents-marketplace.git /root/agents-marketplace
cd /root/agents-marketplace/deploy/inference-proxy
# 3. traefik
echo 'ACME_EMAIL=<operator email>' > .env && chmod 600 .env
docker compose -f docker-compose.traefik.yml up -d
# 4. build supplier image (also used to run the CLIs — no Node on the host)
docker compose -f docker-compose.supplier-local.yml build
# 5. wallet: generate, then write all four values into supplier/.env.local
docker compose -f docker-compose.supplier-local.yml run --rm --no-deps supplier \
node_modules/.bin/tsx supplier/src/cli/gen-keypair.ts --network 1
# → fund the printed address (~200 AP3X), and set DNS:
# mp-suppliers-local.vector.apexfusion.org → A 62.238.38.167 (DNS-only/unproxied)
# 6. advert (after funding confirmed)
docker compose -f docker-compose.supplier-local.yml run --rm --no-deps supplier \
node_modules/.bin/tsx supplier/src/cli/post-advert.ts \
--capability-id llm.chat.v1 \
--model <exact id from http://100.77.146.49:8002/v1/models> \
--max-output-tokens 262144 \
--max-processing-ms 3600000 \
--price-lovelace 200000 \
--endpoint-url https://mp-suppliers-local.vector.apexfusion.org
# → paste printed "<txHash>#0" into supplier/.env.local as ADVERT_REF
# 7. supplier
docker compose -f docker-compose.supplier-local.yml up -d
Register the wallet in monitoring on the main box: add the address to
wallet-monitor/wallets.json and buyer/scripts/monitor-wallets.ts
(OPERATOR_SOURCES).
Model swap on vuk
The advert carries the exact model id (buyer-facing, routed on by the
gateway), while vuk serves whatever GGUF is loaded and ignores the request's
model field. After swapping the model on vuk:
cd /root/agents-marketplace/deploy/inference-proxy
docker compose -f docker-compose.supplier-local.yml run --rm --no-deps supplier \
node_modules/.bin/tsx supplier/src/cli/retire-advert.ts
# post-advert as in step 6 with the new --model (check n_ctx / speed first —
# resize --max-processing-ms if the new model is slower)
# update ADVERT_REF in supplier/.env.local, then:
docker compose -f docker-compose.supplier-local.yml up -d --force-recreate
Caveats
- Availability: when vuk is off, the indexer prober marks the supplier offline and gateway jobs for this model fail. Accepted for a home rig.
- Throughput: ~4.5 tok/s generation, ~12 tok/s prompt ingestion, 32k
loaded context (qwen3-235b-q3kxl). Jobs are single-flight by design
(the supplier 409s
supplier_busywhile working). - 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.105.36.0), OpenClaw version 2026.7.1-2. The
gateway binds to loopback (127.0.0.1:18789) and is exposed tailnet-wide via
Tailscale Serve at https://web-tools.taild99fee.ts.net (valid *.ts.net
TLS, 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.tailscale.mode = "serve"+gateway.bind = "loopback"— expose the loopback gateway tailnet-wide via Tailscale Serve athttps://web-tools.taild99fee.ts.net. (Serve requiresbind=loopback; settingbind=tailnetwhiletailscale.mode=servefails to start with "gateway.bind must resolve to loopback". Verify withtailscale serve status→/ proxy http://127.0.0.1:18789.) Token auth stays on. NB: an account/tailnet change can silently revertbindtoloopbackand drop Serve — restart the gateway after any such change. -
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 (thecompactionkey is rejected underagents.list[]; it must live underagents.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-agenttools, and the built-in HTTP default-deny missescode_executionand uses stale fs names):"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 the inference-proxy host or vuk:
curl -s -m280 -X POST https://web-tools.taild99fee.ts.net/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"}]}'
Reachability (no ACL needed)
The gateway is exposed via Tailscale Serve, which is tailnet-wide, and the tailnet has no restrictive ACLs, so any tailnet node reaches it. Confirm from inference-proxy before bring-up:
curl -s -m10 -o /dev/null -w '%{http_code}\n' -X POST \
https://web-tools.taild99fee.ts.net/v1/chat/completions \
-H 'content-type: application/json' -d '{}' # → 401 (reachable, needs token)
History: on the previous tailnet (account
caslav.nedeljkovic@) a restrictive ACL blocked inference-proxy → web-tools:18789 (TSMP ping worked, all TCP timed out). Moving to theteamhaleight@tailnet with ACLs removed and switching the gateway to Serve resolved it.
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).
cd /root/agents-marketplace
git pull # need the OPENAI_SESSION_PASSTHROUGH
# + OPENAI_MODEL_OVERRIDE supplier code
# 0. PREREQ: gateway reachable — curl https://web-tools.taild99fee.ts.net/... → 401
# (see "Reachability" above)
# 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:
- Browser tool —
browseris 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. - 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) except100.77.146.49:8002(vuk llama.cpp), leaving public internet open for fetch. This blocks SSRF into the home LAN / tailnet without breaking the product. Requiressudoon 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=1keeps 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 whileLIVE_CHAIN=1).