🇫🇷🇺🇸🇧🇷

Adding session memory, like OpenClaw

No more re-explaining the context at every session. Claude automatically retains what you did, the decisions made, the problems encountered — per project, inside your git repo, with no database. Portable, versionable, under $1/month. Here's how to set this system up in less than an hour.


Info

Originally written in French. Translated by AI — the meaning has been preserved, not the prose.

Monday morning. You open Claude Code on a tricky bug you left hanging on Friday. Claude remembers nothing. You re-explain the context, re-describe the architecture, re-list everything that was tried. Fifteen minutes lost before you can even pick up where you left off.

That's exactly the problem session memory solves — and it's what OpenClaw solved first.


What is OpenClaw?

OpenClaw is an open-source framework built for Claude Code. Its goal: to turn Claude from a memoryless assistant into a persistent, autonomous agent, capable of remembering what it did, what was decided, and what's left to do.

OpenClaw runs entirely on Markdown and Python, with no database and no external dependency. Its philosophy: flat files — versionable and human-readable — are the best long-term memory.

Its memory architecture rests on three levels: - Immediate memory — the context loaded in the current session - Daily logs — what happened today (and yesterday) - Long-term memory — the durable facts, condensed and kept up to date

I've adapted this architecture for my Claude Code projects: a Stop hook that runs automatically after each exchange, two Markdown files per project (long_memory.md + one log per day), and two skills to load and consolidate the memory. Here's how it works and how to set it up.


Why it's a genuine step up from default Claude

Claude Code does have a native automatic memory — but it comes with two important limitations:

Claude Code auto-memory OpenClaw-style system
Location ~/.claude/projects/... (local machine) Inside the project's git repo
Portability No — tied to the machine Yes — follows the repo everywhere
Versioning No Yes — git log on the memory
Granularity Global Per project / sub-project
Inspectable Not easily Markdown, readable directly

The CLAUDE.md is another solution, but it's a file of static rules — not an activity log. It doesn't track what was done, the problems encountered, or the decisions made mid-session.

Session memory fills that gap: it captures, automatically, at every exchange, what happened. Without you having to think about it.


Architecture

The system rests on two kinds of Markdown files, a Stop hook, and a Haiku model that does the extraction.

File structure

project/
  memory_sessions/
    long_memory.md          ← long-term memory (permanent digest)
    logs/
      2026-03-22.md         ← daily log structured into sections
      2026-03-21.md
      archives/             ← logs > 30 days
      .debug/               ← raw Haiku JSON (gitignored)

For a multi-project workspace, each sub-project has its own memory_sessions/ directory — the memory of cmms_doc doesn't get mixed up with that of competitors.


Template 1 — The daily log (logs/2026-03-22.md)

One file per day, structured into fixed sections. Haiku appends bullets to it at each exchange — without ever duplicating what's already there.

# Session log — 2026-03-22

## Context
- Overhaul of the session-memory system for the PRODUCT_AGENTS workspace.

## Objective
- Test the new structured JSON format returned by Haiku.

## Problems encountered
- Transcript extraction was failing: tool_result messages filtered incorrectly.

## Knowledge gained
- The JSONL transcript contains many empty-content messages (tool_use, thinking) — filter on `content[].type == "text"`.

## Work produced
- Rewrote the transcript parser in memory-session.sh.
- Added the .debug/ directory to inspect the raw Haiku JSON.

## Decisions made
- Call `claude` from `/tmp` to keep the project's CLAUDE.md from interfering.

## To follow up
- Confirm that skill discovery works after the patch.

## Finalized
- ✅ Confirm that skill discovery works after the patch.

Each section has a precise role. The Finalized section receives the resolved items from "To follow up" — triggered when you send OK-done to Claude.


Template 2 — The long-term memory (long_memory.md)

A permanent digest, capped at 300 lines. It's not a log — it's what Haiku judges to be durable: technical facts, business rules, structuring decisions.

# Long-term memory — cmms_doc

## 2026-03-09
- The memory-session.sh hook must call `claude` from /tmp (not from the project) to avoid injecting the host CLAUDE.md into the Haiku response.
- Intermediate format chosen: JSON structured into sections → Python formats the Markdown.
- Deduplication handled by Haiku, which re-reads the existing log before writing.

