Skip to content

swift (v1.0): unregistered @call function is not routed to agent via callAgentFunction in bindings, checks, and action.functionCall #3689

Open

Description

@jacobsimionato

Summary

In protocol v1.0, a FunctionCall ({"@call": "..."}) whose function has no local renderer implementation in the catalog should be routed to the agent via callAgentFunction (specification: specification/v1_0/docs/a2ui_protocol.md → § "Function Location Resolution & Fallback Routing", lines 1395–1426).

While A2UICore implements the programmatic RPC API (MessageProcessor.callAgentFunction and RPCHandler.callAgentFunction), DataContext and NodeResolver have no integration with RPCHandler:

  1. When a property binding or validation check evaluates a function not implemented locally, DataContext.evaluateFunctionCall fails immediately with FunctionError.functionNotFound(callName) and resolves to .null (swift/core/Sources/A2UICore/State/DataContext.swift:152-158).
  2. When a Button's action is configured as functionCall ({"action": {"functionCall": {"@call": "..."}}}), NodeResolver invokes evaluateDynamicValue(funcCallVal) (swift/core/Sources/A2UICore/Resolution/NodeResolver.swift:1490), triggering the same FunctionError.functionNotFound path in DataContext.
  3. No CallAgentFunctionMessage is dispatched over the wire, and the agent never receives the RPC invocation.

In contrast, the Dart SDK (dart/a2ui_core) injects callAgentFunction into SurfaceModel and DataContext, correctly routing unregistered calls to the agent and awaiting AgentFunctionResponseMessage (tested in dart/a2ui_core/test/agent_function_fallback_test.dart).

Specification Reference

From specification/v1_0/docs/a2ui_protocol.md (§ "Function Location Resolution & Fallback Routing"):

When the renderer evaluates a FunctionCall (from an action handler, validation check, or dynamic value), it determines the execution target using implicit fallback routing:

  1. Local Lookup: The renderer checks if a local renderer-side function with that name is registered in its local catalog/registry. If found, the renderer executes the function locally.
  2. Fallback to Agent RPC: If the function is not registered in the local renderer catalog, the renderer assumes it is an agent-side function and dispatches a callAgentFunction message over the protocol.
  3. Agent Error Handling: If the agent does not recognize the function name, the agent MUST return an agentFunctionResponse message containing an error payload. The renderer then handles the error state locally.

Affected Files

  • swift/core/Sources/A2UICore/State/DataContext.swift (lines 130–168)
  • swift/core/Sources/A2UICore/Resolution/NodeResolver.swift (lines 1456–1495)
  • swift/core/Sources/A2UICore/Processing/MessageProcessor.swift (wiring to surface / resolution context)
  • swift/core/Sources/A2UICore/Models/SurfaceViewModel.swift

Steps to Reproduce

  1. In a v1.0 surface with the Basic Catalog, define a Button with a server-side action:
    {
      "id": "btn",
      "component": "Button",
      "child": "btn_text",
      "action": {
        "functionCall": {
          "@call": "createIssue",
          "args": {"title": "Sample Issue"}
        }
      }
    }
  2. Set up MessageProcessor with an outbound listener to observe emitted renderer-to-agent messages.
  3. Trigger the button's action.
  4. Expected: A CallAgentFunctionMessage for createIssue is emitted to the outbound listener.
  5. Actual: DataContext logs FunctionError.functionNotFound("createIssue"), returns .null, and no outbound message is emitted.

Suggested Fix

  1. Follow the Dart pattern (dart/a2ui_core/lib/src/processing/processor.dart:665 and data_context.dart:724): pass a callAgentFunction delegate from MessageProcessor into SurfaceViewModel / DataContext.
  2. In DataContext.evaluateFunctionCall, if the function is not found locally on a v1.0 surface and an RPC caller is available:
    • Dispatch callAgentFunction via the surface caller.
    • Return a placeholder/pending state that completes with the agentFunctionResponse value.
  3. For action closures (ResolvedAction in NodeResolver), await the async RPC result when activated.
  4. On error or timeout, invoke functionHandler?.handleFunctionError(...) with the structured RPC error.

Activity

  1. added
    type: bugSomething isn't working
    P1This is a priority. Team is actively working on it, or will be soon.
    lang: swiftAffects SDKs or apps in the Swift programming language
    on Oct 28, 2026
  2. added
    status: needs-triageauto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs
    on Oct 28, 2026
  3. removed
    status: needs-triageauto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs
    on Oct 28, 2026
  4. nan-yu commented on Oct 30, 2026

    @nan-yu
    Collaborator

    @gspencergoog, heads-up: #3741 fixes this issue, so I've added Fixes #3689 to its description.

    It routes v1.0 function calls the Swift core can't run to the agent with callAgentFunction, from bindings, check rules, and action.functionCall:

    1. MessageProcessor sets SurfaceViewModel.agentFunctionCaller on v1.0 surfaces while outboundListener is set, and updates it if the listener changes later.
    2. A routed binding is .null until agentFunctionResponse arrives. Agent errors and timeouts are reported through handleFunctionError as EXPRESSION_ERROR.
    3. A Button action like the one in the repro sends callAgentFunction when triggered (sendsActionFunctionCall in AgentFunctionRoutingTests). Each trigger sends a fresh call.
    4. The outbound call always carries the resolved catalogId. Schema-only functions are routed too, with their arguments validated first.

    One difference from the suggested fix: ResolvedAction stays a synchronous closure that starts the call, rather than one the caller awaits. Errors are still reported, and nested agent arguments settle before the call is sent.

    #3741 is stacked on #3735 → #3739 → #3740, and targets the Python branch for now. The Fixes keyword takes effect once it's retargeted to main and merged.

    🤖 Agent-generated

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

Metadata

Metadata

Assignees

Labels

P1This is a priority. Team is actively working on it, or will be soon.component: core sdkAffects the core SDK layer in any languagecomponent: specificationAffects the A2UI specification documents and JSON schemascomponent: standard catalog specificationfeature: agent-side functionsRelated to ability to call agent-side functions which was added in the v1.0 protocollang: dartAffects SDKs or apps in the Dart programming languagelang: swiftAffects SDKs or apps in the Swift programming languagestatus: first-line-handledtype: bugSomething isn't workingv1-bug-bash

Type

No type

Projects

  • Status
    Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions