How It Works โ
This page explains the end-to-end memory flow โ from a conversation happening to a memory being available in the next session.
The Core Problem: Sessions Are Stateless โ
OpenClaw resets the active session daily (default 4:00 AM local time) or after an idle timeout. Every new session starts with a blank context window โ no memory of previous conversations.
This is by design: it keeps context fresh and avoids token bloat. But it means agents need an external mechanism to maintain continuity.
mem0 Memory Service is that mechanism.
How Agents Know What to Do: The Skill System โ
OpenClaw has a built-in skill system. When a skill is enabled, its SKILL.md file is automatically injected into the agent's system prompt at session start.
The mem0-memory skill's SKILL.md contains a ๐ด Agent Memory Behavior section with explicit behavior rules. When an agent reads these rules, it knows:
- When to write to the diary (
memory/YYYY-MM-DD.md) - When to update
MEMORY.md - How to search and write mem0 via CLI
This is why no AGENTS.md changes are needed. Enabling the skill once applies the behavior to every agent, on every session start, automatically.
Skills enabled in OpenClaw Settings
โ
โผ
SKILL.md injected into system prompt at session start
โ
โผ
Agent reads "๐ด Agent Memory Behavior" rules
โ
โโ During conversation โ write diary entries
โโ During heartbeat โ update MEMORY.mdAgent Behavior Rules (What the Skill Instructs) โ
During Conversations โ
When any of the following occurs, the agent proactively writes to memory/YYYY-MM-DD.md:
| Trigger | Example |
|---|---|
| Task completed | PR submitted, bug fixed, deployment done |
| Technical decision made | Why A was chosen over B |
| Lesson learned / pitfall discovered | Root cause of a failed build |
| User expressed a preference | "Always reply in Chinese" |
| Status changed | PR merged, service restarted |
Diary entry format:
## 14:30
- Fixed session_snapshot dedup bug: changed block-level comparison to line-level content matching
- PR #7 submitted and mergedDuring Heartbeats โ
On every heartbeat tick, the agent performs memory maintenance in order:
- Write diary โ append new session conversations to today's
memory/YYYY-MM-DD.md - Review recent files โ skim the last 2โ3 days for insights worth keeping long-term
- Update MEMORY.md โ distill durable facts into
MEMORY.md; prune outdated entries
MEMORY.md is the agent's curated knowledge base โ project cards, active decisions, key configurations. It's maintained by the agent itself, not by automation scripts.
The Full Memory Flow โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Conversation โ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โผ โผ
Agent writes diary Agent writes diary
(SKILL.md guided, (session_snapshot fallback,
high quality) every 5 min, auto-captured)
โ โ
โโโโโโโโโโโโฌโโโโโโโโโโโโ
โผ
memory/YYYY-MM-DD.md
(raw daily diary file)
โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ โ โ โ
โผ โผ โผ โผ
Heartbeat Every 15min UTC 02:00 UTC 01:00
(agent auto_digest auto_dream memory_sync
self- --today (Step1: (sync
distills) (infer=False, yesterday MEMORY.md)
direct text) diary +
Step2: 7d STM)
โ โ โ โ
โผ โผ โผ โผ
MEMORY.md mem0 short- mem0 short- mem0 long-
(updated) term memory term memory term memory
(today, (yesterday, (no run_id)
~20min lag) next day)
โ
UTC 02:00
AutoDream
โ
โโโโโโโโโโโดโโโโโโโโโโโ
โผ โผ
Step 1: digest Step 2: consolidate
yesterday diary 7-day-old short-term
โ long-term โ re-add long-term
(infer=True) (infer=True) + deleteDeployment: Docker vs. systemd โ
The memory pipeline scripts (session_snapshot, auto_digest, auto_dream, memory_sync) run identically regardless of deployment method. Only the process manager differs:
| Docker (recommended) | systemd | |
|---|---|---|
| API server | mem0-api container | systemd service |
| Pipeline runner | mem0-pipeline container (cron) | systemd user timers |
| Diary file access | via bind mount ~/.openclaw โ /openclaw | direct filesystem |
| Patch management | automatic at build time | manual python3 tools/patch_s3vectors_filter.py |
| Restart on crash | restart: unless-stopped | systemd Restart=on-failure |
For deployment details, see Docker Setup or systemd Setup.
Daily Automation Timeline โ
| Time (UTC) | Script | What it does |
|---|---|---|
| Every 5 min | pipelines/session_snapshot.py | Capture session conversations โ diary file |
| Every 15 min | pipelines/auto_digest.py --today | Incremental: read new diary content โ mem0 short-term (infer=False, direct text) |
| 01:00 | pipelines/memory_sync.py | Sync MEMORY.md โ mem0 long-term (hash dedup) |
| 02:00 | pipelines/auto_dream.py | AutoDream: Step 1: yesterday diary โ mem0 long-term (infer=True); Step 2: 7-day-old short-term โ re-add to long-term (infer=True) then delete |
In Docker deployments, these scripts run as cron jobs inside the
mem0-pipelinecontainer. In systemd deployments, they run as user timers. The schedule and behavior are identical regardless of deployment method.
auto_digest Mode โ
auto_digest.py runs in one active mode:
--today mode (every 15 min, incremental) Picks up new content from today's diary since the last run (offset-based). Writes to mem0 short-term memory with infer=False โ diary text is passed directly to mem0 without a custom LLM extraction layer. Skips batches smaller than 500 bytes to avoid noisy micro-writes. On failure, retains the offset so the next run resumes from the same point.
Every 15 min (--today): diary new content โ POST to mem0 (infer=False, direct text)Why short-term entries don't pollute long-term memory
auto_digest writes with a run_id = YYYY-MM-DD. This is the key that controls mem0's dedup scope: when infer=True is used, mem0 searches for similar memories only within the same run_id. Since auto_digest now uses infer=False, no LLM dedup is triggered at write time. Today's entries are stored directly without interfering with existing long-term memories (which have no run_id).
This means:
- Writing every 15 minutes is safe โ no risk of silently overwriting long-term knowledge
- Today's short-term memories accumulate and refine within their own namespace
Promotion to long-term: one global dedup at auto_dream time
The cross-run_id merge happens only during auto_dream (UTC 02:00). When 7-day-old short-term memories are re-added without run_id, mem0 searches the entire long-term store and decides ADD / UPDATE / DELETE / NONE. This is the one moment of global dedup โ keeping long-term memory compact and non-redundant.
Every 15 min โ short-term (run_id=today, dedup within same day only)
UTC 02:00 โ long-term (no run_id, global dedup across all history)Note: The previous default full mode (UTC 01:30, LLM-extract yesterday's diary โ mem0 short-term) has been superseded by
auto_dream.pyStep 1, which writes directly to long-term memory (no run_id) with higher quality.
Two Roles of session_snapshot โ
session_snapshot.py serves two purposes:
Primary: Cross-Session Memory Bridge Agents reset daily. Without snapshot, conversations would be lost between sessions. Snapshot captures every conversation into diary files, which are then distilled into mem0 by auto_digest.py. When a new session starts, the agent retrieves relevant memories from mem0 โ restoring context seamlessly.
Secondary: Compaction Guard When a session's context window grows too large, OpenClaw compresses (compacts) the history. The most recent few minutes of conversation might not survive compaction. Snapshot running every 5 minutes ensures that content is captured to disk before compaction can lose it.
Tertiary: Near-real-time cross-session memory sharing Each agent may have multiple concurrent sessions โ a direct chat session and one or more group chat sessions. Without a sharing mechanism, what an agent says in a group chat is invisible to its direct chat session (and vice versa).
session_snapshot.py writes new conversation to the diary file every 5 minutes. auto_digest.py --today then picks up new diary content every 15 minutes and writes it to mem0 short-term memory with infer=False (run_id=today, direct text). Any other session of the same agent can search mem0 and retrieve that content within ~20 minutes (5 min snapshot + 15 min digest) โ no session restart required.
Session keys are tagged in metadata (session_key), so you can filter by source if needed.
Two Paths for Writing Diary Files โ
Because not all agents actively maintain diary files, there are two parallel capture paths:
| Path | Who writes | Quality | When |
|---|---|---|---|
| Agent-driven | Agent itself (SKILL.md guided) | High โ curated, meaningful entries | Real-time, during conversation |
| Snapshot-driven | session_snapshot.py (automated) | Lower โ raw conversation fragments | Every 5 min, regardless of content |
Both paths write to the same memory/YYYY-MM-DD.md file. Content-level deduplication ensures no entry is written twice.
The agent-driven path produces better memories. Automation is the safety net.
Group chat sessions are now included. Previously, snapshot only captured the
main(direct chat) session. Now all sessions matchingagent:{id}:*are processed โ group chat conversations are written to the same diary file and mem0, enabling cross-context memory sharing within the same agent.
Three Paths to Long-Term Memory โ
| Path | Source | Latency | When to use |
|---|---|---|---|
| memory_sync.py | MEMORY.md | Same day | Agent-curated knowledge, important decisions |
| auto_digest + AutoDream | Daily diary | 7 days | Recurring discussions, gradual context buildup |
| Explicit CLI write | Agent on-demand | Immediate | Time-sensitive facts, mid-conversation decisions |
# Explicit write to long-term memory (no --run = long-term)
python3 cli.py add \
--user boss --agent <your-agent-id> \
--text "Decided to use S3 Vectors as primary vector store" \
--metadata '{"category":"decision"}'Session Start: Restoring Context โ
Why today's AND yesterday's diary files are both needed โ
When a new session starts, the skill instructs the agent to read both diary files before querying mem0:
Session start
โโโ Read memory/today.md โ today's raw diary (real-time, not yet in mem0)
โโโ Read memory/yesterday.md โ yesterday's raw diary (coverage gap buffer)
โโโ Search mem0 --combined โ distilled memories (long-term + recent short-term)Why today's diary?auto_dream.py Step 1 runs at UTC 02:00, digesting yesterday's complete diary. Anything that happened today hasn't been digested yet โ it only exists in memory/today.md. Reading this file is the only way to recover same-day context after a session reset.
Why yesterday's diary? There is a coverage gap: the window between yesterday's late-night conversations and when auto_dream finishes running (UTC 02:00). For example:
Yesterday 23:50 Important discussion happens โ written to memory/yesterday.md
Today 00:10 Session resets, new session starts
Today 02:00 auto_dream runs โ yesterday's diary enters mem0 long-termIf the new session starts before 02:00, mem0 doesn't have last night's content yet. Reading memory/yesterday.md directly covers this gap.
The complete coverage map:
| Time window | Covered by |
|---|---|
| Today (T+0) | memory/today.md (real-time) |
| Yesterday after-midnight to 02:00 | memory/yesterday.md (gap buffer) |
| Yesterday 02:00 onward | mem0 long-term (AutoDream Step1 digested) |
| Last 7 days | mem0 short-term (--combined) |
| Older than 7 days | mem0 long-term (AutoDream-consolidated) |
All three sources together โ today's diary + yesterday's diary + mem0 โ create zero blind spots across all session reset scenarios.
mem0 retrieval โ
# Combined search: long-term + recent 7 days short-term
python3 cli.py search \
--user boss --agent <your-agent-id> \
--query "<topic from current conversation>" \
--combined --recent-days 7This gives the agent:
- Recent discussions (from short-term, last 7 days)
- Durable knowledge (from long-term: decisions, lessons, preferences)
- Shared team knowledge (from
category=experiencepool, all agents)
The result: the agent "remembers" relevant context without the user needing to re-explain anything.
Finding Your Agent ID โ
All CLI commands use --agent <your-agent-id>. Your agent ID is the key under agents.entries in openclaw.json:
cat ~/.openclaw/openclaw.json | python3 -c "
import json, sys
config = json.load(sys.stdin)
entries = config.get('agents', {}).get('entries', {})
for agent_id, cfg in entries.items():
ws = cfg.get('workspace', 'N/A')
print(f' {agent_id}: workspace={ws}')
"Example output:
main: workspace=/home/user/workspace-main
dev: workspace=/home/user/workspace-dev
blog: workspace=/home/user/workspace-blogUse the key (e.g. dev, main, blog) as your --agent value.