Skip to content

AgentResult.Events is never assigned, and every non-worker tool is named after its task type #173

Description

@ambiorix2099

Bug Description

AgentResult.Events is null after WaitAsync, so enumerating it throws. The property is
effectively dead: nothing in the library ever assigns it.

ToolCalls is wrong for every tool kind except plain workers. An HTTP tool is named "HTTP", an
MCP tool "CALL_MCP_TOOL", an agent used as a tool "SUB_WORKFLOW", a human tool "HUMAN", an
image tool "GENERATE_IMAGE".

Tool calls are also missed entirely for non-OpenAI providers, because selection keys on the
provider's tool-call ID format.

Root Cause

Events omitted. Declared at Conductor.AI/Result.cs:156. The object initializer in
BuildResult (:539-548) sets ExecutionId, Status, Output, Error, ToolCalls,
TokenUsage and FinishReason, and omits it. Events appears nowhere else in Conductor.AI, and
AgentResult is not deserialized from a payload that would supply it, so it is always null
rather than empty. StreamAsync (:331) yields events but never builds a result from them.

Name taken from the task type. Result.cs:569:

var tc = new Dictionary<string, object> { ["name"] = task!["taskType"]?.GetValue<string>() ?? "" };

Correct only for a worker tool, because Conductor sets an executed SIMPLE task's taskType to the
task's own name (SimpleTaskMapper.java:86 in conductor-oss/conductor). Every other tool kind
carries its system task type there.

The real name is available and discarded at :576-577. A tool task's inputData:

{"_agent_tool_name": "get_weather", "_agent_state": {},
 "method": "get_weather", "city": "San Francisco"}

method, _agent_tool_name and taskDefName all carry it. None is read.

Selection keys on the provider's tool-call ID. Result.cs:566 requires
refName.StartsWith("call_"). The server does not add that prefix. It seeds the reference from the
provider's toolCall.id(), falling back to a UUID, then appends the fork index and loop iteration,
giving call_PMnNIdOPvm9EQ8e6tn2kbxPY_0__1 for OpenAI. Anthropic IDs start toolu_ and are not
matched. Adding toolu_ is not a fix, since the next provider picks its own format.

There is also no system-task-type filter, so selection rests entirely on the reference-name prefix.

Steps to Reproduce

var result = await handle.WaitAsync();
foreach (var e in result.Events) { }   // NullReferenceException
result.ToolCalls[0]["name"];           // "HTTP" for an HTTP tool

Expected Behavior

Assign Events in BuildResult, or drop the property if it is not intended to be populated on
that path.

Identify a tool task by task type, allowlisting off the server's ToolCompiler.TYPE_MAP plus the
worker case, and resolve the name from inputData._agent_tool_name, then inputData.method, then
taskDefName. Never from the reference name, which carries provider-controlled data.

Two corrections to the above, found while implementing it:

  • TYPE_MAP is not the whole allowlist. It has no generate_pdf entry, and media tools take their
    task type from toolType.toUpperCase() instead (ToolCompiler.java:427-436), so a generate_pdf
    tool compiles to GENERATE_PDF and has to be added by hand.
  • _agent_tool_name is set for every tool kind only on the static dispatch path
    (JavaScriptBuilder.enrichToolsScript, :797). The dynamic-tools path
    (enrichToolsScriptDynamic, :1607-1609) sets no _agent_tool_name at all and sets
    _agent_state only on SIMPLE tasks, so a non-worker tool dispatched there carries no marker.

Additional Notes

Verified against origin/main @ b5d7f22.

AgentStatusMappingTests.cs:164 covers the worker case and passes for the right reason. Its fixture
(:173-179) omits method and _agent_tool_name, and its reference name (call_echo_1) lacks the
fork index and loop suffix that real payloads carry, so neither name resolution nor detection is
genuinely exercised. Nothing covers Events.

Same family of defects filed against python-sdk, javascript-sdk and java-sdk. This
implementation is a port of the Java one (Result.cs:552 names
AgentHandle.extractFromTasks), itself a port of Python's.

A related server-side issue is filed against conductor-oss/conductor:
AgentEventListener.isToolTask excludes HTTP, CALL_MCP_TOOL, SUB_WORKFLOW and HUMAN, so
those kinds emit no tool events server-side either. Fixing this SDK gets their names right in
ToolCalls.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions