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 occupancyThe 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 withadmission: "observer"andobserver: trueon the session. Observers are capacity-exempt: they appear intotalandobserversbut do not consumecapacity_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.