Skip to content

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.md

Agent Behavior Rules (What the Skill Instructs) โ€‹

During Conversations โ€‹

When any of the following occurs, the agent proactively writes to memory/YYYY-MM-DD.md:

TriggerExample
Task completedPR submitted, bug fixed, deployment done
Technical decision madeWhy A was chosen over B
Lesson learned / pitfall discoveredRoot cause of a failed build
User expressed a preference"Always reply in Chinese"
Status changedPR merged, service restarted

Diary entry format:

markdown
## 14:30

- Fixed session_snapshot dedup bug: changed block-level comparison to line-level content matching
- PR #7 submitted and merged

During Heartbeats โ€‹

On every heartbeat tick, the agent performs memory maintenance in order:

  1. Write diary โ€” append new session conversations to today's memory/YYYY-MM-DD.md
  2. Review recent files โ€” skim the last 2โ€“3 days for insights worth keeping long-term
  3. 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) + delete

Deployment: 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 servermem0-api containersystemd service
Pipeline runnermem0-pipeline container (cron)systemd user timers
Diary file accessvia bind mount ~/.openclaw โ†’ /openclawdirect filesystem
Patch managementautomatic at build timemanual python3 tools/patch_s3vectors_filter.py
Restart on crashrestart: unless-stoppedsystemd Restart=on-failure

For deployment details, see Docker Setup or systemd Setup.

Daily Automation Timeline โ€‹

Time (UTC)ScriptWhat it does
Every 5 minpipelines/session_snapshot.pyCapture session conversations โ†’ diary file
Every 15 minpipelines/auto_digest.py --todayIncremental: read new diary content โ†’ mem0 short-term (infer=False, direct text)
01:00pipelines/memory_sync.pySync MEMORY.md โ†’ mem0 long-term (hash dedup)
02:00pipelines/auto_dream.pyAutoDream: 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-pipeline container. 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.py Step 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:

PathWho writesQualityWhen
Agent-drivenAgent itself (SKILL.md guided)High โ€” curated, meaningful entriesReal-time, during conversation
Snapshot-drivensession_snapshot.py (automated)Lower โ€” raw conversation fragmentsEvery 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 matching agent:{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 โ€‹

PathSourceLatencyWhen to use
memory_sync.pyMEMORY.mdSame dayAgent-curated knowledge, important decisions
auto_digest + AutoDreamDaily diary7 daysRecurring discussions, gradual context buildup
Explicit CLI writeAgent on-demandImmediateTime-sensitive facts, mid-conversation decisions
bash
# 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-term

If 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 windowCovered by
Today (T+0)memory/today.md (real-time)
Yesterday after-midnight to 02:00memory/yesterday.md (gap buffer)
Yesterday 02:00 onwardmem0 long-term (AutoDream Step1 digested)
Last 7 daysmem0 short-term (--combined)
Older than 7 daysmem0 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 โ€‹

bash
# 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 7

This gives the agent:

  • Recent discussions (from short-term, last 7 days)
  • Durable knowledge (from long-term: decisions, lessons, preferences)
  • Shared team knowledge (from category=experience pool, 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:

bash
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-blog

Use the key (e.g. dev, main, blog) as your --agent value.

Released under the MIT License.