Session semantics
Omit the session id to start a conversation, pass it only to continue one, and read the id back from every response.
Three rules, and they are the whole of it:
Omit sessionId to start a new conversation
The deployment mints a fresh id.
Pass an existing id only to continue that conversation
It must be an id a previous response actually returned.
Every response returns the id it used
So a continuation never has to be guessed.
Why this needs saying
A session is every event sharing a sid — there is no session object to create
or close. So reusing an id appends to that conversation rather than starting
a new one. The full consequences are set out under
sessions in the read API.
This has already cost real debugging time on this platform. A run of calls all carried one id, landed under a single session, and looked like nothing was being stored. Nothing was wrong except the reused id.
An MCP client driven by a language model will reach for a plausible-looking id unless it is told not to, which is why the tool descriptions say it in as many words and why the id is optional rather than required.
The shape of an id
sessionId must be at most 128 characters of letters, digits, -, _, . or
:. The server checks the shape; the deployment decides whether it issued
that id.
If you pass an id the deployment does not recognise, the turn fails with a 400
and the error says to omit sessionId to start a new conversation. That is the
one failure a model can correct on its own.
Threads map to sessions
An email thread, a chat window, a support ticket — each maps to one sessionId.
Every turn in it passes the same one, and the agent's memory carries the history
without you sending any of it.
Your client owns the mapping. Store the id you got back against your thread and pass it on the next turn. The server just honours what it is given; it has no notion of your threads.
// Pseudocode for the pattern.
const sessionId = threads.get(threadId); // undefined on the first message
const result = await mcp.call("email_send_message", {
core: "support-agent",
from: customerEmail,
subject,
body: customerMessage,
...(sessionId ? { sessionId } : {}), // omitted entirely when new
});
threads.set(threadId, result.sessionId); // always returnedNote the spread rather than sessionId: sessionId ?? null. Omit the argument;
do not send an empty one.
Reading a conversation back
The id is the same id the read API uses. Given a sessionId from a
turn, get_session — or GET /telemetry/sessions/{sid} — returns every event of
that conversation, including the turns you ran yourself.
MCP turns are recorded with run_type: "test", so
list_sessions with runType: "production" will not show them, and your
production metrics are unaffected by testing.