Appearance
gRPC bridge
Status: target state, not implemented. The mapping is defined in
ACTOR-COMMUNICATION-CONTRACT.md. No gRPC bridge actor exists in the codebase today. This page documents the target so that when the bridge is built, the contract is already settled.
The mapping
Source:
WORKSTREAMS/core-and-packaging/ACTOR-COMMUNICATION-CONTRACT.md§ "How they map to protocol gateways".
| Matrix declaration | gRPC primitive |
|---|---|
static accepts | Unary RPCs (request → response) |
static emits | Server streaming (request → stream of responses) |
static streams | Server streaming (durable; replayable from JetStream) |
static state | Unary RPCs (get state → value) |
static blackboard | Bidirectional streaming (read + watch updates) |
static subscribes | (informational; not exposed) |
Why gRPC fits
gRPC is the closest match to Matrix's request/reply + pub/sub model:
- Unary RPCs map exactly to
acceptsops. - Server streaming maps exactly to event subscriptions and durable streams.
- Bidirectional streaming maps to blackboard read+write conversations.
- Protocol Buffers give typed schemas with a stable wire format.
- gRPC's deadline propagation is the analog of Matrix's request timeout.
Schema generation
accepts.<op>.schema and accepts.<op>.returns map to Protocol Buffer messages. Per-field types map naturally:
| FieldDescriptor | Protobuf |
|---|---|
type: 'string' | string |
type: 'number' | double (default) or int64 (with hint) |
type: 'boolean' | bool |
type: 'object', properties: {...} | nested message |
type: 'array', items: ... | repeated <items> |
optional: true | optional <type> |
enum: [...] | enum definition |
The generated .proto file declares one service per actor:
proto
// generated for actor at COM.NIMBLETEC.RICHARD-SANTOMAURO/market.feed
service MarketFeed {
rpc MarketQuote(MarketQuoteRequest) returns (MarketQuoteResponse);
rpc MarketTickStream(MarketTickStreamRequest) returns (stream MarketTick);
rpc MarketTradesStream(MarketTradesStreamRequest) returns (stream MarketTrade);
}
message MarketQuoteRequest {
string symbol = 1;
}
message MarketQuoteResponse {
double price = 1;
double volume = 2;
// ...
}Field numbers are stable per declaration version (see Compatibility rules).
Streaming emits vs streams
Both map to gRPC server streaming, but the semantics differ:
emits→ ephemeral. Late subscribers see only events from after they attached. The gRPC stream produces no historical data.streams→ durable. The stream consumer can request historical replay (startTime,lastSeq). The gRPC stream first replays the requested range, then tails live data.
A gRPC client that wants both ephemeral events and durable history calls two separate RPCs (or one streams RPC if the actor declares both emits and streams).
Authentication
Standard gRPC auth options apply:
| Mechanism | Use case |
|---|---|
| TLS client certs | service-to-service inside an organization |
Bearer token (metadata.authorization) | per-call principal identity |
| Per-tenant API key | server-to-server calls |
The bridge derives the principal from the auth metadata and calls the target actor under that principal — same as the REST bridge.
Deadlines
gRPC's per-call deadline maps to Matrix's request timeout. The bridge:
- Reads the gRPC deadline from the call context.
- Computes
timeoutMs = deadline - now. - Passes
timeoutMsto the actor invocation.
When the deadline expires, the bridge cancels the actor call (sending a session cancel where applicable) and returns gRPC DEADLINE_EXCEEDED.
Error mapping
Matrix error replies map to gRPC status codes:
Matrix { ok: false, error, code? } | gRPC status |
|---|---|
code: 'NOT_FOUND' | NOT_FOUND |
code: 'INVALID_ARGUMENT' | INVALID_ARGUMENT |
code: 'PERMISSION_DENIED' | PERMISSION_DENIED |
code: 'UNAUTHENTICATED' | UNAUTHENTICATED |
code: 'UNAVAILABLE' | UNAVAILABLE |
code: 'DEADLINE_EXCEEDED' | DEADLINE_EXCEEDED |
| (other) | INTERNAL |
The error catalog convergence in Error codes makes this mapping deterministic.
What is missing today
- No gRPC bridge actor exists. (Target package
@open-matrix/bridge-grpcis filed but not implemented.) - The
.protogeneration logic does not exist. - No per-actor
static grpcdeclaration (the analog of thestatic restproposed in REST/OpenAPI bridge).
The building blocks are present — $introspect returns full declarations, system.catalog enumerates them — so when the bridge lands, the schema-generation side is mechanical.
See also
- REST/OpenAPI bridge — sibling adapter.
- MCP — sibling LLM-native adapter.
- AsyncAPI bridge — async-first sibling.
ACTOR-COMMUNICATION-CONTRACT.md— full declaration spec.