Skill: xuyucheneureka/autobiographical-memory (version 1.0.0)

--- name: autobiographical-memory description: "Structured personal memory system that enables agents to persist, consolidate, and recall episodic and semantic memories across sessions. Use when: (1) recording significant events, decisions, or conversations, (2) consolidating daily logs into long-term memory, (3) recalling relevant past context before answering, (4) managing memory files (MEMORY.md, daily notes), (5) reasoning about what to remember vs forget." --- # Autobiographical Memory ## Core Concepts Memory has two complementary layers: | Layer | File | What it stores | |-------|------|---------------| | **Episodic** | `memory/YYYY-MM-DD.md` | Raw daily events, conversations, decisions, observations | | **Semantic** | `MEMORY.md` | Curated knowledge: user preferences, facts, lessons, identity | **The memory lifecycle:** Capture → Consolidate → Recall → Review ## Quick Start ```markdown # Recording an event (episodic) Append to `memory/YYYY-MM-DD.md`: - Met with [person] about [topic]. Decision: [outcome]. - User prefers [preference]. Updated MEMORY.md. # Recalling before responding 1. Run `memory_search` with relevant keywords 2. If results are thin, read recent `memory/YYYY-MM-DD.md` files 3. Check `MEMORY.md` for long-term facts # Consolidating (periodic maintenance) 1. Read recent daily files (last 7-30 days) 2. Extract significant items → update MEMORY.md 3. Remove stale entries from MEMORY.md ``` ## Episodic Memory — Daily Notes ### What to Record Always write to daily notes for: - **Decisions** with rationale: "Chose X over Y because Z" - **User preferences** discovered implicitly or explicitly - **Important conversations** — summary, not transcript - **Mistakes & lessons** — what went wrong, what to do differently - **Project milestones** — what was done, what's blocked - **Identity changes** — if SOUL.md, USER.md, or other self-files changed ### What to Skip - Routine operations ("checked email, nothing new") - Transient states ("feeling tired") - Content better stored elsewhere (code snippets in projects, API keys in config) - Something the user explicitly said doesn't matter ### Format Convention ```markdown ## Events - [event description] ## Decisions - [decision + rationale] ## Observations - [insights or patterns noticed] ## Notes - [anything else worth remembering] ``` ## Semantic Memory — MEMORY.md ### Structure ``` ## User Preferences - Directly stated preferences without inference ## Project Context - Active projects and their status ## Relationships & People - Key people, roles, context ## Technical Environment - Tools, config, quirks discovered ## Lessons Learned - Mistakes to avoid, patterns that work ``` ### When to Update MEMORY.md - User states a clear preference - A project direction is set - A mistake teaches a lesson worth preserving - Every few days during heartbeat: consolidate from daily notes ### When to Remove from MEMORY.md - Project is done / abandoned - Preference was explicitly reversed - Information is now obvious context (e.g. "user speaks Chinese" — that's already in USER.md) - Stale for >3 months without reference ## Consolidation Workflow Suitable for heartbeat routines. Do this every 3-7 days: ``` 1. List memory/*.md, sort by date (newest first) 2. Read files since last consolidation 3. For each significant item: a. Is it already in MEMORY.md? → Skip or update b. Is it transient? → Skip (leave in daily note) c. Is it important? → Add to MEMORY.md 4. Read MEMORY.md for stale entries → remove or archive 5. Write updated MEMORY.md ``` ## Recall Strategy Before answering questions about prior work, people, preferences, or context: 1. **Search first**: `memory_search(query="relevant terms")` — this searches both daily notes and MEMORY.md 2. **Narrow scope**: If search returns weak results, try multiple query phrasings 3. **Deep dive**: For specific periods, `memory_get(path="memory/YYYY-MM-DD.md")` to read raw daily notes 4. **Cross-reference**: Check USER.md, SOUL.md, TOOLS.md for identity/preference info ### When Recall Fails - Say clearly "I checked my records and don't have information about that" - Don't fabricate memories - If the user says "don't you remember? I told you X" — apologize and record it properly this time ## The Forgetting Curve Not everything needs to persist. Use these filters: - **Keep in daily notes**: Everything noteworthy for 30-90 days - **Promote to MEMORY.md**: Only what's likely to be needed again - **Delete/archive**: What's clearly obsolete after review ## References - For detailed consolidation scripts: see [references/consolidation.md](references/consolidation.md) - For memory search patterns: see [references/recall-patterns.md](references/recall-patterns.md) ## Scripts - `scripts/consolidate.py` — Scan recent daily notes and suggest MEMORY.md updates - `scripts/stats.py` — Memory file statistics (sizes, dates, coverage)

path: references/consolidation.md
content: # Consolidation Reference

