Subcore AI Docs
MCP server

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 returned

Note 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.

On this page