Session management in ChannelCoder enables multi-turn conversations with Claude while preserving context across interactions. This is essential for complex tasks that require iterative development, debugging, or long-running projects.
Sessions provide:
- Context Preservation: Each message builds on previous conversation
- Persistence: Save and resume conversations days or weeks later
- Automatic Tracking: Session IDs chain automatically between responses
- Full Integration: Works with all ChannelCoder features
import { session } from 'channelcoder';
// Create a new session
const s = session();
// Have a conversation
await s.claude('What is TypeScript?');
await s.claude('Show me an example'); // Remembers previous context!
// Save for later
await s.save('learning-typescript');
// Load and continue another day
const saved = await session.load('learning-typescript');
await saved.claude('What about generics?');# Start a new session
channelcoder run "Start project" --session my-project
# Continue a session
channelcoder run "Add tests" --load-session my-project
# List all sessions
channelcoder session list
# Remove old sessions
channelcoder session remove old-project- First Message: Creates a new conversation with Claude
- Response: Claude returns a session ID
- Subsequent Messages: Use the session ID to continue
- Context: Each message includes the full conversation history
// 1. Create session
const s = session();
// 2. First interaction - creates conversation
const result1 = await s.claude('Explain async/await');
// Session ID: abc123 (managed internally)
// 3. Continue conversation - uses session ID
const result2 = await s.claude('Show error handling');
// Automatically uses session ID from result1
// 4. Save session state
await s.save('async-tutorial');
// 5. Load later
const restored = await session.load('async-tutorial');
await restored.claude('What about Promise.all?');import { session, FileSessionStorage } from 'channelcoder';
// Basic session (uses FileSessionStorage with auto-save enabled by default)
const s = session();
// With custom name
const s = session({
name: 'my-project-session'
});
// With auto-save disabled
const s = session({
autoSave: false // Default: true
});
// With custom storage location
const storage = new FileSessionStorage('./my-sessions');
const s = session({ storage });
// With custom storage adapter
const s = session({
storage: new DatabaseStorage(myDb)
});
// All options combined
const s = session({
name: 'feature-implementation',
autoSave: true,
storage: new FileSessionStorage('./project-sessions')
});const s = session();
// Core methods
await s.claude(prompt, options); // Run Claude with session
await s.stream(prompt, options); // Stream with session
await s.interactive(prompt, options); // Interactive mode
// Management methods
s.id(); // Get current session ID
s.messages(); // Get conversation history
await s.save(name); // Save session with name
s.setId(newId); // Manually set session ID
// Static methods
await session.load(name); // Load saved session
await session.list(); // List all sessions
await session.remove(name); // Delete session
await session.exists(name); // Check if existsconst s = session({ autoSave: true });
// Automatically saved after each interaction
await s.claude('Implement feature');
await s.claude('Add tests');
// No need to manually save!
// Session saved at: ~/.channelcoder/sessions/{sessionId}.json// List sessions with metadata
const sessions = await session.list();
/*
[{
name: 'feature-auth',
path: '/Users/you/.channelcoder/sessions/feature-auth.json',
messageCount: 5,
lastActive: '2024-01-15T10:30:00Z',
created: '2024-01-14T09:00:00Z'
}]
*/// Access raw session data
const s = session();
await s.claude('Hello');
console.log(s.messages());
/*
[{
role: 'user',
content: 'Hello'
}, {
role: 'assistant',
content: 'Hello! How can I help you today?'
}]
*/// Day 1: Start implementation
const s = session();
await s.claude('Create a REST API for user management');
await s.claude('Add authentication middleware');
await s.save('user-api-project');
// Day 2: Continue
const s = await session.load('user-api-project');
await s.claude('Add input validation');
await s.claude('Implement rate limiting');
await s.save('user-api-project'); // Overwrite with progress
// Day 3: Finish up
const s = await session.load('user-api-project');
await s.claude('Add comprehensive tests');
await s.claude('Write API documentation');// Start debugging session
const debug = session();
await debug.claude('Getting TypeError in production');
await debug.claude('Here is the stack trace: ...');
await debug.save('bug-12345');
// Continue investigation
const debug = await session.load('bug-12345');
await debug.claude('I found this in the logs: ...');
await debug.claude('Could this be related to recent changes?');const review = session();
// Review multiple files with context
await review.claude('Review src/auth.ts for security issues');
await review.claude('Now check src/database.ts');
await review.claude('Are there any inconsistencies between them?');
// Save for team discussion
await review.save('security-review-2024-01');// Feature-specific sessions
const s = session();
await s.claude('Start payment feature', {
worktree: 'feature/payments'
});
await s.save('payment-implementation');
// Continue in same worktree
const saved = await session.load('payment-implementation');
await saved.claude('Add Stripe integration');const s = session({ autoSave: true });
// Consistent Docker environment across session
await s.claude('Analyze security vulnerabilities', {
docker: { image: 'security-scanner' }
});
await s.claude('Fix the SQL injection issue');
// Docker config preserved in sessionconst s = session();
// Stream with session context
for await (const chunk of s.stream('Write comprehensive docs')) {
process.stdout.write(chunk.content);
}export interface SessionOptions {
name?: string;
storage?: SessionStorage;
autoSave?: boolean; // Default: true
}
export interface SessionState {
sessionChain: string[]; // All session IDs in order
currentSessionId?: string; // Latest session ID
messages: Message[]; // Conversation history
metadata: {
name?: string;
created: Date;
lastActive: Date;
};
}
export interface Message {
role: 'user' | 'assistant';
content: string;
timestamp: Date;
sessionId: string;
}
export interface SessionInfo {
name: string;
path: string;
created: Date;
lastActive: Date;
messageCount: number;
}
export interface Session {
// Wrapped functions with session context
claude: ClaudeFunction;
stream: typeof streamBase;
interactive: typeof interactiveBase;
run: typeof runBase;
detached: typeof detachedBase;
// Session management methods
id(): string | undefined;
messages(): Message[];
save(name?: string): Promise<string>;
clear(): void;
}Sessions are stored in:
- macOS/Linux:
~/.channelcoder/sessions/ - Windows:
%USERPROFILE%\.channelcoder\sessions\
{
"id": "session_abc123",
"messages": [
{
"role": "user",
"content": "Create a React component"
},
{
"role": "assistant",
"content": "I'll help you create a React component..."
}
],
"metadata": {
"created": "2024-01-15T10:00:00Z",
"lastActive": "2024-01-15T11:30:00Z",
"messageCount": 2
}
}The session system supports pluggable storage through the SessionStorage interface:
export interface SessionStorage {
save(state: SessionState, name?: string): Promise<string>;
load(nameOrPath: string): Promise<SessionState>;
list(): Promise<SessionInfo[]>;
}import { FileSessionStorage } from 'channelcoder';
// Use custom directory
const storage = new FileSessionStorage('./project-sessions');
const s = session({ storage });
// Default storage location: ~/.channelcoder/sessions/
const s = session(); // Uses FileSessionStorage automaticallyImplement your own storage backend:
import type { SessionStorage, SessionState, SessionInfo } from 'channelcoder';
class DatabaseStorage implements SessionStorage {
constructor(private db: Database) {}
async save(state: SessionState, name?: string): Promise<string> {
const id = name || `session-${Date.now()}`;
await this.db.sessions.upsert({
id,
data: JSON.stringify(state),
updated: new Date()
});
return id;
}
async load(nameOrPath: string): Promise<SessionState> {
const record = await this.db.sessions.findUnique({
where: { id: nameOrPath }
});
if (!record) {
throw new Error(`Session ${nameOrPath} not found`);
}
const state = JSON.parse(record.data) as SessionState;
// Convert date strings back to Date objects
state.metadata.created = new Date(state.metadata.created);
state.metadata.lastActive = new Date(state.metadata.lastActive);
state.messages.forEach(msg => {
msg.timestamp = new Date(msg.timestamp);
});
return state;
}
async list(): Promise<SessionInfo[]> {
const records = await this.db.sessions.findMany({
orderBy: { updated: 'desc' }
});
return records.map(record => {
const state = JSON.parse(record.data) as SessionState;
return {
name: state.metadata.name || record.id,
path: record.id,
created: new Date(state.metadata.created),
lastActive: new Date(state.metadata.lastActive),
messageCount: state.messages.length
};
});
}
}
// Use custom storage
const dbStorage = new DatabaseStorage(myDatabase);
const s = session({ storage: dbStorage });class S3Storage implements SessionStorage {
constructor(private s3Client: S3Client, private bucket: string) {}
async save(state: SessionState, name?: string): Promise<string> {
const key = name || `session-${Date.now()}.json`;
await this.s3Client.send(new PutObjectCommand({
Bucket: this.bucket,
Key: `sessions/${key}`,
Body: JSON.stringify(state, null, 2),
ContentType: 'application/json'
}));
return key;
}
async load(nameOrPath: string): Promise<SessionState> {
const response = await this.s3Client.send(new GetObjectCommand({
Bucket: this.bucket,
Key: `sessions/${nameOrPath}`
}));
const body = await response.Body?.transformToString();
if (!body) {
throw new Error(`Session ${nameOrPath} not found`);
}
const state = JSON.parse(body) as SessionState;
// Convert dates...
return state;
}
async list(): Promise<SessionInfo[]> {
const response = await this.s3Client.send(new ListObjectsV2Command({
Bucket: this.bucket,
Prefix: 'sessions/'
}));
const sessions: SessionInfo[] = [];
for (const object of response.Contents || []) {
// Load and parse each session for metadata...
}
return sessions;
}
}
const s3Storage = new S3Storage(s3Client, 'my-sessions-bucket');
const s = session({ storage: s3Storage });class RedisStorage implements SessionStorage {
constructor(private redis: RedisClient) {}
async save(state: SessionState, name?: string): Promise<string> {
const key = name || `session:${Date.now()}`;
await this.redis.set(key, JSON.stringify(state));
await this.redis.sadd('sessions:all', key);
return key;
}
async load(nameOrPath: string): Promise<SessionState> {
const data = await this.redis.get(nameOrPath);
if (!data) {
throw new Error(`Session ${nameOrPath} not found`);
}
const state = JSON.parse(data) as SessionState;
// Convert dates...
return state;
}
async list(): Promise<SessionInfo[]> {
const keys = await this.redis.smembers('sessions:all');
const sessions: SessionInfo[] = [];
for (const key of keys) {
const data = await this.redis.get(key);
if (data) {
const state = JSON.parse(data) as SessionState;
sessions.push({
name: state.metadata.name || key,
path: key,
created: new Date(state.metadata.created),
lastActive: new Date(state.metadata.lastActive),
messageCount: state.messages.length
});
}
}
return sessions.sort((a, b) =>
b.lastActive.getTime() - a.lastActive.getTime()
);
}
}
const redisStorage = new RedisStorage(redisClient);
const s = session({ storage: redisStorage });When implementing custom storage:
- Handle Date Serialization: Convert Date objects to/from strings properly
- Error Handling: Throw meaningful errors for missing sessions
- Atomic Operations: Ensure save operations are atomic when possible
- Performance: Consider caching for frequently accessed sessions
- Cleanup: Implement garbage collection for old sessions if needed
class CustomStorage implements SessionStorage {
async save(state: SessionState, name?: string): Promise<string> {
// 1. Generate unique identifier
// 2. Serialize state (handle dates)
// 3. Store atomically
// 4. Return identifier
}
async load(nameOrPath: string): Promise<SessionState> {
// 1. Retrieve by identifier
// 2. Parse and validate
// 3. Convert dates back to Date objects
// 4. Return state
}
async list(): Promise<SessionInfo[]> {
// 1. Get all session identifiers
// 2. Load metadata efficiently (avoid full content)
// 3. Sort by last active
// 4. Return session info
}
}// Good: Descriptive session names
await s.save('oauth-implementation-jan-2024');
await s.save('bug-fix-memory-leak-issue-123');
await s.save('feature-user-notifications');
// Avoid: Generic names
await s.save('session1');
await s.save('temp');// List and clean old sessions periodically
const sessions = await session.list();
const oldSessions = sessions.filter(s =>
Date.now() - new Date(s.lastActive).getTime() > 30 * 24 * 60 * 60 * 1000
);
for (const old of oldSessions) {
await session.remove(old.name);
}// Start fresh when context changes significantly
const s = session();
await s.claude('Implement auth system');
// ... many messages later ...
// New feature - start fresh session
const s2 = session();
await s2.claude('Now let\'s work on the payment system');
await s2.save('payment-system');-
"Session not found"
// Check if session exists if (await session.exists('my-session')) { const s = await session.load('my-session'); } else { console.log('Session does not exist'); }
-
Session size limits
// Very long sessions may hit token limits // Start fresh session if needed if (s.messages().length > 50) { const summary = await s.claude('Summarize our progress'); const fresh = session(); await fresh.claude(`Continue from: ${summary}`); }
-
Concurrent session access
// Sessions are not thread-safe // Use one session object per process
// Inspect session state
const s = session();
console.log('Session ID:', s.id());
console.log('Message count:', s.messages().length);
console.log('Last message:', s.messages().slice(-1)[0]);
// Enable verbose mode
await s.claude('Debug this', { verbose: true });Create prompts that require session context:
---
session:
required: true
systemPrompt: "You are helping with an ongoing project"
---
Continue implementing the feature we discussed.# Start named session
channelcoder run "Create TODO app" --session todo-app
# Continue with inline prompt
channelcoder run -p "Add user authentication" --load-session todo-app
# Continue with prompt file
channelcoder run continue-task.md --load-session todo-app
# Interactive mode with session
channelcoder interactive --load-session todo-app- Learn about Stream Parser SDK for parsing session logs
- Explore Docker Mode for isolated sessions
- Try Worktree Mode for branch-specific sessions
- See Examples for practical patterns