Skip to content

BUG: A2A dispatch uses tasks/send (v0.2) — incompatible with nullclaw A2A v0.3.0 message/send #42

Closed

Description

@addadi

Bug: A2A dispatch uses tasks/send (v0.2) — incompatible with nullclaw A2A v0.3.0 message/send

Summary

buildA2aRequestBody builds the A2A JSON-RPC request with method: "tasks/send" and message parts using the type discriminator. Current nullclaw (src/a2a.zig) implements A2A v0.3.0, which requires:

  • method message/send (not tasks/send)
  • parts discriminated by kind (not type)
  • message object with kind: "message" and a messageId

Dispatching a step to an A2A worker (e.g. nullclaw with an a2a worker) fails protocol validation on the worker side.

Root cause

src/dispatch.zig:407-441:

const parts = [_]struct {
    type: []const u8,   // v0.2 discriminator
    text: []const u8,
}{ .{ .type = "text", .text = prompt } };

const message = .{ .role = "user", .parts = parts[0..] };  // no kind, no messageId

return std.json.Stringify.valueAlloc(allocator, .{
    .jsonrpc = "2.0",
    .id = context_id,
    .method = "tasks/send",   // v0.2 method
    .params = params,
}, .{});

Reproduction

  1. Register an A2A worker (nullclaw instance exposing the a2a endpoint).
  2. Run a workflow with a step whose worker_tags select the A2A worker.
  3. Dispatch fails / response cannot be parsed — the worker rejects tasks/send and the part/message shape.

Expected behavior

Request body per A2A v0.3.0:

{
  "jsonrpc": "2.0",
  "id": "<context_id>",
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [{ "kind": "text", "text": "..." }],
      "kind": "message",
      "messageId": "msg-<context_id>"
    },
    "configuration": { "contextId": "<context_id>" }
  }
}

Context/session semantics unchanged: same context_id → same conversation.

Activity

  1. vernonstinebaker commented on Sep 28, 2026

    @vernonstinebaker
    Contributor

    Fixed by #48 (merged). Validated the wire shape against nullclaw's A2A server contract (message/send, kind discriminators, required messageId, result-is-Message response).

    One heads-up from live testing today: the org now has three A2A dialects — NullBoiler (was v0.2 tasks/send, now v0.3.0 message/send), NullClaw (v0.3.0 message/send), ZeroClaw (A2A 1.0: SendMessage, ROLE_USER enums, A2A-Version: 1.0 header — empirically verified end-to-end with a live agent turn). This fix restores Boiler↔NullClaw interop; Boiler↔ZeroClaw A2A will need 1.0 support (version negotiation via the worker's agent card would cover both). Filed separately as a follow-up concern on #53's thread.

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