Overview
Session Scope isolates evolution state and memory to a specific identifier, preventing cross-contamination in multi-channel or multi-project environments.
EVOLVER_SESSION_SCOPE=channel_123456
Location: src/gep/paths.js:53
Problem Statement
Without session scope, a single evolver instance reading from multiple OpenClaw agent sessions will mix signals:
- Channel A error: “Feishu API 429 rate limit”
- Channel B error: “GitHub webhook timeout”
Evolver would try to fix both in a single cycle, leading to unfocused evolution and constraint violations.
How It Works
1. Session Log Filtering
When EVOLVER_SESSION_SCOPE is set, only session files whose names contain the scope identifier are read.
// src/evolve.js:172-208
const sessionScope = getSessionScope();
if (sessionScope && nonEvolverFiles.length > 0) {
const scopeLower = sessionScope.toLowerCase();
const scopedFiles = nonEvolverFiles.filter(f =>
f.name.toLowerCase().includes(scopeLower)
);
if (scopedFiles.length > 0) {
nonEvolverFiles = scopedFiles;
console.log(`[SessionScope] Filtered to ${scopedFiles.length} session(s) matching scope "${sessionScope}".`);
}
}
Example:
~/.openclaw/agents/main/sessions/
├── channel_123456_session1.jsonl ← Matched
├── channel_123456_session2.jsonl ← Matched
├── channel_789012_session1.jsonl ← Ignored
└── general_session.jsonl ← Ignored
With EVOLVER_SESSION_SCOPE=channel_123456, only the first two files are read.
2. Scoped MEMORY.md
Each scope gets its own MEMORY.md file:
workspace/memory/scopes/
├── channel_123456/
│ └── MEMORY.md ← Scope-specific memory
├── channel_789012/
│ └── MEMORY.md
└── global/
└── MEMORY.md ← Fallback
Location: src/evolve.js:399-414
Fallback behavior: If scoped MEMORY.md doesn’t exist, use global MEMORY.md.
3. Scoped Memory Graph
Memory graph events are isolated by scope:
workspace/memory/evolution/
├── memory_graph.jsonl ← Global (when no scope)
└── scopes/
├── channel_123456/
│ └── memory_graph.jsonl ← Scope-specific graph
└── channel_789012/
└── memory_graph.jsonl
This prevents cross-scope causal inference pollution.
Use Cases
Multi-Channel Bot
A Feishu bot serving multiple channels:
# Channel A evolver
EVOLVER_SESSION_SCOPE=channel_123456 node index.js --loop
# Channel B evolver
EVOLVER_SESSION_SCOPE=channel_789012 node index.js --loop
Each channel evolves independently:
- Different genes selected based on channel-specific signals
- Separate memory graphs
- No blast radius interference
Multi-Project Agent
An agent managing multiple GitHub repositories:
# Project A
EVOLVER_SESSION_SCOPE=project_alpha node index.js
# Project B
EVOLVER_SESSION_SCOPE=project_beta node index.js
Development vs Production
Isolate dev and prod evolution:
# Development
EVOLVER_SESSION_SCOPE=dev node index.js
# Production
EVOLVER_SESSION_SCOPE=prod node index.js
Configuration
Environment Variable
export EVOLVER_SESSION_SCOPE=channel_123456
node index.js --loop
Agent Config
Set in ~/.openclaw/openclaw.json:
{
"env": {
"EVOLVER_SESSION_SCOPE": "channel_123456"
}
}
Session Filename Convention
Recommended: Include scope in session filenames:
channel_123456_2024-01-15.jsonl
project_alpha_session_42.jsonl
The scope filter is case-insensitive substring match.
Observations Object
The scope is recorded in evolution observations:
// src/evolve.js:1129-1144
const observations = {
agent: AGENT_NAME,
session_scope: sessionScope || null,
drift_enabled: IS_RANDOM_DRIFT,
// ...
};
This allows memory graph queries to filter by scope:
grep '"session_scope":"channel_123456"' memory/evolution/memory_graph.jsonl
Graceful Degradation
No Matching Sessions
If no session files match the scope, evolver falls back to all sessions:
if (scopedFiles.length > 0) {
nonEvolverFiles = scopedFiles;
} else {
console.log(`[SessionScope] No sessions match scope "${sessionScope}". Using all sessions (fallback).`);
}
Location: src/evolve.js:202-207
Rationale: Better to evolve with global context than not at all.
No Scoped MEMORY.md
On first run with a new scope, global MEMORY.md is used:
if (fs.existsSync(scopedMemory)) {
memFile = scopedMemory;
} else {
console.log(`[SessionScope] No scoped MEMORY.md for "${scope}". Using global MEMORY.md.`);
}
Location: src/evolve.js:407-412
After the first evolution, a scoped MEMORY.md is created.
Multi-Scope Operation
Parallel Evolvers
Run multiple evolver instances with different scopes:
# Terminal 1
EVOLVER_SESSION_SCOPE=channel_a node index.js --loop &
# Terminal 2
EVOLVER_SESSION_SCOPE=channel_b node index.js --loop &
# Terminal 3 (global, for cross-scope optimization)
node index.js --loop &
Ensure sufficient system resources. Each evolver instance consumes CPU and memory.
Process Manager
Using pm2:
{
"apps": [
{
"name": "evolver-channel-a",
"script": "index.js",
"args": "--loop",
"env": {
"EVOLVER_SESSION_SCOPE": "channel_a"
}
},
{
"name": "evolver-channel-b",
"script": "index.js",
"args": "--loop",
"env": {
"EVOLVER_SESSION_SCOPE": "channel_b"
}
}
]
}
Memory Isolation Guarantees
What is isolated
- Session log reading
- MEMORY.md (scoped file)
- Memory graph (
memory_graph.jsonl in scoped directory)
- Signal extraction (only from scoped sessions)
- Evolution state (separate
evolution_state.json per scope)
What is shared
- Gene store (
assets/gep/genes.json)
- Capsule store (
assets/gep/capsules.json)
- Event log (
assets/gep/events.jsonl)
- A2A assets (external candidates)
- Git repository state
Rationale: Genes and capsules are reusable across scopes. Only the invocation context is isolated.
Debugging
Check active scope
grep -r "session_scope" memory/evolution/memory_graph.jsonl | tail -5
List scoped files
ls -la ~/.openclaw/agents/main/sessions/ | grep channel_123456
Verify scoped MEMORY.md
cat workspace/memory/scopes/channel_123456/MEMORY.md
Best Practices
- Consistent naming: Use predictable scope identifiers (
channel_<id>, project_<name>)
- Session filename convention: Include scope in session filenames for reliable filtering
- Monitor resource usage: Each scope runs its own evolution cycle
- Periodic cleanup: Archive old scoped memory graphs to prevent disk bloat
- Document scope mapping: Maintain a registry of active scopes and their purposes
Troubleshooting
Scope not filtering sessions
Symptom: Logs show all sessions, not just scoped ones.
Cause: Session filenames don’t contain the scope identifier.
Solution: Rename session files or adjust scope identifier:
# Check session filenames
ls ~/.openclaw/agents/main/sessions/
# Adjust scope to match
EVOLVER_SESSION_SCOPE=actual_session_prefix
Scoped MEMORY.md not created
Symptom: Always using global MEMORY.md.
Cause: First evolution hasn’t completed yet.
Solution: Run one full evolution cycle. Scoped MEMORY.md is created during solidify.
Cross-scope contamination
Symptom: Genes from channel A appearing in channel B evolutions.
Cause: Genes/capsules are global by design.
Solution: This is expected. Genes are reusable. If you need full isolation, run separate evolver instances in different repositories.