docs/concepts/presence.md
OpenClaw "presence" is a lightweight, best-effort view of:
Presence is used primarily to render the macOS app's Instances tab and to provide quick operator visibility.
Presence entries are structured objects with fields like:
instanceId (optional but strongly recommended): stable client identity (usually connect.client.instanceId)host: human-friendly host nameip: best-effort IP addressversion: client version stringdeviceFamily / modelIdentifier: hardware hintsmode: ui, webchat, cli, backend, node, probe, testlastInputSeconds: seconds since last user input, if knownreason: free-form client-supplied string; the Gateway itself only emits self, connect, and disconnectdeviceId, roles, scopes: device identity and role/scope hints from the connect handshakets: last update timestamp (ms since epoch)Presence entries are produced by multiple sources and merged.
The Gateway always seeds a "self" entry at startup so UIs show the gateway host even before any clients connect.
Every WS client begins with a connect request. On successful handshake the
Gateway upserts a presence entry for that connection.
The CLI often connects for short, one-off commands. To avoid spamming the
Instances list, client.mode === "cli" is not turned into a presence entry.
system-event beaconsClients can send richer periodic beacons via the system-event method. The mac
app uses this to report host name, IP, and lastInputSeconds.
When a node connects over the Gateway WebSocket with role: node, the Gateway
upserts a presence entry for that node (same flow as other WS clients).
instanceId matters)Presence entries are stored in a single in-memory map, keyed case-insensitively
by the first available of, in order: a paired device id, connect.client.instanceId,
or the per-connection id as a last resort.
CLI clients are excluded from tracking entirely (see above), so their
connection id never becomes a key. For every other client, the connection id
fallback means a client that reconnects without a stable instanceId shows up
as a duplicate row.
Presence is intentionally ephemeral:
This keeps the list fresh and avoids unbounded memory growth.
When a client connects over an SSH tunnel / local port forward, the Gateway
may see the remote address as 127.0.0.1. To avoid recording that tunnel
address as the client's IP, connect handling omits ip entirely for
detected-local (loopback) clients rather than writing the loopback address
into the entry.
The macOS app renders the output of system-presence and applies a small status
indicator (Active/Idle/Stale) based on the age of the last update.
system-presence against the Gateway.client.instanceId in the handshakeinstanceIdinstanceId (duplicates are expected)