This file provides detailed guidance for the consolidation workflow.

## Full Consolidation Run

A thorough consolidation, recommended every 7 days:

```python
# Pseudocode for the consolidation process
1. Collect all memory/*.md files
2. Sort by modification time (newest first)
3. Read the N most recent files (N = days since last consolidation + 2)
4. For each file:
   a. Extract sections: Events, Decisions, Observations, Notes
   b. Classify each item:
      - PERSISTENT: user preferences, decisions with long-term impact, lessons
      - PROJECT: ties to an active project
      - SOCIAL: relationship details, important interactions
      - TRANSIENT: one-off status updates, routine mentions
   c. For PERSISTENT items → propose addition to MEMORY.md
   d. For PROJECT items → update relevant project context
   e. For TRANSIENT items → leave in daily note, don't promote
5. Check MEMORY.md for:
   - Duplicates (same info already there)
   - Stale entries (old projects, reversed preferences)
   - Outdated information
6. Write updated MEMORY.md with consolidated content
```

## Lightweight Consolidation (Heartbeat)

A quick check, suitable for every heartbeat cycle:

```
1. Read today's daily note if it exists
2. Scan for any MUST-REMEMBER items
3. If found, update MEMORY.md immediately
4. Otherwise, skip
```

## Categorization Heuristics

| Cue | Likely Category | Action |
|-----|----------------|--------|
| "I prefer/like/don't like..." | PERSISTENT | Add to MEMORY.md |
| "We decided to..." | PERSISTENT | Add to MEMORY.md |
| "The project X is..." | PROJECT | Update project context |
| "[X person] said..." | SOCIAL | Add if significant relationship |
| "Today I checked..." | TRANSIENT | Leave in daily note |
| "I learned that..." | PERSISTENT | Add to MEMORY.md if broadly useful |


path: references/recall-patterns.md
content: # Recall Patterns Reference

## Search Query Strategies

### Single Domain Search
When you know the domain but not the exact terms:
```
memory_search(query="task name status")     # Too specific
memory_search(query="task project alpha")   # Better — broader terms
memory_search(query="project status")       # Also try
```

### Temporal Search
When you know roughly when something happened:
```
# If today is April 2026, try:
memory_get(path="memory/2026-04-*.md")  # Globbing not supported, use specific dates
memory_get(path="memory/2026-04-15.md")
memory_get(path="memory/2026-04-14.md")
```

### Multi-angle Search
If the first search is weak, try different phrasing:
```
memory_search(query="user name preference")  → weak
memory_search(query="preferred tool")        → try
memory_search(query="what they like")        → try
```

## Priority-Sensitive Recall

**High priority** — always check MEMORY.md + recent daily notes:
- User preferences and stated dislikes
- Active project context
- Recent decisions (<7 days)

**Medium priority** — check MEMORY.md + search daily notes:
- Technical environment quirks
- Social relationships
- Lessons learned

**Low priority** — search only, don't proactively read:
- Obsolete project details
- Routine logs

## Cross-File Memory

Some information lives outside `memory/`:

