Agents Agent-to-agent (A2A)

Agent-to-agent (A2A)

A2A lets one governed agent (the caller) open a task on another governed agent (the callee), in the A2A protocol shape (SendMessage, GetTask, CancelTask), through the same door that governs a model call. Forgebench never executes the callee itself — it relays the task to the callee's own endpoint (push) or hands it to the callee's own polling process (pull), and it writes the whole exchange to the ledger either way.

Think of it as the same governance Forgebench already applies to a model call, extended to "one agent asking another to do something": a binding instead of a model allowlist, a task instead of a completion, but the same audit row, the same budget-adjacent guardrails, the same idea that nothing crosses this edge ungoverned.

How it works

  1. Authorization check. The control plane checks that a binding exists from caller to callee, that it's approved and not revoked, and that the callee is in a callable state (not paused or retired). That's the only check on this edge — not the caller's tools, not the callee's tools, not either agent's budget. A binding answers exactly one question: may this caller open a task on this callee.
  2. Ledger write. A task row and an audit row are written before delivery is attempted, tagged with the caller's lineage (X-Parent-Call). Whichever way delivery goes next — relayed, claimed, answered, failed, never picked up — the task already exists on the record.
  3. Delivery. If the callee has an endpoint_url set, the control plane POSTs the task there (push). If not, the callee's own process claims the task by long-polling GET /v1/agents/me/tasks/next (pull).

A binding only grants "caller may open a task on callee" — it says nothing about what the callee is allowed to do while working on it. The callee's own model and tool calls are still gated by the callee's own bindings, independent of who opened the task. That separation is what keeps a multi-agent workflow auditable per-agent instead of collapsing into one shared blast radius: agent A calling agent B never means A borrowed B's tools.

1. Grant a binding

A caller can only open a task on a callee it's bound to. Grant one from the caller's Calls tab, or the same operation via the API:

billing-orchestrator's Calls tab: an approved outbound binding to invoice-classifier, and the empty inbound side.
billing-orchestrator's Calls tab: an approved outbound binding to invoice-classifier, and the empty inbound side.
# caller's owner or an admin, granting caller -> callee(s)
curl -X POST https://api.forgebench.ai/v1/agents/$CALLER_ID/agents \
-H "Authorization: Bearer $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"callees": ["invoice-classifier", "refund-approver"]}'

Each entry in the response has caller_agent_id, callee_agent_id, source, revoked, and approved_at.

  • If the granting principal can also speak for the callee (an admin, or a person who owns both agents), approved_at is set immediately and the binding is live right away — that's the row in the screenshot above.
  • Otherwise approved_at is null: the binding is pending until the callee's owner (or an admin) approves it from the callee's own Calls tab, or via the API:
# callee's owner or an admin — agent_id is the CALLEE, binding_id from the grant response
curl -X POST https://api.forgebench.ai/v1/agents/$CALLEE_ID/agents/$BINDING_ID/approve \
-H "Authorization: Bearer $ADMIN_KEY"

This two-sided approval is deliberate: it's the same "both owners sign off" shape a cross-team integration needs anywhere else, so a caller can't grant itself access to a callee it doesn't own by asking nicely through the API. Same-owner or admin-granted pairs skip the second step because the same person already speaks for both sides.

Re-granting an already-revoked pair un-revokes the same row instead of creating a new one — the history of who was ever bound to whom stays intact.

Read both directions for one agent:

GET /v1/agents/{agent_id}/agents
-> {"outbound": [...], "inbound": [...]}

Revoke a binding from either side, from the Calls tab's Revoke button or DELETE /v1/agents/{agent_id}/agents/{binding_id}. The row is kept and marked revoked: true rather than deleted — a security review six months later should be able to see that an edge existed and was cut, not just that it's gone. Revocation takes effect on the caller's next task.

Next