API Guide¶
HoloMUSH uses a gRPC API for communication between the Gateway (protocol servers) and the Core (game engine). If you are building a custom client, writing integration tests, or just want to understand how the pieces fit together, this guide walks through the protocol.
The Big Picture¶
The Core service is the single source of truth for game state. Gateways -- telnet, web, or anything you build -- are thin protocol translators that forward requests to Core over gRPC with mTLS.
A gateway never accesses the database directly. It authenticates players, relays commands, subscribes to event streams, and translates between its native protocol (telnet escape codes, WebSocket frames, etc.) and gRPC.
The Core RPCs¶
The Core service exposes a small set of RPCs for the runtime data path plus two-phase login. The runtime path is what most clients spend their time on:
service CoreService {
rpc HandleCommand(HandleCommandRequest) returns (HandleCommandResponse);
rpc Subscribe(SubscribeRequest) returns (stream SubscribeResponse);
rpc Disconnect(DisconnectRequest) returns (DisconnectResponse);
// ... plus two-phase login: AuthenticatePlayer, SelectCharacter,
// CreateGuest, CreatePlayer, etc.
}
Two-Phase Login¶
Login is split into two RPCs so that registered players can choose between multiple characters:
AuthenticatePlayer(username, password)validates credentials and returns aplayer_session_tokenplus the list of characters owned by the player.SelectCharacter(player_session_token, character_id)creates or reattaches a game session for the chosen character and returns asession_id.
For ephemeral guest play, CreateGuest() short-circuits step 1 by issuing a
guest player + character + token in a single call; the client then calls
SelectCharacter exactly the same way.
HandleCommand¶
Sends a game command for execution. Pass the session_id and the raw command
string (e.g., "say Hello everyone!").
The response indicates whether the command succeeded. Command output is not returned in the response itself -- it arrives as events on the Subscribe stream. This keeps the command path simple and the event stream the single channel for all game output.
Supported commands: say, pose, look, move, quit, who,
describe, page, whisper, help, alias.
Subscribe¶
Opens a server-streaming connection that delivers events in real time. This is how a client receives everything that happens: speech, movement, system messages, and command responses.
The request includes the session_id and optionally a list of streams to
subscribe to. If you omit streams, the server subscribes to defaults based on
the character's current location.
Disconnect¶
Ends a session and cleans up server-side resources. After disconnecting, the
session_id is no longer valid.
Connection Lifecycle¶
A typical client session looks like this:
1. Connect to Core (gRPC + mTLS)
2. AuthenticatePlayer (or CreateGuest) --> receive player_session_token
3. SelectCharacter --> receive session_id
4. Subscribe --> start receiving events
5. HandleCommand --> send commands as the player types them
6. Disconnect --> clean up when done
Steps 4 and 5 happen concurrently. The Subscribe stream runs for the lifetime of the session, while HandleCommand calls happen on demand.
The Subscribe Response and oneof Frames¶
SubscribeResponse uses a protobuf oneof to deliver two kinds of frames:
EventFrame carries a game event -- speech, movement, system messages. It includes the event ID, stream, type, timestamp, actor information, and a JSON-encoded payload.
ControlFrame carries out-of-band signals from the server:
| Signal | Meaning |
|---|---|
CONTROL_SIGNAL_REPLAY_COMPLETE |
Historical replay is done; live events follow |
CONTROL_SIGNAL_STREAM_CLOSED |
A stream has been closed (location destroyed, etc.) |
When you first subscribe, the server may replay recent events so the client has
context. The REPLAY_COMPLETE signal tells you the replay is finished and
everything after it is live.
Cursor-Based Reconnection¶
If a client disconnects and reconnects (network blip, Core restart), it can
resume where it left off. The SubscribeRequest has a replay_from_cursor
field. When set to true, the server replays events from the client's last
known position rather than from the beginning.
Track the id field of each EventFrame you receive. On reconnection, the
server uses the session's stored cursor to avoid re-sending events the client
already has.
Error Handling¶
Errors come in two flavors:
gRPC status codes for transport and session problems:
| Code | When |
|---|---|
OK |
Request succeeded |
UNAUTHENTICATED |
Invalid or expired session |
INTERNAL |
Server error |
UNAVAILABLE |
Core is temporarily unreachable |
DEADLINE_EXCEEDED |
Request timed out |
Application-level errors in the response's error field with
success=false. For example, HandleCommandResponse may return
error="Unknown command" while the gRPC call itself succeeds.
The distinction matters: a gRPC UNAUTHENTICATED means the session is gone and
you need to re-authenticate. An application-level error means the session is
fine but the specific request had a problem.
Request Metadata¶
Every request includes a RequestMeta with a request_id (ULID) and
timestamp. The server echoes the request_id back in ResponseMeta. Use this
for log correlation -- when something goes wrong, the request ID lets you trace
the full path through Gateway and Core logs.
Security¶
All connections require mutual TLS (mTLS). The Gateway presents a client
certificate signed by the Root CA, and Core verifies it. Core's certificate
includes the game_id in its Subject Alternative Name, which the Gateway
validates to ensure it is connecting to the right server.
Certificates are stored under $XDG_CONFIG_HOME/holomush/certs/.
Further Reading¶
The proto definition is at api/proto/holomush/core/v1/core.proto. For
field-level details on every request and response message, see the
gRPC API Reference.