If you’re using agentex agents on Temporal, you don’t need this page — agentex provides its own SGP tracing integration through its tracing processors. This guide is for users writing their own Temporal workflows or activities that emit SGP spans directly.
Pattern 1: Trace at the activity boundary (recommended)
Use this when “one activity = one span” gives you the granularity you need. It is the simplest and matches how most Temporal applications are observed.- Activities call
tracing.create_span(...)directly using the SDK’s context manager. - The caller of the workflow creates the parent span and passes
trace_idandparent_span_idas workflow input. - The workflow forwards those IDs to each activity. Each activity span is rooted under the caller’s parent.
The SDK’s background queue worker is a daemon thread inside the activity worker process, not the workflow. It is fine to use the default
tracing.init() configuration. The queue flushes on size or cadence triggers and on worker shutdown.Pattern 2: Buffer in workflow, flush via activity (advanced)
Use this when you want span boundaries that mirror workflow steps rather than activity invocations, or when activity calls are too coarse-grained to capture the hierarchy you want. The shape:- The workflow buffers lightweight span dicts (the SDK’s
ItemTypedDict) in workflow state. No network IO, no SDK calls. - A dedicated
flush_spansactivity receives the buffered list and writes them viaclient.spans.upsert_batch(items=...). - The flush activity runs:
- when the buffer hits a size threshold,
- on a periodic timer,
- before the workflow completes or calls
continue_as_new.
Item is a TypedDict, so values are plain dict at runtime. That means no asdict(...) conversion before the flush activity, and Temporal’s default data converter serializes them as JSON cleanly.Why upsert_batch and not create
- Idempotency. Temporal retries activities on transient failures.
upsert_batchis keyed on the client-supplied spanid, so a retry that partially succeeded will re-converge to the right state.client.spans.createis not safe under retry because a partial success leaves orphan rows. - Throughput. One HTTP call carries many spans. The batch endpoint accepts up to 1000 items per request.
Keep batches at 200 to 500 items per flush in practice. That keeps payloads under 1 MB and HTTP timeouts comfortable, and gives you headroom under the 1000-item server cap.
Span integrity discipline
The server rejects the whole batch if any child’strace_id does not match its parent’s. This is easy to trip if parent IDs are minted in workflow code but child IDs are minted in an activity without the trace context piped through. Always pass trace_id (and parent_span_id where relevant) explicitly when crossing the workflow/activity boundary.
Distributed tracing across workflows
If one workflow starts another (child workflow, or an externalstart_workflow call), propagate trace_id and the originating span’s id via workflow input or memo, and use them in the child workflow as parent_id for its root span. The same propagation rule from the Distributed Tracing page applies; Temporal’s workflow-input mechanism is a clean place to carry it.
