Skill: harrey401/brain-cms (version 1.0.0)

--- name: brain-cms description: Continuum Memory System (CMS) for OpenClaw agents. Replaces flat MEMORY.md with a brain-inspired multi-layer memory architecture — semantic schemas, a hippocampal router (INDEX.md), vector store (LanceDB + nomic-embed-text), and automated NREM/REM sleep cycles for consolidation. Based on neuroscience research (LTP, spreading activation, CMS theory). Use when setting up persistent agent memory, improving context efficiency, or reducing token cost on long-running agents. Triggers: brain, memory system, CMS, long-term memory, vector store, sleep cycle, NREM, REM, memory architecture, semantic memory, context efficiency. metadata: openclaw: emoji: 🧠 requires: bins: ["python3", "ollama"] install: - id: python-deps kind: shell label: "Install Python dependencies" command: "cd ~/.openclaw/workspace/memory_brain && python3 -m venv .venv && .venv/bin/pip install lancedb numpy pyarrow requests --quiet" - id: ollama-models kind: shell label: "Pull Ollama models (nomic-embed-text + llama3.2:3b)" command: "ollama pull nomic-embed-text && ollama pull llama3.2:3b" --- # Brain CMS 🧠 A neuroscience-inspired memory architecture for OpenClaw agents. Replaces flat file injection with sparse, semantic, frequency-gated memory loading. ## What This Installs ``` memory/ ├── INDEX.md ← Hippocampus: topic router + cross-links ├── ANCHORS.md ← Permanent high-significance event store └── schemas/ ← Domain-specific semantic schemas (you create these) memory_brain/ ├── index_memory.py ← Embeds schemas into LanceDB vector store ├── query_memory.py ← Semantic similarity search ├── nrem.py ← NREM sleep cycle (compression + anchor promotion) ├── rem.py ← REM sleep cycle (LLM consolidation via Ollama) └── vectorstore/ ← LanceDB database (auto-created) ``` ## Setup (one-time) ```bash # 1. Run the installer python3 ~/.openclaw/workspace/skills/brain-cms/install.py # 2. Index your schemas cd ~/.openclaw/workspace/memory_brain .venv/bin/python3 index_memory.py # 3. Test retrieval .venv/bin/python3 query_memory.py "your topic here" --sources-only ``` ## How It Works **Boot sequence:** Load MEMORY.md (lean core) + today's daily log. Nothing else. **When a topic appears:** Read `memory/INDEX.md` → load only the relevant schemas (spreading activation). Check `memory/ANCHORS.md` for high-significance events. **For ambiguous topics:** Run semantic search: ```bash memory_brain/.venv/bin/python3 memory_brain/query_memory.py "message text" --sources-only ``` **Auto-schema creation:** When a new significant project or domain appears: 1. Create `memory/<topic>.md` 2. Add to INDEX.md with triggers + priority + cross-links 3. Re-index: `memory_brain/.venv/bin/python3 memory_brain/index_memory.py` **Sleep cycles:** ```bash # NREM — run on shutdown (~30s, no LLM) cd ~/.openclaw/workspace/memory_brain && .venv/bin/python3 nrem.py # REM — run weekly (2-5 min, uses local llama3.2:3b, free) cd ~/.openclaw/workspace/memory_brain && .venv/bin/python3 rem.py ``` ## Memory Layers (CMS) | Layer | Files | When loaded | Purpose | |-------|-------|-------------|---------| | Working | `MEMORY.md` + today log | Every session | Core context | | Episodic | `memory/YYYY-MM-DD.md` | Session boot | Recent events | | Semantic | `memory/*.md` schemas | On trigger | Domain knowledge | | Anchors | `memory/ANCHORS.md` | On CRITICAL topics | Permanent ground truth | | Vector | `memory_brain/vectorstore/` | On demand | Semantic search | ## Tagging Anchors In any daily log, tag high-significance events: ``` [ANCHOR] Major demo success — full pipeline working end-to-end ``` NREM auto-promotes these to ANCHORS.md on next shutdown. ## Token Savings Typical MEMORY.md: 150-300 lines injected every session. With Brain CMS: ~50-line core + schemas loaded only when relevant. Estimated savings: 40-60% reduction in context tokens per session. ## Requirements - Python 3.10+ - Ollama (for embeddings + REM consolidation) - 500MB+ storage for vector store and models - `lancedb`, `numpy`, `pyarrow`, `requests` (auto-installed)

