ADR 0016: Versioned interactive-environment contract and lane-scoped tools
Status: accepted. The Minecraft lifecycle and mission/task bindings below are historical. The provider-neutral versioning, lease, and bounded-media decisions remain in the GBA environment stack; no separate ADR file survives for the provider-profiled v2 follow-up.
Context
At ratification Minecraft introduced a durable external body whose motor loop outlived any one model turn. Operator, Discord voice, and gameplay reasoning use separate pi sessions but present one Clankie identity. A global Minecraft tool catalog or a blocking tool call would couple those sessions, waste model context, and let a stale background decision override a newer foreground command.
The runtime also crosses several trust boundaries: model-authored arguments, untrusted Minecraft content, runner-owned account credentials, and server-authoritative verification. Mineflayer implementation types are not a stable product API.
Decision
At ratification @clankie/protocol owned cross-product identity, captain-lane,
authority, intent, character-snapshot, and mission/task binding schemas, while
@clankie/interactive-environment owned provider-neutral session, lease,
command, action-result, observation, event, and Minecraft profile schemas.
The enduring decision is that runtime adapters depend inward on versioned,
provider-neutral contracts rather than leaking implementation types. Neither
shared package imported Mineflayer or Paper types.
Every command carries a source lane, principal and authority tier,
correlation ID, and expected goalVersion. The arbiter rejects a command when
its expected version is stale. Long-running commands return an action handle;
completion and failure arrive as later results and semantic events.
Tool exposure was a deterministic projection of session phase and lane:
The runner owned the lease, credentials, connection, and cancellation. Lease schemas name server, world, character, quotas, and capabilities but reject credential fields. Ticks, chunks, packets, audio, and video never enter the semantic event stream; bounded artifacts reference them when evidence needs the data.
Each top-level contract carries integer schemaVersion: 1. Additive optional
fields may retain the version. A breaking semantic or structural change
increments the version. During a migration, boundaries dual-read the current
and immediately previous version, translate to the current in-memory shape,
and single-write the current version. Unknown versions fail closed and emit a
compatibility event; stored history is migrated by explicit, tested
translators rather than reinterpretation.
VUH-1635 clarifies how clients uphold the additive-field rule. An authenticated JSON response reader projects the fields its bundled schema knows, recursively discarding unknown object keys while retaining validation of known values, bounds, versions, discriminators and refinements. Request validation, persisted history and encryption envelopes remain strict. The protocol owns the response parser so app, TUI and relay upstream clients share this rule rather than maintaining field-specific compatibility lists. A new host's optional metadata cannot grant authority or make a malformed known field valid. Already shipped strict readers need updated JavaScript; the host can temporarily omit optional metadata to recover them, but cannot retrofit a client parser remotely.
VUH-2530 extends the rule to lists whose values grow. A new enum value is not
an unknown key, so a display catalog that names settings, pickers or checks
failed its whole response when a host added one: a new Discord setting made
older apps reject the entire setup snapshot. A response enum a newer host may
extend is declared with the protocol's additiveEnum. Strict parsing still
rejects an unknown value; the response reader drops the nearest list entry that
holds one and validates the rest again. An additive value outside a list, or an
entry that is also malformed in a known way, still fails. Statuses, versions
and discriminators a client must act on stay plain enums. Because an older
client cannot carry a newer setting back, the Discord settings write keeps the
current value of every defaulted setting the writer omits; current writers send
every defaulted setting they know. VUH-2648 extends this to optional settings,
which a writer omits both to clear them and when it predates them. Each one
names the defaulted settings introduced with it, and an omitted optional
setting is cleared only when the save names one of those; otherwise it keeps
its value. A new optional setting names such settings or ships with a default.
Options weighed
- Expose every Minecraft MCP tool to every captain session — rejected because dormant TUI and voice turns do not need motor schemas, and ambient channels must not gain gameplay authority.
- Use one pi session for operator, voice, and gameplay — rejected because a long voice or gameplay turn would block foreground TUI work and mingle continuation-token authority.
- Represent long actions as blocking tool calls — rejected because model and channel availability would inherit pathfinding and server latency.
- Adopt Mineflayer objects as the protocol — rejected because adapter and Minecraft-version changes would become service breaking changes.
- Place raw tick and packet data in domain events — rejected because it makes replay, privacy, and support bundles unbounded.
Consequences
- Downstream components share one validated contract and can evolve adapters independently.
- The historical Minecraft adapter exposed only
joinandstatuswhile dormant and rejected model-forged capability sets. - Version negotiation and migration require explicit boundary code when v2 is introduced.
- Schemas describe authority requests and results but grant no capability and connect to no server by themselves.
