NAVis · Four Layers, Three Entry Paths, One Filesystem

corrected against live ss -tlnp output · single gateway process () on two loopback ports

LAYER 1 · TRANSPORT (Tailscale WireGuard mesh) LAYER 2 · KERNEL SOCKETS (verified against live ss -tlnp on ) LAYER 3 · GATEWAY PROCESS & AGENT LOOP (single user-space daemon, process title "openclaw-gateway") LAYER 4 · STORAGE (filesystem = ground truth) Browser Terminal VS Code :localhost:18789 ssh via Tailscale (VS Code Remote-SSH port-forwards :18789) Your Laptop · tailscale node Tailscale (WireGuard) — NETWORK, not a single tunnel tailnet carries all TCP flows between these two nodes one encrypted overlay · multiple concurrent TCP streams on top stream A: port 22 (SSH) · stream B: forwarded :18789 (webchat) SSH rides on top like any app. It does not own the transport. Hetzner CPX22 · VPS · tailscale node tailscale0 → eth0 → (public) lo → 127.0.0.1 (loopback) three interfaces, same machine kernel accepts a SYN only if a listening socket is bound to the (interface, port) the packet lands on. from live ss -tlnp: SOCKETS VISIBLE ON eth0 (public) port 22 (sshd, 0.0.0.0) ✓ listening port 18789 (gateway) ✗ NO SOCKET Hetzner firewall blocks public inbound anyway SOCKETS ON tailscale0 () port 22 (sshd wildcard) ✓ reachable port 18789 (gateway) ✗ NO SOCKET gateway did not bind tailscale0 by choice SOCKETS ON lo (127.0.0.1) — loopback only :18789 gateway API + dashboard :18791 gateway internal/RPC (same) one process, two loopback listeners. verified via ss -tlnp. kernel enforces binding — no firewall needed for this layer "openclaw-gateway" · single Node.js process · systemd user service · /usr/bin/node /usr/lib/node_modules/openclaw/dist/index.js gateway --port 18789 :18789 · API + dashboard serves webchat HTML + chat API app-layer token auth on this listener bad token → HTTP 401 :18791 · internal RPC used by "openclaw" CLI on same box status probe · cron admin · config ops not for user traffic AGENT LOOP (per session — main, isolated-cron, or named) 1 · BUILD CONTEXT re-read SOUL · USER AGENTS · MEMORY each turn 2 · LLM CALL (outbound) gpt-4.1-mini · OpenAI gateway dials out over eth0 3 · DECIDE reply? call a skill? run a shell cmd? done? 4 · EXECUTE (tool use) skill / shell / fs write the real side-effect happens here 5 · RESULT → CONTEXT tool result appended fed back to next LLM call 6 · LOOP or FINISH more tools? → step 2 else → emit reply 7 · EMIT REPLY response → webchat / Telegram / session log 8 · PERSIST STATE session log · memory/ written to filesystem loop trust gap lives between steps 3 and 4 the model may CLAIM "I wrote the file" in step 3 text without issuing the step-4 tool call. upgrading gpt-4o-mini → gpt-4.1-mini narrowed but did not close this. verify on disk. INTERNAL SCHEDULER reads jobs.json + HEARTBEAT.md every tick → any jobs due? (NOT OS crontab — in-process) job · 0 4 * * * Europe/Berlin sessionTarget: isolated → spawns new agent loop → spawns isolated session OUTBOUND CHANNELS Telegram (long-poll · outbound) LLM API · OpenAI skill HTTP calls (search, fetch) gateway DIALS out · no inbound port IDENTITY FILES (~/.openclaw/) SOUL · USER · AGENTS · MEMORY jobs.json · HEARTBEAT.md re-read on every turn (not just session start) terminal edits visible next turn webchat → lo:18789 TERMINAL PATH — GATEWAY BYPASSED sshd :22 (tailscale0 reachable) → bash shell → syscalls on filesystem no LLM · no agent loop · no prediction cat / ls / git log · ground truth SHARED FILESYSTEM · ~/.openclaw/ + any project dirs the only layer with no LLM in front of it · ground truth for verification writes from agent loop skill output, memory/*.md session logs, SOUL edits steps 4 + 8 above may fail silently → verify reads/writes from terminal cat · ls · git log vim · direct edits deterministic · no prediction ground-truth lens writes from cron/isolated daily reflection → memory/ runs/jobId.jsonl same loop, new session may fail silently → verify concurrency reality main, isolated cron, and terminal can all touch MEMORY.md in the same minute. OpenClaw does not do file locking. Last-writer-wins on overlapping writes. PRACtis solves this with snapshot-before-edit + git concurrency groups Phone Telegram app Telegram cloud (bot long-poll) gateway polls Telegram outbound → messages arrive as "user turns" in the agent loop webchat terminal (bypasses gateway) cron-triggered isolated session Tailscale / Telegram LLM API (outbound)

Technical Architecture

What this diagram shows

NAVis is a personal AI agent running as a single systemd user service on a Hetzner VPS, reached only through a Tailscale-only private network. The diagram traces three entry paths (webchat, terminal, cron) through four layers (transport, sockets, gateway+loop, filesystem) and shows where the real work happens — and where the trust gap lives.

The one-line architecture

Process openclaw-gateway (, Node.js) runs as a user systemd service. It binds two loopback-only ports (:18789 for the webchat/API, :18791 for the internal RPC the CLI uses). Nothing listens on eth0:18789 or tailscale0:18789 — verified with ss -tlnp.

Layer 1 · Transport

Laptop and VPS are nodes on the same tailnet. Over the encrypted WireGuard overlay, two independent TCP streams run: ssh :22 and VS Code Remote-SSH's port-forward of :18789. SSH doesn't own the transport — it rides on it like any application. The gateway never has to bind tailscale0, because the port-forward turns laptop-localhost into vps-localhost.

Layer 2 · Kernel sockets

A listening socket is (interface, port). The kernel accepts a SYN only if a socket exists for the pair the packet lands on. Live ss -tlnp confirms: loopback has gateway on :18789 and :18791. eth0 and tailscale0 have zero gateway sockets. Hetzner's firewall adds a second layer on top of eth0 but is not what enforces the gateway's isolation — the bind choice is.

Layer 3 · Gateway process & agent loop

One Node.js process, two listeners, one agent runtime. The agent loop runs per-session (main conversation, isolated cron job, or named persistent session): build context → LLM call → decide → execute tools → feed result back → loop or finish → emit → persist. The trust gap lives between steps 3 and 4: the model's claim in text versus an actual tool call. Upgrading from gpt-4o-mini to gpt-4.1-mini narrowed this gap but did not eliminate it. Terminal verification closes it.

Cron as a first-class entry path

The OpenClaw internal scheduler reads jobs.json and spawns isolated sessions directly into the same agent loop — not via OS crontab. Daily reflection at 0 4 * * * Europe/Berlin (job ) is the concrete example.

Layer 4 · Filesystem is the only truth

Three writers, no locking. Webchat-agent, terminal, and cron-isolated-agent all write to the same paths. OpenClaw does not arbitrate. Last-writer-wins on overlapping writes — a real concurrency weakness worth naming.

What this diagram is not

Not shown: retries/backoff on LLM calls, streaming vs buffered responses, per-skill permission scoping, Telegram webhook alternative (long-poll is what's deployed), audit logging. Those belong in drill-down diagrams, not here.