Presence
Low-latency "who is active here right now" tracking. Subjects - users, agents, services, devices - heartbeat into named contexts (a room, a document, a world shard), and liveness is a TTL: stop heartbeating and the session evaporates on its own. On top sit agent cognitive states, server- computed activity states, capacity policies with overflow behavior, geo queries, and durable last-seen summaries.
When to reach for it: presence avatars in a document or room, live occupancy counts for capacity-limited spaces, agent cognitive-state dashboards, "last seen 5 minutes ago" labels, nearby-session queries.
When not to: uptime monitoring of services and devices - "is this thing healthy" rather than "who is in this place" - belongs to Liveness; ephemeral broadcast messaging between clients belongs to Pub/Sub.
Concepts
- Context - a named place where presence is tracked, with a
kindlike room, document, or world. Contexts are created by admins and carry the policy: heartbeat thresholds, capacity, metadata schema, geo. See Context policies. - Session - one subject's presence in one context, created by its first heartbeat. A subject can hold several sessions (one per tab or device) and be present in many contexts at once.
- The subject is the caller. Sessions belong to the JWT subject - you cannot heartbeat on another subject's behalf. The kind (user, agent, service, device) is inferred from the principal and overridable per beat.
- Cognitive state vs activity - state is what the subject reports
(
available,thinking,busy,offline); activity is what the server computes from heartbeat age (active,idle,stale); both are filterable. - TTL is the source of truth - miss heartbeats past the context's
ttl_secondsand the session disappears. No disconnect handling needed. - Last-seen is durable - live sessions evaporate, but a per-context last-seen summary persists for "last seen at" queries.
Heartbeats
snug presence heartbeat --context docs4-lobby --session-id docs4-s1 \
--state available --device-id dev-laptopSend the same session_id to refresh; omit it and the server generates a
fresh prs_<uuid> - which creates a new session per beat, so clients
that refresh must send a stable id. --active false reports the session
as idle without dropping it. Over HTTP the same call is:
curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"session_id": "docs4-s1", "state": "thinking"}' \
http://localhost:4000/api/v1/presence/docs4-lobby/heartbeat{
"status": 200,
"msg": "OK",
"data": {
"session_id": "docs4-s1",
"context_id": "docs4-lobby",
"state": "thinking",
"activity": "active",
"admission": "admitted",
"observer": false,
"expires_at": "2026-08-28T05:02:03.937773Z",
"occupancy": {
"active": 1, "idle": 0, "stale": 0, "total": 1,
"observers": 0, "available_agents": 0,
"capacity_remaining": null, "occupancy_state": "open"
}
}
}Every heartbeat returns the context's occupancy summary for free.
Heartbeating a nonexistent context is 404 context_not_found - contexts
are never auto-created. The CLI heartbeat command has no metadata or geo
flags; send those over HTTP or in a multiplex
file.
The activity lifecycle
Verified against a context with ttl_seconds: 4, idle_after_seconds: 1,
stale_after_seconds: 2 - one heartbeat, then watched:
| Seconds since beat | activity |
|---|---|
| 0 | active |
| 1.3 | idle |
| 2.5 | stale |
| 4.5 | gone - session expired |
Transitions are applied lazily when sessions are listed and by a
background cleanup worker (every 30 seconds by default), so a list
always reflects current heartbeat age.
Who is here
snug presence list docs4-lobby
snug presence list docs4-lobby --state available --kind agent
snug presence list docs4-lobby --activity idle
snug presence list docs4-lobby --aggregate-by-subject # collapse multi-tab
snug presence count --context docs4-lobbylist returns full sessions (subject, device, state, activity, metadata,
expiry). With two device sessions for one subject, plain list returned
count: 2 and --aggregate-by-subject returned count: 1. count
returns the same occupancy summary heartbeats carry, plus
per-cognitive-state counts:
"by_state": { "available": 1, "busy": 1, "offline": 0, "thinking": 0 }Cognitive state
state moves a session between available, thinking, busy, and
offline without resending the full heartbeat payload:
snug --output json presence state --context docs4-lobby \
--state busy --session-id docs4-s1{ "session_id": "docs4-s1", "previous_state": "available", "state": "busy", "context_id": "docs4-lobby" }Leaving, and who was here
snug presence leave --context docs4-lobby --session-id docs4-s1
snug presence leave --context docs4-lobby # all of my sessions there
snug presence last-seen --subject docs-wavelast-seen survives session expiry and leave - it returns the overall
last_seen_at plus one summary per context (context_id, context_kind,
last_state, last_seen_at). It is self-or-admin: querying another
subject with a regular token was 403 insufficient_permissions
("cannot act on another subject"), verified with a second user's token.
Multiplexed heartbeats
One request keeps many contexts alive (up to 25 by default). Items are processed independently and failures are reported per context, verified with one valid and one missing context:
snug presence multiplex --file multiplex.json{ "items": [
{ "context_id": "docs4-lobby", "heartbeat": { "session_id": "docs4-s1", "state": "available" } },
{ "context_id": "docs4-missing", "heartbeat": {} }
] }The response carried succeeded: 1, failed: 1, with the full heartbeat
result for the first item and error: "context_not_found" for the second.
Limits and configuration
Defaults: 45-second heartbeat TTL, idle after 15 seconds, stale after 30
(all overridable per context), 4 KB of metadata per session, 25 multiplex
contexts, and list pages of 100 (max 500) - all tunable via the
PRESENCE_* variables in the
Presence CONFIG reference.
Reference
- Context policies - capacity and overflow, metadata schemas, geo and nearby queries, threshold tuning, the admin sweep
- Presence API - every endpoint, callable
- Related: Liveness for entity uptime monitoring, Pub/Sub for ephemeral messaging, Geo for persistent location tracking and geofences