path: brain_scripts/index_memory.py
content: #!/usr/bin/env python3
"""
Brain CMS — Memory Indexer
Embeds all schema files into LanceDB vector store using Ollama nomic-embed-text.
Run after any schema changes to keep vector store current.

Usage: python3 index_memory.py
"""

import os, sys, json, hashlib, requests, datetime
from pathlib import Path
import lancedb, numpy as np, pyarrow as pa

WORKSPACE   = Path(__file__).parent.parent
MEMORY_DIR  = WORKSPACE / "memory"
STORE_DIR   = Path(__file__).parent / "vectorstore"
OLLAMA_URL  = "http://localhost:11434/api/embeddings"
EMBED_MODEL = "nomic-embed-text"
CHUNK_SIZE  = 400
CHUNK_OVERLAP = 80

# Auto-detect schema files (all .md files in memory/ except daily logs, INDEX, ANCHORS)
def get_schema_files() -> list[str]:
    files = []
    for p in MEMORY_DIR.rglob("*.md"):
        name = p.name
        rel  = str(p.relative_to(MEMORY_DIR))
        # Skip daily logs (YYYY-MM-DD.md) and system files
        if name[0].isdigit() or name in ("INDEX.md",):
            continue
        files.append(rel)
    return sorted(files)

def embed(text: str) -> list[float] | None:
    try:
        r = requests.post(OLLAMA_URL, json={"model": EMBED_MODEL, "prompt": text}, timeout=30)
        r.raise_for_status()
        return r.json()["embedding"]
    except Exception as e:
        print(f"[EMBED ERROR] {e}")
        return None

def chunk_text(text: str, source: str) -> list[dict]:
    chunks, current, current_len, section = [], [], 0, "general"
    for line in text.split("\n"):
        if line.startswith("## ") or line.startswith("### "):
            section = line.lstrip("#").strip()
        current.append(line)
        current_len += len(line) + 1
        if current_len >= CHUNK_SIZE:
            ct = "\n".join(current).strip()
            if ct:
                chunks.append({"text": ct, "source": source, "section": section,
                                "chunk_id": hashlib.md5(ct.encode()).hexdigest()[:8]})
            overlap, olen = [], 0
            for l in reversed(current):
                olen += len(l) + 1
                overlap.insert(0, l)
                if olen >= CHUNK_OVERLAP: break
            current, current_len = overlap, olen
    if current:
        ct = "\n".join(current).strip()
        if ct:
            chunks.append({"text": ct, "source": source, "section": section,
                           "chunk_id": hashlib.md5(ct.encode()).hexdigest()[:8]})
    return chunks

