Skip to content

fix(harness): name a served coordinator's own calls by its run, as its jobs' hand-offs do - #318

Draft
KarthikAvinashFI wants to merge 1 commit into
devfrom
fix/TH-9023-coordinator-calls-in-served-runs
Draft

KarthikAvinashFI wants to merge 1 commit into
devfrom
fix/TH-9023-coordinator-calls-in-served-runs

Conversation

@KarthikAvinashFI

Copy link
Copy Markdown
Member

Why

Follow-up tracked in TH-9023, raised separately so the open multi-agent PRs (future-agi #3699, #3791, agent-learning-kit #265) keep their review as they are. It does not block them.

A LangGraph deep agent started with start_async_task runs each worker as its own background run. Run live on the pinned async deep agents reference app, current dev already records both jobs as hand-offs (start and end), each joined to the call that launched it, and every worker tool call carries its worker. One gap was left: the coordinator's own calls carried no participant.

The cause is the shape a graph server gives these spans:

  • The coordinator's run and its tools node carry no span kind and no agent name, so no agent span sits above the launch.
  • The same launch's hand-off already names the coordinator by the run's name (the graph id).

So one call was "Unknown" on one row and agent on the other.

What changed

File Change Preserved
src/fi/alk/harness/simulation_worker_events.py A tool call that nothing else names is given the run's own name, but only when it sits in a served conversation's run (the span carries gen_ai.conversation.id or session.id). This is the same name a launch from that run already passes to its job's hand-off. An agent span above the call, a LangGraph namespace, lc_agent_name and any agent named on the span all still win, in the same order. Outside a conversation, a run's name (a request handler, a chain) is never read as an agent.
tests/harness/test_framework_tool_spans.py Two tests

Tests

Test Locks
test_a_served_coordinator_s_own_calls_and_its_jobs_hand_offs_name_the_same_agent Uses the live span shape. The coordinator's launch and its job's hand-off both name agent, the hand-off points at the launch, and the job's tool stays the job's.
test_a_call_in_a_run_with_no_conversation_and_no_agent_stays_unnamed A call in a plain POST /chat run with no conversation keeps no agent.

Both were mutation-checked:

  • With the fix reverted, the first test fails (None == 'agent').
  • With the conversation guard removed, the second test fails.

The span module's whole suite (109 tests), plus the bootstrap and process runtime suites, pass: 500 in total.

pytest -q tests/harness/test_framework_tool_spans.py tests/harness/test_simulation_mark_bootstrap.py tests/harness/test_process_runtime.py

Tested on

These runs use the harness's own process hook (sitecustomize with ALK_OBSERVE_FRAMEWORKS=1 and HARNESS_TOOL_TRACE) and the instrumentor version the build installs (traceai-langchain==0.2.1). The apps run on a local machine, not in a hosted sandbox.

Situation How Result
LangGraph async deep agents: the coordinator launches two workers and returns both task ids without waiting The pinned reference app (langgraph dev), one chat turn, on dev and then with this change On dev: both jobs became hand-offs (start and end), each joined to its launch, and the worker tools were attributed (analyze_topic to researcher, generate_tests to coder). The two launching start_async_task calls had no agent. With this change: the launching calls read agent, the same agent as their hand-offs, and everything else is unchanged.
A job still running when the coordinator's turn ends (TH-9023) Same run. The jobs started after the coordinator's reply, and the second job only after the first one finished. The hand-off was written when each job started (before it ended) and again when it ended.
OpenAI Agents SDK returns reference (regression) The same refund request against dev's module and against this change The trace rows are identical.

The trace rows from the LangGraph run with this change (outputs left out):

start_async_task  agent_key=agent       launch
start_async_task  agent_key=agent       launch
start_async_task  agent_key=agent       delegated_to=researcher  parent=<first launch>   start
analyze_topic     agent_key=researcher
start_async_task  agent_key=agent       delegated_to=researcher  parent=<first launch>   end
start_async_task  agent_key=agent       delegated_to=coder       parent=<second launch>  start
run_code_check    agent_key=coder
generate_tests    agent_key=coder
start_async_task  agent_key=agent       delegated_to=coder       parent=<second launch>  end

Scope

In: the coordinator's own calls in a served graph run.

Out:

  • A job that has not started when the call ends. The LangGraph dev server queues background runs, so a job can start after a short call has ended. That call then shows only the launch, and the job's records arrive after its trace was read. It is never placed in the wrong call. Seeing the job inside that call needs the platform to keep reading after the call ends. This is not in this PR.
  • A hosted live run of the deep agents app on the platform. Not done here.

Linear

TH-9023

🤖 Generated with Claude Code

https://claude.ai/code/session_019gWFtEXoqSfwg3LAdyoEUb

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant