This document specifies the wire protocol used by the Restate runtime to invoke a service handler exposed by a service deployment.
The protocol is versioned. The current version and the changelog between versions are defined by
ServiceProtocolVersion in protocol.proto. This document describes
the latest stable version.
A service deployment is composed of an SDK and the user business logic written on top of it. The runtime invokes the deployment via HTTP; the SDK implements this protocol and exposes context APIs to user code.
Each invocation is modeled as a state machine whose transitions are recorded in a journal. The journal is the source of truth for the invocation: it captures every observable interaction with the runtime so that the invocation can be replayed deterministically across failures and restarts.
Runtime and deployment exchange the journal — and runtime-side events — as messages over a single HTTP stream per invocation.
The runtime opens an HTTP POST request against:
{prefix}/invoke/{serviceName}/{handlerName}
{prefix} is implementation-defined and MAY be empty. If the path is malformed, or serviceName or
handlerName are unknown, the SDK MUST respond with 404.
The runtime selects the protocol version through the content-type header:
content-type: application/vnd.restate.invocation.vX
X is the chosen ServiceProtocolVersion. If the SDK supports the version, it MUST respond 200 with
the same content-type. If it does not, it MUST respond 415.
The SDK MAY send back an x-restate-server header for observability:
x-restate-server: <sdk-name>/<sdk-version>
The stream is layered on top of HTTP and operates in one of two modes:
- Bidirectional (HTTP/2 only): messages flow in both directions concurrently.
- Request/Response: the runtime first sends all of its messages, then the SDK opens its response body and sends messages back. Once the SDK starts writing, the runtime cannot send any further messages on that stream.
Both modes share the same framing and semantics; they differ only in the order in which the two sides may write.
sequenceDiagram
Note over Runtime,SDK: Bootstrapping
Runtime->>SDK: POST /invoke/{service}/{handler}
Runtime->>SDK: StartMessage
Note over Runtime,SDK: Replaying
Runtime->>SDK: [..]Command and Notification messages
Note over Runtime,SDK: Processing
SDK->>Runtime: 200 OK + headers
loop
SDK->>Runtime: [..]Command / ProposeRunCompletion / SendSignal
Runtime->>SDK: [..]Notification / ProposeRunCompletionAck
end
Note over SDK: Close condition reached
alt End
SDK->>Runtime: EndMessage
else Suspension
SDK->>Runtime: SuspensionMessage
else Failure
SDK->>Runtime: ErrorMessage
end
SDK->>Runtime: Close HTTP Response
The stream goes through three phases:
- Bootstrapping: the SDK receives
StartMessageand learns the invocation metadata. - Replaying: the SDK consumes the journal already known to the runtime —
StartMessage.known_entriesworth of command and notification messages. User code runs normally, but every interaction with the SDK is served from the replayed journal; no new entries are produced. - Processing: the journal is exhausted. The SDK is now the source of truth for new entries. User code drives new commands, and the runtime responds with notifications and acks.
The stream MUST end with exactly one of EndMessage, SuspensionMessage, or ErrorMessage. A stream
closed without one of these terminators is treated as if it had ended with an ErrorMessage carrying an
unspecified failure code.
EndMessage marks the end of the invocation lifecycle — the journal is closed and the invocation has
returned a value or a terminal failure (see Commands).
Each message on the stream is prefixed by a fixed 64-bit header:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type |A| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- Type (16 bits): identifies the message kind. See Message types.
- A (1 bit, MSB of the flag block):
REQUESTED_ACKflag. Only meaningful for message types that opt into ack semantics (see Run mechanics). All other types MUST set this bit to zero. - Reserved flags (15 bits)
- Length (32 bits): length in bytes of the serialized message payload, excluding the header.
The payload is the Protobuf-encoded message
described in protocol.proto.
The 16-bit type field is partitioned into namespaces:
| Range | Namespace |
|---|---|
0x0000–0x03FF |
Control frames (Start, Suspension, End, Error, …) |
0x0400–0x7FFF |
Commands |
0x8000–0xFBFF |
Notifications |
0xFC00–0xFFFF |
Custom commands (see Custom commands) |
The authoritative type table lives in header.rs.
The journal is an ordered sequence of two kinds of messages:
- Commands describe operations the invocation has performed: setting state, calling another service, sleeping, running a side effect, etc. Commands are written either by the runtime during replay or by the SDK during processing.
- Notifications carry asynchronous information delivered to the invocation. They come in two flavours — completions (results of a previously-issued command) and signals (out-of-band events not tied to a command). See below.
The relative ordering of notifications delivered by the runtime — completions, signals, and run-completion acks alike — is significant: on replay the SDK MUST observe the same relative order it observed when processing. See Run mechanics.
Every notification message — regardless of whether it is a completion or a signal — conforms to the
duck-type NotificationTemplate defined in protocol.proto.
The concrete notification messages (SleepCompletionNotification, CallCompletionNotification,
GetLazyStateKeysCompletionNotification, SignalNotificationMessage, …) are not subtypes — they are
distinct messages that share the same field numbers and reserve the subset of id and result
variants that don't apply. For instance:
| Message | Allowed id |
Allowed result |
|---|---|---|
SleepCompletionNotification |
completion_id |
void |
GetLazyStateCompletionNotification |
completion_id |
void, value |
CallCompletionNotification |
completion_id |
value, failure |
CallInvocationIdCompletionNotification |
completion_id |
invocation_id |
GetLazyStateKeysCompletionNotification |
completion_id |
state_keys |
SignalNotificationMessage |
signal_id, signal_name |
void, value, failure |
This shape lets the SDK parse any notification message generically — pick out the id, pick out the result — and route it to the correct awaiter via a single lookup table.
A completion is a notification produced by the runtime as the result of a command. The SDK allocates
one or more result_completion_id values at command-creation time and embeds them in the command
message; the runtime sends back a matching notification carrying the same id.
Some commands yield more than one notification, each with its own completion id. CallCommand, for
example, allocates two: it first receives a CallInvocationIdCompletionNotification (the callee's
invocation id, available as soon as the call is durably enqueued) and later a
CallCompletionNotification (the actual call result). The SDK assigns the ids at sys-call time and
exposes them to user code as separate awaitables.
Completion ids are scoped to a single invocation and are opaque to the runtime — round-trip tokens that the SDK uses to match a result back to the command that produced it.
A signal is a notification that is not the result of a command. The runtime delivers it
spontaneously, typically because another invocation issued a SendSignalCommand targeting this
invocation. Signals are encoded as SignalNotificationMessage and addressed in one of two ways:
- by signal index — a small reserved namespace defined by
BuiltInSignal(CANCEL, …) and used by the runtime to deliver well-known events. - by signal name — an arbitrary user-defined string. Used for application-level signals.
Signals can be combined with completions in suspension future trees: waiting_completions,
waiting_signals, and waiting_named_signals are the three leaf-id sets of Future (see
Suspension and await tracking).
StartMessage is the first message of every stream. It carries the metadata required to bootstrap the
invocation state machine:
id,debug_id,key,scope,limit_key,idempotency_key: invocation metadata.known_entries: number of command and notification messages the runtime will replay before the SDK takes over.state_map/partial_state: see Eager state.random_seed: seed for the deterministic RNG exposed in the context API.retry_count_since_last_stored_entry/duration_since_last_stored_entry: retry telemetry. Best-effort — may reset across leader changes.
The full command catalogue lives in protocol.proto.
Commands divide into two groups by how they relate to notifications.
A completable command declares one or more result_completion_id fields. The runtime delivers a
matching notification per id at some point in the future.
| Command | Notification(s) |
|---|---|
GetLazyStateCommand |
GetLazyStateCompletionNotification |
GetLazyStateKeysCommand |
GetLazyStateKeysCompletionNotification |
GetPromiseCommand |
GetPromiseCompletionNotification |
PeekPromiseCommand |
PeekPromiseCompletionNotification |
CompletePromiseCommand |
CompletePromiseCompletionNotification |
SleepCommand |
SleepCompletionNotification |
CallCommand |
CallInvocationIdCompletionNotification + CallCompletionNotification |
OneWayCallCommand |
CallInvocationIdCompletionNotification |
RunCommand |
RunCompletionNotification (see Run mechanics) |
AttachInvocationCommand |
AttachInvocationCompletionNotification |
GetInvocationOutputCommand |
GetInvocationOutputCompletionNotification |
A non-completable command is a one-shot: writing the command is the action. The runtime does not respond.
| Command | Purpose |
|---|---|
InputCommand |
First entry of the journal; carries the invocation input. |
OutputCommand |
Last entry written by the SDK; carries the invocation result or terminal failure. |
SetStateCommand |
Set a state key. |
ClearStateCommand |
Clear a state key. |
ClearAllStateCommand |
Clear all state. |
GetEagerStateCommand |
Get a state key with the result inlined (see Eager state). |
GetEagerStateKeysCommand |
List state keys with the result inlined (see Eager state). |
SendSignalCommand |
Deliver a signal to another invocation. Also used internally to cancel invocations. |
CompleteAwakeableCommand |
Resolve a previously-created awakeable in another invocation. |
Every command carries an entry_name field (string name = 12) used by the Restate observability tooling.
When the SDK creates an awakeable, it MUST surface to user code an id suitable for being handed off to
another invocation. The id is the string prom_1 concatenated with the
Base64 URL Safe encoding of:
StartMessage.id,- followed by the awakeable's signal index, encoded as a 32-bit big-endian unsigned integer.
Example: prom_1NMyOAvDK2CcBjUH4Rmb7eGBp0DNNDnmsAAAAAQ.
ctx.run is the durable execution primitive that lets non-deterministic user code write to the journal.
It uses a small dedicated protocol layered on top of the journal model.
- The SDK records a
RunCommandMessagewith aresult_completion_id. - The SDK executes the user closure.
- The SDK sends a
ProposeRunCompletionMessagecarrying the result and the sameresult_completion_id. This message is not itself a notification — it is a proposal that the runtime durably stores and then echoes back. - The runtime's reply depends on the
REQUESTED_ACKflag set in theProposeRunCompletionMessageheader:REQUESTED_ACK = 1— the runtime replies with aProposeRunCompletionAckMessagecarrying thecompletion_id. SDKs SHOULD set this flag.REQUESTED_ACK = 0— the runtime replies with a fullRunCompletionNotificationMessagecarrying the result.
ProposeRunCompletionAckMessage is ordered relative to other notifications. On replay the SDK MUST
observe the same relative order; the position previously occupied by the ack is now filled by the
RunCompletionNotificationMessage that the runtime persisted — the result replaces the ack.
An invocation may suspend when it is awaiting one or more notifications that have not arrived yet. To
suspend, the SDK MUST send a SuspensionMessage describing the await point as a tree of future
combinators, then close the stream. The runtime resumes the invocation as soon as it has enough new
notifications to potentially unblock the await point.
The await point is a Future value:
Future {
combinator_type: FIRST_COMPLETED | ALL_COMPLETED | ...,
waiting_completions: [completion ids],
waiting_signals: [signal indexes],
waiting_named_signals: [signal names],
nested_futures: [Future, ...], // combined with combinator_type
}
combinator_type follows JavaScript-style promise combinator semantics:
| Combinator | Resolves on |
|---|---|
FIRST_COMPLETED |
the first child to complete (success or failure). |
ALL_COMPLETED |
every child completing, regardless of outcome. |
FIRST_SUCCEEDED_OR_ALL_FAILED |
the first success; fails only if all children fail. |
ALL_SUCCEEDED_OR_FIRST_FAILED |
all children succeeding; short-circuits on first failure. |
During processing — before suspension is necessary — the SDK MAY send an AwaitingOnMessage to inform
the runtime of the current await point. The runtime treats this as a hint for observability and
scheduling, and MUST discard it as soon as a notification covered by the future tree is produced.
At the protocol level there are two distinct ways an invocation can fail:
- As an output: the invocation ends normally and its result is an
OutputCommandMessagewith thefailurevariant set. The journal closes withEndMessage. The runtime treats this as a regular end state and does not retry. - As an
ErrorMessage: the SDK closes the stream with anErrorMessage. The runtime treats this as an attempt failure and applies its retry policy.
How a user-thrown exception is mapped to one or the other is up to the SDK — typically by distinguishing "terminal" from "retryable" errors in the user-facing API, but the protocol does not mandate any particular taxonomy.
ErrorMessage carries an HTTP-style code, a human-readable message, and optional
related_command_index, related_command_name, and related_command_type fields locating the
failure in the journal.
Two SDK-specific codes are reserved:
570 JOURNAL_MISMATCH— the SDK cannot replay the journal because its contents diverge from what user code expects.571 PROTOCOL_VIOLATION— the SDK received an unexpected message given its current state.
The runtime owns the retry policy. The SDK MAY influence the next retry attempt by setting:
next_retry_delay(milliseconds) to override the default delay for the next attempt only.should_pause = trueto instruct the runtime to stop retrying and pause the invocation. Supersedesnext_retry_delay.
The runtime discovers services via a GET /discovery request returning an endpoint manifest. The
manifest schema lives in endpoint_manifest_schema.json and is
versioned independently of the invocation protocol, via ServiceDiscoveryProtocolVersion in
discovery.proto.
The runtime advertises supported manifest versions through Accept:
accept: application/vnd.restate.endpointmanifest.v2+json, application/vnd.restate.endpointmanifest.v1+json
The SDK MUST reply with the chosen version in content-type:
content-type: application/vnd.restate.endpointmanifest.v1+json
State reads can be served without a runtime round-trip by populating the journal at startup.
StartMessage.state_map carries known key/value pairs; StartMessage.partial_state indicates whether
the map is exhaustive.
When user code reads key k:
- If
k ∈ state_map: the SDK records aGetEagerStateCommandMessagewith the result inlined, and returns to user code synchronously. - If
k ∉ state_mapandpartial_state = false: the SDK records aGetEagerStateCommandMessagewithvoid, and returns not found to user code synchronously. - If
k ∉ state_mapandpartial_state = true: the SDK falls back to the lazy path — record aGetLazyStateCommandMessageand await the correspondingGetLazyStateCompletionNotificationMessage.
SetStateCommand, ClearStateCommand, and ClearAllStateCommand MUST also update the SDK's local view
of state_map so subsequent reads within the same invocation see the new value.
GetEagerStateKeysCommand follows the same pattern for the set of known keys.
The SDK MAY register custom commands in the 0xFC00–0xFFFF range. The runtime treats them as opaque
journal entries: it persists them and replays them in order, but does not interpret their payload.
Custom commands MAY carry the entry_name field, MUST NOT use proto field numbers 13–15 (reserved for
future use), and MUST NOT set the REQUESTED_ACK flag.