Envelopes
Every message traveling through the actor system is wrapped in an Envelope before it enters a mailbox. The envelope adds routing metadata and distributed-tracing identifiers so message flows are traceable across actor boundaries and across process boundaries in the worker pool.
The design
When you call tell() or ask(), the runtime constructs an Envelope around your message. The envelope is immutable — a final readonly class — and carries:
message— the original user message object.senderandtarget—ActorPathvalues identifying the two parties. These survive serialization, unlike liveActorRefobjects.senderRef— optional liveActorRefto the sender. Present when the runtime can supply it;nullfor anonymous sends.requestId— a unique ID for this specifictell()orask()call.correlationId— ties together all messages belonging to the same logical request chain (fan-out messages share the correlation ID of the originating message).causationId— identifies the direct parent message that triggered this one, enabling a causal DAG for distributed tracing.metadata—array<string, string>for arbitrary key-value pairs (trace headers, tenant IDs, etc.).
When Envelope::of() creates a new envelope from scratch, requestId, correlationId, and causationId all start as the same generated ID. Child messages produced in response to a parent message should propagate correlationId and set causationId to the parent's requestId.
Tradeoffs
| You gain | You give up |
|---|---|
| Full message provenance for every in-flight message | One heap allocation per message send |
| Serialization-safe routing (paths, not live refs) | Slightly more payload when crossing process boundaries |
| Pluggable tracing without touching user message classes | — |
When you encounter it
Most application code never touches Envelope directly — tell() and ask() handle construction. You work with envelopes directly in three situations:
- Custom transports — the worker-pool
WorkerTransportinterface passesEnvelopeobjects directly across Swoole threads. Implementing a transport means constructing and consuming envelopes. - Serialization adapters —
EnvelopeSerializerwraps message serialization, and its implementations accept and produceEnvelopeinstances. - Tracing instrumentation — intercepting the mailbox layer to stamp
correlationId/causationIdheaders onto outbound HTTP requests or log entries.
<?php
declare(strict_types=1);
use Monadial\Nexus\Core\Mailbox\Envelope;
use Monadial\Nexus\Core\Actor\ActorPath;
// Reconstruct an envelope from wire bytes in a custom transport
$envelope = Envelope::of($message, $senderPath, $targetPath)
->withCorrelationId($incomingCorrelationId)
->withCausationId($incomingRequestId)
->withMetadata(['x-tenant' => $tenantId]);
$transport->send($targetWorkerId, $envelope);
See also
- Mailboxes — the queue that envelopes flow through.
- Ask pattern — how
ask()constructs a temporaryFutureRefand stamps envelopes with matching IDs. - Worker pool scaling — where
WorkerTransportpasses envelopes across Swoole threads.