From 699906a0154af0afdc8261eb328e4bc7bd54a002 Mon Sep 17 00:00:00 2001 From: arc53-machine <232052973+arc53-machine@users.noreply.github.com> Date: Wed, 23 Sep 2026 22:09:58 +0100 Subject: [PATCH] Give a scheduled run's trace to the user who scheduled it On a shared agent the run's Logs row belongs to the scheduling user, not the agent owner, so the trace is stored under that user. --- docsgpt/agents/headless_runner.py | 6 +++++- docsgpt/api/user/scheduler_worker.py | 4 +++- tests/tracing/test_entry_points.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docsgpt/agents/headless_runner.py b/docsgpt/agents/headless_runner.py index e50e2478..d97e6407 100644 --- a/docsgpt/agents/headless_runner.py +++ b/docsgpt/agents/headless_runner.py @@ -71,6 +71,7 @@ def run_agent_headless( chat_history: Optional[List[Dict[str, Any]]] = None, conversation_id: Optional[str] = None, request_id: Optional[str] = None, + trace_user_id: Optional[str] = None, ) -> Dict[str, Any]: """Run an agent with no live client; returns a structured outcome dict. @@ -78,6 +79,9 @@ def run_agent_headless( source. ``request_id`` links that trace to the caller's own record (the scheduler passes its run id, the webhook worker its task id); it is kept off the LLM's token-usage rows, whose request ids drive request counts. + ``trace_user_id`` owns the trace when the run belongs to someone other + than the agent's owner (a schedule a user set on a shared agent), so the + trace is visible wherever that user sees the run; it defaults to the owner. Raises: QuotaExceededError: If the agent owner's usage quota is exhausted. @@ -85,7 +89,7 @@ def run_agent_headless( trace = tracing.start_trace( source=endpoint, request_id=request_id, - user_id=_resolve_owner(agent_config), + user_id=trace_user_id or _resolve_owner(agent_config), agent_id=_resolve_agent_id(agent_config), conversation_id=conversation_id, ) diff --git a/docsgpt/api/user/scheduler_worker.py b/docsgpt/api/user/scheduler_worker.py index 15c73f76..be05c101 100644 --- a/docsgpt/api/user/scheduler_worker.py +++ b/docsgpt/api/user/scheduler_worker.py @@ -277,8 +277,10 @@ def execute_scheduled_run_body(run_id: str, celery_task_id: Optional[str]) -> Di endpoint="schedule", conversation_id=schedule.get("origin_conversation_id"), chat_history=chat_history, - # Links the run's execution trace to its Logs row. + # Links the run's execution trace to its Logs row, which belongs + # to the user who scheduled it (not always the agent's owner). request_id=str(run_id), + trace_user_id=run.get("user_id") or schedule.get("user_id"), ) except SoftTimeLimitExceeded: timed_out = True diff --git a/tests/tracing/test_entry_points.py b/tests/tracing/test_entry_points.py index 221b0c29..95ba3809 100644 --- a/tests/tracing/test_entry_points.py +++ b/tests/tracing/test_entry_points.py @@ -84,6 +84,17 @@ class TestHeadless: assert trace.status == "ok" assert [s.kind for s in trace.spans] == ["retrieval", "agent"] + def test_trace_can_belong_to_the_scheduling_user(self, monkeypatch, flushed): + """A schedule on a shared agent is the scheduler's run, not the owner's.""" + _headless( + [{"answer": "done"}], + monkeypatch, + endpoint="schedule", + request_id="run-2", + trace_user_id="scheduler-user", + ) + assert flushed[0].user_id == "scheduler-user" + def test_stream_error_marks_trace_error(self, monkeypatch, flushed): outcome = _headless([{"type": "error", "error": "boom"}], monkeypatch, endpoint="webhook") assert outcome["error_type"] == "stream_error"