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.
This commit is contained in:
arc53-machine committed 2026-09-23 22:09:58 +01:00
1 parent 9f7f0b2f1e
commit 699906a015
3 files changed
+19 -2

No files matched your search

+5 -1
View File
@@ -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,
)
+3 -1
View File
@@ -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
+11
View File
@@ -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"