Skip to content

Request envelope

Every message published to a Matrix $inbox is an MxEnvelope. The shape is defined in projects/matrix-3/packages/core/src/engine/messaging/MxEnvelope.ts and it is the canonical request envelope.

The shape (verbatim)

typescript
// projects/matrix-3/packages/core/src/engine/messaging/MxEnvelope.ts
export interface MxEnvelope {
  /** Operation name: domain op ("ExpandDir") or system op ("$join", "$reply") */
  op: string;

  /** The actual message data */
  payload: unknown;

  /** For contracted interface calls (e.g., "FsDaemon@1") */
  iface?: string;

  /** For request/reply: unique correlation identifier */
  correlationId?: string;

  /** For request/reply: reply-to inbox topic */
  replyTo?: string;

  /** Lamport timestamp for causal ordering */
  lamport?: number;

  /** Sender mount path */
  from?: string;

  /** Trace context — propagated across actor messages for distributed tracing */
  traceId?: string;
  spanId?: string;
  parentSpanId?: string;
}

Field reference

FieldRequiredNotes
opyesOperation name. Domain ops use the convention <area>.<verb> (registry.resolve); system ops are $-prefixed ($introspect, $ping).
payloadyesThe op's input. JSON-serializable. May be null/{} for ops with no parameters.
ifacenoThe interface contract name when calling a typed contract op (e.g., "FsDaemon@1"). Used by RequestReply.execute for contracted invocations.
correlationIdyes for request/replyUnique per request. Used to match the response. Conventionally a short opaque token (UUID, nanoid). See Correlation IDs.
replyToyes for request/replyThe semantic reply subject ($replies.<cid>). The actor publishes its response there.
lamportnoLogical clock. Lets the actor reason about message ordering. Increments on every send/receive.
fromnoSender's mount path. Useful for logging and tracing. NOT trusted for authorization (per CLAUDE.md Rule 5, identity comes from transport metadata, not envelope payload).
traceId / spanId / parentSpanIdnoOpenTelemetry-style trace context propagation.

Concrete example

A request to resolve a logical mount through system.registry:

json
{
  "op": "registry.resolve",
  "payload": { "logicalMount": "chat.conversation" },
  "correlationId": "f12d-7af0-9c3a-417e",
  "replyTo": "$replies.f12d-7af0-9c3a-417e",
  "from": "system.gateway.http",
  "lamport": 4291,
  "traceId": "5b8aa5a2d2c872e8321cf37308d69df2",
  "spanId": "051581bf3cb55c13",
  "parentSpanId": "00f067aa0ba902b7"
}

This envelope is published on COM.NIMBLETEC.RICHARD-SANTOMAURO.system.registry.$inbox. The registry actor handles it and publishes its response on COM.NIMBLETEC.RICHARD-SANTOMAURO.$reply.f12d-7af0-9c3a-417e.

Op naming conventions

Source: WORKSTREAMS/core-and-packaging/MATRIX-DISCOVERY-METADATA-SPEC.md and observed package code.

PatternExampleUse
<area>.<verb>registry.resolve, chat.send, inference.startDomain ops
<area>.<noun>.<verb>devices.runtimes.listNested namespaces
$<verb>$introspect, $ping, $leaveSystem commands
$<verb>.<modifier>$cognitive.set, $skills.addScoped system commands

The op name appears in the actor's static accepts declaration and is what gateway adapters surface as REST POST routes, MCP tool names, and gRPC unary methods.

Fire-and-forget

Not every publish to an inbox is a request/reply call. Pure side-effect ops (e.g., a "log this" notification) may publish without correlationId/ replyTo. The actor handles the message and produces no reply. Most production ops are request/reply, because the caller usually wants to confirm the side effect happened.

$introspect

The most universal op. Every actor handles it without explicit declaration. Default invocation:

json
{
  "op": "$introspect",
  "payload": {},
  "correlationId": "abc-123",
  "replyTo": "$replies.abc-123"
}

The actor returns:

json
{
  "op": "$reply",
  "correlationId": "abc-123",
  "payload": {
    "mount": "chat.conversation",
    "canonicalId": "...",
    "accepts": { ... },
    "emits": { ... },
    "subscribes": { ... },
    "children": [ ... ]
  }
}

This is how an LLM agent or a protocol gateway discovers an actor's full surface without prior knowledge.

Note: the MatrixActor class declares both 'introspect' (no dollar) and '$introspect' as system commands. Both are accepted; $introspect is preferred in new code. See MatrixActor.ts line 111.

Wire encoding

The envelope is JSON-serialized by the transport (NatsTransport.encodePayload, jsonMode: true by default — line 132). Empty payloads encode as null. Non-JSON payloads (binary, raw text) are supported when jsonMode: false is set on the transport, but no production code uses that path today.

See also