def main():
    print("[INDEX] Starting memory indexer...")
    STORE_DIR.mkdir(parents=True, exist_ok=True)
    db = lancedb.connect(str(STORE_DIR))

    schema_files = get_schema_files()
    all_chunks = []
    for rel_path in schema_files:
        fpath = MEMORY_DIR / rel_path
        if not fpath.exists(): continue
        chunks = chunk_text(fpath.read_text(encoding="utf-8"), rel_path)
        print(f"[READ] {rel_path} → {len(chunks)} chunks")
        all_chunks.extend(chunks)

    if not all_chunks:
        print("[ERROR] No chunks to index.")
        return

    print(f"[EMBED] Embedding {len(all_chunks)} chunks...")
    rows = []
    for i, chunk in enumerate(all_chunks):
        vec = embed(chunk["text"])
        if vec is None: continue
        rows.append({"vector": [float(x) for x in vec], "text": chunk["text"],
                     "source": chunk["source"], "section": chunk["section"],
                     "chunk_id": chunk["chunk_id"],
                     "indexed_at": datetime.datetime.now(datetime.UTC).isoformat()})
        if (i+1) % 10 == 0: print(f"  ... {i+1}/{len(all_chunks)}")

    if not rows: return
    dim = len(rows[0]["vector"])
    schema = pa.schema([pa.field("vector", pa.list_(pa.float32(), dim)),
                        pa.field("text", pa.string()), pa.field("source", pa.string()),
                        pa.field("section", pa.string()), pa.field("chunk_id", pa.string()),
                        pa.field("indexed_at", pa.string())])

    tables = db.list_tables()
    if hasattr(tables, 'tables'): tables = tables.tables
    if "memory_chunks" in tables: db.drop_table("memory_chunks")

    db.create_table("memory_chunks", data=rows, schema=schema)
    print(f"\n[DONE] Indexed {len(rows)} chunks.")

    meta = {"indexed_at": datetime.datetime.now(datetime.UTC).isoformat(),
            "chunk_count": len(rows), "files": schema_files, "model": EMBED_MODEL}
    (Path(__file__).parent / "index_meta.json").write_text(json.dumps(meta, indent=2))

if __name__ == "__main__":
    main()


path: brain_scripts/nrem.py
content: #!/usr/bin/env python3
"""
Brain CMS — NREM Sleep Cycle
Fast nightly compression. No LLM required. Run on shutdown.

Trigger: user says "good night", "shutting down", "heading off"
Runtime: ~15-30 seconds.
"""

import json, datetime, subprocess, sys
from pathlib import Path

WORKSPACE   = Path(__file__).parent.parent
MEMORY_DIR  = WORKSPACE / "memory"
BRAIN_DIR   = Path(__file__).parent
LOG_FILE    = BRAIN_DIR / "sleep_log.json"

def today_str():    return datetime.datetime.now().strftime("%Y-%m-%d")
def yesterday_str():
    return (datetime.datetime.now() - datetime.timedelta(days=1)).strftime("%Y-%m-%d")

def extract_anchors(text: str) -> list[str]:
    return [l.replace("[ANCHOR]", "").strip()
            for l in text.split("\n") if "[ANCHOR]" in l and l.replace("[ANCHOR]","").strip()]

def promote_anchors(anchors: list[str], date_str: str) -> int:
    if not anchors: return 0
    path = MEMORY_DIR / "ANCHORS.md"
    if not path.exists(): return 0
    existing = path.read_text(encoding="utf-8")
    new_entries = [f"\n### {date_str} | Auto-promoted | {a}\nStatus: Active\n"
                   for a in anchors if a[:40] not in existing]
    if new_entries:
        with open(path, "a") as f: f.write("\n".join(new_entries))
    return len(new_entries)

def compress_log(text: str) -> str:
    lines, prev_blank = [], False
    for line in text.split("\n"):
        if line.strip() == "":
            if prev_blank: continue
            prev_blank = True
        else:
            prev_blank = False
        lines.append(line)
    return "\n".join(lines)

def reindex():
    venv_py = BRAIN_DIR / ".venv" / "bin" / "python3"
    python  = str(venv_py) if venv_py.exists() else sys.executable
    try:
        r = subprocess.run([python, str(BRAIN_DIR / "index_memory.py")],
                          capture_output=True, text=True, timeout=120)
        print("[NREM] Vector store re-indexed ✅" if r.returncode == 0
              else f"[NREM] Indexer error: {r.stderr[:100]}")
    except Exception as e: print(f"[NREM] Indexer failed: {e}")

def log_run(stats: dict):
    runs = []
    if LOG_FILE.exists():
        try: runs = json.loads(LOG_FILE.read_text())
        except: runs = []
    LOG_FILE.write_text(json.dumps(runs[-29:] + [stats], indent=2))

