Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ htmlcov/
.mypy_cache/
.dmypy.json
dmypy.json
.pyre/
.pyre/
165 changes: 165 additions & 0 deletions examples/session_persist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Session Persistence Examples

This directory contains examples demonstrating the `SessionPersistentClient` that provides automatic session persistence by wrapping `ClaudeSDKClient`.

## Design Philosophy

The `SessionPersistentClient` follows a clean wrapper pattern:

1. **Wraps ClaudeSDKClient**: Does not modify the core client, just wraps it
2. **Uses receive_messages()**: Extracts session data from actual message responses
3. **Server-Generated IDs**: Uses session IDs from Claude's server, not client-generated UUIDs
4. **Automatic Persistence**: Saves conversation data transparently in the background

## Examples

### 1. `simple_persist.py`
**Purpose**: Basic demonstration of automatic session persistence

**Key Features**:
- Shows how SessionPersistentClient wraps ClaudeSDKClient
- Demonstrates automatic session ID extraction from messages
- Shows session data inspection capabilities
- Simple conversation with automatic saving

**Run it**:
```bash
python examples/session_persist/simple_persist.py
```

### 2. `multi_turn_conversation.py`
**Purpose**: Multi-turn conversation with session resumption demonstration

**Key Features**:
- Multi-turn conversation that maintains context
- Session disconnect and resumption workflow
- Resume session using `start_or_resume_session()`
- Shows both local data loading and CLI --resume functionality
- Demonstrates conversation context continuity across disconnect/resume
- Context reference across multiple turns spanning session boundaries

**Demo Flow**:
1. **Phase 1**: Initial conversation (Turns 1-2) with automatic persistence
2. **Disconnect**: Session is saved and connection closed
3. **Phase 2**: Resume session with new client instance
4. **Continue**: Turns 3-4 with preserved context from earlier turns

**Run it**:
```bash
python examples/session_persist/multi_turn_conversation.py
```

## How SessionPersistentClient Works

### Architecture

```python
SessionPersistentClient
├── ClaudeSDKClient (wrapped) # Handles all Claude interactions
├── SessionPersistence # Manages file storage
└── Message Processing # Extracts session data from messages
```

### Key Methods

```python
# Initialize with automatic persistence
client = SessionPersistentClient(
options=ClaudeCodeOptions(),
storage_path="./my_sessions"
)

# All ClaudeSDKClient methods are available:
await client.connect()
await client.query("Hello")
async for message in client.receive_response():
# Session data is automatically extracted and saved
print(message)

# Session management:
await client.start_or_resume_session(id) # Resume existing session (local + server)
session_id = client.get_current_session_id() # Server-generated ID
sessions = await client.list_sessions() # List all saved sessions
session_data = await client.load_session(id) # Load session for inspection
await client.delete_session(session_id) # Delete a session
```

### Automatic Session Extraction

The client automatically extracts session data from messages:

1. **Session ID Detection**: Looks for `session_id` in message metadata
2. **Message Conversion**: Converts Claude messages to `ConversationMessage` format
3. **Automatic Saving**: Saves session data after each message
4. **Context Preservation**: Maintains conversation history and metadata

### File Structure

Sessions are saved as JSON files:
```
~/.claude_sdk/sessions/
├── 2aecab00-6512-4e29-9da3-9321cac6eb2.json
├── 7b8f3c45-2d19-4e7a-b6c1-f9d2e8a3c7b5.json
└── ...
```

Each file contains:
```json
{
"session_id": "server-generated-uuid",
"start_time": "2025-07-30T15:25:31.069594",
"last_activity": "2025-07-30T15:27:45.123456",
"conversation_history": [...],
"working_directory": "/path/to/working/dir",
"options": {...}
}
```

## Benefits of This Design

### ✅ Clean Separation
- Core `ClaudeSDKClient` remains unchanged
- Persistence is an optional wrapper layer
- No mixing of concerns

### ✅ Server-Driven
- Uses actual session IDs from Claude's server
- No client-side UUID generation
- Matches Claude's internal session management

### ✅ Automatic Operation
- No manual session management required
- Transparent persistence in background
- Works with all `ClaudeSDKClient` features

### ✅ Easy Migration
- Existing `ClaudeSDKClient` code works unchanged
- Just replace `ClaudeSDKClient` with `SessionPersistentClient`
- All methods and features preserved

## Usage Pattern

### Old (no persistence):
```python
async with ClaudeSDKClient() as client:
await client.query("Hello")
async for message in client.receive_response():
print(message)
```

### New (with automatic persistence):
```python
async with SessionPersistentClient() as client:
await client.query("Hello")
async for message in client.receive_response():
print(message) # Same code, automatic persistence!
```

