Skip to main content

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

  1. Consistent naming: Use predictable scope identifiers (channel_<id>, project_<name>)
  2. Session filename convention: Include scope in session filenames for reliable filtering
  3. Monitor resource usage: Each scope runs its own evolution cycle
  4. Periodic cleanup: Archive old scoped memory graphs to prevent disk bloat
  5. 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.

Build docs developers (and LLMs) love