Skip to content

Connect a calling agent

Another AI agent can hold a conversation with one of yours and act on what comes back. It sends a message through ask_agent, or calls a routine you have exposed as a named tool, and gets a structured reply envelope beside the prose: what the answer covered, who owns the conversation, and where a routine landed. That envelope is what lets a calling model decide its next move instead of parsing sentences. A client that speaks A2A reaches the same agent over JSON-RPC; Connect an A2A client covers it.

Start with MCP server to issue the credential and point a client at the server, or with Publish and open an agent if you want callers to reach the agent with no credential at all. This page covers what the client does once it is connected.

Share an open connection

When Allow connecting without a credential is on, Channels → MCP includes Share with agent developers. Copy the agent endpoint or its discovery card into your own developer guide, or copy one of the Claude, ChatGPT, Cursor, or OpenAI Responses API examples. The examples contain the public id in the URL and no secret; they work only while open access remains enabled.

The discovery card is useful when a caller first needs to learn what the agent does and which routine tools it exposes. The endpoint is useful when the caller already knows which agent it needs. If you turn open access off, issue an MCP credential from the same card and give the developer the credential-specific configuration instead.

Agent converse setup

The dashboard credential is bound to one agent and the MCP audience. Send its original secret to standalone /mcp; the server exchanges it for a short-lived session token. If a process restarts or its cache expires, it exchanges the original credential again and the backend resumes the conversation mapped to that credential version in PostgreSQL. A session token returned by the internal converse endpoint is for direct calls to that endpoint, not for /mcp. Revoke the credential from the dashboard when the client should lose access; the server re-checks credential state for every request.

The converse surface provides ask_agent, which sends a message through the agent’s full turn loop and continues its conversation. Retrieval can participate in that turn when the agent’s configuration calls for it. Direct document search and grounded-answer generation remain REST API workflows.

What ask_agent returns

The tool’s text content is the agent’s answer followed by the reply envelope as JSON; its structuredContent is that agent reply envelope alone, so a calling model can act on the outcome instead of guessing from prose:

json
{
  "conversationId": "5f3c…",
  "answer": { "text": "You can return an order within 30 days of delivery.", "citations": [] },
  "answerCoverage": { "availability": "assessed", "coverage": "answered", "reason": "sufficient_evidence", "originatingTurnId": "c3…", "originatingRequestId": "c3…" },
  "ownership": { "state": "ai_owned", "suppressed": false },
  "routine": { "name": "Book a demo", "status": "waiting_for_input", "pendingInput": [{ "key": "email", "type": "email", "required": true }] },
  "traceId": "d4…"
}
  • answerCoverage is the same coverage verdict the dashboard trace shows: coverage is answered, partial, unanswered, or unclear, and reason explains it. availability: "not_recorded" means no assessment ran for that turn.
  • ownership says who owns the conversation. human_owned with suppressed: true means a person has taken over and the agent generated nothing on this turn; come back for the reply.
  • routine is present when the turn touched a routine: the one it ran, or one waiting for an operator’s approval. status is active, waiting_for_input, waiting_for_approval, completed, or abandoned; pendingInput lists every required slot still open plus the current step’s optional ones, each with key, type, required, and description, so the client can supply all of them in one message.
  • invocation is present only on a routine tool call (next section): toolName plus an outcome.
  • traceId is the turn’s trace id, the one an operator sees in Activity.

The REST agent channel, POST /api/v1/agents/{agentId}/chat, returns the same answerCoverage, ownership, routine, invocation, and traceId fields beside its answer string and citations array. Both routes publish the envelope as AgentReplyEnvelopeCore in the OpenAPI document.

Routines as tools

A routine an operator has exposed as a tool is listed for the session by GET /api/v1/mcp/converse/tools, one AgentToolDescriptor per routine with a JSON Schema inputSchema built from the routine’s declared slots. A direct converse client invokes it by sending routine instead of message on the ask route:

json
{ "routine": { "toolName": "start_return", "input": { "orderId": "A-1001" } } }

The routine starts with those slots filled and walks past the steps they satisfy. On that turn the input is its only source of values: it stops on the first step whose required slots the call left out (or, on a step that collects only optional slots, any of its slots), runs nothing after that step, skill steps included, and lists what that step still needs in pendingInput. A step moves on once its required slots have values, so an optional slot beside them stays empty unless the call gives it. The caller supplies a missing value in a follow-up message or a second call. The reply reports where the routine landed in routine (toolName names the tool) and what the call did in invocation.outcome: started; reentered (a completed routine started again, as its reentry setting allows); declined (completed under Once per conversation — the turn answers normally and routine still reports status: "completed"); not_started (a routine, the one called or another, was mid-flight or awaiting approval and kept the turn; an active routine reads the call as it reads any message, which is how a second call fills what the first left out — routine describes it); or unknown_tool. Input is validated before anything is recorded: an unknown tool returns 404 with error.details.code routine_tool_unknown; mismatched input returns 400 with error.details.code routine_invocation_invalid and one { path, code } entry per field in error.details.errors (required, type, format, unknown_field, too_long over 2000 characters; a blank string counts as missing). Both routes check the tool name against the release the conversation is pinned to, while GET /api/v1/mcp/converse/tools lists the current published catalog, so a routine exposed after the conversation began can be listed yet answer routine_tool_unknown.

An MCP client gets the same catalog as tools. The standalone server reads it once, when it exchanges the credential for a session, and pins the result, so the session’s tools/list is stable for its lifetime: ask_agent, the two documentation tools, and one tool per exposed routine — start_return(orderId, reason?) above — each carrying the operator’s description and the descriptor’s JSON Schema. Calling a routine tool checks the arguments against that schema first (a miss is a tool error before the backend is involved, audited with the tool name only), then runs the invocation above. The result’s structuredContent is the full envelope and its text content is answer.text followed by that envelope as JSON, exactly as for ask_agent.

A routine exposed, renamed, or withdrawn after the session opened appears when the client’s next session is established; the server sends no notifications/tools/list_changed.

The converse session does not grant access to other agents, workspace administration, account management, API-access settings, Ray, the skill catalogue, connector secrets, or provider keys.

Come back after a handoff

When a turn hands off to a person, ownership.state becomes human_owned and the agent stops answering. A calling agent cannot sit in the chat, so it comes back: get_conversation_updates({ cursor, waitMs }) returns what has happened since your cursor, and waits for it.

json
{
  "messages": [{ "id": "9a…", "author": "human", "createdAt": "2026-09-22T10:14:02.117Z", "text": "I have refunded the order." }],
  "cursor": "eyJ2ZXJza…",
  "ownership": { "state": "human_owned" }
}

author is human for a person’s reply, agent otherwise. The cursor is opaque: pass back the one the last reply gave you; with none, the call returns the most recent page. The page reports ownership as a state alone — suppressed is a fact about a turn, and a read runs none. waitMs holds the call open for up to 25 seconds and returns an empty messages list at the deadline — nothing new yet, not a failure. Poll again with the same cursor.

Over standalone MCP the session must survive between calls. The server returns an Mcp-Session-Id header on first contact; echo it on every later request to stay in the same conversation. A client that drops it gets a fresh conversation each call.

Connect an A2A client

A client that speaks A2A v1.0 reaches the same agent over JSON-RPC, under the same walk-in and grant access, and follows routines and handoffs as tasks. Connect an A2A client walks through finding the endpoint, sending a message, keeping the contextId, and polling a task.