Skip to content

Name every tool kind correctly and populate events when polling - #172

Merged
ambiorix2099 merged 4 commits into
mainfrom
agentspan-evals
Sep 10, 2026
Merged

Name every tool kind correctly and populate events when polling#172
ambiorix2099 merged 4 commits into
mainfrom
agentspan-evals

Conversation

@ambiorix2099

@ambiorix2099 ambiorix2099 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Pull Request type

  • Bugfix
  • Feature
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • WHOSUSING.md
  • Other (please describe):

Summary

  • waitForResult() named every non-worker tool after its task type, returned no events, and found no tool calls at all against Anthropic.
  • Root cause: AgentHandle.extractFromTasks took the name from getTaskType(), correct only for a worker; required refName.startsWith("call_"), OpenAI's tool-call id format; and passed null for events.
  • Fix: detect by the server's _agent_tool_name, name from it then method then getTaskDefName(), and synthesize the missing events.

User impact

Callers could not tell which tool ran, or a run that used three tools from one that used none.

getToolCalls() getEvents()
before [get_weather, HTTP] []
after [get_weather, fetch_page] tool_call, tool_result, tool_call, tool_result, done

Rows captured by running both extractions over workflow records a live server executed, tool tasks dispatched by hand. Against Anthropic, before is []. Polled events carry no thinking or message.

On a server that sets no _agent_tool_name, detection falls back to the dynamic fork and the name to method or the task's own name; an agent-as-tool and a media tool still report their task-def name there, which is all the record keeps of them.

Changes

  • AgentHandle.java: detect by _agent_tool_name with the dynamic fork as fallback, resolve the name, synthesize events
  • AgentEvent.java: strip every _-prefixed argument key (the only streamed change)
  • AgentResult.java, docs/agents/reference/runtime.md: state what getEvents() holds per path
  • AgentHandleToolExtractionTest.java: new, 15 tests over the fromWorkflow seam

Fixes #170

waitForResult() reported an HTTP tool as "HTTP", an MCP tool as
"CALL_MCP_TOOL", an agent-as-tool as "SUB_WORKFLOW", a human tool as "HUMAN"
and an image tool as "GENERATE_IMAGE" — the tool name came from getTaskType(),
which is only the tool's own name for a worker, because Conductor rewrites an
executed SIMPLE task's type to the task name. It also missed tool calls
entirely on non-OpenAI providers: selection keyed on the reference name
starting "call_", which is the provider's tool-call id format, so an
Anthropic-backed run (toolu_) recorded none. And getEvents() was hard-coded to
null on this path, so a run that called three tools looked like one that did
nothing.

- identify a tool task by task type, allowlisting off the server's
  ToolCompiler.TYPE_MAP plus _agent_tool_name, never the reference name
- resolve the name from inputData._agent_tool_name, then inputData.method,
  then getTaskDefName()
- synthesize tool_call/tool_result events per tool task and close with a
  terminal done/error event, so both paths report the same call the same way
- skip a tool task that has neither finished nor produced output, and report
  the whole output map for a tool whose output isn't wrapped in "result"
- strip every _-prefixed key from a streamed event's args, as the polled path
  already did

extractFromTasks had no coverage at either level; AgentHandleToolExtractionTest
covers it through the fromWorkflow seam.
Detection rested entirely on the server tagging a tool task's input with
_agent_tool_name. Against a server predating that tag, every tool kind whose
per-kind compile step replaces the task input carries nothing that names it, so
isToolTask matched none of them and the calls vanished from getToolCalls()
rather than arriving under a wrong name. Two tool calls became zero.

TOOL_TASK_TYPES could not cover for it: an executed worker tool reports its own
name as its taskType, so "SIMPLE" in that set never matches a task that ran,
and the type-and-method branch only ever reached the system-task kinds.

- fall back to the dynamic fork when no task carries the tag: an agent
  dispatches tool calls through FORK_JOIN_DYNAMIC, and Conductor records the
  reference names it forked on the fork task's own input
- recognise an executed worker tool by its type agreeing with its task-def name
- keep the tag authoritative where the server sets it, because an agent can
  fan out dynamically for reasons of its own, so the fork list is a fallback
  rather than a second source of truth

Behaviour against a tagging server is unchanged.
@ambiorix2099

ambiorix2099 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

This PR fixes the tool names in getToolCalls(), which is built from the polled task list. The event stream is a separate path, and it still misses four tool kinds.

The server picks which tasks become stream events in AgentEventListener.isToolTask, and that excludes HTTP, CALL_MCP_TOOL, SUB_WORKFLOW and HUMAN. No SDK change reaches it. HTTP and agent-as-tool need more even then, since they finish as async system tasks that never notify the task listener.

Tracked in conductor-oss/conductor#1584. This PR doesn't move it.

The untagged-server fallback compared a task's reference name to the fork
list verbatim, and the two never agree for a real agent. Conductor records
forkedTasks when it maps the FORK_JOIN_DYNAMIC, before the enclosing DO_WHILE
appends __<iteration> to each task it schedules, so the list holds
toolu_ab_0 while the tool task is toolu_ab_0__1. Every tool call inside the
ReAct loop failed the lookup, which is all of them.

Verified against a live server: a dynamic fork inside a DO_WHILE records
forkedTasks ["toolu_99_0"] and schedules toolu_99_0__1.

The three earlier tests agreed with the defect because their fork lists
carried the iteration suffix, which the server never does.

- normalize both sides on the __<iteration> suffix, matched only at the end
  of the name rather than TaskUtils' split on the first "__"
- fix the fixtures to record fork lists as the server does, and cover a call
  forked inside the loop
- share one internal-key predicate with AgentEvent so both paths strip the
  same set structurally, rather than by a test asserting they agree
- name the tool-call and tool-result event builders
- warn rather than debug when the task walk fails, since the empty result it
  leaves is indistinguishable from a run that used no tools
@ambiorix2099
ambiorix2099 marked this pull request as ready for review September 9, 2026 17:03
@ambiorix2099
ambiorix2099 merged commit 0ed0a1a into main Sep 10, 2026
4 of 6 checks passed
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.

waitForResult() returns no events, and every non-worker tool is named after its task type

2 participants