@@ -191,9 +191,14 @@ async def run(self):
191191 async def _run_claude_agent_sdk (self , prompt : str ):
192192 """Execute the Claude Code SDK with the given prompt."""
193193 try :
194+ # Extract user context for observability (will be added to OTLP spans)
195+ user_id = os .getenv ('USER_ID' , '' ).strip ()
196+ user_name = os .getenv ('USER_NAME' , '' ).strip ()
197+
194198 # Initialize Langfuse for observability if configured
195199 langfuse_client = None
196200 langfuse_session_span = None
201+ langfuse_enabled = False
197202 if LANGFUSE_AVAILABLE :
198203 langfuse_enabled = os .getenv ('LANGFUSE_ENABLED' , '' ).strip ().lower () in ('1' , 'true' , 'yes' )
199204 if langfuse_enabled :
@@ -204,33 +209,15 @@ async def _run_claude_agent_sdk(self, prompt: str):
204209 host = os .getenv ('LANGFUSE_HOST' )
205210 )
206211
207- # Extract user context for observability
208- user_id = os .getenv ('USER_ID' , '' ).strip ()
209- user_name = os .getenv ('USER_NAME' , '' ).strip ()
210-
211- # Create a trace for this session with userId (Langfuse SDK 3.x)
212- # Pass user_id directly to trace() for proper user tracking
213- langfuse_session_span = langfuse_client .trace (
214- name = "claude_agent_session" ,
215- input = {"prompt" : prompt },
216- user_id = user_id if user_id else None ,
217- metadata = {
218- "session_id" : self .context .session_id ,
219- "namespace" : self .context .get_env ('AGENTIC_SESSION_NAMESPACE' , 'unknown' ),
220- "user_name" : user_name if user_name else None ,
221- },
222- )
223-
224212 if user_id :
225- logging .info (f"Langfuse: Tracking session for user { user_name } ({ user_id } )" )
213+ logging .info (f"Langfuse: User context available for user { user_name } ({ user_id } )" )
226214
227- logging .info (f"Langfuse tracing enabled for session " )
215+ logging .info (f"Langfuse client initialized (user tracking via OTLP) " )
228216 except Exception as e :
229- logging .warning (f"Failed to initialize Langfuse: { e } " )
217+ logging .warning (f"Failed to initialize Langfuse client : { e } " )
230218 import traceback
231219 logging .warning (traceback .format_exc ())
232220 langfuse_client = None
233- langfuse_session_span = None
234221
235222 # Initialize OpenTelemetry for distributed tracing
236223 # Can send to Langfuse OTLP endpoint OR separate OTEL Collector
@@ -299,14 +286,26 @@ async def _run_claude_agent_sdk(self, prompt: str):
299286 # Get tracer (works whether we just set it or it was already set)
300287 otel_tracer = trace .get_tracer (__name__ )
301288
302- # Start session span
289+ # Start session span with user tracking
290+ # Use OpenTelemetry semantic convention 'enduser.id' for user tracking
291+ # Langfuse will automatically extract this from OTLP spans
292+ span_attributes = {
293+ "session.id" : self .context .session_id ,
294+ "namespace" : self .context .get_env ('AGENTIC_SESSION_NAMESPACE' , 'unknown' ),
295+ "prompt.length" : len (prompt ),
296+ }
297+
298+ # Add user attributes if available (OpenTelemetry semantic conventions)
299+ if user_id :
300+ span_attributes ["enduser.id" ] = user_id
301+ logging .info (f"OTLP: Adding user tracking for { user_name } ({ user_id } )" )
302+ if user_name :
303+ # Not a standard OTEL attribute, but Langfuse may recognize it
304+ span_attributes ["user.name" ] = user_name
305+
303306 otel_span = otel_tracer .start_span (
304307 "claude_agent_session" ,
305- attributes = {
306- "session.id" : self .context .session_id ,
307- "namespace" : self .context .get_env ('AGENTIC_SESSION_NAMESPACE' , 'unknown' ),
308- "prompt.length" : len (prompt ),
309- }
308+ attributes = span_attributes
310309 )
311310
312311 logging .info (f"OpenTelemetry tracing enabled (endpoint: { otel_endpoint } )" )
0 commit comments