A2A Tasks

Linux Foundation A2A (Agent-to-Agent) standard — agents advertise identity, declared capabilities, endpoints, and policies via a signed "agent card" that a peer agent fetches before initiating collaboration. Cards are JSON documents canonicalized via RFC 8785 (sorted keys, deterministic whitespace), hashed with SHAKE256 (64-byte output), and signed under the issuing agent's identity key. The default signing algorithm follows b.crypto.sign — ML-DSA-87 (FIPS 204) or SLH-DSA-SHAKE-256f (FIPS 205) auto-detected from the PEM. Verifiers refuse unsigned, expired, future-signed, or shape-malformed cards and emit audit events on every accept / deny outcome.

The card schema is intentionally narrow: required fields are issuer, agentId, version (semver), and capabilities (string array). Optional fields are endpoints (each must be HTTPS or a localhost loopback), policies, contact, and a free-form metadata bag. Capability names are bounded to 128 chars; identifiers match [a-zA-Z0-9._:/-]{1,256}. Operators build cards via createCard, sign with signCard, and the peer side calls verifyCard against the issuer's published public key.

b.a2a.tasks.send(opts) #

stable0.8.85
{
  peerUrl:   string,    // peer's A2A endpoint URL (https only)
  task:      object,    // { skill, input } per A2A v1 §4
  timeoutMs: number,    // optional — default 30s
  headers:   object,    // optional — extra HTTP headers (signed
                        //            auth / mTLS / RFC 9421 sig)
  audit:     boolean,   // default true
}

Post a tasks/send JSON-RPC request to a peer A2A agent. Returns the peer's tasks/send result — typically { taskId, status } or a final-state response when the task ran synchronously.

var rsp = await b.a2a.tasks.send({
  peerUrl: "https://agent.example.com/a2a",
  task:    { skill: "summarize", input: { url: "..." } },
});
// rsp.taskId === ""
// rsp.status === "queued" | "running" | "completed"

b.a2a.tasks.get(opts) #

stable0.8.85
{
  peerUrl: string,
  taskId:  string,
  timeoutMs: number,
  headers: object,
}

Poll a peer task's current status via tasks/get. Returns the peer's status record — { taskId, status, result?, error? }.

var st = await b.a2a.tasks.get({ peerUrl: url, taskId: "abc" });
if (st.status === "completed") console.log(st.result);

b.a2a.tasks.cancel(opts) #

stable0.8.85
{
  peerUrl:   string,    // peer's A2A endpoint URL (https only)
  taskId:    string,    // peer-assigned task identifier
  timeoutMs: number,    // optional — default 15s
  headers:   object,    // optional — extra HTTP headers
  audit:     boolean,   // default true
}

Request peer cancellation via tasks/cancel. Peer MAY refuse with -32003 task-not-cancelable for tasks that have completed or passed a cancellation point.

try {
  await b.a2a.tasks.cancel({ peerUrl: url, taskId: "abc" });
} catch (e) {
  if (e.rpcCode === -32003) console.log("task already past cancel point");
}

b.a2a.middleware.tasks(opts) #

stable0.8.85
{
  handler:    function (ctx) → result  — REQUIRED
  scopes:     { skillName: scopeString } — optional per-skill scope map
  maxBytes:   number — body cap (default 1 MiB)
  audit:      boolean — default true
}

Build the server-side A2A tasks middleware. Returns a connect-style (req, res, next) => void that:

- Parses inbound JSON-RPC 2.0 requests from POST request bodies. Refuses non-POST + non-application/json with 405 / 415. - Refuses methods not in ["tasks/send", "tasks/get", "tasks/cancel"] with JSON-RPC -32601 method-not-found. - Enforces per-skill scope via opts.scopes — a map { skillName: requiredScope }. The middleware reads req.a2aScopes (populated by the operator's auth layer; e.g. parsed from a bearer token's claims or an mTLS cert SAN) and refuses calls whose required scope isn't granted with -32001. - Dispatches to opts.handler({ method, taskId?, task?, req }) which returns the task state (or throws for errors that get mapped to -32603).

The middleware writes the JSON-RPC response itself; it does NOT call next(). Operators chaining additional middleware after this one should mount on a separate path.

var mw = b.a2a.middleware.tasks({
  scopes: { summarize: "a2a:summarize", search: "a2a:search" },
  handler: async function ({ method, task, taskId }) {
    if (method === "tasks/send") {
      var newId = "t-" + Math.random().toString(36).slice(2, 10);
      queue.push({ id: newId, task });
      return { taskId: newId, status: "queued" };
    }
    if (method === "tasks/get") {
      return tasks.get(taskId);
    }
    if (method === "tasks/cancel") {
      return tasks.cancel(taskId);
    }
  },
});
app.post("/a2a", mw);

b.a2a.middleware.agentCard(opts) #

stable0.8.85
{
  card:        object (REQUIRED) — the signed-card envelope from b.a2a.signCard
  maxAgeSec:   number (default 300) — Cache-Control max-age
}

