propagate_attributes()

Overview

propagate_attributes() is a context manager that attaches attributes to all spans within its scope — including spans created by auto-instrumentation and instrumentation plugins. Uses contextvars for async safety.

from respan import propagate_attributes

This is the recommended way to attach per-request context (user ID, thread ID, metadata) to traces.

Parameters

All parameters are keyword-only:

ParameterTypeDescription
customer_identifierstrUser/customer identifier. Maps to respan.customer_params.customer_identifier.
customer_emailstrCustomer email. Maps to respan.customer_params.customer_email.
customer_namestrCustomer display name. Maps to respan.customer_params.customer_name.
thread_identifierstrConversation thread ID. Maps to respan.threads.thread_identifier.
custom_identifierstrIndexed custom identifier. Maps to respan.custom_identifier.
group_identifierstrGroup related traces. Maps to respan.trace_group.group_identifier.
environmentstrEnvironment name. Maps to respan.environment.
metadatadictCustom key-value pairs. Each key maps to respan.metadata.<key>. Nested calls merge metadata.
promptdictPrompt config with prompt_id and variables. Stored as JSON for server-side template resolution.

Unsupported keys are logged as warnings and ignored.

Examples

Per-request user and thread tracking

from respan import Respan, propagate_attributes
from respan_instrumentation_openai_agents import OpenAIAgentsInstrumentor
from agents import Agent, Runner
respan = Respan(
api_key="your-api-key",
instrumentations=[OpenAIAgentsInstrumentor()],
)
agent = Agent(name="assistant", instructions="You are helpful.")
async def handle_request(user_id: str, thread_id: str, message: str):
with propagate_attributes(
customer_identifier=user_id,
thread_identifier=thread_id,
metadata={"source": "web", "plan": "pro"},
):
result = await Runner.run(agent, message)
return result.final_output

All spans created during Runner.run() — the trace, agent, LLM response, tool calls — will carry the customer_identifier, thread_identifier, and metadata.

Prompt logging

Use the prompt parameter for server-side prompt template resolution:

with propagate_attributes(
prompt={
"prompt_id": "abc123",
"variables": {"topic": "Python", "style": "concise"},
},
):
result = await Runner.run(agent, "Tell me about Python")

The Respan backend resolves the template and displays the rendered prompt alongside the raw messages.

With direct OpenAI SDK

from respan import Respan, propagate_attributes
from respan_instrumentation_openai import OpenAIInstrumentor
from openai import OpenAI
respan = Respan(
api_key="your-api-key",
instrumentations=[OpenAIInstrumentor()],
)
client = OpenAI()
def handle_request(user_id: str, message: str):
with propagate_attributes(
customer_identifier=user_id,
metadata={"source": "api"},
):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": message}],
)
return response.choices[0].message.content

Nested contexts

Nested calls merge attributes. Inner values override outer values for the same key. Metadata dicts are merged (not replaced).

with propagate_attributes(
customer_identifier="user-A",
metadata={"plan": "free", "region": "us"},
):
# Spans here: customer_identifier="user-A", plan="free", region="us"
with propagate_attributes(
customer_identifier="user-B",
metadata={"plan": "pro"},
):
# Spans here: customer_identifier="user-B", plan="pro", region="us"
# customer_identifier overridden, plan overridden, region inherited
pass

How it works

  1. Attributes are stored in a ContextVar (_PROPAGATED_ATTRIBUTES).
  2. RespanSpanProcessor.on_start() reads the ContextVar and merges attributes onto every new span.
  3. build_readable_span() also reads the ContextVar and merges into plugin-created spans.
  4. On context exit, the ContextVar is reset to the previous value.

This works correctly with asyncio — each task gets its own copy of the context.

vs respan_span_attributes()

Featurepropagate_attributes()respan_span_attributes()
ScopeAll spans in context (including auto-instrumented)Current active span only
Async safeYes (ContextVar-based)Yes
Nested mergingYes (metadata merges)Yes (inner overrides outer)
Works with pluginsYesOnly for decorator-created spans
Prompt loggingYesNo

Use propagate_attributes() in most cases. Use respan_span_attributes() only when you need to set attributes on a specific decorator-created span.