Skip to content

Client sends taskId/contextId only in top-level params, not on the message #62

Description

@col

Summary

A2A.Client.send_message/3 and stream_message/3 place :task_id and
:context_id only at the JSON-RPC params top level (params.id /
params.contextId). They are never set on the message object itself.

Per the A2A spec, MessageSendParams is { message, configuration, metadata }
and taskId / contextId are fields of the Message. Spec-compliant
servers therefore read them from params.message, not from the top-level
params.

See: https://a2a-protocol.org/latest/specification/#414-message

For v0.2.0
See: https://a2a-protocol.org/v0.2.0/specification/#64-message-object

Impact

When the Elixir client talks to a server that reads the ids only from the
message — e.g. the reference JS SDK @a2a-js/sdk, whose
handler does const contextId = incomingMessage.contextId || task?.contextId || uuidv4()
against incomingMessage = params.message — the client-supplied contextId
is invisible. The server mints a fresh contextId (uuidv4()) on every
request, so multi-turn conversation continuity is lost: each message/send
starts a brand-new context.

This works today only because the Elixir A2A.Plug server is lenient and reads
the top-level params as a fallback (build_call_opts/2 +
maybe_put_fallback(:context_id, message.context_id)), which masks the bug in
Elixir-to-Elixir setups.

Reproduction

{:ok, _} = A2A.Client.send_message(client, "Remember 42", context_id: "ctx-1")
{:ok, _} = A2A.Client.send_message(client, "What number?", context_id: "ctx-1")

Against a JS-SDK-based agent, the second call does not share context with the
first — the agent has no memory of "42".

Fix

Mirror :task_id / :context_id onto the Message struct in
build_send_params/2 before encoding, so they serialize as message.taskId /
message.contextId (spec-compliant). Keeping the top-level params is
backward-compatible with the current Elixir server.

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