--- name: hipocampus-compaction description: "Build 5-level compaction tree (daily/weekly/monthly/root) with smart thresholds and fixed/tentative lifecycle. Run at session start when triggers are met, or via external scheduler." --- # Memory Compaction Tree 5-level hierarchical index over raw memory logs. Compaction nodes are **search indices** — originals are never deleted. ## Hierarchy ``` memory/ ROOT.md <- root node (topic index, ~3K tokens, Layer 1) 2026-03-15.md <- raw daily log (permanent, append-only) daily/2026-03-15.md <- daily compaction node weekly/2026-W11.md <- weekly compaction node monthly/2026-03.md <- monthly compaction node ``` **Compaction chain:** Raw → Daily → Weekly → Monthly → Root **Tree traversal (search):** Root → Monthly → Weekly → Daily → Raw ## Fixed vs Tentative Nodes Every compaction node has a status: - **tentative** — period is still ongoing, regenerated when new data arrives - **fixed** — period ended, never updated again ```yaml # Indicated in YAML frontmatter --- type: weekly status: tentative period: 2026-W11 --- ``` **Key: tentative nodes are created immediately — ROOT.md is usable from day one.** ## When to Run Called from hipocampus:core Session Start step 7, or directly by an external scheduler (e.g., OpenClaw heartbeat). Check trigger conditions below. ## Trigger Conditions | Level | Tentative Create/Update | Fixed Transition | |-------|------------------------|-----------------| | Raw → Daily | On each new raw addition | Date changes | | Daily → Weekly | On daily add/change | ISO week ended + 7 days elapsed | | Weekly → Monthly | On weekly add/change | Month ended + 7 days elapsed | | Monthly → Root | On monthly add/change | Never (root accumulates forever) | ## Smart Thresholds Below threshold: copy/concat verbatim (no information loss). Above threshold: generate LLM keyword-dense summary. | Level | Threshold | Above | Below | |-------|-----------|-------|-------| | Raw → Daily | ~200 lines | LLM keyword-dense summary | Copy raw verbatim | | Daily → Weekly | ~300 lines combined | LLM keyword-dense summary | Concat dailies | | Weekly → Monthly | ~500 lines combined | LLM keyword-dense summary | Concat weeklies | | Monthly → Root | Always | Recursive recompaction | (N/A) | ## Type-Aware Compaction ### Memory Types Entries in daily logs are tagged: `## Topic [type]`. Four types exist: | Type | Compaction behavior | |------|-------------------| | `user` | Always preserve core content. Never compress to Historical Summary. | | `feedback` | Always preserve rule + why + how-to-apply structure. Never compress to Historical Summary. | | `project` | Completed → compress to Historical Summary. Active → keep in Active Context. | | `reference` | Preserve pointer + 1-line description. Mark `[?]` if >30 days unverified. | **Backward compat:** Untagged entries (no `[type]` in heading) → treat as `[project]`. ### Topics Keyword Extraction At every compaction level, extract topic keywords from content and write to frontmatter `topics` field: - Scan headings (`## Topic [type]`) for keywords - Scan Key Decisions for decision keywords - Include type tag: `topics: [hipocampus [project], terse-responses [feedback]]` ### Exclusion Filtering When generating LLM summaries, strip: - Code blocks (triple backtick) → replace with `→ filepath:lines` - Stack traces → 1-line error message - Entries containing "임시", "테스트 중", "나중에 삭제", "temporary", "test run", "delete later" → remove entirely ## Algorithm **CRITICAL — STRICT CHAIN ORDER: Steps 2→3→4→5 MUST execute in sequence. NEVER skip a level.** Each step feeds the next. Root reads from monthly. Monthly reads from weekly. Weekly reads from daily. If you skip a level, the chain breaks and data is lost or corrupted. ``` Raw → [Step 2] → Daily → [Step 3] → Weekly → [Step 4] → Monthly → [Step 5] → Root ↑ ↑ ↑ ↑ reads raw reads daily reads weekly reads monthly writes daily/ writes weekly/ writes monthly/ writes ROOT.md ``` **NEVER:** - Modify ROOT.md based on daily or weekly data (root reads ONLY from monthly) - Modify monthly based on daily data (monthly reads ONLY from weekly) - Skip Step 2 or 3 because "there's nothing new" — always verify by checking files - Touch ROOT.md directly without going through the full chain ### Step 0: Pre-Compaction Snapshot Before starting the compaction chain, preserve current working state: 1. Read `WORKING.md` 2. If it contains active task content (not just the empty template): - Append a snapshot to today's daily log (`memory/YYYY-MM-DD.md`): ``` ## Working State Snapshot [project] - context: pre-compaction automatic snapshot - state: [copy WORKING.md content] ``` - This ensures in-progress work is captured before any context compression 3. If WORKING.md is empty or contains only the template, skip this step ### Step 1: Discover Candidates Scan `memory/` for raw files. Group by date, ISO week, and month. Check each group against trigger conditions. ### Step 2: Daily Compaction (max 1 per cycle) **Input:** raw files (`memory/YYYY-MM-DD.md`) **Output:** daily nodes (`memory/daily/YYYY-MM-DD.md`) For each date where raw exists and daily needs create/update: 1. Read raw file `memory/YYYY-MM-DD.md` 2. Count lines — compare against ~200 line threshold 3. Below threshold: copy raw verbatim to `memory/daily/YYYY-MM-DD.md` 4. Above threshold: generate keyword-dense summary 5. Write with frontmatter: ```markdown --- type: daily status: tentative period: YYYY-MM-DD source-files: [memory/YYYY-MM-DD.md] topics: [keyword1, keyword2, keyword3] --- ## Topics ## Key Decisions ## Tasks Completed ## Lessons Learned ## Open Items ``` 6. If date has changed (raw is from a past date): set `status: fixed` **Secret scanning:** The mechanical compaction (`hipocampus compact`) automatically redacts secrets in compaction nodes using regex patterns. When generating LLM summaries for above-threshold nodes, also avoid reproducing any API keys, tokens, passwords, or credentials from the source material. If you encounter a secret in the source, write `[REDACTED]` in its place. **CHECKPOINT:** Verify `memory/daily/` has the updated file before proceeding to Step 3. ### Step 3: Weekly Compaction (max 1 per cycle) **Input:** daily nodes (`memory/daily/YYYY-MM-DD.md`) — NEVER raw files **Output:** weekly nodes (`memory/weekly/YYYY-WNN.md`) **STOP-CHECK:** Did Step 2 produce or update a daily node? If not, skip Steps 3-5 entirely — there's nothing new to propagate. For each ISO week where dailies exist and weekly needs create/update: 1. Read all daily compaction files for that week (from `memory/daily/`, NOT from `memory/`) 2. Count combined lines — compare against ~300 line threshold 3. Below threshold: concat all dailies 4. Above threshold: generate keyword-dense weekly summary 5. Write to `memory/weekly/YYYY-WNN.md` with frontmatter 6. If ISO week ended + 7 days elapsed: set `status: fixed` **CHECKPOINT:** Verify `memory/weekly/` has the updated file before proceeding to Step 4. ### Step 4: Monthly Compaction (max 1 per cycle) **Input:** weekly nodes (`memory/weekly/YYYY-WNN.md`) — NEVER daily or raw files **Output:** monthly nodes (`memory/monthly/YYYY-MM.md`) **STOP-CHECK:** Did Step 3 produce or update a weekly node? If not, skip Steps 4-5 — there's nothing new to propagate. For each month where weeklies exist and monthly needs create/update: 1. Read all weekly compaction files for that month (from `memory/weekly/`, NOT from `memory/daily/`) 2. Count combined lines — compare against ~500 line threshold 3. Below threshold: concat all weeklies 4. Above threshold: generate keyword-dense monthly summary 5. Write to `memory/monthly/YYYY-MM.md` with frontmatter 6. If month ended + 7 days elapsed: set `status: fixed` **CHECKPOINT:** Verify `memory/monthly/` has the updated file before proceeding to Step 5. ### Step 5: Root Compaction **Input:** monthly nodes (`memory/monthly/YYYY-MM.md`) — NEVER weekly, daily, or raw files **Output:** `memory/ROOT.md` **STOP-CHECK:** Did Step 4 produce or update a monthly node? If not, DO NOT touch ROOT.md. When a monthly node is created or updated: 1. Read existing `memory/ROOT.md` (if exists) 2. Read the new/updated monthly node (from `memory/monthly/`, NOT from any other directory) 3. Recursive compaction: `root = recompact(existing_root + monthly_changes)` - **Active Context**: replace with current week's highlights — what's in progress, immediate priorities - **Recent Patterns**: update with newly emerged cross-cutting insights - **Historical Summary**: append/compress older context — merge periods, keep brief summaries - **Topics Index**: merge new topics, update existing entries with new sub-keywords and references 4. Write to `memory/ROOT.md` 5. If root exceeds size cap (`compaction.rootMaxTokens` in config, default 3000 tokens / ~100 lines): self-compress — compress Historical Summary first, keep Active Context and Topics Index intact ```markdown --- type: root status: tentative last-updated: YYYY-MM-DD --- ## Active Context (recent ~7 days) - topic: current state, what's happening now ## Recent Patterns - pattern: cross-cutting insight that emerged recently ## Historical Summary - YYYY-MM~MM: high-level summary of that period - YYYY-MM: key events ## Topics Index - topic-keyword [type, Nd]: sub-keywords, references → knowledge/file.md - topic-keyword [type]: sub-keywords ``` **Age calculation:** For each topic, find the most recent source-file date that mentions it. Compute days since that date. Write as `Nd` (e.g., `2d`, `30d`). **Type-specific root rules:** - `user`/`feedback` topics: always in Topics Index, never in Historical Summary only - `project` topics: active → Active Context + Topics Index; completed >90d → Historical Summary only (remove from Topics Index if root exceeds size cap) - `reference` topics: mark `[?]` if >30 days since last mention ### Step 6: OpenClaw ROOT.md Sync **OpenClaw only:** Sync ROOT.md content into the "Compaction Root" section of MEMORY.md: - Read MEMORY.md, find `## Compaction Root` section - Replace everything between `## Compaction Root` and the next `##` heading (or EOF) with the Active Context, Recent Patterns, and Topics Index sections from ROOT.md - This keeps the auto-loaded MEMORY.md in sync with the canonical ROOT.md ### Step 7: Re-index After writing any compaction files: ```bash qmd update ``` If vector search is enabled (`search.vector: true` in `hipocampus.config.json`): ```bash qmd embed ``` ## Guards - **CHAIN ORDER IS MANDATORY:** Daily→Weekly→Monthly→Root. Never skip a level. Never read from a wrong source directory. - **Each level reads ONLY from its immediate predecessor:** Root←Monthly←Weekly←Daily←Raw - Raw files: **never delete** (permanent leaf nodes) - Max 1 daily + 1 weekly + 1 monthly + 1 root per compaction cycle - No empty summaries (minimum 50 bytes) - Skip failed file reads — never abort entire compaction - qmd update failure: warning only, not fatal - Root self-compresses when exceeding size cap (shrink older topics first) - Keyword-dense format only — no prose, no narrative. Optimized for BM25 recall. - **If you feel tempted to "just update ROOT.md quickly" — STOP. Run the full chain.** ## Agent Memory (Optional) If `memory/agents/compaction/AGENT.md` exists, read it before starting compaction. Use learned patterns to inform decisions (e.g., typical raw log size, common threshold behavior). After compaction completes, if you observed a new pattern worth remembering: - Append to `memory/agents/compaction/AGENT.md` - Keep under ~30 lines - Example patterns: "this project averages ~80 raw lines/day — daily threshold rarely hit", "weekly nodes frequently need LLM summary (>300 lines)" If no new patterns were observed, skip the update. ## Edge Cases - **Empty days:** No daily compaction node is generated for days without raw logs. Weekly naturally skips those days. - **First day:** Create the full tentative tree immediately (daily → weekly → monthly → root). ROOT.md is usable from day one. - **Lifecycle example:** - Day 1: raw created → daily(tentative) → weekly(tentative) → monthly(tentative) → ROOT - Day 2: daily(tentative) updated, weekly(tentative) updated, monthly(tentative) updated, ROOT updated - Week ends + 7 days: weekly → fixed, new weekly(tentative) starts - Month ends + 7 days: monthly → fixed, new monthly(tentative) starts