{
  "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": []
}