ADR 0129: Each player owns a body
Status: accepted (James, 2026-08-19); the local-body half superseded
(2026-08-30) by ADR 0145, which leaves one
body and keeps this record's identity boundary intact. Supersedes
ADR 0053 and the possession-specific
parts of ADR 0059. The
EnvironmentRuntime session/capability lease and its expiry recovery remain in
force.
Context
The earlier local architecture treated one emulator as Clankie's body and let an external harness temporarily take it under a cross-process lock. That coupled a testing transport to identity, Discord room input, activity publication, and the resident play loop. It also made ordinary resource ownership sound like one agent could become another.
PokeAgents already models multiplayer correctly: the world authenticates separate player seats, each seat addresses its own session and body, and the player contract defines what that identity may observe or do. The local architecture should preserve the same identity boundary even when every process runs on one machine.
Decision
No process possesses Clankie. No harness may take, steal, suspend, or speak through his body, and no harness receives input from his Discord room. Clankie's local play process owns its emulator/runtime for that process lifetime. An external process owns a different body.
Local contract sandbox
apps/gba-mcp is an isolated emulator contract sandbox. Every stdio process
creates one private core, adapter, EnvironmentRuntime, session, and checkpoint
scope. Launching it grants the caller control only over that private core. It
has no Activity producer, play-voice client, Discord credential, room-input
subscription, possession API, or path to Clankie's running emulator.
Hosted co-play
Co-play happens in PokeAgents hosted worlds through separately provisioned
player credentials and identities. @pokeagents/world-protocol owns the player
operation schemas and capability contract. The PokeAgents MCP server derives a
transport projection from that catalog and retains its caller's session token;
MCP does not define identity, authority, or gameplay semantics.
Clankie uses the pinned published @pokeagents/world-protocol package and its
native client transport because his existing play loop needs a body seam and
continuous frames, not a second tool-driven play loop. Clankie imports no
PokeAgents host, emulator, persistence, or world-MCP package in product source.
The operation catalog and MCP derivation are shipped in the sibling PokeAgents
repository. A stronger session-bound typed client or tighter catalog-only
dispatch belongs there as follow-up work; this ADR does not claim those
improvements are already integrated into Clankie.
Clankie's voice belongs to Clankie
Clankie's local and hosted play use the neutral @clankie/play-voice loopback
package to connect his own game experience to whichever Discord body is active.
The Discord body remains the sole room author and sends only already-admitted
room input back to his play loop. GBA MCP and other external harnesses do not
receive this package, bearer, narration path, or room input.
Runtime leases are not possession
EnvironmentRuntime keeps its internal session/capability lease. That lease
fences actions, expiry, pause, resume, cancellation, and restart recovery inside
the runtime that owns the environment. It does not transfer identity or let one
process take another process's body.
Historical files remain inert
Existing body.lock and possession-events.jsonl files may remain on disk as
historical evidence. Current startup and play paths neither read nor write them.
There is no migration and no automatic deletion.
Consequences
- Starting a harness cannot interrupt Clankie, publish as him, speak as him, or hear his room.
- Local parallelism costs one emulator/runtime per process instead of coordinating writers around one shared core.
- Hosted interaction uses the world's native player identity and capability model; transports project that contract rather than redefining it.
- Current operating details live in the play guide and play-voice guide.
