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.
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.
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:
{
"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…"
}answerCoverageis the same coverage verdict the dashboard trace shows:coverageisanswered,partial,unanswered, orunclear, andreasonexplains it.availability: "not_recorded"means no assessment ran for that turn.ownershipsays who owns the conversation.human_ownedwithsuppressed: truemeans a person has taken over and the agent generated nothing on this turn; come back for the reply.routineis present when the turn touched a routine: the one it ran, or one waiting for an operator’s approval.statusisactive,waiting_for_input,waiting_for_approval,completed, orabandoned;pendingInputlists every required slot still open plus the current step’s optional ones, each withkey,type,required, anddescription, so the client can supply all of them in one message.invocationis present only on a routine tool call (next section):toolNameplus anoutcome.traceIdis 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:
{ "routine": { "toolName": "start_return", "input": { "orderId": "A-1001" } } }The routine starts with those slots filled, skips the steps that would have asked for them, and reports where it 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 (another routine was mid-flight or awaiting approval and kept the turn — 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.
{
"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.
Read next
- Publish and open an agent — the agent’s public id, its discovery documents, and credential-free access.
- MCP server — issue the agent credential, connect a client, and run the standalone server.
- Author a routine — expose a routine so a calling agent can start it by name.
- Agents and skills — configure the agent behind the conversation.