Skip to documentation
Documentation

A2A

Let other vendors' agents call a Keiki agent over the Agent2Agent protocol.

A2A is the open protocol agents use to talk to each other. Turn it on for an agent and any A2A client (the official SDKs, or another vendor's agent) can find it by its Agent Card and send it messages. Keiki speaks A2A v1.0, and v0.3 for older clients.

Each message is one ordinary turn of the agent: its own model, instructions, tools and memory. The conversation is stored, traced and billed like one that arrived over SMS.

Turn it on

Go to Agents, open your agent, then Channels, and select Turn on on the A2A row. While it is off, the agent has no card and every call is refused.

From the SDK, add the channel to sync:

TypeScript
import {  } from '@onkeiki/agents/cloud'

const  = ({ : ..! })
const  = await .({
  : 'support',
  : { : 'Support', : '<model-id>', : 'You answer order questions.' },
  : [],
  : [{ : 'a2a' }],
})
const  = `https://onkeiki.com/a2a/${..}/.well-known/agent-card.json`

The developer API's POST /deployments response lists the agent's url and cardUrl under that channel's detail in its top-level channels.

Addresses

Agent Cardhttps://<host>/a2a/<agent id>/.well-known/agent-card.json
JSON-RPC endpointhttps://<host>/a2a/<agent id>
Keiki's cardhttps://<host>/.well-known/agent-card.json

The address uses the agent's id, so renaming the agent never breaks a saved URL. Keiki's own card is a directory: send it a message with your API key and it answers with the card URL of every agent that key can reach.

Authentication

Callers send one of your organization's developer API keys as a bearer token (Authorization: Bearer sk_live_…). A key scoped to one agent can only call that agent. Revoking the key revokes its A2A access.

Call an agent

TypeScript
import {  } from '@a2a-js/sdk'
import { , , ,  } from '@a2a-js/sdk/client'

// Sends the Keiki API key on every request, including the Agent Card fetch.
const  = (, {
  : async () => ({ : `Bearer ${..}` }),
  : async () => ,
})

const  = await new ({
  : [new ({ :  })],
  : new ({ :  }),
  // The card is under the agent's path, so pass its full URL and an empty path.
}).('https://onkeiki.com/a2a/<agent id>/.well-known/agent-card.json', '')

const  = await .({
  : '',
  : {
    : .(),
    : 'order-1234',
    : '',
    : .,
    : [{ : { : 'text', : 'Where is my order?' }, : 'text/plain', : '', :  }],
    : ,
    : [],
    : [],
  },
  : ,
  : ,
})

Tasks and conversations

  • Every message starts a task. The task completes with the agent's reply as both the final status message and a reply artifact. SendStreamingMessage streams the reply as artifact chunks while the agent writes it.
  • Messages that share a contextId are one conversation, so the agent remembers earlier turns. Contexts are private to the API key that started them: two keys sending the same contextId get two conversations.
  • GetTask, ListTasks and CancelTask only see tasks created with the same API key. Cancelling a running task stops the agent's turn.
  • One message per context runs at a time. A message sent while the agent is still answering in the same context is rejected; send it once the earlier task finishes.
  • Text and data parts are read. Files sent by URL are passed to the agent as links; inline file bytes are not supported yet. A message is limited to 100,000 characters, and a request body to 1 MB.

Push notifications, SubscribeToTask and the gRPC and REST bindings are not supported yet.