Repository navigation
feat(a2a): serve and call A2A agents - #42
Draft
dbernheisel wants to merge 12 commits into
Draft
dbernheisel wants to merge 12 commits into
dbernheisel wants to merge 12 commits into
Conversation
Routers declare A2A agents with `agent`, implemented with `Phantom.A2A.Agent` and served by `Phantom.A2A.Plug` over JSON-RPC: SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, and SubscribeToTask, streaming over SSE with a pubsub. Agents store their own tasks and report progress with `Phantom.A2A.publish/2`. The A2A conformance suite (ACTS) runs against a test agent with `mix test --only acts`.
Agent routes with path params, such as `agent ":org/:agent_key", Mod, :resolve`, resolve agents per request after `connect/2`. Agents with `push_notifications: true` serve the push config methods, and `Phantom.A2A.publish/2` posts each event to the task's webhooks over :httpc. Webhook URLs must be https and resolve to public addresses, checked when a config is created and again before each delivery, which connects to the checked address without redirects.
Agents with `extended_card: true` answer GetExtendedAgentCard through an `extended_card/2` callback, and agents can declare protocol extensions, with required ones enforced from the `A2A-Extensions` header. Requests and push deliveries emit `[:phantom, :a2a, :dispatch]` and `[:phantom, :a2a, :push]` telemetry spans with the client's W3C trace context. `Phantom.Router.call_tool/4` calls a router's tool from an agent or any other code.
`Phantom.A2A.Events.apply/2` now numbers each event after the task's last
one and returns `{:ok, task, event}`; the task keeps the number in
`sequence`. Streams skip events the task already includes, so a client that
subscribes while an agent publishes doesn't get an event twice, which for an
appended artifact chunk corrupted the artifact.
`Phantom.A2A.Plug` serves the HTTP+JSON binding next to JSON-RPC, with the routes from the A2A protobuf under each agent's path and an optional tenant segment. Both bindings share every method; HTTP+JSON answers with application/a2a+json and `google.rpc.Status` errors under their HTTP status. Cards list both interfaces, and `bindings:` limits a mount to one. Agent paths can no longer use the segments the binding routes on. ACTS now grades both bindings.
`Phantom.A2A.Client` fetches an agent's card, picks a binding it lists, and calls every A2A method over JSON-RPC or HTTP+JSON, streaming lazily. It needs the optional Req dependency, and checks the card before optional operations. `c:Phantom.Router.list_agents/2` lists the agents a session may use. The ACTS agent parses canned responses with the client, so Phantom now passes every ACTS test on both bindings.
`Phantom.A2A.Bridge`, mounted with `agent "tools", Phantom.A2A.Bridge`, lists a router's tools as skills, with their input schemas in an "MCP tools" card extension. A message whose data part names a tool calls it with `Phantom.Router.call_tool/4` and answers with its result as message parts. Messages that name no tool go to a fallback agent or get the list of tools. `c:Phantom.A2A.Agent.card/2` lets any agent adjust its card for its route.
When the router implements get_task/2, the bridge calls tools with the Tasks extension (`call_tool/4` takes `tasks: true`), and a tool's `Phantom.Tasks` becomes an A2A task with the same ID. Input requests become input_required and are answered with an "inputResponses" data part that goes to update_task/3; GetTask and CancelTask go to the router. The A2A plug now follows a task whose ID the handler chose, subscribing to it and fetching it again so no event is missed.
Agents whose tasks are MCP tasks (`follows_mcp_tasks?/0`, true for `Phantom.A2A.Bridge`) listen through `Phantom.Tracker` while a request follows one of their tasks, the way MCP `subscriptions/listen` streams do. After each `Phantom.Tracker.notify_task_updated/1`, a waiting SendMessage or a stream fetches the task again with `get_task/2` and sends it. Requests stop listening when they end.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
agentrouter macro and thePhantom.A2A.Agentbehaviour. A router serves A2A agents and uses the sameconnect/2for MCP and A2A.Phantom.A2A.Plug. It serves the JSON-RPC binding and the HTTP+JSON binding, agent cards, extended cards, and SSE streams through PubSub.":org/:agent_key". Static agents match first. A static agent path followed by one segment gives the tenant.Phantom.A2A.Pushchecks each webhook URL against private networks and connects to the address that it checked.Phantom.A2A.Push.Deliverysends the events of a task to each webhook in order, with retries. A maximum of 100 events wait for each webhook.Phantom.A2A.Client(optional:req) for calls to other agents, andlist_agents/2.Phantom.A2A.Bridge. It serves the MCP tools of a router as an A2A agent and changes MCP tasks into A2A tasks.guides/a2a.md), a README section, telemetry spans, and the conformance tests for the server, the client, and the bridge.Router.call_tool/4in a supervised task, so that late tool replies do not stay in the HTTP process. Add demand control toClient.stream_message/3. Send 401 instead of-32007when an anonymous client asks for an extended card.Evidence
Phantom.A2A.Plugruns in Bandit at/a2a.Phantom.A2A.Clientcalls it over HTTP. The router has an echo agent and a bridge for one MCP tool,add.iex
Configuration: the router option
a2a: [push_retry_backoff: [...]]sets the retry delays in milliseconds. The default is 1, 5, and 30 seconds.a2a: [allow_private_push_urls: true]permits private webhook addresses. The default isfalse.