Files
gh-jezweb-claude-skills-ski…/references/session-management.md
2025-11-30 08:23:58 +08:00

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_id from 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