- AI & Agents
- Expose an agent over A2A
Expose an agent over A2A
The Agent2Agent (A2A) protocol lets an agent in one system send a task to an agent in a different system. ironflow a2a exposes an Ironflow agent to such callers. One A2A task is one durable Ironflow run.
Before you start
Section titled “Before you start”- An Ironflow server is running.
- A worker serves an agent that you built with
agent(). - You have an API key with the
developerrole for the adapter.
1. Write the agent for the A2A input
Section titled “1. Write the agent for the A2A input”The adapter always gives the agent this input:
{ "text": "all text parts of the message, joined with a newline", "data": { "the first data part": "or null" }, "a2a": { "contextId": "...", "messageId": "...", "message": { "the full A2A message": "..." } }}import { agent } from "@ironflow/node/agent";
export const reviewAgent = agent( { id: "code-review", name: "Code review", description: "Reviews a pull request", triggers: [] }, async ({ event, llm, approve }) => { const { text } = event.data as { text: string };
const findings = await llm.complete({ messages: [{ role: "user", content: `Review:\n${text}` }], call: () => provider.complete(text), });
const decision = await approve("code-review.post-review.approved", { ttl: "24h", payload: findings }); return { approved: decision.approved, findings }; },);The agent’s name and description go into the Agent Card. To expose an agent that has a different input shape, write a small agent with this input that calls the other agent with spawn().
2. Start the adapter
Section titled “2. Start the adapter”set -x IRONFLOW_A2A_BEARER_TOKEN (openssl rand -hex 32)set -x IRONFLOW_API_KEY ifkey_...ironflow a2a --agent code-review --caller-input code-review/code-review.post-review.approvedIn zsh or bash, use export NAME=value in place of set -x NAME value.
The Agent Card is now at http://127.0.0.1:9124/agents/code-review/.well-known/agent-card.json. Give the callers that address and the token.
3. Decide which pauses a caller can answer
Section titled “3. Decide which pauses a caller can answer”An approve() call stops the run until an event with the name that you pass arrives. By default the adapter hides each such wait from remote callers: the task shows as working, and a reply is refused. This keeps a caller from answering a wait that is for your own staff.
--caller-input <function-id>/<event-name> lists one event name that a caller can send. --caller-input code-review/code-review.post-review.approved opens the wait in the agent above. The adapter splits the entry at the first /, so the event name can contain /. The adapter emits the event name with { runId, approved }, so the name must be the one that the agent passes to approve(). The task then shows as TASK_STATE_INPUT_REQUIRED, and its status message carries the approve() payload. The caller answers with a message for the same task that has a data part:
{ "approved": true, "reason": "optional text", "payload": { "optional": "JSON" } }The adapter does not read an approval from text. A reply without a boolean approved gets an error that shows this shape.
The caller sees the approve() payload, so put only caller-safe data in it.
When a gate reaches its ttl with no reply, the agent’s SDK decides the result. The Node SDK fails the run, and the task becomes failed. The Go SDK returns a result that is not approved, and the agent continues.
4. Put it on a network
Section titled “4. Put it on a network”The adapter serves plain HTTP and listens on loopback by default. For remote callers:
- Put a TLS proxy in front of the adapter.
- Start the adapter with
--public-url https://agents.example.com, so the Agent Card shows the public address.
On a loopback listener, the adapter refuses a request whose Host header is not loopback and is not the --public-url host. This stops a web page that points its own domain at 127.0.0.1 (DNS rebinding). A proxy on the same machine can send the loopback address or the public host.
What callers can do
Section titled “What callers can do”| Operation | Result |
|---|---|
| Send a message | Starts a run. The message ID is an idempotency key, so a retry gets the same task. |
| Send a streaming message | Starts a run and streams its states until it finishes or pauses. |
| Get or list tasks | Reads the runs of this agent. |
| Subscribe to a task | Streams the states of a run that is in progress. |
| Cancel a task | Cancels the run. |
Limits
Section titled “Limits”- All callers share one token and can see the tasks of the other callers for the same agent.
- Callers see only the runs that the adapter started. Runs of the same function that staff, events or schedules started stay hidden.
- A send waits 30 seconds at most. A longer task comes back as working.
- Push notifications and model-token streaming are not available.
See the ironflow a2a reference for all flags.