def main():
    print("\n💤 NREM Sleep Cycle starting...")
    start = datetime.datetime.now()
    stats = {"date": today_str(), "type": "nrem", "anchors_promoted": 0, "logs_processed": 0}

    for d in [today_str(), yesterday_str()]:
        path = MEMORY_DIR / f"{d}.md"
        if not path.exists(): continue
        text = path.read_text(encoding="utf-8")
        stats["logs_processed"] += 1
        stats["anchors_promoted"] += promote_anchors(extract_anchors(text), d)
        compressed = compress_log(text)
        if len(compressed) < len(text):
            path.write_text(compressed, encoding="utf-8")
            print(f"[NREM] Compressed {d}.md ({len(text)}→{len(compressed)} chars)")

    reindex()
    stats["elapsed_seconds"] = (datetime.datetime.now() - start).seconds
    log_run(stats)

    print(f"\n✅ NREM complete in {stats['elapsed_seconds']}s")
    print(f"   Logs: {stats['logs_processed']} | Anchors: {stats['anchors_promoted']}")
    print("   Sweet dreams 🌙\n")

if __name__ == "__main__":
    main()


path: brain_scripts/query_memory.py
content: #!/usr/bin/env python3
"""
Brain CMS — Semantic Memory Query
Finds the most relevant schema files for a given query.

Usage:
    python3 query_memory.py "working on robot demo this weekend"
    python3 query_memory.py "job application status" --top 5
    python3 query_memory.py "what is my project" --sources-only
"""

import sys, argparse, requests
from pathlib import Path
import lancedb

STORE_DIR  = Path(__file__).parent / "vectorstore"
OLLAMA_URL = "http://localhost:11434/api/embeddings"
EMBED_MODEL = "nomic-embed-text"

def embed(text: str) -> list[float]:
    r = requests.post(OLLAMA_URL, json={"model": EMBED_MODEL, "prompt": text}, timeout=30)
    r.raise_for_status()
    return [float(x) for x in r.json()["embedding"]]

def query(text: str, top_k: int = 4) -> list[dict]:
    if not STORE_DIR.exists():
        print("[ERROR] Vector store not found. Run index_memory.py first.")
        return []
    db = lancedb.connect(str(STORE_DIR))
    tables = db.list_tables()
    if hasattr(tables, 'tables'): tables = tables.tables
    if "memory_chunks" not in tables:
        print("[ERROR] No memory_chunks table. Run index_memory.py first.")
        return []
    return db.open_table("memory_chunks").search(embed(text)).limit(top_k).to_list()

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("query", nargs="+")
    parser.add_argument("--top", type=int, default=4)
    parser.add_argument("--sources-only", action="store_true")
    args = parser.parse_args()

    results = query(" ".join(args.query), args.top)
    if not results:
        print("No results.")
        return

    if args.sources_only:
        seen = set()
        for r in results:
            src = r["source"]
            if src not in seen:
                seen.add(src)
                print(f"memory/{src}")
        return

    print(f"\n🧠 Results for: \"{' '.join(args.query)}\"\n")
    for i, r in enumerate(results, 1):
        score = r.get("_distance", "?")
        print(f"[{i}] {r['source']} | {r['section']}")
        print(f"     Score: {score:.4f}" if isinstance(score, float) else f"     Score: {score}")
        print(f"     {r['text'][:200].strip()}...\n")

if __name__ == "__main__":
    main()


path: brain_scripts/rem.py
content: #!/usr/bin/env python3
"""
Brain CMS — REM Sleep Cycle
Weekly deep consolidation using local Ollama LLM. Zero API cost.

Trigger: Weekly (Sunday evenings), confirm before running, or "run REM cycle"
Runtime: 2-5 minutes.
"""

import json, datetime, requests, subprocess, sys
from pathlib import Path

