Agents Agent-to-agent (A2A) Opening & reading tasks

Opening and reading tasks

Open a task (SendMessage)

With an approved binding, the caller opens a task by naming the callee. Every route that takes a callee (.../tasks, .../tasks/{id}, .../tasks/{id}/cancel) accepts either its name or its id — pick whichever your code already has on hand. By name is the agent's name field (for example invoice-classifier); By id is its id field, a permanent UUID (hover either tab for the short version):

import os
from forgebench import Forgebench, PermissionDeniedError

client = Forgebench(api_key=os.environ["CALLER_AGENT_KEY"],
               base_url="https://api.forgebench.ai")

try:
  task = client.agents.call(
      "invoice-classifier",
      text="Classify this invoice",
      data={"invoice_id": "INV-2026-0912"},
      wait=30,  # block up to 30s for an answer
  )
except PermissionDeniedError:
  # no binding yet, still pending the callee's approval, or revoked —
  # see /docs/a2a#1-grant-a-binding and /docs/a2a/cards#errors-specific-to-a2a
  log.error("not permitted to call invoice-classifier")
  raise

if task.state == "completed":
  print(task.artifact_text())          # every artifact's text, joined
elif task.state == "input_required":
  print(task.question)                 # the callee is asking something back
elif task.state == "failed":
  print(task.error)
else:
  # still "working" after 30s — poll it
  task = client.agents.task("invoice-classifier", task.id, wait=20)

That curl call, run against a real pull callee that claimed and answered its task, looks like this end to end:

Opening a task on invoice-classifier and getting back a completed task with its artifact.
Opening a task on invoice-classifier and getting back a completed task with its artifact.

A few things worth knowing before you wire this into real code:

  • wait blocks the request up to that many seconds (max 30) for the task to leave submitted/working. If it's still working when wait runs out, the response is the task as it currently stands, not an error — poll it again with GetTask.
  • wait=0 (or omitted) returns immediately with whatever state the task is in right after it's opened. Useful if you'd rather fire-and-poll than hold a request open.
  • A message part is exactly one of text, data (structured JSON), or file ({uri, name, mime}) — never more than one on the same part. Use parts= directly when you need a file part or more than one of a kind; text=/data= build a single-part message for you.

Continue a conversation

An input_required task means the callee is asking a question instead of finishing. Answer it by opening a new task on the same callee with that task's context_id. That keeps a back-and-forth in one conversation:

task = client.agents.call("invoice-classifier", text="Classify this invoice", wait=30)
if task.state == "input_required":
  task = client.agents.call(
      "invoice-classifier",
      text="It's a recurring SaaS charge, category: software",
      context_id=task.context_id,
      wait=30,
  )

Read or cancel a task (GetTask / CancelTask)

task = client.agents.task("invoice-classifier", task_id, wait=10)
client.agents.cancel("invoice-classifier", task_id)

Reading a task you're not the caller or callee of returns 404, not 403 — its existence isn't something an uninvolved reader should learn either way. The caller, the callee or a console viewer role can read a task. Only the caller can cancel one, and canceling a finished task is a no-op, not an error.

Next

Cookie settings

Our Cookie Policy lists every cookie and what it does.