NanoClaw Pro uses Claude’s session system to maintain conversation context across interactions. Each group has an isolated session that persists between agent invocations.
Overview
Sessions enable the agent to remember:
Previous conversation turns
Files it has read or created
Tool usage history
Context from earlier in the conversation
How Sessions Work
1. Session Storage
Sessions are stored per-group in isolated directories:
data/
└── sessions/
├── whatsapp_family-chat/
│ └── .claude/
│ ├── settings.json # Session config
│ ├── session_abc123.jsonl # Conversation transcript
│ └── memory.json # Auto-memory (user preferences)
├── whatsapp_work-team/
│ └── .claude/
│ └── session_xyz789.jsonl
└── whatsapp_main/
└── .claude/
└── session_main001.jsonl
Key points:
Each group has its own .claude/ directory
Sessions are isolated - groups can’t see each other’s history
JSONL format allows incremental updates
Location: Created by src/container-runner.ts:115-145
2. Session Mounting
The group’s .claude/ directory is mounted into the container:
// From src/container-runner.ts:115-162
const groupSessionsDir = path . join (
DATA_DIR ,
'sessions' ,
group . folder ,
'.claude' ,
);
fs . mkdirSync ( groupSessionsDir , { recursive: true });
// Create settings.json if missing
const settingsFile = path . join ( groupSessionsDir , 'settings.json' );
if ( ! fs . existsSync ( settingsFile )) {
fs . writeFileSync (
settingsFile ,
JSON . stringify (
{
env: {
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1' ,
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: '1' ,
CLAUDE_CODE_DISABLE_AUTO_MEMORY: '0' ,
},
},
null ,
2 ,
) + ' \n ' ,
);
}
mounts . push ({
hostPath: groupSessionsDir ,
containerPath: '/home/node/.claude' ,
readonly: false ,
});
Mount details:
Host: data/sessions/{group}/.claude/
Container: /home/node/.claude/
Read-write (agent needs to update transcript)
Location: src/container-runner.ts:115-162
The container runs as node user (uid 1000) with HOME=/home/node, so the mount path must be /home/node/.claude/, not /root/.claude/.
3. Session ID Tracking
Session IDs are stored in SQLite and passed to the agent:
// From src/index.ts:260-339
async function runAgent (
group : RegisteredGroup ,
prompt : string ,
chatJid : string ,
onOutput ?: ( output : ContainerOutput ) => Promise < void >,
) : Promise < 'success' | 'error' > {
const sessionId = sessions [ group . folder ];
const output = await runContainerAgent (
group ,
{
prompt ,
sessionId , // <-- Resume session if exists
groupFolder: group . folder ,
chatJid ,
isMain ,
},
...
);
// Save new/updated session ID
if ( output . newSessionId ) {
sessions [ group . folder ] = output . newSessionId ;
setSession ( group . folder , output . newSessionId );
}
return output . status === 'error' ? 'error' : 'success' ;
}
Flow:
Load sessionId from in-memory cache (initialized from SQLite)
Pass to container via stdin
Agent resumes session (or creates new one if null)
Agent returns updated sessionId in output
Save to cache and SQLite
Location: src/index.ts:260-339
4. Claude Agent SDK Integration
Inside the container, the agent-runner uses Claude Agent SDK’s resume option:
// From container/agent-runner/src/index.ts (conceptual)
import { Agent } from '@anthropic-ai/claude-agent-sdk' ;
const input : ContainerInput = JSON . parse ( await readStdin ());
const agent = new Agent ({
settingSources: [ 'project' ], // Loads CLAUDE.md files
... ( input . sessionId && { resume: input . sessionId }),
});
const result = await agent . query ( input . prompt );
// Return session ID for persistence
const output : ContainerOutput = {
status: 'success' ,
result: result . response ,
newSessionId: agent . sessionId ,
};
console . log ( OUTPUT_START_MARKER );
console . log ( JSON . stringify ( output ));
console . log ( OUTPUT_END_MARKER );
Resume behavior:
If sessionId provided: Load existing transcript, continue conversation
If sessionId is null: Create new session, start fresh
Location: container/agent-runner/src/index.ts
Session Lifecycle
First Interaction (New Session)
Subsequent Interactions (Resume Session)
Session Isolation
Each group has completely isolated sessions:
// Group A cannot see Group B's session
// Group A (family-chat)
data / sessions / whatsapp_family - chat / . claude /
├── session_abc123 . jsonl # Family conversation
└── memory . json # Family preferences
// Group B (work-team)
data / sessions / whatsapp_work - team / . claude /
├── session_xyz789 . jsonl # Work conversation
└── memory . json # Work preferences
Why isolation matters:
Prevents information leakage between groups
Different groups have different contexts
Security boundary between trusted (main) and untrusted groups
Enforced by: Container mounts - each container only gets its group’s session dir
Location: src/container-runner.ts:115-162
Session Settings
Each group’s session is configured via settings.json:
{
"env" : {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS" : "1" ,
"CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD" : "1" ,
"CLAUDE_CODE_DISABLE_AUTO_MEMORY" : "0"
}
}
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS
Enables agent swarms (subagent orchestration):
// Agent can spawn subagents for parallel work
await agent . query ( "Research topic X while I draft the report" );
Docs
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD
Loads CLAUDE.md files from mounted directories:
/workspace/group/CLAUDE.md # Group memory
/workspace/global/CLAUDE.md # Global memory (if mounted)
/workspace/project/groups/CLAUDE.md # Main group only
Docs
CLAUDE_CODE_DISABLE_AUTO_MEMORY
Enables Claude’s auto-memory feature:
Automatically extracts user preferences from conversation
Stores in memory.json
Applies to future interactions
Docs
Location: src/container-runner.ts:122-144
Sessions are stored as JSONL (JSON Lines):
{ "role" : "user" , "content" : "What's the weather?" }
{ "role" : "assistant" , "content" : "I'll check the weather for you." , "tool_use" :[{ "name" : "web_search" , "input" :{ "query" : "weather" }}]}
{ "role" : "tool" , "tool_use_id" : "toolu_123" , "content" : "Sunny, 72°F" }
{ "role" : "assistant" , "content" : "The weather is sunny and 72°F." }
Each line is a complete JSON object representing:
User messages
Assistant responses
Tool calls and results
System context
Benefits:
Incremental appends (no need to rewrite entire file)
Easy to parse and inspect
Efficient for long conversations
Session Persistence
In-Memory Cache
Session IDs are cached in memory for fast access:
// From src/index.ts:59-60
let sessions : Record < string , string > = {};
function loadState () : void {
sessions = getAllSessions ();
// ...
}
Location: src/index.ts:59-77
SQLite Storage
Sessions are persisted in the sessions table:
CREATE TABLE sessions (
group_folder TEXT PRIMARY KEY ,
session_id TEXT NOT NULL
);
Operations:
// From src/db.ts:514-538
export function getSession ( groupFolder : string ) : string | undefined {
const row = db
. prepare ( 'SELECT session_id FROM sessions WHERE group_folder = ?' )
. get ( groupFolder ) as { session_id : string } | undefined ;
return row ?. session_id ;
}
export function setSession ( groupFolder : string , sessionId : string ) : void {
db . prepare (
'INSERT OR REPLACE INTO sessions (group_folder, session_id) VALUES (?, ?)' ,
). run ( groupFolder , sessionId );
}
export function getAllSessions () : Record < string , string > {
const rows = db
. prepare ( 'SELECT group_folder, session_id FROM sessions' )
. all () as Array <{ group_folder : string ; session_id : string }>;
const result : Record < string , string > = {};
for ( const row of rows ) {
result [ row . group_folder ] = row . session_id ;
}
return result ;
}
Location: src/db.ts:514-538
Session Updates During Streaming
When streaming output, session IDs are updated immediately:
// From src/index.ts:295-302
const wrappedOnOutput = onOutput
? async ( output : ContainerOutput ) => {
if ( output . newSessionId ) {
sessions [ group . folder ] = output . newSessionId ;
setSession ( group . folder , output . newSessionId );
}
await onOutput ( output );
}
: undefined ;
Why update during streaming?
Session may change mid-conversation (agent creates subagents)
Prevents session ID loss if container crashes after first output
Enables piped messages to use updated session
Location: src/index.ts:295-302
Container Reuse and Sessions
When messages are piped to an active container, the session continues in-memory:
// Container already running for group
queue . sendMessage ( chatJid , formatted );
// Agent processes piped message, updates session transcript
Benefits:
No session reload overhead
Conversation context stays hot in memory
Faster response times
Trade-off:
Session updates only written to disk when agent SDK flushes
Container crash loses unflushed updates
Session Debugging
To inspect a session:
# View session transcript
cat data/sessions/whatsapp_family-chat/.claude/session_abc123.jsonl | jq .
# View auto-memory
cat data/sessions/whatsapp_family-chat/.claude/memory.json | jq .
# Check session ID in database
sqlite3 store/messages.db "SELECT * FROM sessions WHERE group_folder = 'whatsapp_family-chat';"
Session Reset
To reset a group’s session (forget all context):
# Delete session files
rm -rf data/sessions/whatsapp_family-chat/.claude/session_ * .jsonl
# Clear session ID from database
sqlite3 store/messages.db "DELETE FROM sessions WHERE group_folder = 'whatsapp_family-chat';"
# Restart NanoClaw
launchctl kickstart -k gui/ $( id -u ) /com.nanoclaw
Next message will create a fresh session.
Session Limitations
Context Window
Claude has a finite context window (200K tokens). Very long conversations may:
Truncate early messages
Require session reset
Benefit from periodic summarization
Disk Usage
Sessions grow over time. A 1000-message conversation may use:
5-10 MB for transcript
100-500 KB for memory
Monitor with:
Cross-Group Context
Sessions are isolated per-group. The agent cannot reference:
Conversations from other groups
Files created in other group folders
Use global memory (groups/CLAUDE.md) for shared context.
Best Practices
1. Periodic Memory Updates
Encourage the agent to write important facts to CLAUDE.md:
User: Remember that I prefer concise responses
Agent: I've noted your preference in CLAUDE.md.
This persists preferences even if sessions are reset.
2. Session Hygiene
Reset sessions when:
Switching topics completely
Conversation becomes too long (>500 messages)
Agent seems confused about context
3. Group Naming
Use descriptive group folder names:
✅ whatsapp_family-chat
✅ telegram_dev-team
❌ group1
❌ chat
Helps when inspecting sessions manually.
Next Steps
Message Flow How sessions integrate with message processing
Security Session isolation and security