From 55759e1333776480ce5d772f236d88722e582a3c Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 20 Aug 2026 15:23:54 +0000 Subject: [PATCH] Include conversation trigger in observability metadata and tags The app server already knows why a conversation was started (ConversationTrigger: gui, automation, resolver, slack, jira, ...), but that signal never reached observability backends, so automation-driven and UI-driven traces were indistinguishable in Laminar. Thread the trigger through _build_observability_context so every start request stamps trace metadata 'trigger' and a 'trigger:' root span tag on both the OpenHands and ACP conversation paths. Co-authored-by: openhands --- .../live_status_app_conversation_service.py | 6 +++++ ...st_live_status_app_conversation_service.py | 23 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/openhands/app_server/app_conversation/live_status_app_conversation_service.py b/openhands/app_server/app_conversation/live_status_app_conversation_service.py index 840c8db..11150b0 100644 --- a/openhands/app_server/app_conversation/live_status_app_conversation_service.py +++ b/openhands/app_server/app_conversation/live_status_app_conversation_service.py @@ -1591,6 +1591,7 @@ def _build_observability_context( conversation_id: UUID, *, agent_kind: str, + trigger: ConversationTrigger | None = None, selected_repository: str | None = None, selected_branch: str | None = None, git_provider: ProviderType | None = None, @@ -1608,6 +1609,9 @@ def _build_observability_context( } tags = ['app:openhands', f'agent_kind:{agent_kind}'] + if trigger: + metadata['trigger'] = trigger.value + tags.append(f'trigger:{trigger.value}') if selected_repository: metadata['repo_name'] = selected_repository tags.append(f'repo:{selected_repository}') @@ -2163,6 +2167,7 @@ async def _build_start_conversation_request_for_user( observability_metadata, observability_tags = self._build_observability_context( conversation_id, agent_kind='openhands', + trigger=trigger, selected_repository=selected_repository, selected_branch=selected_branch, git_provider=git_provider, @@ -2453,6 +2458,7 @@ async def _build_acp_start_conversation_request( observability_metadata, observability_tags = self._build_observability_context( conversation_id, agent_kind='acp', + trigger=trigger, selected_repository=selected_repository, selected_branch=selected_branch, git_provider=git_provider, diff --git a/tests/unit/app_server/test_live_status_app_conversation_service.py b/tests/unit/app_server/test_live_status_app_conversation_service.py index 0d8c7bd..682baa5 100644 --- a/tests/unit/app_server/test_live_status_app_conversation_service.py +++ b/tests/unit/app_server/test_live_status_app_conversation_service.py @@ -1151,6 +1151,29 @@ def test_build_observability_context_includes_repository(self): assert 'branch:main' in tags assert 'git_provider:github' in tags + def test_build_observability_context_includes_trigger(self): + conversation_id = uuid4() + + metadata, tags = self.service._build_observability_context( + conversation_id, + agent_kind='openhands', + trigger=ConversationTrigger.AUTOMATION, + ) + + assert metadata['trigger'] == 'automation' + assert 'trigger:automation' in tags + + def test_build_observability_context_omits_trigger_when_unset(self): + conversation_id = uuid4() + + metadata, tags = self.service._build_observability_context( + conversation_id, + agent_kind='openhands', + ) + + assert 'trigger' not in metadata + assert not any(tag.startswith('trigger:') for tag in tags) + def test_app_conversation_start_request_accepts_observability_fields(self): request = AppConversationStartRequest( observability_span_name='mySpanName',