| File | Stores | When to Check |
|------|--------|--------------|
| MEMORY.md | Long-term curated | Always, before every task |
| USER.md | User identity | When understanding context |
| SOUL.md | Agent identity | When deciding tone/manner |
| TOOLS.md | Environment specifics | When referencing tools |
| IDENTITY.md | Agent persona | When communicating style matters |
| AGENTS.md | Core rules | When uncertain about protocols |
| memory/*.md | Raw logs | When MEMORY.md lacks detail |

## Recovery from Weak Recall

If memory_search returns nothing useful:

1. **Ask specifically**: "I checked my records — do you remember when we discussed this? A rough date would help me find it."
2. **Accept the gap**: Sometimes you just don't have it. Say so.
3. **Start fresh**: Record the new information properly so future-you has it.


path: scripts/consolidate.py
content: #!/usr/bin/env python3
"""
Consolidation helper: scan recent daily memory files and suggest
MEMORY.md updates. Run periodically (every 3-7 days) to identify
what deserves promotion from episodic daily notes to semantic memory.

Usage:
    python3 consolidate.py [--days N] [--memory-dir DIR]
"""

import argparse
import os
import re
import sys
from datetime import datetime, timedelta
from pathlib import Path

CATEGORIES = {
    "preference": {
        "patterns": [r"(喜欢|不喜欢|prefer|like|don't like|喜欢|preferred tool|favorite|偏好|习惯)"],
        "label": "PREFERENCE — consider adding to MEMORY.md's User Preferences section",
    },
    "decision": {
        "patterns": [r"(决定|决策|decided|决定|选择|choose|选|settled on|went with)"],
        "label": "DECISION — consider adding to MEMORY.md as a long-term record",
    },
    "lesson": {
        "patterns": [r"(教训|learned|学到|lesson|学到了|下次注意|mistake|error|坑|踩坑)"],
        "label": "LESSON — consider adding to MEMORY.md's Lessons Learned section",
    },
    "project": {
        "patterns": [r"(项目|project|repo|仓库|deployed|上线|发布|release|版本)"],
        "label": "PROJECT — update project context in MEMORY.md",
    },
    "relationship": {
        "patterns": [r"(认识|met|见了|见了|together|co-work|同事|朋友)"],
        "label": "SOCIAL — significant relationship detail",
    },
}


def find_memory_files(memory_dir: Path, days: int) -> list[Path]:
    """Find daily note files within the specified number of days."""
    cutoff = datetime.now() - timedelta(days=days)
    files = []
    for f in sorted(memory_dir.glob("*.md"), reverse=True):
        # Parse date from filename YYYY-MM-DD.md
        m = re.match(r"(\d{4}-\d{2}-\d{2})", f.stem)
        if m:
            try:
                fdate = datetime.strptime(m.group(1), "%Y-%m-%d")
                if fdate >= cutoff:
                    files.append(f)
            except ValueError:
                continue
    return files


def classify_line(line: str) -> str | None:
    """Classify a line into a category if it matches known patterns."""
    for cat, info in CATEGORIES.items():
        for pat in info["patterns"]:
            if re.search(pat, line, re.IGNORECASE):
                return cat
    return None


def scan_files(files: list[Path]) -> dict[str, list[tuple[str, str]]]:
    """Scan files and return categorized items."""
    categorized: dict[str, list[tuple[str, str]]] = {c: [] for c in CATEGORIES}
    for f in files:
        content = f.read_text(encoding="utf-8")
        # Simple line-by-line scan (skip headers and empty lines)
        for line in content.splitlines():
            line = line.strip()
            if not line or line.startswith("#") or line.startswith("<!--"):
                continue
            cat = classify_line(line)
            if cat:
                categorized[cat].append((f.stem, line))
    return categorized


def print_report(categorized: dict[str, list[tuple[str, str]]]):
    """Print a human-readable consolidation suggestion report."""
    print("=" * 60)
    print("Memory Consolidation Report")
    print(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
    print("=" * 60)

    has_content = False
    for cat, label_info in [
        ("preference", CATEGORIES["preference"]["label"]),
        ("decision", CATEGORIES["decision"]["label"]),
        ("lesson", CATEGORIES["lesson"]["label"]),
        ("project", CATEGORIES["project"]["label"]),
        ("relationship", CATEGORIES["relationship"]["label"]),
    ]:
        items = categorized.get(cat, [])
        if items:
            has_content = True
            print(f"\n## {label_info}")
            print("-" * 40)
            for date, line in items:
                # Clean up markdown bullets
                clean = re.sub(r"^[-*+]\s*", "", line)
                print(f"  [{date}] {clean}")

    if not has_content:
        print("\nNo significant items found for promotion.")
        print("Memory is up-to-date or all recent entries are transient.")

    print("\n" + "=" * 60)
    print("Review the items above and decide what to add to MEMORY.md.")
    print("Items already in MEMORY.md can be skipped.")


def main():
    parser = argparse.ArgumentParser(description="Suggest MEMORY.md updates from daily notes")
    parser.add_argument("--days", type=int, default=7, help="Days of history to scan (default: 7)")
    parser.add_argument(
        "--memory-dir",
        type=str,
        default=None,
        help="Path to memory directory (default: <workspace>/memory)",
    )
    args = parser.parse_args()

    # Find memory directory
    if args.memory_dir:
        memory_dir = Path(args.memory_dir)
    else:
        # Try to find workspace from common locations
        candidates = [
            Path.cwd() / "memory",
            Path.home() / ".openclaw" / "workspace" / "memory",
        ]
        memory_dir = None
        for c in candidates:
            if c.exists() and c.is_dir():
                memory_dir = c
                break

    if not memory_dir or not memory_dir.exists():
        print("Error: Could not find memory directory.", file=sys.stderr)
        print("Specify with --memory-dir or run from your workspace.", file=sys.stderr)
        sys.exit(1)

    files = find_memory_files(memory_dir, args.days)
    if not files:
        print(f"No memory files found in the last {args.days} days.")
        sys.exit(0)

    print(f"Scanning {len(files)} memory file(s)...")
    categorized = scan_files(files)
    print_report(categorized)


if __name__ == "__main__":
    main()


path: scripts/stats.py
content: #!/usr/bin/env python3
"""
Memory stats: show sizes, dates, coverage, and health of your memory files.

Usage:
    python3 stats.py [--memory-dir DIR]
"""

import argparse
import os
import re
import sys
from datetime import datetime, date
from pathlib import Path


def gather_stats(memory_dir: Path) -> dict:
    """Gather statistics from memory files."""
    stats = {
        "daily_files": [],
        "total_daily_size": 0,
        "memories_file": None,
        "memories_size": 0,
        "gaps": [],
        "monthly_counts": {},
    }

    for f in sorted(memory_dir.glob("*.md")):
        m = re.match(r"(\d{4}-\d{2})", f.stem)
        if f.stem == "README":
            continue
        if m:
            month_key = m.group(1)
            stats["monthly_counts"].setdefault(month_key, 0)
            stats["monthly_counts"][month_key] += 1
        # Check if it's a daily note (date format)
        dm = re.match(r"(\d{4}-\d{2}-\d{2})", f.stem)
        if dm:
            fdate = dm.group(1)
            fsize = f.stat().st_size
            stats["daily_files"].append({"date": fdate, "size": fsize, "path": f.name})
            stats["total_daily_size"] += fsize
            continue

        # MEMORY.md, USER.md, etc.
        if f.stem == "MEMORY":
            stats["memories_file"] = f
            stats["memories_size"] = f.stat().st_size

    return stats


def find_gaps(daily_files: list[dict]) -> list[str]:
    """Find notable gaps (>3 days) in daily note coverage."""
    if not daily_files:
        return []
    dates = sorted([d["date"] for d in daily_files])
    gaps = []
    for i in range(len(dates) - 1):
        d1 = date.fromisoformat(dates[i])
        d2 = date.fromisoformat(dates[i + 1])
        gap_days = (d2 - d1).days
        if gap_days > 3:
            gaps.append(f"{dates[i]} → {dates[i+1]} ({gap_days - 1} days missing)")
    return gaps


def print_report(stats: dict):
    """Print a formatted stats report."""
    print("=" * 50)
    print("📊 MEMORY STATS")
    print("=" * 50)

    # MEMORY.md
    if stats["memories_file"]:
        size_kb = stats["memories_size"] / 1024
        print(f"\n📝 MEMORY.md: {size_kb:.1f} KB")
    else:
        print("\n⚠️  MEMORY.md not found in this directory")

    # Daily notes
    daily = stats["daily_files"]
    print(f"\n📅 Daily note files: {len(daily)}")
    total_kb = stats["total_daily_size"] / 1024
    print(f"   Total size: {total_kb:.1f} KB")

    if daily:
        sizes = [d["size"] for d in daily]
        print(f"   Avg file size: {sum(sizes)/len(sizes)/1024:.1f} KB")
        print(f"   Largest: {max(sizes)/1024:.1f} KB ({max(daily, key=lambda x: x['size'])['path']})")

    # Monthly breakdown
    monthly = stats["monthly_counts"]
    if monthly:
        print(f"\n📆 Monthly coverage:")
        for month in sorted(monthly.keys()):
            count = monthly[month]
            bar = "█" * min(count, 20)
            print(f"   {month}: {count:2d} days {bar}")

    # Gaps
    gaps = stats.get("gaps", [])
    if gaps:
        print(f"\n⚠️  Notable gaps (>3 days):")
        for g in gaps:
            print(f"   {g}")
    else:
        print(f"\n✅ No significant gaps detected")

    # Coverage period
    if daily:
        dates = sorted([d["date"] for d in daily])
        print(f"\n📆 Coverage: {dates[0]} → {dates[-1]}")
        coverage_days = len(daily)
        total_days = (date.fromisoformat(dates[-1]) - date.fromisoformat(dates[0])).days + 1
        pct = (coverage_days / total_days) * 100 if total_days > 0 else 0
        print(f"   Density: {coverage_days}/{total_days} days ({pct:.0f}%)")

    print("=" * 50)


def main():
    parser = argparse.ArgumentParser(description="Memory file statistics")
    parser.add_argument(
        "--memory-dir",
        type=str,
        default=None,
        help="Path to memory directory (default: <workspace>/memory)",
    )
    args = parser.parse_args()

    if args.memory_dir:
        memory_dir = Path(args.memory_dir)
    else:
        candidates = [
            Path.cwd() / "memory",
            Path.home() / ".openclaw" / "workspace" / "memory",
        ]
        memory_dir = None
        for c in candidates:
            if c.exists() and c.is_dir():
                memory_dir = c
                break

    if not memory_dir or not memory_dir.exists():
        print("Error: Could not find memory directory.", file=sys.stderr)
        print("Specify with --memory-dir or run from your workspace.", file=sys.stderr)
        sys.exit(1)

    stats = gather_stats(memory_dir)
    stats["gaps"] = find_gaps(stats["daily_files"])
    print_report(stats)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)