| name | memory-manager |
| description | Persistent project memory using SQLite + FTS5 + BM25. Fast, zero dependencies (stdlib only), production-ready. Progressive disclosure with 3 layers (compact/index/detailed).
|
| version | 2.0.0 |
| author | forgewright |
| tags | ["memory","sqlite","fts5","persistence","knowledge-base","context"] |
Memory Manager Skill
Identity: The long-term memory for AI agents. Every decision, blocker, and lesson is preserved across sessions — so context isn't lost when models reset.
Critical Rules
| Rule | Why It Matters |
|---|
| Store AFTER each task | If you don't save it, it's lost when context resets. Memory is only useful if it's populated. |
| Query BEFORE starting work | Don't re-derive context. Query memory first — the answer may already exist. |
| Redact secrets automatically | Never store API keys, passwords, or tokens. Auto-redaction prevents credential leaks. |
| Layer your retrieval | L1 (compact) first for overview, L2 (search) for details, L3 (full) on-demand. |
Architecture
System Design
┌─────────────────────────────────────────────────────────────────┐
│ MEMORY SYSTEM ARCHITECTURE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌───────────┐ │
│ │ CLI/API │ ──▶ │ MemoryDB │ ──▶ │ SQLite │ │
│ │ (mem0-v2) │ │ (Python) │ │ + FTS5 │ │
│ └──────────────┘ └──────────────────┘ └───────────┘ │
│ │ │ │ │
│ │ ┌──────┴───────┐ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌────────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐ │
│ │ add() │ │ search() │ │ list() │ │ gc() │ │
│ │ store obs │ │ BM25 FTS │ │by category│ │ clean up │ │
│ └────────────┘ └───────────┘ └───────────┘ └──────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ TOKEN BUDGET OPTIMIZATION │ │
│ │ │ │
│ │ L1 Index: ~15 tokens/result (overview) │ │
│ │ L2 Search: ~60 tokens/result (BM25 ranked) │ │
│ │ L3 Full: ~200 tokens/result (complete detail) │ │
│ │ │ │
│ │ Max injection: 500 tokens (configurable) │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Technology Stack
| Component | Technology | Why |
|---|
| Database | SQLite + FTS5 | WAL mode, crash-safe, concurrent reads, full-text search |
| Search | BM25 ranking (FTS5) | Production-grade relevance scoring |
| Token Optimization | 3-layer progressive disclosure | 75% token reduction vs naive approach |
| Dependencies | Zero (stdlib only) | No API key, no pip install, no external service |
When to Use Memory
Step 0.5 — Memory Retrieval (MANDATORY on every request)
┌─────────────────────────────────────────────────────────────────────┐
│ Step 0.5 — MEMORY RETRIEVAL (MANDATORY) │
├─────────────────────────────────────────────────────────────────────┤
│ Run BEFORE interpreting the user's request: │
│ │
│ 1. Extract keywords from the user's request (nouns, verbs) │
│ 2. Run: bash scripts/memory-retrieve.sh "<request>" │
│ OR: python3 scripts/mem0-v2.py search "<keywords>" --limit 3│
│ 3. Also run: bash scripts/memory-suggest.sh "<request>" │
│ 3. If relevant memories found: │
│ → Inject as MEMORY BLOCK at top of context │
│ → Note: "Found N relevant memories from previous sessions" │
│ 4. Also load: │
│ - .forgewright/subagent-context/CONVERSATION_SUMMARY.md │
│ - .forgewright/memory-bank/activeContext.md │
│ - .forgewright/business-analyst/handoff/ba-package.md (if exists)│
│ 5. Log: "✓ Memory retrieval done — N memories loaded" │
│ │
│ Max tokens: 500 (configurable via MEM0_MAX_TOKENS) │
└─────────────────────────────────────────────────────────────────────┘
Evidence-first note: Every assumption about project history should be verified against mem0 before acting. If mem0 has a memory about a previous decision, cite it. If it contradicts the assumption, update the assumption.
Session Lifecycle Hooks
| Hook | Trigger | Action |
|---|
| SESSION_START | Pipeline begins | Run Step 0.5 — search project context + request keywords |
| TURN_CLOSE | After every user request | Store request/done/open summary |
| PHASE_COMPLETE | After DEFINE/BUILD/HARDEN/SHIP | Store phase completion summary |
| GATE_DECISION | After Gate 1/2/3 | Store gate decision and feedback |
| SESSION_END | Pipeline completes | Store final session summary |
| ERROR | Task failure | Store blocker details |
Query Patterns
| Situation | Query Approach |
|---|
| Start new session | search "<project-name> recent" |
| Before complex task | search "<task-keywords>" |
| Finding decisions | list --category decisions --limit 10 |
| Checking blockers | list --category blockers --limit 5 |
| Getting project context | index "<project-name>" --limit 30 |
| Full detail on item | get <id> |
Memory Model
Category Weights
Categories affect search relevance and GC prioritization:
| Category | Weight | Examples | GC Priority |
|---|
| decisions | 10 | "Chose PostgreSQL because...", "Architecture decision X" | Low (keep) |
| architecture | 8 | "Using Next.js + Prisma", "Service boundaries" | Low (keep) |
| blockers | 7 | "Waiting on API key", "Dependency conflict" | Medium |
| session | 6 | "Session completed: built auth", "Turn summary" | Medium |
| tasks | 5 | "BUILD complete: 142 tests pass" | Medium |
| conversation | 4 | Auto-generated conversation summaries | High |
| general | 4 | User-added notes | Medium |
| git-activity | 3 | Recent commits | High |
| ingested | 2 | Project file summaries | High |
| reference | 1 | Imported external docs | High (clean up) |
CLI Commands
Primary CLI: scripts/mem0-v2.py
python3 scripts/mem0-v2.py setup
python3 scripts/mem0-v2.py add "Decided to use JWT + refresh tokens for auth" --category decisions
python3 scripts/mem0-v2.py add "Migration from REST to GraphQL planned for Q2" --category architecture --tags graphql,rest,migration
python3 scripts/mem0-v2.py add "User authentication via Auth0" --category decisions --id auth-system-001
python3 scripts/mem0-v2.py add --batch memories.txt
python3 scripts/mem0-v2.py index "project context" --limit 30
python3 scripts/mem0-v2.py search "authentication flow" --limit 5
python3 scripts/mem0-v2.py get 123
python3 scripts/mem0-v2.py list --limit 50
python3 scripts/mem0-v2.py list --category decisions --limit 20
python3 scripts/mem0-v2.py list --tag authentication --limit 10
python3 scripts/mem0-v2.py list --recent 10
python3 scripts/mem0-v2.py stats
python3 scripts/mem0-v2.py gc --max-obs 200
python3 scripts/mem0-v2.py gc --category session --older-than 7d
python3 scripts/mem0-v2.py export --format json > memories.json
python3 scripts/mem0-v2.py search "auth" --format json
python3 scripts/mem0-v2.py stats --by-category
python3 scripts/mem0-v2.py related 123 --limit 5
Python API
Quick Start
import sys
sys.path.insert(0, 'scripts')
from mem0_v2 import MemoryDB, get_db
db = get_db()
result = db.add(
"Decided to use JWT for auth with 15min access token + 7d refresh token",
category="decisions"
)
print(f"Created memory with ID: {result['id']}")
result = db.add(
"GraphQL migration planned for Q2 2024",
category="architecture",
tags=["graphql", "rest", "migration"]
)
result = db.add(
"Auth system chosen: Auth0",
category="decisions",
custom_id="auth-system-001"
)
index = db.memory_index("forgewright project", limit=30)
for item in index["results"]:
print(f"ID: {item['id']}, Preview: {item['preview']}")
results = db.search("authentication", limit=5)
for result in results["results"]:
print(f"ID: {result['id']}, Score: {result['score']}, Text: {result['text']}")
detail = db.memory_get(123)
print(f"Full text: {detail['text']}")
print(f"Metadata: {detail['metadata']}")
memories = db.list_all(category="decisions", limit=10, offset=0)
memories = db.list_all(tag="authentication", limit=10)
memories = db.list_all(recent=20)
stats = db.stats()
print(f"Total memories: {stats['total']}")
print(f"By category: {stats['by_category']}")
print(f"Storage size: {stats['size_mb']}MB")
removed = db.gc(max_obs=200)
print(f"Removed {removed['count']} memories")
removed = db.gc(category="session", older_than_days=7)
print(f"Removed {removed['count']} old session memories")
db.update(123, text="Updated: Now using RS256 instead of HS256")
db.delete(123)
db.delete(category="reference")
Token Optimization Strategy
Progressive Disclosure Layers
| Layer | Method | Tokens/Result | Use Case |
|---|
| L1: Compact | index() | ~15 | Always first — quick overview |
| L2: Search | search() | ~60 | Top matches get detail |
| L3: Full | get() | ~200 | On-demand for specific items |
Token Budget Management
MAX_TOKENS = 500
def smart_retrieve(query: str, max_tokens: int = 500):
overview = db.memory_index(query, limit=30)
tokens_used = len(overview["results"]) * 15
remaining = max_tokens - tokens_used
if remaining > 60:
details_needed = remaining // 60
results = db.search(query, limit=details_needed)
return merge_results(overview, results)
return overview
def safe_retrieve(query: str, max_tokens: int = 500):
results = []
for layer in ['index', 'search', 'get']:
if token_budget_exceeded(results, max_tokens):
break
layer_results = fetch_layer(layer, query)
results.extend(layer_results)
return results
When to Retrieve
| Situation | Approach |
|---|
| Session start | Search project name + keywords, limit top-5 |
| Before complex task | Search task keywords, limit top-3 |
| At gate decisions | Fetch relevant decisions/blockers by category |
| After completing work | Store summary + decisions |
| On error | Store blocker with context |
Safety Features
Automatic Secret Redaction
The CLI automatically redacts patterns matching:
| Pattern Type | Examples |
|---|
| API Keys | sk-*, key-*, api_key, apikey |
| Tokens | Bearer *, token=*, jwt.* |
| Passwords | password=*, passwd=* |
| Secrets | secret=*, private_key, -----BEGIN RSA PRIVATE KEY----- |
| Connection Strings | postgres://user:pass@host |
| AWS Keys | AKIA*, aws_secret |
input_text = "API key: sk-1234567890abcdef"
input_text = "User reported issue with login flow"
.memignore File
Create .memignore at project root to exclude files from ingestion:
# Patterns to exclude from memory ingestion
.env
*.log
node_modules/
dist/
build/
.git/
secrets/
credentials.json
Strict Compliance & Forced Enablement
Under the Forgewright Compliance Policy, memory is a non-negotiable hard constraint and cannot be disabled.
If any attempt is made to bypass or disable memory (using MEM0_DISABLED=true or FORGEWRIGHT_SKIP_MEM0=1), the system will automatically override the bypass flag to false, log an enforcement warning, and force-enable the full memory mechanism.
Configuration
Environment Variables
MEM0_PROJECT_ID=my-project
MEM0_MAX_TOKENS=500
MEM0_MAX_OBS=200
MEM0_REDACT_SECRETS=true
MEM0_DISABLED=false
MEM0_DB_PATH=.forgewright/memory.db
Project Config (mem0.yaml)
project: my-project
database:
path: .forgewright/memory.db
wal_mode: true
retrieval:
max_tokens: 500
layers:
index:
tokens_per_result: 15
default_limit: 30
search:
tokens_per_result: 60
default_limit: 5
full:
tokens_per_result: 200
default_limit: 3
gc:
max_observations: 200
category_weights:
decisions: 10
architecture: 8
blockers: 7
session: 6
tasks: 5
conversation: 4
general: 4
git-activity: 3
ingested: 2
reference: 1
redaction:
enabled: true
patterns:
- api_key
- password
- token
- secret
- private_key
Integration with Forgewright Pipeline
Active Lifecycle Integration
class MemoryLifecycleHooks:
"""Hooks for automatic memory management"""
def on_session_start(self, project: str, user_request: str):
"""Query relevant context before starting work"""
db = get_db()
context = db.search(f"{project} {user_request}", limit=5)
return context
def on_phase_complete(self, phase: str, summary: str):
"""Store phase completion"""
db = get_db()
db.add(
f"Phase {phase} completed: {summary}",
category="tasks",
tags=[phase.lower()]
)
def on_gate_decision(self, gate: int, decision: str, feedback: str):
"""Store gate decisions"""
db = get_db()
db.add(
f"Gate {gate} decision: {decision}. Feedback: {feedback}",
category="decisions"
)
def on_error(self, task: str, error: str, context: dict):
"""Store blocker details"""
db = get_db()
db.add(
f"BLOCKER: {task} failed: {error}",
category="blockers",
metadata=context
)
def on_session_end(self, summary: str, tasks_completed: list):
"""Store session summary"""
db = get_db()
db.add(
f"Session completed: {summary}. Tasks: {', '.join(tasks_completed)}",
category="session"
)
Session Tracking
python3 scripts/mem0-v2.py list --category session --recent 5
python3 scripts/mem0-v2.py search "session" --limit 3
File Layout
forgewright/
├── skills/memory-manager/
│ └── SKILL.md ← this file
├── scripts/
│ ├── mem0-v2.py ← PRIMARY CLI (SQLite + FTS5)
│ ├── mem0-cli.py ← DEPRECATED (TF-IDF + JSONL)
│ ├── local_memory.py ← DEPRECATED (ChromaDB + embeddings)
│ ├── migrate-chroma-to-sqlite.py ← Migration helper
│ └── memory_session.sh ← Shell helpers
└── .forgewright/
├── memory.db ← PRIMARY storage (SQLite + FTS5)
├── memory.db-wal ← WAL journal
├── memory.db-shm ← Shared memory
├── memory.jsonl ← Legacy storage (read-only, migrate then delete)
├── memory_db/ ← DEPRECATED ChromaDB storage
└── project-profile.json ← project fingerprint (committed)
Migration
From Old Systems
python3 scripts/mem0-v2.py migrate
python3 scripts/migrate-chroma-to-sqlite.py
python3 scripts/mem0-v2.py stats
Migration Verification
grep -r "mem0-cli\|local_memory" .
python3 scripts/mem0-v2.py stats
echo "Old (JSONL):" && wc -l .forgewright/memory.jsonl
echo "New (SQLite):" && python3 scripts/mem0-v2.py list --format json | jq length
Deprecated Systems
| System | Status | Migration |
|---|
| mem0-cli.py | DEPRECATED | Migrate to mem0-v2.py |
| local_memory.py | DEPRECATED | Migrate to mem0-v2.py |
| ChromaDB storage | DEPRECATED | Migrate to SQLite |
Advanced Features
RRF Fusion (Future)
Hybrid search combining multiple ranking signals:
results = db.rrf_fusion(
sources=[
("fts5_bm25", search_results),
("category_boost", weighted_results),
("recency", time_decayed_results)
],
weights=[0.6, 0.3, 0.1]
)
Observation Links
Link related observations:
Session Tracking (Advanced)
db.create_session(
request_summary="Implement user authentication",
completed_tasks=["auth-flow", "jwt-tokens"],
next_steps=["add refresh tokens", "implement logout"],
notes="Using Auth0, JWT with RS256"
)
sessions = db.list_sessions(limit=5)
for session in sessions:
print(f"{session['date']}: {session['request_summary']}")
Common Mistakes
| Mistake | Fix |
|---|
| Not storing after tasks | Automate via lifecycle hooks. Store after every meaningful action. |
| Querying without budget | Always set --limit or max_tokens. Avoid unbounded retrieval. |
| Storing secrets | Memory auto-redacts, but avoid manually bypassing. |
| Ignoring GC | Old memories accumulate. Run GC periodically. |
| Using wrong category | Categories affect search relevance. Use consistently. |
| One-time import | Memory only works if populated continuously. Make it a habit. |
Execution Checklist