WORKSPACE   = Path(__file__).parent.parent
MEMORY_DIR  = WORKSPACE / "memory"
BRAIN_DIR   = Path(__file__).parent
LOG_FILE    = BRAIN_DIR / "sleep_log.json"
OLLAMA_URL  = "http://localhost:11434/api/generate"
REM_MODEL   = "llama3.2:3b"

def get_schema_files() -> dict[str, Path]:
    """Auto-detect schema files → {domain_name: path}"""
    schemas = {}
    for p in MEMORY_DIR.rglob("*.md"):
        name = p.name
        if name[0].isdigit() or name in ("INDEX.md", "ANCHORS.md"): continue
        domain = p.stem.lower().replace("-", "_")
        schemas[domain] = p
    return schemas

def load_weekly_logs() -> str:
    parts = []
    for i in range(7):
        day  = datetime.datetime.now() - datetime.timedelta(days=i)
        path = MEMORY_DIR / f"{day.strftime('%Y-%m-%d')}.md"
        if path.exists():
            parts.append(f"\n## {day.strftime('%Y-%m-%d')}\n{path.read_text(encoding='utf-8')}")
    return "\n".join(reversed(parts))

def ollama_generate(prompt: str, max_tokens: int = 600) -> str:
    try:
        r = requests.post(OLLAMA_URL, json={"model": REM_MODEL, "prompt": prompt,
            "stream": False, "options": {"num_predict": max_tokens, "temperature": 0.3}}, timeout=120)
        r.raise_for_status()
        return r.json().get("response", "").strip()
    except Exception as e:
        print(f"[REM] Ollama error: {e}")
        return ""

def extract_updates(weekly_logs: str, domains: list[str]) -> dict:
    domain_list = ", ".join(f'"{d}"' for d in domains)
    prompt = f"""You are a memory consolidation system. Read the week's logs and extract NEW factual updates per domain.

Output ONLY valid JSON with keys: {{{domain_list}}}
Each value: list of short factual strings (max 15 words). Empty list [] if nothing new.
Only include genuinely new facts, decisions, or status changes — not general knowledge.

LOGS:
{weekly_logs[:4000]}

JSON:"""
    response = ollama_generate(prompt)
    try:
        start, end = response.find("{"), response.rfind("}") + 1
        if start >= 0 and end > start:
            return json.loads(response[start:end])
    except Exception as e:
        print(f"[REM] Parse error: {e}")
    return {}

def reindex():
    venv_py = BRAIN_DIR / ".venv" / "bin" / "python3"
    python  = str(venv_py) if venv_py.exists() else sys.executable
    try:
        r = subprocess.run([python, str(BRAIN_DIR / "index_memory.py")],
                          capture_output=True, text=True, timeout=180)
        print("[REM] Vector store re-indexed ✅" if r.returncode == 0
              else f"[REM] Indexer error: {r.stderr[:100]}")
    except Exception as e: print(f"[REM] Indexer failed: {e}")

def log_run(stats: dict):
    runs = []
    if LOG_FILE.exists():
        try: runs = json.loads(LOG_FILE.read_text())
        except: runs = []
    LOG_FILE.write_text(json.dumps(runs[-29:] + [stats], indent=2))

