Memory workflow

May 26, 2026 · View on GitHub

This is what working with the 3-layer memory architecture actually looks like, end-to-end, on a single project.

The setup

~/.claude/projects/<cwd-hash>/memory/
├── MEMORY.md                  # always loaded · ≤200 lines
├── _global/                   # cross-project (always-loaded global memories)
│   └── user_profile.md
├── project-a/                 # project-scoped (lazy-loaded)
│   ├── _INDEX.md
│   ├── feedback_deploy_path.md
│   └── reference_s3_creds.md
└── project-b/                 # another project (lazy-loaded)
    ├── _INDEX.md
    └── feedback_no_pua_tone.md

MEMORY.md lists only the always-loaded global section + project routing table (one line per project). Project specifics live in <scope>/_INDEX.md and are loaded only when keywords from that scope appear in conversation.

Day 1 — first conversation with a new project

You start a session and the agent reads MEMORY.md. It sees:

# Memory Index

## [global] always loaded
- [User profile](_global/user_profile.md) — calls user "boss", prefers terse replies
- [No PUA tone](_global/feedback_no_pua_tone.md) — no fake enthusiasm, no emoji decoration

## [projects] lazy load on keyword
| scope     | trigger keywords                          |
|-----------|-------------------------------------------|
| project-a | "project-a", "the SaaS", "billing"        |
| project-b | "project-b", "the trading bot", "binance" |

You ask: "how should I deploy project-a?"

The agent sees project-a in your message → opens project-a/_INDEX.md → finds the feedback_deploy_path.md entry → reads it → answers based on real history, not generic advice.

Project-b context never enters this session. Zero cross-contamination.

Day 1 (continued) — adding a memory

After deploying, you tell the agent: "the deploy path is /var/www/project-a, not /opt/app — I just spent 30 min debugging this."

The agent writes a new memory:

# ~/.claude/projects/<cwd-hash>/memory/project-a/feedback_deploy_path.md
---
name: project-a deploy path
description: production deploy lives at /var/www/project-a (not /opt/app); cost the boss 30min today
type: feedback
expires_when: superseded_by_better_path_or_project_archived
updated: 2026-05-26
---

Production deploy is `/var/www/project-a`, NOT `/opt/app`.

**Why:** Today (2026-05-26) the boss spent 30 minutes debugging because I assumed `/opt/app`.

**How to apply:** Before suggesting any deploy command for project-a, default to `/var/www/project-a`. If the user mentions `/opt/app`, push back that's the wrong path.

Then updates project-a/_INDEX.md:

+ - [⭐ deploy path](feedback_deploy_path.md) — /var/www/project-a (not /opt/app) · cost 30min

The frontmatter expires_when tells future-you when this memory can be deleted.

Day 30 — same project, different agent session

You start fresh. Agent reads MEMORY.md (sees only global + routing table). You say: "redeploy project-a".

Trigger fires → project-a/_INDEX.md loads → the deploy_path memory loads → agent uses /var/www/project-a immediately, no asking, no /opt/app mistake.

The fact survived the 30-day gap because it was written down on day 1.

Day 45 — searching memory

You vaguely remember telling Claude about something months ago. You don't remember which project:

$ ~/.claude/scripts/memory-search.sh "deploy path debugging"

  4.21  project-a/feedback_deploy_path.md
        └─ /var/www/project-a (not /opt/app) · cost 30min

  1.83  project-b/feedback_postdeploy_smoketest.md
        └─ binance api ping must precede any trade enable

BM25-style relevance ranking across all memory files in <50ms. No vector DB, no embeddings, no LangChain — just bash + awk + a tuned scoring function. Works up to ~1000 files; past that, swap for a vector backend.

Day 60 — memory health check

$ ~/.claude/scripts/memory-health.sh

[OK] MEMORY.md = 142 lines (limit 200)
[OK] 47 memory files (markdown-only threshold 1000)
[OK] all MEMORY.md links resolve
[OK] all files have valid frontmatter
[WARN] 3 files untouched >90d
        - project-a/reference_dead_endpoint.md
        - project-b/feedback_yolo_mode.md
        - _global/reference_old_vendor.md
        review their `expires_when` clause

== SUMMARY: 0 err / 1 warn ==

The warn surface flags stale memories. You triage each — keep, update, or delete. The system stays clean by design instead of by hope.

The principle

Memory is for facts, not behavior. Behavior goes in rules/ (which auto-loads). Facts go in memory/ (which loads on demand).

If you find yourself writing the same instruction every project ("always use pnpm, never npm"), that's a rules/ candidate. If it's project-specific ("project-a's pnpm-lock is in apps/web/"), it's memory/.

Getting this split right keeps both surfaces small and load fast.

Quick reference

OperationCommand
Audit memory health~/.claude/scripts/memory-health.sh
Search memory~/.claude/scripts/memory-search.sh "query"
Search a single scope~/.claude/scripts/memory-search.sh -s project-a "query"
Search by type~/.claude/scripts/memory-search.sh -t feedback "query"
Read a specific memorycat ~/.claude/projects/*/memory/<scope>/<file>.md

See memory-system/memory-optimization.md for the 8 hard rules and the full design rationale.