Build a middleware that serves the operator's Agent Card at /.well-known/agent.json per the A2A v1 discovery convention. The middleware writes a 200 JSON response on GET; refuses other methods with 405. Mount on a router path that resolves the well-known prefix (operator-side).

var raw = b.a2a.createCard({
  agent: { name: "my-agent", version: "1.0.0" },
  skills: [{ name: "summarize" }],
});
var card = b.a2a.signCard(raw, pair.privateKey);
app.get("/.well-known/agent.json", b.a2a.middleware.agentCard({ card: card }));

b.a2a.canonicalize(card) #

stable0.7.45

Returns the RFC 8785 JCS (JSON Canonicalization Scheme) string form of an agent card — sorted keys, deterministic number form, no insignificant whitespace. Exposed so operators that store the canonical bytes alongside the signature can recompute the digest without re-walking the object tree. signCard and verifyCard use the same canonicalizer internally.

var b = require("blamejs");
var bytes = b.a2a.canonicalize({
  issuer:       "agent.example.com",
  agentId:      "ops-bot-1",
  version:      "1.0.0",
  capabilities: ["chat.respond", "tool.search"]
});
bytes.indexOf("\"agentId\":\"ops-bot-1\"") >= 0;
// → true (keys appear in lexicographic order)

b.a2a.createCard(opts) #

stable0.7.45
{
  {
    issuer:       string,         // 1..256 chars, [a-zA-Z0-9._:/-]
    agentId:      string,         // 1..256 chars, same shape
    version?:     string,         // semver; default "1.0.0"
    capabilities: string[],       // each 1..128 chars
    endpoints?:   { url: string, ... }[],  // each url HTTPS or localhost
    policies?:    object,
    contact?:     object,
    metadata?:    object
  }
}

Validates and returns a fresh agent-card object from opts. All fields are shape-checked: issuer and agentId against the ID regex, version against semver, every entry in capabilities bounded to 128 chars, every endpoints[].url required to be HTTPS (or a localhost loopback). Throws A2aError with codes MISSING_FIELD / BAD_FIELD / INSECURE_ENDPOINT when input is malformed — fail-at-config-time so a typo doesn't reach the wire.

var b = require("blamejs");
var card = b.a2a.createCard({
  issuer:       "agent.example.com",
  agentId:      "ops-bot-1",
  version:      "1.0.0",
  capabilities: ["chat.respond", "tool.search"],
  endpoints:    [{ url: "https://agent.example.com/a2a/v1" }]
});
card.version;
// → "1.0.0"

b.a2a.signCard(card, privateKeyPem, opts) #

stable0.7.45
{
  {
    ttlMs?:     number,    // expiresAt = signedAt + ttlMs; default 24 h
    audit?:     boolean,   // default true
    errorClass?: ErrorClass // default A2aError
  }
}

Canonicalizes the envelope { card, signedAt, expiresAt } via RFC 8785, hashes the result with SHAKE256 (64-byte output), and signs the digest under privateKeyPem. The signing algorithm is whatever the PEM declares — ML-DSA-87 by default, SLH-DSA-SHAKE- 256f for the hash-based posture. Returns a base64-signature envelope ready to publish over the A2A discovery channel. Emits a a2a.card_signed audit event unless opts.audit === false.

var b = require("blamejs");
var card = b.a2a.createCard({
  issuer:       "agent.example.com",
  agentId:      "ops-bot-1",
  version:      "1.0.0",
  capabilities: ["chat.respond"]
});
var kp = b.crypto.generateSigningKeyPair();
var envelope = b.a2a.signCard(card, kp.privateKeyPem);
envelope.signature.length > 0;
// → true (base64 ML-DSA-87 signature)

b.a2a.verifyCard(envelope, publicKeyPem, opts) #

stable0.7.45
{
  {
    maxBytes?:        number,   // canonical-bytes cap; default 64 KiB
    clockSkewMs?:     number,   // skew on signedAt/expiresAt; default 5 min
    expectedIssuer?:  string,   // refuse when card.issuer mismatches
    audit?:           boolean,  // default true
    errorClass?:      ErrorClass // default A2aError
  }
}

Verifies a signed A2A envelope: shape-checks card, applies the expectedIssuer filter when present, refuses if expiresAt is in the past or signedAt is in the future (allowing clockSkewMs), refuses if the canonical bytes exceed maxBytes, recomputes the SHAKE256 digest, and runs b.crypto.verify against publicKeyPem. Returns { valid, claims, reason } — never throws on a verification failure, so a peer agent can branch on reason and emit its own audit event. Emits an a2a.card_verified / a2a.card_rejected audit event unless opts.audit === false.

var b = require("blamejs");
var result = b.a2a.verifyCard(envelope, peerPublicKeyPem, {
  expectedIssuer: "agent.example.com"
});
result.valid;
// → true (or false with reason "expired" / "signature-mismatch" / ...)

Last updated 2026-08-08T16:39:49.652Z by seeder.