Bug Description
Tool-call SSE events are wrong in both directions.
Four real tool task types are on the denylist, so no tool_call or tool_result event is
emitted for them: mcp, human, http/api and agent_tool tools. And the default branch
applies no effective filter, so a task that is not a tool emits a spurious pair. LIST_MCP_TOOLS,
the discovery task compiled into every MCP-using agent, is reported as a tool call named
list_mcp_tools.
So a consumer asking "was tool X called?" can get the wrong answer either way, and one asking
"was tool X not called?" gets a confident yes.
Root Cause
isToolTask (AgentEventListener.java:403-429) denylists twelve task types and treats
everything else as a tool.
Four denylist entries are real tool types. SUB_WORKFLOW (:419), HUMAN (:420), HTTP
(:422) and CALL_MCP_TOOL (:423). ToolCompiler.TYPE_MAP (ToolCompiler.java:140-153)
compiles real tools onto exactly those: http and api to HTTP, mcp to CALL_MCP_TOOL,
agent_tool to SUB_WORKFLOW, human to HUMAN. That is five SDK tool types across four task
types.
The default branch filters nothing. :427 returns
"SIMPLE".equals(taskType) || task.getTaskDefinition().isPresent(). The second disjunct reads as
"has a registered TaskDef", but MetadataMapperService.java:120-135 populates a TaskDef for any
task with a non-blank name, synthesizing an ad-hoc one when the DAO has none, under a gate
(:195-200) that never looks at task type. So it is true for essentially every task, and any
worker task type not explicitly denylisted is treated as a tool. LIST_MCP_TOOLS
(ai/.../MCPWorkers.java:58) is the case that bites today; the structural problem is that a new
worker task type becomes a tool by default, with nobody deciding.
Steps to Reproduce
Register an agent with one MCP tool and subscribe to its SSE stream.
- A spurious
tool_call/tool_result pair arrives named list_mcp_tools, from the compiled
discovery task.
- No
tool_call event arrives for the MCP tool itself.
Expected Behavior
Allowlist on task type off TYPE_MAP, plus the worker case where an executed SIMPLE task's
taskType is the task's own name. Keep the existing _fw_ reference skip.
The allowlist needs a naming change alongside it. The enrich script replaces
inputParameters wholesale for three tool kinds, dropping the method key that
LLMHelper.java:407 had put there:
- HTTP:
JavaScriptBuilder.java:716 sets {http_request: hrequest}, and hrequest.method
(:709) is the HTTP verb, not the tool name.
- agent-tool:
:740-747 sets prompt, session_id and __today__ only.
PULL_WORKFLOW_MESSAGES: :789 sets {batchSize} only.
MCP keeps method explicitly (:722), and HUMAN and the media/RAG kinds keep it by merging
tc.inputParameters. So with the denylist removed, resolveToolName (:439-461) would return
"http" for an HTTP tool and "sub_workflow" for an agent tool via its :455-456 branch. The
only key set for every dispatched tool is _agent_tool_name (:797), which resolveToolName
does not read. Resolution order should be inputData._agent_tool_name, then inputData.method,
then taskDefName.
Additional Notes
Verified against origin/main @ ce46eac33.
The allowlist alone does not fix HTTP or agent-as-tool, and that is a separate change.
onTaskCompleted (:99) is only reachable from TaskStatusListener.onTaskCompletedIfEnabled,
whose sole production call site is WorkflowExecutorOps.java:1115 inside updateTask(TaskResult),
the worker and task-update path. Async system tasks complete through AsyncSystemTaskExecutor
(:200, :245) and never notify the listener. HttpTask.isAsync() returns true
(http-task/.../HttpTask.java:229) and so does SubWorkflow.isAsync()
(core/.../tasks/SubWorkflow.java:328-330). So removing those two from the denylist changes
nothing on its own; they need a completion notification for async system tasks. CALL_MCP_TOOL
and HUMAN are unblocked by the denylist fix alone.
The dispatch script and tool-spec compilation are each duplicated
(JavaScriptBuilder.java:1430-1540, ToolCompiler.java:1597-1658), so a fix likely applies
twice. Marker-based identification was tried and reverted in 09842ab16, which is why this is
framed as a selection change rather than a new marker.
Four SDKs reconstruct tool calls from the polled task list and each has its own version of this
problem (filed against python-sdk, javascript-sdk, java-sdk, csharp-sdk). Getting
selection and naming right here is what makes a shared rule worth stating: a tool call is
identified by task type, and its name comes from inputData._agent_tool_name or taskDefName,
never from the task reference name.
Bug Description
Tool-call SSE events are wrong in both directions.
Four real tool task types are on the denylist, so no
tool_callortool_resultevent isemitted for them:
mcp,human,http/apiandagent_tooltools. And thedefaultbranchapplies no effective filter, so a task that is not a tool emits a spurious pair.
LIST_MCP_TOOLS,the discovery task compiled into every MCP-using agent, is reported as a tool call named
list_mcp_tools.So a consumer asking "was tool X called?" can get the wrong answer either way, and one asking
"was tool X not called?" gets a confident yes.
Root Cause
isToolTask(AgentEventListener.java:403-429) denylists twelve task types and treatseverything else as a tool.
Four denylist entries are real tool types.
SUB_WORKFLOW(:419),HUMAN(:420),HTTP(
:422) andCALL_MCP_TOOL(:423).ToolCompiler.TYPE_MAP(ToolCompiler.java:140-153)compiles real tools onto exactly those:
httpandapitoHTTP,mcptoCALL_MCP_TOOL,agent_tooltoSUB_WORKFLOW,humantoHUMAN. That is five SDK tool types across four tasktypes.
The
defaultbranch filters nothing.:427returns"SIMPLE".equals(taskType) || task.getTaskDefinition().isPresent(). The second disjunct reads as"has a registered TaskDef", but
MetadataMapperService.java:120-135populates aTaskDeffor anytask with a non-blank name, synthesizing an ad-hoc one when the DAO has none, under a gate
(
:195-200) that never looks at task type. So it is true for essentially every task, and anyworker task type not explicitly denylisted is treated as a tool.
LIST_MCP_TOOLS(
ai/.../MCPWorkers.java:58) is the case that bites today; the structural problem is that a newworker task type becomes a tool by default, with nobody deciding.
Steps to Reproduce
Register an agent with one MCP tool and subscribe to its SSE stream.
tool_call/tool_resultpair arrives namedlist_mcp_tools, from the compileddiscovery task.
tool_callevent arrives for the MCP tool itself.Expected Behavior
Allowlist on task type off
TYPE_MAP, plus the worker case where an executed SIMPLE task'staskTypeis the task's own name. Keep the existing_fw_reference skip.The allowlist needs a naming change alongside it. The enrich script replaces
inputParameterswholesale for three tool kinds, dropping themethodkey thatLLMHelper.java:407had put there:JavaScriptBuilder.java:716sets{http_request: hrequest}, andhrequest.method(
:709) is the HTTP verb, not the tool name.:740-747setsprompt,session_idand__today__only.PULL_WORKFLOW_MESSAGES::789sets{batchSize}only.MCP keeps
methodexplicitly (:722), and HUMAN and the media/RAG kinds keep it by mergingtc.inputParameters. So with the denylist removed,resolveToolName(:439-461) would return"http"for an HTTP tool and"sub_workflow"for an agent tool via its:455-456branch. Theonly key set for every dispatched tool is
_agent_tool_name(:797), whichresolveToolNamedoes not read. Resolution order should be
inputData._agent_tool_name, theninputData.method,then
taskDefName.Additional Notes
Verified against
origin/main@ce46eac33.The allowlist alone does not fix HTTP or agent-as-tool, and that is a separate change.
onTaskCompleted(:99) is only reachable fromTaskStatusListener.onTaskCompletedIfEnabled,whose sole production call site is
WorkflowExecutorOps.java:1115insideupdateTask(TaskResult),the worker and task-update path. Async system tasks complete through
AsyncSystemTaskExecutor(
:200,:245) and never notify the listener.HttpTask.isAsync()returnstrue(
http-task/.../HttpTask.java:229) and so doesSubWorkflow.isAsync()(
core/.../tasks/SubWorkflow.java:328-330). So removing those two from the denylist changesnothing on its own; they need a completion notification for async system tasks.
CALL_MCP_TOOLand
HUMANare unblocked by the denylist fix alone.The dispatch script and tool-spec compilation are each duplicated
(
JavaScriptBuilder.java:1430-1540,ToolCompiler.java:1597-1658), so a fix likely appliestwice. Marker-based identification was tried and reverted in
09842ab16, which is why this isframed as a selection change rather than a new marker.
Four SDKs reconstruct tool calls from the polled task list and each has its own version of this
problem (filed against
python-sdk,javascript-sdk,java-sdk,csharp-sdk). Gettingselection and naming right here is what makes a shared rule worth stating: a tool call is
identified by task type, and its name comes from
inputData._agent_tool_nameortaskDefName,never from the task reference name.