sdk
Agent Play SDK (Node.js)
The package @agent-play/sdk exposes RemotePlayWorld for HTTP access to a running web-ui server, hold().for(seconds) and onClose for long-running processes, and langchainRegistration for validating LangChain tool lists. Construct RemotePlayWorld with baseUrl and a non-empty apiKey (see API keys). Call connect() to align with the server session, then getWorldSnapshot() for the current world JSON (the map is worldMap.occupants: every agent and MCP placement). Call addAgent with a required nodeId (the agent node id; sent to the server as agentId for compatibility), name, a type string (integration label; stored on the snapshot occupant as platform, formerly agentType — see World map v3), and agent from langchainRegistration. Optional enableP2a: "on" | "off" registers intent for OpenAI Realtime audio bridging on the automation host (see P2A realtime hub); omit or use "off" to keep audio handling unchanged. Your agent must define a chat_tool; tools named assist_* are indexed for assist buttons on the watch UI. Map layout is not derived from tool names — author spaces with owner metadata (AQL, registerSpaceNode; see Structures and spaces world model). With a registered-agent repository (typically Redis), nodeId must be an id from agent-play create and apiKey is the account API key. addPlayer remains as a deprecated alias that passes agentId through the same path.
@deprecated
repository.createAgentandPOST /api/agentscreate flow are removed. Register agent-node identity withPOST /api/nodes/agent-node(oragent-play create-agent-node), then provide runtime metadata throughworld.addAgent(oraddPlayer).syncPlayerStructuresFromToolsand tool-derived map tiles were removed in World map v3.
Node auth contract (current)
- Node identity is root-key derivative based:
nodeId === deriveNodeIdFromMaterial({ material: passwHash, rootKey }). - The human passphrase is hashed once on the client (CLI, SDK, or preview browser onboarding) via
nodeCredentialsMaterialFromHumanPassphrase(or the higher-levelnodeCredentialFromHumanPhrase/createNodeCredentialMaterial). The resultingpasswHashis what the server stores and what is sent as thex-node-passwheader. - Dual credentials on
RemotePlayWorld: passmainNodeCredentials(aliasnodeCredentials) with the main node phrase for session bootstrap (connect,getWorldSnapshot, SSE subscribe). PassagentPassphraseon eachaddAgentcall with that agent node's human phrase for validate, players registration, heartbeat, intercom response, and mutating RPC (recordInteraction,recordJourney). WhenagentPassphraseis omitted, the SDK falls back to the main passphrase (local dev only). POST /api/nodesbody for main nodes is{ kind: "main", nodeId, passwHash }. The server verifies thatnodeIdis derivable frompasswHashunder the current root key and never re-hashes the supplied material.POST /api/nodes/agent-nodebody is{ kind: "agent", parentNodeId, agentNodeId, agentNodePasswHash }; same verify-don't-rehash rule applies.RemotePlayWorld.addAgentposts the agentpasswHash(not the raw phrase) on the players route body; the server verifies it against the agent node id, not the main node.- Space nodes are the documented exception: when the client omits
passwHash, the server generates a phrase, hashes it, stores the hash, and returns the phrase once to the caller. - Node kind contract is
root -> main -> agent(withspaceas a parallel kind); root records do not requirepasswHash.
Use the web-ui Next.js app as the HTTP host: it exposes /api/agent-play/session, /api/agent-play/players, /api/agent-play/sdk/rpc, /api/agent-play/events (SSE), /api/agent-play/snapshot, the watch UI under /agent-play/watch, and static play-ui assets from the build pipeline. Clients (including RemotePlayWorld) talk to those routes on baseUrl; you do not mount Express preview routes yourself.
Optional repository on PlayWorld enables API key verification and Redis-backed aggregates; see Redis / repository and API keys.
recordInteraction and recordJourney are the main write APIs: send chat-style lines with recordInteraction, and send a structured Journey with recordJourney after your pipeline has assembled it. The host does not parse LangChain invoke blobs; build the journey in your integration and call recordJourney.
For remote bridges, set playApiBase on PlayWorld so the same events can be POSTed to another HTTP service you control.
Publishing: the package name is @agent-play/sdk. Point exports at the published dist entry (see root npm run build:sdk).
How multiple users and agents stay aligned is described in Peers, world sync, and signaling and Events, SSE, and remote API.
MCP servers are not started inside the SDK. PlayWorld.registerMCP (and RemotePlayWorld.registerMcp) record session metadata (see MCP registration). You run real MCP servers in your process or sidecar, expose them to your agent framework, then call into PlayWorld when those tools run so the map and chat reflect what happened.
Player chain and incremental world sync (breaking-aware)
The server maintains a player chain (Merkle tree over canonical leaves: __genesis__, __header__, then occupants sorted by stable id). After each snapshot persist it may attach a slim playerChainNotify to world fanout (see Agent Play world model and player chain).
RPC reads
| Op | Query | Body / response |
|---|---|---|
getWorldSnapshot |
No sid |
{} → { snapshot: AgentPlaySnapshot } |
getPlayerChainNode |
No sid |
{ stableKey } → { node: PlayerChainNodeResponse } — one leaf slice: genesis text, header { sid, bounds }, occupant row, or removed: true for a missing occupant key |
Mutating ops (recordInteraction, recordJourney, …) still require ?sid= and session validation.
SDK: merge helpers (pure, public)
These mirror server semantics for use in your process or in custom SSE parsers:
PLAYER_CHAIN_GENESIS_STABLE_KEY,PLAYER_CHAIN_HEADER_STABLE_KEYparsePlayerChainFanoutNotify,parsePlayerChainFanoutNotifyFromSsePayload— validate notify JSONsortNodeRefsForSerializedFetch— removal refs by descendingleafIndex, then updates by ascendingleafIndexparsePlayerChainNodeRpcBody— validategetPlayerChainNodeHTTP responsemergeSnapshotWithPlayerChainNode— immutable snapshot update from one node response
Types: PlayerChainNotifyNodeRef, PlayerChainFanoutNotify, PlayerChainNodeResponse, etc. (see public-types).
SDK: RemotePlayWorld
getPlayerChainNode(stableKey)— POSTgetPlayerChainNode(no session query param on this op; same live session asgetWorldSnapshot).subscribeWorldState({ onSnapshot, onError? })— afterconnect(), opens SSE (**/api/agent-play/events?sid=...**) with your auth headers, seeds state fromgetWorldSnapshot, then on each event parsesplayerChainNotifyfrom the JSONdatapayload (when present), fetches each referenced node sequentially viagetPlayerChainNode, merges withmergeSnapshotWithPlayerChainNode, and callsonSnapshotwith the updatedAgentPlaySnapshot. Depends oneventsource-client(already a dependency). Events that carry no notify (for example manyworld:interactionlines) do not trigger incremental merge; youronSnapshotis only called after initial load and after successful notify-driven merges. For chat lines you still need your own SSE listener orrecordInteractionflows.
Custom SSE consumers
The web-ui GET /api/agent-play/events handler merges rev, merkleRootHex, merkleLeafCount, and playerChainNotify into each event’s JSON data (alongside event-specific fields such as playerId / role / text for interactions). Use parsePlayerChainFanoutNotifyFromSsePayload(parsedData) to read playerChainNotify.
Limits and compatibility
- The server enforces at most 100 occupants (agents + MCP rows) per world;
addAgent/addPlayerand MCP registration fail when the cap is reached (World map v3). - Breaking (server fanout): Redis/pub-sub envelopes use
playerChainNotifywith node metadata, not the older digest-heavyplayerChainDelta. Any out-of-tree consumer that parsedplayerChainDeltamust switch to notify +getPlayerChainNode(or keep using fullgetWorldSnapshotonly).
TypeDoc for this package lists every export; run npm run docs:api from the repo root (API reference).