ServicesPresence

Context Policies

A context is where all per-place behavior lives: heartbeat thresholds, capacity and overflow, the metadata schema, and geo. Managing contexts requires a platform admin token or the presence_admin capability - creating one with a regular token fails with 403 insufficient_permissions ("admin or presence_admin role required").

Creating and inspecting

snug presence contexts create --file world-presence.json
snug presence contexts get --context docs4-lobby   # policy + live occupancy

The file is the full policy; only id and kind are required:

{
  "id": "docs4-lobby",
  "kind": "room",
  "resource_id": "world_123",
  "heartbeat": { "ttl_seconds": 45, "idle_after_seconds": 15, "stale_after_seconds": 30 },
  "capacity": { "max_active": 3, "soft_limit": 1, "overflow_policy": "reject" },
  "metadata_schema": { "allowed_keys": ["display_name"] },
  "geo": { "enabled": true }
}

create is an upsert, and omitted fields reset to defaults. Verified: re-creating a context with only id and kind replaced its custom heartbeat policy with the defaults and bumped version to 2. Always send the full policy when updating. There is no delete endpoint - a context policy stays until overwritten.

Heartbeat thresholds

ttl_seconds bounds liveness; idle_after_seconds and stale_after_seconds drive the activity states. Idle must be less than stale - {"ttl_seconds": 30, "idle_after_seconds": 20, "stale_after_seconds": 10} is rejected with 400 invalid_context ("idle_after_seconds must be less than stale_after_seconds").

Capacity and overflow

max_active caps admitted sessions, soft_limit marks the warning zone, and both are off when 0. The occupancy summary on every heartbeat, get, and count reports the resulting occupancy_state: open, then near_capacity once the soft limit is reached (verified: one session in a max_active: 3, soft_limit: 1 context reported near_capacity with capacity_remaining: 2), then full.

What happens to a heartbeat that would exceed max_active is the overflow_policy, each verified with a second subject against a full one-seat context:

  • reject - 409 capacity_reached.
  • observer - admitted anyway with admission: "observer" and observer: true on the session. Observers are capacity-exempt: they appear in total and observers but do not consume capacity_remaining.
  • redirect - 409 capacity_redirect ("Context at capacity; redirect required"), telling the client to try another context of its own choosing; the service does not pick one.

Metadata schema

Sessions can carry display-safe metadata (name, avatar, role, cursor summary) on each heartbeat - over HTTP, since the CLI heartbeat command has no metadata flag. When the context declares allowed_keys, anything else is rejected, with a context allowing only display_name:

{ "status": 400, "msg": "Invalid metadata: key not allowed: password", "error": "invalid_metadata" }

Metadata is also capped at 4 KB per session by default.

Geo and nearby

With geo.enabled on the context, heartbeats may attach a position and nearby finds sessions within a radius, with distances:

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"session_id": "docs4-g1", "geo": {"lat": 41.8827, "lon": -87.6233}}' \
  http://localhost:4000/api/v1/presence/docs4-geo/heartbeat

snug --output json presence nearby --context docs4-geo \
  --lat 41.88 --lon=-87.62 --radius-meters 5000
{
  "context_id": "docs4-geo",
  "count": 1,
  "sessions": [
    {
      "session_id": "docs4-g1",
      "subject": { "id": "docs-wave", "kind": "user" },
      "distance_meters": 406.1473,
      "geo": { "lat": 41.8827, "lon": -87.6233 }
    }
  ]
}

Two gotchas, both reproduced: a geo payload sent to a context without geo enabled is 400 geo_not_enabled, and negative coordinates need the --lon=-87.62 form - a bare --lon -87.62 is parsed as a flag and the CLI errors with "unexpected argument '-8' found".

Manual sweep

Expired members are reconciled and idle/stale transitions applied lazily on reads and by the background cleanup worker, but an admin can force a pass and see what changed:

snug --output json presence sweep --context docs4-quick
{ "context_id": "docs4-quick", "reconciled": 0, "transitions": {} }

Like context creation, sweep is admin-only: a regular token gets 403 insufficient_permissions.

The exact request and response schemas for everything above are in the Presence API reference.

On this page