Skip to content

Context

Context propagation for metadata across service calls


The context object carries metadata that propagates through the entire call chain. When Service A calls Service B, and B calls Service C, context flows A→B→C.

┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Service A │────▶│ Service B │────▶│ Service C │
│ │ │ │ │ │
│ tenant: X │ │ tenant: X │ │ tenant: X │
│ caller: A │ │ caller: B │ │ caller: C │
└─────────────┘ └─────────────┘ └─────────────┘

{
"context": {
"caller": "checkout-service",
"tenant_id": "tenant_acme",
"user_id": "user_42"
}
}
FieldTypeRequiredDescription
callerstringNoIdentifier of the calling service

Applications MAY add custom fields to context for domain-specific propagation:

{
"context": {
"caller": "checkout-service",
"tenant_id": "tenant_acme",
"user_id": "user_42",
"feature_flags": ["new_checkout", "beta_pricing"],
"correlation_id": "order_12345"
}
}

Common custom fields:

  • tenant_id — Multi-tenant isolation
  • user_id — Acting user (for audit)
  • feature_flags — Active feature flags
  • correlation_id — Business correlation
  • environment — Environment hint (staging, production)

When receiving a request, servers MUST:

  1. Extract context from the request
  2. Preserve custom fields unchanged
  3. Update caller to current service name for downstream calls

When making downstream calls, clients MUST:

  1. Copy custom fields from current context
  2. Set caller to current service name
  3. Propagate custom fields as appropriate

Request A→B:

{
"context": {
"caller": "api-gateway",
"tenant_id": "tenant_acme"
}
}

Request B→C:

{
"context": {
"caller": "order-service",
"tenant_id": "tenant_acme"
}
}

Context and extensions serve different purposes:

AspectContextExtensions
PropagatesYes, through call chainPer-request (may influence downstream)
PurposeBusiness metadataOptional capabilities
Examplestenant_id, user_id, callerdeadline, idempotency, tracing

Rule of thumb:

  • If downstream services need it automatically → Context
  • If it’s an optional capability → Extensions

For distributed tracing (trace_id, span_id, parent_span_id), use the Tracing Extension. This keeps observability concerns separate from business context.


Servers MUST handle requests without context gracefully:

// Request without context
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_123",
"call": {
"function": "orders.get",
"version": "1",
"arguments": { "order_id": 42 }
}
}

When context is missing:

  • Servers SHOULD log a warning
  • Servers MUST process the request normally

{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_basic",
"call": {
"function": "orders.create",
"version": "1",
"arguments": { ... }
},
"context": {
"caller": "web-frontend"
}
}
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_multi_tenant",
"call": {
"function": "reports.generate",
"version": "1",
"arguments": { ... }
},
"context": {
"caller": "dashboard-service",
"tenant_id": "tenant_acme_corp",
"user_id": "user_42",
"feature_flags": ["new_report_engine"]
}
}

For full distributed tracing, combine context with the tracing extension:

{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_traced",
"call": {
"function": "orders.create",
"version": "1",
"arguments": { ... }
},
"context": {
"caller": "checkout-service",
"tenant_id": "tenant_acme"
},
"extensions": [
{
"urn": "urn:mesh:ext:tracing",
"options": {
"trace_id": "tr_abc123def456",
"span_id": "sp_001"
}
}
]
}