When long_memory.md exceeds 300 lines, the /memory_promotion skill consolidates and archives the oldest entries.


The mechanism: the daily log feeds the long-term memory

Claude exchange
    ↓
Haiku analyzes: what to retain in today's log?
    ↓
Daily log updated (sections appended, deduplication)
    ↓
If a durable fact is detected → long_memory.md updated
    ↓
/memory_promotion (weekly/monthly) → archives old logs,
    consolidates long_memory.md if > 300 lines

The hook flow (simplified version)

The central trigger is a Stop hook — a shell script that Claude Code runs automatically at the end of each conversation turn.

Claude replies
    ↓
Stop hook triggered (settings.json)
    ↓
[Anti-loop] stop_hook_active = true? → exit 0
    ↓
Parse the JSONL transcript → extract the last human + assistant exchange
    ↓
Read the day's existing logs (to avoid duplication)
    ↓
Call Haiku from /tmp with the structured prompt
    ↓
Haiku returns a JSON by sections (objective, problems, work…)
    ↓
Python merges into the day's Markdown log
    ↓
exit 0

The anti-loop deserves an explanation: when the hook calls claude, that invocation finishes and re-triggers the Stop hook. Claude Code then injects stop_hook_active: true into the stdin JSON. The hook detects this flag first and exits immediately, with no processing.

Why call claude from /tmp? If the hook runs from the project directory, the project's CLAUDE.md gets injected into Haiku's context. The result: Haiku follows your business rules instead of returning pure JSON. Calling from /tmp neutralizes that effect.


How to set the system up

1. The memory skills

Two skills round out the hook:

  • /memory_load [project] — to call at the start of a session. Reads long_memory.md + today's and yesterday's logs. Displays a consolidated summary. This loading is manual — the memory is recorded automatically, but it does not reload on its own.
  • /memory_promotion [project] — consolidates recent logs into long_memory.md and archives logs older than 30 days. To run manually, once a week or once a month depending on your activity.

The skills are Markdown files in .claude/skills/{name}/SKILL.md. Claude Code recognizes them as /name commands.

2. Wiring up the Stop hook

The Stop hook is declared in .claude/settings.json:

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/memory-session.sh" }
        ]
      }
    ]
  }
}

When does the Stop hook fire? Claude Code fires Stop at the end of each turn — that is, after each complete Claude response, whether simple (a sentence) or complex (with tool calls). This is different from the end of a session: the hook runs after each exchange, not only when you close Claude Code.

3. Adapting the project list

In the hook, a PROJECT_PATHS variable lists the memory directories of each sub-project:

PROJECT_PATHS = {
    "root": "memory_sessions",
    "cmms_doc": "projects/cmms_doc/memory_sessions",
    "competitors": "projects/competitors/memory_sessions",
}

Haiku analyzes the exchange and determines which projects are concerned — then writes to the right files.

4. Cost

Haiku 4.5 is a deliberate choice: fast (~1-2 seconds) and cheap. With heavy use of 20 exchanges a day, the cost stays under $1/month. That's negligible for what it delivers.


What it changes in practice

With this system in place:

  • Picking back up after the weekend: /memory_load at the start of the session → Claude knows the state of the bug, the decisions made, what was left to do.
  • Multi-project: each sub-project has its own memory. Working on cmms_doc then competitors in the same session → the logs stay separate.
  • Traceability: the history of technical decisions is in the repo. git log memory_sessions/ tells the story of the project's evolution.
  • Portability: clone the repo onto another machine, and the memory is right there.

The main limitation

/memory_load is manual. The memory is captured automatically at the end of a turn, but it does not reload on its own when a new session starts. You have to remember to run /memory_load at the start of a session to benefit from it.

This is a deliberate choice — forcing automatic loading would risk polluting the context of short sessions, or ones unrelated to the project.


Attachments

The full technical specification (complete hook code, JSON format, implementation choices, known bugs) is available as an attachment:

spec_session_memory.md

A concrete example — a real daily log: the file below is the session log generated automatically the day this article was written. It illustrates what the system actually captures at each exchange: context, work produced, decisions made, points to follow up on.

session-log-exemple.md

To go further

From the Single File to a System of Contexts: Why an LLM's Memory Won't Fit in One Document One file, a few directives, and Claude does the rest — how I structured 500 emails effortlessly