Message vs Envelope¶
Message: Raw bytes transmitted through message queue (RabbitMQ, SQS).
Envelope: Structured JSON object parsed from queue bytes, containing routing information and application data.
Payload: Application-specific data within envelope, processed by actors.
Envelope Structure¶
{
"id": "unique-envelope-id",
"parent_id": "original-envelope-id",
"route": {
"actors": ["prep", "infer", "post"],
"current": 0
},
"headers": {
"trace_id": "abc-123",
"priority": "high"
},
"payload": {
"text": "Hello world"
}
}
Fields:
id(required): Unique envelope identifierparent_id(optional): Parent envelope ID for fanout children (see Fan-Out section)route(required): Actor list and current positionactors: Pipeline definitioncurrent: Current actor index (0-based, incremented by runtime)payload(required): User data processed by actorsheaders(optional): Routing metadata (trace IDs, priorities)
Queue Naming Convention¶
All actor queues follow pattern: asya-{actor_name}
Examples:
- Actor
text-analyzerβ Queueasya-text-analyzer - Actor
image-processorβ Queueasya-image-processor - System actors:
asya-happy-end,asya-error-end
Benefits:
- Fine-grained IAM policies:
arn:aws:sqs:*:*:asya-* - Clear namespace separation
- Automated queue management by operator
Message Acknowledgment¶
Ack: Message processed successfully, remove from queue - Runtime returns valid response - Sidecar routes to next actor or end queue
Nack: Message processing failed in sidecar, requeue - Sidecar crashes before processing - Queue automatically sends to DLQ after max retries
End Queues¶
happy-end: Pipeline completed or aborted successfully
- Automatically routed by sidecar when no more actors in route
- Automatically routed when runtime returns empty response
error-end: Processing error occurred
- Automatically routed when runtime returns error
- Automatically routed on timeout
Important: Do not include happy-end or error-end in route configurations - managed by sidecar.
Response Patterns¶
Single Response¶
Runtime returns mutated payload:
{"processed": true, "timestamp": "2025-11-18T12:00:00Z"}
Action: Sidecar creates envelope β Increments current β Routes to next actor
Fan-Out (Array)¶
Runtime returns array:
[
{"chunk": 1, "text": "Hello"},
{"chunk": 2, "text": "world"}
]
Action: Sidecar creates multiple envelopes (one per item) β Routes to next actor
Fanout ID semantics:
- First envelope retains original ID (for SSE streaming compatibility)
- Subsequent envelopes receive suffixed IDs:
{original_id}-{index} - All fanout children have
parent_idset to original envelope ID
Example: Envelope abc-123 returns 3 items:
- Index 0:
id="abc-123",parent_id=null(original ID preserved) - Index 1:
id="abc-123-1",parent_id="abc-123"(fanout child) - Index 2:
id="abc-123-2",parent_id="abc-123"(fanout child)
Empty Response¶
Runtime returns null or []:
Action: Sidecar routes envelope to happy-end (no increment)
Error Response¶
Runtime returns error object:
{
"error": "processing_error",
"message": "Invalid input format"
}
Action: Sidecar routes to error-end (no increment)
Payload Enrichment Pattern¶
Recommended: Actors append results to payload instead of replacing it.
Example pipeline: ["data-loader", "recipe-generator", "llm-judge"]
// Input to data-loader
{"product_id": "123"}
// Output of data-loader β Input to recipe-generator
{
"product_id": "123",
"product_name": "Ice-cream Bourgignon"
}
// Output of recipe-generator β Input to llm-judge
{
"product_id": "123",
"product_name": "Ice-cream Bourgignon",
"recipe": "Cook ice-cream in tomato sauce for 3 hours"
}
// Output of llm-judge β Final result
{
"product_id": "123",
"product_name": "Ice-cream Bourgignon",
"recipe": "Cook ice-cream in tomato sauce for 3 hours",
"recipe_eval": "INVALID",
"recipe_eval_details": "Recipe is nonsense"
}
Benefits:
- Better actor decoupling - each actor only needs specific fields
- Full traceability - complete processing history in final payload
- Routing flexibility - later actors can access earlier results
Envelope Status Tracking¶
When gateway is enabled, envelopes have lifecycle statuses tracked throughout processing:
Status Values¶
| Status | Description | When Set |
|---|---|---|
pending |
Envelope created, not yet processing | Gateway creates envelope from MCP tool call |
running |
Envelope is being processed by actors | Sidecar sends first progress update |
succeeded |
Pipeline completed successfully | happy-end crew actor reports success |
failed |
Pipeline failed with error | error-end crew actor reports failure |
unknown |
Status cannot be determined | Edge cases, missing updates |
Progress Reporting¶
Sidecars report progress to gateway at three points per actor:
1. Received (received):
- Message pulled from queue
- Before forwarding to runtime
2. Processing (processing):
- Message sent to runtime via Unix socket
- Runtime is executing handler
3. Completed (completed):
- Runtime returned successful response
- Before routing to next actor
Progress calculation:
progress_percent = (actors_completed / total_actors) * 100
Example: Route ["prep", "infer", "post"] (3 actors)
- Actor prep completed β 33%
- Actor infer completed β 66%
- Actor post completed β 100% (final status from happy-end)
Progress Update Flow¶
Sidecar Gateway Client
------- ------- ------
1. Receive from queue
ββ> POST /envelopes/{id}/progress
{status: "received", current_actor_idx: 0}
ββ> Update DB: running
ββ> SSE: progress 10%
2. Send to runtime
ββ> POST /envelopes/{id}/progress
{status: "processing", current_actor_idx: 0}
ββ> SSE: progress 15%
3. Runtime returns
ββ> POST /envelopes/{id}/progress
{status: "completed", current_actor_idx: 0}
ββ> SSE: progress 33%
4. Route to next actor...
Final Status Reporting¶
Success path:
Actor N completes β Sidecar routes to happy-end
β happy-end persists to S3
β happy-end reports: POST /envelopes/{id}/final
{status: "succeeded", result: {...}}
β Gateway updates: status=succeeded, progress=100%
β SSE: final success event
Error path:
Runtime error β Sidecar routes to error-end
β error-end persists to S3
β error-end reports: POST /envelopes/{id}/final
{status: "failed", error: "..."}
β Gateway updates: status=failed
β SSE: final error event
Design Principles¶
- Small payloads: Use object storage (S3, MinIO) for large data, pass references
- Clear names: Use descriptive actor names (
preprocess-textnotactor1) - Monitor errors: Alert on
error-endqueue depth - Version schema: Include version in payload for breaking changes