def main():
    print("\n🌙 REM Sleep Cycle starting...")
    print("   Using local Ollama — zero API cost\n")
    start = datetime.datetime.now()
    date_str = start.strftime("%Y-%m-%d")
    stats = {"date": date_str, "type": "rem", "domains_updated": 0, "total_updates": 0}

    weekly_logs = load_weekly_logs()
    if not weekly_logs:
        print("[REM] No daily logs found. Nothing to consolidate.")
        return
    print(f"[REM] Loaded weekly logs ({len(weekly_logs)} chars)")

    schemas = get_schema_files()
    if not schemas:
        print("[REM] No schema files found. Create some in memory/.")
        return

    print(f"[REM] Synthesizing {len(schemas)} domains with {REM_MODEL}...")
    updates = extract_updates(weekly_logs, list(schemas.keys()))

    for domain, new_facts in updates.items():
        if not new_facts: continue
        schema_path = schemas.get(domain)
        if not schema_path: continue
        section = f"\n\n## REM Update — {date_str}\n" + "".join(f"- {f}\n" for f in new_facts[:10])
        with open(schema_path, "a", encoding="utf-8") as f: f.write(section)
        print(f"[REM] {domain}: {len(new_facts)} new facts")
        stats["domains_updated"] += 1
        stats["total_updates"] += len(new_facts)

    reindex()

    stats["elapsed_seconds"] = (datetime.datetime.now() - start).seconds
    log_run(stats)
    print(f"\n✅ REM complete in {stats['elapsed_seconds']}s")
    print(f"   Domains: {stats['domains_updated']} | New facts: {stats['total_updates']}")
    print("   Memory consolidated. Good morning. 🌅\n")

if __name__ == "__main__":
    main()


path: install.py
content: #!/usr/bin/env python3
"""
Brain CMS Installer
Sets up the full Continuum Memory System in your OpenClaw workspace.
Run once to bootstrap the architecture.
"""

import os
import sys
import shutil
import subprocess
from pathlib import Path

SKILL_DIR = Path(__file__).parent
WORKSPACE = Path.home() / ".openclaw" / "workspace"

# ─── Templates ────────────────────────────────────────────────────────────────
INDEX_TEMPLATE = """# Memory Index — Hippocampus
_Last updated: {date}_

> **This file is the router. Check it before loading any schema.**
> It tells you what to load, what's connected, and why it matters.
> Add new schemas here immediately when you create them.

---

## Schema Registry

<!-- Add your schemas here. Example:

### [ProjectName]
- **Files:** `memory/project.md`
- **Triggers:** keyword1, keyword2, keyword3
- **Priority:** HIGH / MEDIUM / LOW
- **Cross-links:** → **OtherDomain** (reason)
- **Status:** Active / Complete
- **Why it matters:** One sentence on why this schema exists.

-->

---

## Cross-Link Map
```
(Add cross-links between domains as they emerge)
```

---

## Permanent Anchors
Check `memory/ANCHORS.md` when:
- A CRITICAL domain is triggered
- User mentions a past milestone or commitment
- Something feels inconsistent with known history

---

## Vector Store
Location: `memory_brain/vectorstore/` (LanceDB)
Re-index after schema changes: `memory_brain/.venv/bin/python3 memory_brain/index_memory.py`
Query: `memory_brain/.venv/bin/python3 memory_brain/query_memory.py "text" --sources-only`

---

## Auto-Schema Protocol
New significant project/domain → create `memory/<topic>.md` → add entry above → note cross-links → re-index.
"""

ANCHORS_TEMPLATE = """# Memory Anchors — Permanent High-Significance Events
_Last updated: {date}_

> These are strong memory anchors. Never prune. Update in place if superseded.
> Tag new anchors in daily logs with [ANCHOR] — NREM promotes them here automatically.

---

## Format
`Date | Domain | Event | Why it matters | Status`

---

## Active Anchors

### {date} | Setup | Brain CMS installed
Continuum Memory System installed and configured.
Status: Active foundation.

---

## Anchor Promotion Protocol
NREM scans daily logs for [ANCHOR] tags → appends here → removes tag from log.
REM reviews anchors → updates Status if superseded → never deletes (marks [ARCHIVED]).
"""

# ─── Brain scripts to copy ─────────────────────────────────────────────────────
BRAIN_SCRIPTS = ["index_memory.py", "query_memory.py", "nrem.py", "rem.py"]

# ─── Install ───────────────────────────────────────────────────────────────────
def run(cmd: str, check=True) -> bool:
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if check and result.returncode != 0:
        print(f"  [WARN] {result.stderr[:100]}")
        return False
    return True

