8.9 KiB
Session Management Guide
Complete guide to sessions, resuming, and forking in Claude Agent SDK.
What Are Sessions?
Sessions enable:
- Persistent conversations - Resume where you left off
- Context preservation - Agent remembers everything
- Alternative paths - Fork to explore different approaches
Session Lifecycle
Start → Capture Session ID → Resume → Resume → ... → End
↓
Fork (alternative path)
Starting a Session
Every query() call creates a session.
let sessionId: string | undefined;
const response = query({
prompt: "Build a REST API",
options: { model: "sonnet" }
});
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
sessionId = message.session_id;
console.log(`Session started: ${sessionId}`);
}
}
CRITICAL: Capture session_id from system init message.
Resuming a Session
Continue a previous conversation.
const resumed = query({
prompt: "Now add authentication",
options: {
resume: sessionId, // Resume previous session
model: "sonnet"
}
});
What's preserved:
- All previous messages
- Agent's understanding of context
- Files created/modified
- Decisions made
What's NOT preserved:
- Environment variables
- Tool availability (specify again)
- Permission settings (specify again)
Forking a Session
Create alternative path without modifying original.
const forked = query({
prompt: "Actually, make it GraphQL instead",
options: {
resume: sessionId,
forkSession: true, // Creates new branch
model: "sonnet"
}
});
Result:
- New session created
- Starts from same point as original
- Original session unchanged
- Can compare approaches
Use Case Patterns
Pattern 1: Sequential Development
Step-by-step feature building.
// Step 1: Initial implementation
let session = await startSession("Create user authentication");
// Step 2: Add feature
session = await resumeSession(session, "Add OAuth support");
// Step 3: Add tests
session = await resumeSession(session, "Write integration tests");
// Step 4: Deploy
session = await resumeSession(session, "Deploy to production");
Pattern 2: Exploration & Decision
Try multiple approaches, choose best.
// Start main conversation
let mainSession = await startSession("Design payment system");
// Explore option A
let optionA = await forkSession(mainSession, "Use Stripe");
// Explore option B
let optionB = await forkSession(mainSession, "Use PayPal");
// Explore option C
let optionC = await forkSession(mainSession, "Use Square");
// Choose winner
let chosenSession = optionA; // Decision made
await resumeSession(chosenSession, "Implement chosen approach");
Pattern 3: Multi-User Collaboration
Multiple developers, independent work.
// Developer A starts work
let sessionA = await startSession("Implement user profile page");
// Developer B forks for different feature
let sessionB = await forkSession(sessionA, "Add avatar upload");
// Developer C forks for another feature
let sessionC = await forkSession(sessionA, "Implement search");
// All can work independently
Pattern 4: Error Recovery
Backup and restore points.
// Save checkpoint before risky operation
let checkpoint = sessionId;
try {
await resumeSession(checkpoint, "Refactor entire auth system");
} catch (error) {
console.log("Refactor failed, restoring from checkpoint");
await forkSession(checkpoint, "Try safer incremental refactor");
}
Pattern 5: A/B Testing
Test different implementations.
let baseline = await startSession("Implement feature X");
// Approach A
let approachA = await forkSession(baseline, "Use algorithm A");
const metricsA = await measurePerformance(approachA);
// Approach B
let approachB = await forkSession(baseline, "Use algorithm B");
const metricsB = await measurePerformance(approachB);
// Compare and choose
const winner = metricsA.better(metricsB) ? approachA : approachB;
Session Best Practices
✅ Do
- Always capture
session_idfrom init message - Store session IDs for later use
- Use descriptive prompts when resuming
- Fork for alternative approaches
- Test resuming before deploying
- Consider session lifetime limits
❌ Don't
- Forget to capture session ID
- Assume sessions last forever
- Resume with completely unrelated prompts
- Fork excessively (creates many branches)
- Rely on sessions for critical state
- Skip testing resume functionality
Helper Functions
async function startSession(prompt: string): Promise<string> {
let sessionId: string | undefined;
const response = query({ prompt, options: { model: "sonnet" } });
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
sessionId = message.session_id;
}
}
if (!sessionId) throw new Error('Failed to start session');
return sessionId;
}
async function resumeSession(
sessionId: string,
prompt: string
): Promise<void> {
const response = query({
prompt,
options: { resume: sessionId, model: "sonnet" }
});
for await (const message of response) {
if (message.type === 'assistant') {
console.log(message.content);
}
}
}
async function forkSession(
sessionId: string,
prompt: string
): Promise<string> {
let newSessionId: string | undefined;
const response = query({
prompt,
options: {
resume: sessionId,
forkSession: true,
model: "sonnet"
}
});
for await (const message of response) {
if (message.type === 'system' && message.subtype === 'init') {
newSessionId = message.session_id;
} else if (message.type === 'assistant') {
console.log(message.content);
}
}
if (!newSessionId) throw new Error('Failed to fork session');
return newSessionId;
}
Session Storage
Store sessions for later use:
// In-memory storage
const sessions = new Map<string, { id: string; created: Date }>();
async function saveSession(prompt: string) {
const id = await startSession(prompt);
sessions.set(id, { id, created: new Date() });
return id;
}
function getSession(id: string) {
return sessions.get(id);
}
// Database storage
async function saveSessionToDb(sessionId: string, metadata: any) {
await db.insert('sessions', {
id: sessionId,
created_at: new Date(),
metadata
});
}
Session Limits
Context Window
Sessions have context window limits (200k tokens for Sonnet).
Strategies:
- SDK auto-compacts context
- Fork to start fresh from a point
- Summarize and start new session
Lifetime
Sessions may expire after inactivity.
Strategies:
- Don't rely on sessions lasting indefinitely
- Store important state separately
- Test resume functionality
Troubleshooting
Session Not Found
Problem: "Invalid session ID"
Causes:
- Session expired
- Invalid session ID
- Session from different CLI instance
Solution: Start new session
Context Preserved Incorrectly
Problem: Agent doesn't remember previous work
Causes:
- Different settings/tools specified
- Context window exceeded
- Fork instead of resume
Solution: Verify using resume not forkSession
Too Many Forks
Problem: Hard to track branches
Solution: Limit forking, clean up unused branches
Complete Example
class SessionManager {
private sessions = new Map<string, string>();
async start(name: string, prompt: string): Promise<string> {
const id = await startSession(prompt);
this.sessions.set(name, id);
console.log(`✨ Started: ${name}`);
return id;
}
async resume(name: string, prompt: string): Promise<void> {
const id = this.sessions.get(name);
if (!id) throw new Error(`Session ${name} not found`);
console.log(`↪️ Resuming: ${name}`);
await resumeSession(id, prompt);
}
async fork(
fromName: string,
newName: string,
prompt: string
): Promise<string> {
const fromId = this.sessions.get(fromName);
if (!fromId) throw new Error(`Session ${fromName} not found`);
console.log(`🔀 Forking: ${fromName} → ${newName}`);
const newId = await forkSession(fromId, prompt);
this.sessions.set(newName, newId);
return newId;
}
list(): string[] {
return Array.from(this.sessions.keys());
}
}
// Usage
const manager = new SessionManager();
await manager.start("main", "Build a web app");
await manager.resume("main", "Add authentication");
await manager.fork("main", "option-a", "Use JWT tokens");
await manager.fork("main", "option-b", "Use sessions");
console.log("Sessions:", manager.list());
// ["main", "option-a", "option-b"]
For more details: See SKILL.md Template: templates/session-management.ts