occupant-model-v1
Occupant Model v1
Occupant Model v1 defines how the shared world is represented and how communication flows across all clients (SDK and web-ui) as the world changes.
This document is written as a developer contract and an implementation story: why this model exists, what it guarantees, and how it enables a durable communication infrastructure.
Why this model exists
Agent Play is moving from ad hoc "peer" language to a concrete occupancy model. The goal is to make world state and world communication predictable at scale:
- one canonical world snapshot shape,
- one fanout path for updates,
- one incremental sync strategy for clients,
- one policy layer for allowed interactions.
Occupants are the unit of presence, visibility, and synchronization.
Occupant kinds (v1)
The world supports three occupant kinds:
human— user presence in the world.agent— business-capable automated actor.arcade— game cabinet structures on Maple Ave (gameIdon structure occupants).
Legacy mcp occupants may appear in older snapshots; new deployments seed arcade cabinets instead.
The world can contain many occupants of each kind. Humans are first-class occupants and are always modeled explicitly in world state.
Interaction policy (v1)
Communication policy is directional and role-aware:
- disallowed:
human -> humanchat / assist / text proximity actions - allowed:
human -> agent - allowed:
humanenters arcade cabinets (built-in mini-games, no external MCP) - allowed (exception): opt-in proximity peer voice (
peerCallInvite→ Accept/Decline) — separate from chat H2H ban
Humans can see other humans. Text H2H stays off; peer voice requires Accept and uses dedicated peerCall* APIs (not recordProximityAction).
Story: from world mutation to every client
When any world interaction changes state, the system follows a single end-to-end path:
- A mutation is applied against canonical snapshot state (
worldMap.occupants, bounds, metadata). - Snapshot persistence increments revision metadata and recomputes player-chain Merkle state.
- Fanout is published with world event payloads plus chain metadata.
- Connected clients receive events through SSE transport.
- Clients either:
- refresh with full
getWorldSnapshot, or - apply incremental updates using
playerChainNotify+getPlayerChainNode.
- refresh with full
This is the core infrastructure guarantee: all clients converge on the same occupancy state from the same event stream.
Player-chain (Merkle) role in Occupant Model v1
Player-chain is the integrity and convergence layer for occupant state:
- leaves are built from canonical world components (genesis, header, occupants),
- each occupant has a stable key for deterministic ordering,
- diffs are transported as lightweight node references (
playerChainNotify), - full row payloads are fetched on demand (
getPlayerChainNode) and merged client-side.
Result: low fanout payload size, deterministic merge order, and consistent state across SDK and browser clients.
Client definition
In this project, "client" means any runtime consuming world updates:
- SDK clients (
@agent-play/sdk, including server-side agent processes), - web-ui clients (watch UI sessions/tabs).
Occupant Model v1 is designed so both client classes consume the same world semantics and synchronization protocol.
Developer implementation guidance
- Prefer occupant terminology in code/docs over "peer".
- Keep world state changes routed through canonical snapshot mutation + fanout.
- Preserve interaction policy checks at API and world-domain boundaries.
- Treat
playerChainNotify+ node RPC merge as the primary incremental sync path. - Keep type unions and parser logic aligned between server and SDK for all occupant kinds.
Backward-compatibility notes
Older docs and comments may still reference "peers." Treat those as legacy phrasing. New features and refactors should align with Occupant Model v1 semantics.