def main():
    from datetime import datetime
    today = datetime.now().strftime("%Y-%m-%d")

    print("\n🧠 Brain CMS Installer")
    print("=" * 50)

    # 1. Create memory directories
    memory_dir = WORKSPACE / "memory"
    brain_dir = WORKSPACE / "memory_brain"
    memory_dir.mkdir(parents=True, exist_ok=True)
    brain_dir.mkdir(parents=True, exist_ok=True)
    print("✅ Directories created")

    # 2. Create INDEX.md if not exists
    index_path = memory_dir / "INDEX.md"
    if not index_path.exists():
        index_path.write_text(INDEX_TEMPLATE.format(date=today))
        print("✅ INDEX.md (hippocampus) created")
    else:
        print("⏭️  INDEX.md already exists — skipping")

    # 3. Create ANCHORS.md if not exists
    anchors_path = memory_dir / "ANCHORS.md"
    if not anchors_path.exists():
        anchors_path.write_text(ANCHORS_TEMPLATE.format(date=today))
        print("✅ ANCHORS.md created")
    else:
        print("⏭️  ANCHORS.md already exists — skipping")

    # 4. Copy brain scripts
    copied = 0
    for script in BRAIN_SCRIPTS:
        src = SKILL_DIR / "brain_scripts" / script
        dst = brain_dir / script
        if src.exists() and not dst.exists():
            shutil.copy2(src, dst)
            copied += 1
        elif dst.exists():
            pass  # Already there
        else:
            print(f"  [WARN] Script not found: {src}")
    if copied:
        print(f"✅ Copied {copied} brain scripts")

    # 5. Set up Python venv + deps
    venv_path = brain_dir / ".venv"
    if not venv_path.exists():
        print("📦 Creating Python venv...")
        run(f"python3 -m venv {venv_path}")
        pip = venv_path / "bin" / "pip"
        print("📦 Installing dependencies...")
        run(f"{pip} install lancedb numpy pyarrow requests --quiet")
        print("✅ Dependencies installed")
    else:
        print("⏭️  Venv already exists — skipping")

    # 6. Create requirements.txt
    req_path = brain_dir / "requirements.txt"
    if not req_path.exists():
        req_path.write_text("lancedb>=0.29.0\nnumpy>=1.24.0\npyarrow>=12.0.0\nrequests>=2.28.0\n")

    # 7. Add AGENTS.md instructions (append if file exists)
    agents_path = WORKSPACE / "AGENTS.md"
    cms_note = """
## 🧠 Brain CMS — Memory Architecture

This workspace uses the Continuum Memory System. Full instructions in `memory/INDEX.md`.

**Boot:** Load MEMORY.md + today's daily log. Nothing else.
**On topic:** Read INDEX.md → load relevant schemas → check ANCHORS.md if CRITICAL.
**Semantic search:** `memory_brain/.venv/bin/python3 memory_brain/query_memory.py "text" --sources-only`
**Sleep:** NREM on shutdown → `memory_brain/.venv/bin/python3 memory_brain/nrem.py`
**Weekly:** REM on Sunday → `memory_brain/.venv/bin/python3 memory_brain/rem.py`
"""
    if agents_path.exists():
        content = agents_path.read_text()
        if "Brain CMS" not in content:
            agents_path.write_text(content + cms_note)
            print("✅ AGENTS.md updated with CMS instructions")
    else:
        agents_path.write_text(f"# AGENTS.md\n{cms_note}")
        print("✅ AGENTS.md created")

    print("\n" + "=" * 50)
    print("✅ Brain CMS installed successfully!")
    print("\nNext steps:")
    print("  1. Index your schemas:")
    print(f"     cd {brain_dir} && .venv/bin/python3 index_memory.py")
    print("  2. Test retrieval:")
    print(f"     .venv/bin/python3 query_memory.py 'your topic' --sources-only")
    print("  3. Read INDEX.md and add your first schema")
    print("\n🧠 Your agent now has a proper brain.\n")

if __name__ == "__main__":
    main()


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