## Dependencies

These examples use `trio` for async operations. Install with:
```bash
pip install trio
```

Or use standard `asyncio` by replacing `trio.run()` with `asyncio.run()`.
144 changes: 144 additions & 0 deletions examples/session_persist/multi_turn_conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Multi-turn conversation with session resumption example using SessionPersistentClient.

This demonstrates how the SessionPersistentClient automatically captures
session context across multiple turns of conversation, and how to resume
sessions after disconnecting. The demo shows:

1. Initial conversation (Turns 1-2) with automatic session persistence
2. Disconnect from the session
3. Resume the session using start_or_resume_session()
4. Continue conversation (Turns 3-4) with preserved context

Key features demonstrated:
- Local session data loading from storage
- CLI --resume option for server-side session continuity
- Context retention across disconnect/resume cycles
- Seamless multi-turn conversation flow
"""

import trio
from pathlib import Path

from claude_code_sdk import (
AssistantMessage,
ClaudeCodeOptions,
SessionPersistentClient,
TextBlock,
)


async def multi_turn_demo():
"""Demonstrate multi-turn conversation with session resumption."""
print("=== Multi-Turn Conversation with Session Resumption ===")

storage_path = Path("./conversation_sessions")

# Phase 1: Initial conversation (Turns 1-2)
print("\n🚀 Phase 1: Initial Conversation")
session_id = None

async with SessionPersistentClient(
options=ClaudeCodeOptions(),
storage_path=storage_path
) as client:

# Turn 1: Introduction
print("\n💬 Turn 1: Introduction")
intro_msg = "Hello! My name is Alex and I'm a software developer."
await client.query(intro_msg)
print(f"User: {intro_msg}")

async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")

# Turn 2: Ask about a topic
print("\n💬 Turn 2: Technical question")
await client.query("What's the difference between async and sync programming?")

async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")

# Capture session ID for resumption
session_id = client.get_current_session_id()
session_data = await client.load_session(session_id)

print(f"\n📋 Session after 2 turns:")
print(f" ID: {session_id}")
print(f" Total Messages: {len(session_data.conversation_history) if session_data else 'Unknown'}")
print(f" 🔌 Disconnecting to demonstrate session resumption...")

# Phase 2: Resume session (Turns 3-4)
print(f"\n🔄 Phase 2: Resuming Session {session_id}")

# Create new client instance and resume the session
client = SessionPersistentClient(
options=ClaudeCodeOptions(),
storage_path=storage_path
)

try:
# Resume the session - this loads local data AND configures CLI --resume
await client.start_or_resume_session(session_id)
print(f"✅ Session resumed. Local data: {len(client._session_data.conversation_history) if client._session_data else 0} messages")

# Connect and continue the conversation
await client.connect()

# Turn 3: Follow-up question (tests context retention across disconnect/resume)
print("\n💬 Turn 3: Follow-up question")
await client.query("Can you give me a Python example of what you just explained?")

async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")

# Turn 4: Reference earlier context
print("\n💬 Turn 4: Reference earlier context")
await client.query("Thanks! What was my name again that I mentioned at the beginning?")

async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")

# Show final session info
final_session_id = client.get_current_session_id()
session_data = await client.load_session(final_session_id)

print(f"\n📋 Final Session Summary:")
print(f" Original ID: {session_id}")
print(f" Current ID: {final_session_id}")
print(f" Total Messages: {len(session_data.conversation_history) if session_data else 'Unknown'}")
print(f" Duration: {session_data.last_activity - session_data.start_time if session_data else 'Unknown'}")
print(f" Auto-saved to: {storage_path.absolute()}")

finally:
await client.disconnect()


async def main():
"""Run the multi-turn conversation with session resumption demo."""
await multi_turn_demo()

print("\n" + "="*60)
print("✅ Session resumption demo complete!")
print("📝 Key features demonstrated:")
print(" • Local session data is loaded from storage")
print(" • CLI --resume option is set for server-side continuity")
print(" • Conversation context is maintained across disconnect/resume")
print(" • Multi-turn conversations work seamlessly across sessions")


if __name__ == "__main__":
trio.run(main)
2 changes: 2 additions & 0 deletions src/claude_code_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
)
from .client import ClaudeSDKClient
from .query import query
from .session_persistent_client import SessionPersistentClient
from .types import (
AssistantMessage,
ClaudeCodeOptions,
Expand All @@ -30,6 +31,7 @@
# Main exports
"query",
"ClaudeSDKClient",
"SessionPersistentClient",
# Types
"PermissionMode",
"McpServerConfig",
Expand Down
Loading