Session Management in the Claude Agent SDK
Resume sessions, fork conversations from specific messages, and list session history. The session ID patterns that power multi-turn agents and persistent conversation tools.
How sessions work by default
Every time you call query, the SDK creates a new session. The agent starts with a clean context — no memory of previous runs. For one-off tasks, that is fine. Generate a report, summarize a document, extract data from a file — these do not need memory.
For agents you interact with over time — a research assistant you return to across multiple calls, a client brief agent that builds context session by session, a Slack bot that remembers what it already told a teammate — you need session management.
Session resumption
Resuming a session requires the session ID from the previous run. The SDK returns it in the result message:
let sessionId: string | undefined;
for await (const message of query(messages(), {
model: "claude-sonnet-4-5",
permissionMode: "bypassPermissions",
dangerouslyAllowBypassPermissions: true,
})) {
if (message.type === "result") {
sessionId = message.session_id;
console.log("Response:", message.result);
}
}
// Next turn — resume the same session
for await (const message of query(nextMessages(), {
model: "claude-sonnet-4-5",
resume: sessionId,
permissionMode: "bypassPermissions",
dangerouslyAllowBypassPermissions: true,
})) {
if (message.type === "result") {
console.log("Response:", message.result);
}
}
Save the session ID the moment you receive it. If you run another query before capturing it, the session ID is gone and resumption is impossible. Build the save step into your result handler before anything else.
The practical pattern: persist session IDs to a SQLite database or a JSON file, keyed by whatever identifier makes sense for your workflow — a client name, a project slug, a Slack thread timestamp.
Session forking
Forking creates a new session branched from an existing one at a specific message. The original session stays intact. You get a parallel thread starting from that point.
The operator use case: you have gathered research and produced a first synthesis. You want to explore two different angles — one for an investor audience, one for an operational team — without mixing their contexts.
for await (const message of query(forkMessages(), {
model: "claude-sonnet-4-5",
resume: originalSessionId,
forkSession: true,
resumeAt: specificMessageId,
permissionMode: "bypassPermissions",
dangerouslyAllowBypassPermissions: true,
})) {
if (message.type === "result") {
const forkSessionId = message.session_id;
// Independent session from this point forward
}
}
resumeAt takes the message ID from an assistant turn in the original session — a UUID logged when you captured that message. Each fork produces its own session ID and evolves independently from the branch point.
Listing sessions and messages
The SDK exports two utility functions for working with session history:
import { query, listSessions, getSessionMessages } from "@anthropic-ai/claude-agent-sdk";
// List all sessions in a directory
const sessions = await listSessions(process.cwd());
// Get all messages for a specific session
const messages = await getSessionMessages(sessionId);
listSessions is useful for building session browsers or debugging which session holds the context you need. getSessionMessages lets you load prior conversation history into a new agent — useful for building brief-generation tools that summarize what happened across a client engagement.
Session IDs in multi-user deployments
For any agent serving more than one person — a team Slack bot, a client-facing research tool — the session ID is what separates one conversation thread from another. Each thread gets its own session. Resuming a thread means passing that thread's session ID.
The practical Slack pattern:
// Key: channel_id + thread_timestamp → session_id
const existingSessionId = sessionStore.get(threadKey);
const { response, sessionId } = await agentChat(message, existingSessionId);
sessionStore.set(threadKey, sessionId);
A new Slack thread gets a new session. Every message in the same thread resumes the same session. The agent remembers what it said two messages ago, what data it pulled, what it already summarized. The conversation builds naturally instead of resetting every time.
The mistake to avoid: Using one user's session ID for another user's request. If your Slack bot serves five team members, each thread needs its own session ID scoped to that thread. A DM and a channel thread are different conversations — mixing their sessions corrupts both.
---
Author: FractionalSkill