{ "title": "Presence", "content": "OpenClaw “presence” is a lightweight, best‑effort view of:\n\n* the **Gateway** itself, and\n* **clients connected to the Gateway** (mac app, WebChat, CLI, etc.)\n\nPresence is used primarily to render the macOS app’s **Instances** tab and to\nprovide quick operator visibility.\n\n## Presence fields (what shows up)\n\nPresence entries are structured objects with fields like:\n\n* `instanceId` (optional but strongly recommended): stable client identity (usually `connect.client.instanceId`)\n* `host`: human‑friendly host name\n* `ip`: best‑effort IP address\n* `version`: client version string\n* `deviceFamily` / `modelIdentifier`: hardware hints\n* `mode`: `ui`, `webchat`, `cli`, `backend`, `probe`, `test`, `node`, ...\n* `lastInputSeconds`: “seconds since last user input” (if known)\n* `reason`: `self`, `connect`, `node-connected`, `periodic`, ...\n* `ts`: last update timestamp (ms since epoch)\n\n## Producers (where presence comes from)\n\nPresence entries are produced by multiple sources and **merged**.\n\n### 1) Gateway self entry\n\nThe Gateway always seeds a “self” entry at startup so UIs show the gateway host\neven before any clients connect.\n\n### 2) WebSocket connect\n\nEvery WS client begins with a `connect` request. On successful handshake the\nGateway upserts a presence entry for that connection.\n\n#### Why one‑off CLI commands don’t show up\n\nThe CLI often connects for short, one‑off commands. To avoid spamming the\nInstances list, `client.mode === \"cli\"` is **not** turned into a presence entry.\n\n### 3) `system-event` beacons\n\nClients can send richer periodic beacons via the `system-event` method. The mac\napp uses this to report host name, IP, and `lastInputSeconds`.\n\n### 4) Node connects (role: node)\n\nWhen a node connects over the Gateway WebSocket with `role: node`, the Gateway\nupserts a presence entry for that node (same flow as other WS clients).\n\n## Merge + dedupe rules (why `instanceId` matters)\n\nPresence entries are stored in a single in‑memory map:\n\n* Entries are keyed by a **presence key**.\n* The best key is a stable `instanceId` (from `connect.client.instanceId`) that survives restarts.\n* Keys are case‑insensitive.\n\nIf a client reconnects without a stable `instanceId`, it may show up as a\n**duplicate** row.\n\n## TTL and bounded size\n\nPresence is intentionally ephemeral:\n\n* **TTL:** entries older than 5 minutes are pruned\n* **Max entries:** 200 (oldest dropped first)\n\nThis keeps the list fresh and avoids unbounded memory growth.\n\n## Remote/tunnel caveat (loopback IPs)\n\nWhen a client connects over an SSH tunnel / local port forward, the Gateway may\nsee the remote address as `127.0.0.1`. To avoid overwriting a good client‑reported\nIP, loopback remote addresses are ignored.\n\n### macOS Instances tab\n\nThe macOS app renders the output of `system-presence` and applies a small status\nindicator (Active/Idle/Stale) based on the age of the last update.\n\n* To see the raw list, call `system-presence` against the Gateway.\n* If you see duplicates:\n * confirm clients send a stable `client.instanceId` in the handshake\n * confirm periodic beacons use the same `instanceId`\n * check whether the connection‑derived entry is missing `instanceId` (duplicates are expected)", "code_samples": [], "headings": [ { "level": "h2", "text": "Presence fields (what shows up)", "id": "presence-fields-(what-shows-up)" }, { "level": "h2", "text": "Producers (where presence comes from)", "id": "producers-(where-presence-comes-from)" }, { "level": "h3", "text": "1) Gateway self entry", "id": "1)-gateway-self-entry" }, { "level": "h3", "text": "2) WebSocket connect", "id": "2)-websocket-connect" }, { "level": "h3", "text": "3) `system-event` beacons", "id": "3)-`system-event`-beacons" }, { "level": "h3", "text": "4) Node connects (role: node)", "id": "4)-node-connects-(role:-node)" }, { "level": "h2", "text": "Merge + dedupe rules (why `instanceId` matters)", "id": "merge-+-dedupe-rules-(why-`instanceid`-matters)" }, { "level": "h2", "text": "TTL and bounded size", "id": "ttl-and-bounded-size" }, { "level": "h2", "text": "Remote/tunnel caveat (loopback IPs)", "id": "remote/tunnel-caveat-(loopback-ips)" }, { "level": "h2", "text": "Consumers", "id": "consumers" }, { "level": "h3", "text": "macOS Instances tab", "id": "macos-instances-tab" }, { "level": "h2", "text": "Debugging tips", "id": "debugging-tips" } ], "url": "llms-txt#presence", "links": [] }