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
(e.g. invoice-classifier); By id is its id field, a permanent UUID
(hover either tab below for the short version):
curl -X POST "https://api.forgebench.ai/v1/agents/invoice-classifier/tasks?wait=30" \
-H "Authorization: Bearer $CALLER_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"role": "user",
"parts": [
{"text": "Classify this invoice"},
{"data": {"invoice_id": "INV-2026-0912"}}
]
}
}'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)import { Forgebench, PermissionDeniedError, artifactText, messageText } from "@seedlinglabs/forgebench-sdk";
const forgebench = new Forgebench({
baseUrl: "https://api.forgebench.ai",
apiKey: process.env.CALLER_AGENT_KEY!,
});
let task;
try {
task = await forgebench.agents.call("invoice-classifier", {
text: "Classify this invoice",
data: { invoice_id: "INV-2026-0912" },
wait: 30,
});
} catch (e) {
if (e instanceof 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
console.error("not permitted to call invoice-classifier");
}
throw e;
}
if (task.status.state === "completed") {
console.log(artifactText(task));
} else if (task.status.state === "input_required") {
console.log(messageText(task.status.message));
} else if (task.status.state === "failed") {
console.log(task.error);
} else {
const polled = await forgebench.agents.getTask("invoice-classifier", task.id, { wait: 20 });
}curl -X POST "https://api.forgebench.ai/v1/agents/706608c0-4160-452a-86ac-ded1186326ac/tasks?wait=30" \
-H "Authorization: Bearer $CALLER_AGENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"role": "user",
"parts": [
{"text": "Classify this invoice"},
{"data": {"invoice_id": "INV-2026-0912"}}
]
}
}'import os
from forgebench import Forgebench, PermissionDeniedError
client = Forgebench(api_key=os.environ["CALLER_AGENT_KEY"],
base_url="https://api.forgebench.ai")
CALLEE_ID = "706608c0-4160-452a-86ac-ded1186326ac"
try:
task = client.agents.call(
CALLEE_ID,
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 %s", CALLEE_ID)
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(CALLEE_ID, task.id, wait=20)import { Forgebench, PermissionDeniedError, artifactText, messageText } from "@seedlinglabs/forgebench-sdk";
const forgebench = new Forgebench({
baseUrl: "https://api.forgebench.ai",
apiKey: process.env.CALLER_AGENT_KEY!,
});
const CALLEE_ID = "706608c0-4160-452a-86ac-ded1186326ac";
let task;
try {
task = await forgebench.agents.call(CALLEE_ID, {
text: "Classify this invoice",
data: { invoice_id: "INV-2026-0912" },
wait: 30,
});
} catch (e) {
if (e instanceof 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
console.error("not permitted to call", CALLEE_ID);
}
throw e;
}
if (task.status.state === "completed") {
console.log(artifactText(task));
} else if (task.status.state === "input_required") {
console.log(messageText(task.status.message));
} else if (task.status.state === "failed") {
console.log(task.error);
} else {
const polled = await forgebench.agents.getTask(CALLEE_ID, 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:

A few things worth knowing before you wire this into real code:
waitblocks the request up to that many seconds (max 30) for the task to leavesubmitted/working. If it's still working whenwaitruns out, the response is the task as it currently stands, not an error — poll it again withGetTask.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), orfile({uri, name, mime}) — never more than one on the same part. Useparts=directly when you need afilepart 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, passing that
task's context_id — this is what keeps a back-and-forth as one
conversation instead of a pile of unrelated tasks:
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,
)let task = await forgebench.agents.call("invoice-classifier", { text: "Classify this invoice", wait: 30 });
if (task.status.state === "input_required") {
task = await forgebench.agents.call("invoice-classifier", {
text: "It's a recurring SaaS charge, category: software",
contextId: task.context_id,
wait: 30,
});
}CALLEE_ID = "706608c0-4160-452a-86ac-ded1186326ac"
task = client.agents.call(CALLEE_ID, text="Classify this invoice", wait=30)
if task.state == "input_required":
task = client.agents.call(
CALLEE_ID,
text="It's a recurring SaaS charge, category: software",
context_id=task.context_id,
wait=30,
)const CALLEE_ID = "706608c0-4160-452a-86ac-ded1186326ac";
let task = await forgebench.agents.call(CALLEE_ID, { text: "Classify this invoice", wait: 30 });
if (task.status.state === "input_required") {
task = await forgebench.agents.call(CALLEE_ID, {
text: "It's a recurring SaaS charge, category: software",
contextId: task.context_id,
wait: 30,
});
}Read or cancel a task (GetTask / CancelTask)
# read (0-30s optional long-poll via ?wait=)
curl "https://api.forgebench.ai/v1/agents/invoice-classifier/tasks/$TASK_ID?wait=10" \
-H "Authorization: Bearer $CALLER_AGENT_KEY"
# cancel — only the caller may; idempotent
curl -X POST "https://api.forgebench.ai/v1/agents/invoice-classifier/tasks/$TASK_ID/cancel" \
-H "Authorization: Bearer $CALLER_AGENT_KEY"task = client.agents.task("invoice-classifier", task_id, wait=10)
client.agents.cancel("invoice-classifier", task_id)const task = await forgebench.agents.getTask("invoice-classifier", taskId, { wait: 10 });
await forgebench.agents.cancelTask("invoice-classifier", taskId);CALLEE_ID=706608c0-4160-452a-86ac-ded1186326ac
# read (0-30s optional long-poll via ?wait=)
curl "https://api.forgebench.ai/v1/agents/$CALLEE_ID/tasks/$TASK_ID?wait=10" \
-H "Authorization: Bearer $CALLER_AGENT_KEY"
# cancel — only the caller may; idempotent
curl -X POST "https://api.forgebench.ai/v1/agents/$CALLEE_ID/tasks/$TASK_ID/cancel" \
-H "Authorization: Bearer $CALLER_AGENT_KEY"CALLEE_ID = "706608c0-4160-452a-86ac-ded1186326ac"
task = client.agents.task(CALLEE_ID, task_id, wait=10)
client.agents.cancel(CALLEE_ID, task_id)const CALLEE_ID = "706608c0-4160-452a-86ac-ded1186326ac";
const task = await forgebench.agents.getTask(CALLEE_ID, taskId, { wait: 10 });
await forgebench.agents.cancelTask(CALLEE_ID, taskId);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. Any of the caller, the callee, or a console viewer role can read a
task; only the caller can cancel one, and canceling a task that's already
finished is a no-op, not an error.

