skills/hmem-write/SKILL.md
Complete protocol for writing to hmem long-term memory. Covers tree navigation (find correct insertion point before writing), prefix selection (L/E/D/P/N/R/I/H/...), hashtag assignment, title/body formatting, deduplication, and append-vs-write decisions. Do NOT call write_memory or append_memory directly without this skill — skipping the protocol creates duplicates and misplaced entries. Invoke whenever you are about to persist anything: lessons learned, bug fixes, architecture decisions, project state, user preferences, E-entries, N-entries, or P-entry updates. Also invoke when the user says "remember this", "save this", "don't forget", "store this", "write that down", "add to memory", "create an E-entry", "log this", or invokes /hmem-write.
npx skillsauth add Bumblebiber/hmem hmem-writeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Call the MCP tool write_memory to save lessons, errors, decisions, or project insights to long-term memory.
If write_memory is not available:
/mcp, in other tools: restart the tool)."write_memory(
prefix: "E",
content: "Short Title (~50 chars)\n\nL1 body — detailed explanation, can span multiple lines\nsecond body line with more context\n\tL2 node title\n\n\tL2 body text (supports newlines)\n\tmore L2 body\n\t\tL3 detail (2 tabs)\n\t\t\tL4 raw data (3 tabs — rarely needed)"
)
Title + Body convention (git-commit style): Every node has a title (short navigation label) and an optional body (detailed content loaded on drill-down). Separate them with a blank line — just like a git commit message.
> prefix: Still works for backward compatibility, but blank-line separation is preferred.content and the title is auto-extracted from the first maxTitleChars characters.L1 example with body:
Short Error Title
SQLite connection failed because .mcp.json used a relative path.
The fix was to use an absolute path in the HMEM_PATH env var.
Details about reproduction
Steps: 1. Set HMEM_PATH=./hmem 2. Run hmem serve 3. Observe SQLITE_CANTOPEN
L1 example without body (still works):
SQLite connection failed due to wrong path in .mcp.json
Fix: use absolute path in env var
Indentation: 1 tab = 1 level. Alternatively: 2 or 4 spaces per level (auto-detected). Warning: A tab at the start of any line always means "go one level deeper" — it is structural, not content. If you need to store code or text that contains leading tabs, use spaces instead. IDs and timestamps are assigned automatically — never write them yourself.
Hashtags connect entries across all prefixes and hierarchy levels. They are the only cross-prefix discovery mechanism.
Add 3–5 tags per call (max 10):
write_memory(prefix="E", content="...", tags=["#hmem", "#sqlite", "#bug", "#migration", "#windows"])
append_memory(id="P0029", content="...", tags=["#hmem", "#sync", "#cli"])
Rules:
#, only letters/digits/hyphen/underscore: #hmem-sync, #api_keyappend_memory tags are additive — they do not replace existing tagswrite_memory tags: if entry has children → land on first child node; if leaf (no children) → land on rootappend_memory tags are stored only on the target node — no upward propagationGood tags: #hmem, #sync, #sqlite, #windows, #release, #bug, #security, #althing, #cli, #migration
Bad tags: #fix (too generic), #important (no context), #2026 (not a topic)
Bulk-tagging for existing entries:
tag_bulk(filter={prefix: "E"}, add_tags=["#bug"]) # all E-entries
tag_bulk(filter={search: "hmem-sync"}, add_tags=["#sync"]) # by full-text search
tag_rename(old_tag="#hmem-store", new_tag="#hmem") # rename a tag everywhere
| Prefix | Category | When to use | |--------|----------|-------------| | P | (P)roject | Project entries — standardized L1 format (see below) | | L | (L)esson | Lessons learned, best practices — cross-project knowledge | | E | (E)rror | Bugs, errors + their fix — auto-scaffolded schema (see below) | | D | (D)ecision | Architecture decisions with reasoning — cross-project knowledge | | T | (T)ask | Cross-project or infrastructure tasks ONLY (see note below) | | M | (M)ilestone | Cross-project milestones ONLY — project milestones go in P-entry L2 "Protocol" | | S | (S)kill | Skills, processes, how-to guides | | N | (N)avigator | Code pointers — where something lives in the codebase | | H | (H)uman | Knowledge about the user — preferences, context, working style | | R | (R)ule | User-defined rules and constraints — "always do X", "never do Y" | | I | (I)nfrastructure | Devices, servers, deployments, network — one entry per device/server |
Tasks belong inside the project's P-entry L2 "Open tasks" node:
append_memory(id="P0048.8", content="Implement multi-server sync\n\tPush/pull to all configured servers", tags=["#hmem-sync"])
Use the T-prefix ONLY for tasks that span multiple projects or are infrastructure/meta tasks (e.g. "Set up Strato server", "Run curation pass"). These get links=["P00XX"] to the most relevant project.
Milestones belong in the P-entry L2 "Protocol" node as a chronological entry:
append_memory(id="P0048.7", content="v4.0.0 published — project gate + load_project tool (2026-03-27)", tags=["#release"])
Use the M-prefix ONLY for milestones that span multiple projects (e.g. "First cross-device sync working").
Errors (E), Lessons (L), Decisions (D) stay as independent root entries — they are cross-project knowledge. An SQLite lesson learned in hmem applies to every SQLite project. Always add tags and links to connect them back:
write_memory(prefix="E", content="...", tags=["#hmem", "#sqlite"], links=["P0048"])
P-entry "Known issues" (L2) contains short summaries pointing to E-entries — not the errors themselves:
append_memory(id="P0048.6", content="Auto-sync fails with multiple .hmem in CWD → E0097, T0043", tags=["#hmem-sync"])
Custom prefixes: If none of the above fit, you can use any single uppercase letter. To register it officially (so the system validates it), add it to hmem.config.json under "prefixes":
{ "prefixes": { "R": "Research" } }
Custom prefixes are merged with the defaults — they don't replace them. Without registering, the system will reject the prefix.
The MCP server enforces schemas for any prefix that has a schemas entry in hmem.config.json.
For those prefixes, two rules apply:
write_memory: L2 node names must match the defined section names (error otherwise).append_memory to root entry (e.g. P0029, no dot): blocked for arbitrary content — you cannot
add new L2 sections outside the schema. Exception: if the first line of content is a valid schema
section name (e.g. "Rules"), the append is allowed — useful for adding a missing section to an
existing entry. Otherwise append to a specific section: append_memory(id="P0029.N", content="...").By default, P has a schema. If L, D, or other prefixes are configured with schemas, the same rules apply.
Every project entry MUST follow this structure.
L1 Title: Name | Status | Tech Stack | GH: owner/repo | Short description
The GH field is optional — include it when a GitHub repo exists, omit otherwise.
L1 Body: (same line or next non-indented line) One-sentence project summary.
Status values:
| Status | Meaning | |--------|---------| | New | Just started, concept phase | | Active | In active development | | Mature | Feature-complete, only bugfixes | | Paused | On hold, will resume later | | Archived | Done or abandoned |
L2 categories (fixed order, skip sections that are empty):
The MCP server validates that L2 nodes start with one of these names. Minimum for a new project: Overview + Codebase (or Usage).
| L2 Category | What goes here | L3 children |
|-------------|---------------|-------------|
| Overview | First thing an agent reads (like CLAUDE.md /init) | Current state, Goals, Architecture, Environment |
| Codebase | Code structure — NO code, only names + signatures | Entry point, Core modules (each module = L4 node with signature + purpose + return), Helpers, Config, Tests |
| Usage | How the project is used | Installation/Setup, CLI/API commands, Common workflows |
| Context | Background and motivation | Initiator, Target audience, Business context, Dependencies (links) |
| Deployment | Build/CI/CD/publish process | (flat or with L3 sub-steps) |
| Bugs | Active bugs + known limitations | L3: inline report (symptom + cause) OR pointer to E-entry (→ E0097). L4: reproduction steps |
| Protocol | Session log, chronological | One-liner per session + links to O-entries |
| Open tasks | Project-specific TODOs | One per L3 node. Cross-project tasks → T-prefix with links |
| Ideas | Feature ideas, brainstorming | L3: short description, L4: implementation details |
load_project tool: Use load_project(id="P0048") to activate a project and get the full briefing (L2 content + L3 titles) in one call. This is the recommended way to start working on a project — it combines read + activate.
Markers you may see on entries:
| Marker | Meaning |
|--------|---------|
| [♥] | Favorite — always expanded in bulk reads |
| [★] | Top-accessed — high weighted access score |
| [≡] | Top-subnode — many children |
| [⚡] | Task-promoted — relevant to an active T/P/D entry (tag overlap) |
| [*] | Active — currently in focus |
| [P] | Pinned — super-favorite, shows full L2 |
| [!] | Obsolete — superseded, kept for history |
| [-] | Irrelevant — hidden from bulk reads |
| ✓ | Synced — backed up to all sync servers |
Complete P-entry example (WeatherBot):
write_memory(
prefix="P",
content="WeatherBot | New | Python/Discord.py | GH: user/weatherbot\n\nDiscord bot for weather forecasts — slash commands for current weather and multi-day forecasts\n\tOverview\n\t\tCurrent state\n\n\t\tScaffolding done, no commands yet. Bot connects to Discord but has no slash commands registered.\n\t\tGoals\n\n\t\tDaily/hourly forecasts via slash commands, multi-city support, embed formatting\n\t\tArchitecture\n\n\t\tDiscord slash command → OpenWeatherMap API → formatted embed. Single-file cog pattern.\n\t\tEnvironment\n\n\t\t/home/user/weatherbot, python bot.py, needs DISCORD_TOKEN + WEATHER_API_KEY in .env\n\tCodebase\n\t\tEntry point — bot.py, start: python bot.py\n\t\tCore modules\n\t\t\tweather_cog.py — WeatherCog(Cog); fetch_forecast(city: str) → discord.Embed\n\t\t\tformatter.py — format_embed(data: dict) → discord.Embed\n\t\tHelpers / Utilities\n\t\t\tapi_client.py — get_weather(city: str) → dict; wraps HTTP to OpenWeatherMap\n\t\tConfig / Constants — .env: DISCORD_TOKEN, WEATHER_API_KEY, DEFAULT_CITY\n\t\tTests — pytest, test_weather_cog.py (3 tests)\n\tUsage\n\t\tInstallation / Setup — pip install -r requirements.txt, cp .env.example .env\n\t\tCLI / API — /weather <city>, /forecast <city> (planned)\n\tContext\n\t\tInitiator — personal project, Mar 2026\n\t\tTarget audience — personal Discord server\n\t\tDependencies — discord.py, OpenWeatherMap API, aiohttp\n\tOpen tasks\n\t\tImplement /forecast command\n\n\t\tMulti-day view with daily highs/lows and weather icons per day\n\t\tAdd city autocomplete",
tags=["#discord", "#python", "#weather", "#bot"],
links=[]
)
Note: L2 nodes use 1 tab, L3 uses 2 tabs, L4 uses 3 tabs. Separate title from body with a blank line at the same indent level. Skip empty sections — no need for placeholder text.
Any prefix with a schema defined in hmem.config.json gets its sections auto-created on write when you don't supply L2 nodes. This includes I, A, P, and any custom prefix with a schema. The write response shows the full section list:
write_memory(prefix="I", content="Laptop | Active | macOS 14 | macbook", tags=["#device"])
→ Schema: .1 Specs, .2 OS, .3 IP Address, .4 Access, .5 Services, .6 Apps, .7 Storage, .8 Notes
Fill sections afterwards with append_memory(id="I00XX.1", content="..."). If you supply L2 nodes yourself, they are validated against the schema but no additional nodes are created.
E-entries have a pre-built structure — just provide a title and short description, the server creates the rest:
write_memory(prefix="E", content="hmem sync bug on v1.0.1\n\nConnection fails when HMEM_PATH contains spaces", tags=["#hmem", "#sync", "#path"])
This auto-creates:
Plus #open tag. Fill in the nodes as you debug with append_memory/update_memory. The response shows similar E/D entries by tag overlap — check them before reinventing the wheel. When solved, replace #open with #solved and fill .4 + .5 + .6.
E-entries are not shown in bulk reads — they surface automatically via tag overlap when you create new E/D entries. Solved bugs are knowledge, not clutter.
Mark any entry as a favorite to ensure it always appears with its L2 detail in bulk reads (alongside a [♥] marker). Use this for reference info you need to see every session — API endpoints, key decisions, frequently looked-up patterns.
write_memory(prefix="D", content="...", favorite=true) # set at creation
update_memory(id="D0010", content="...", favorite=true) # set on existing
update_memory(id="D0010", content="...", favorite=false) # clear
Favorites are not a prefix — they are a flag on any entry regardless of category. Use sparingly: if everything is a favorite, nothing is. Prefer high-value reference entries over fleeting notes.
When you notice that an entry is outdated — superseded by a newer approach, a fixed bug, or changed architecture — do not delete it. Mark it as obsolete with a correction reference:
# Step 1: Write the correction FIRST
write_memory(prefix="E", content="Correct approach is XYZ\n\tDetails...") # → E0076
# Step 2: Mark old entry obsolete — MUST include [✓ID] tag
update_memory(id="E0023", content="Wrong approach — see [✓E0076]", obsolete=true)
The [✓ID] tag is enforced. The system will reject obsolete=true without a correction reference. This ensures every obsolete entry points to its replacement. The system also creates bidirectional links automatically (E0023↔E0076).
The entry stays in memory with a [!] marker. Past errors still carry learning value ("we tried this and it failed because..."). The curator may eventually prune it, but that's their decision, not yours.
Shortcut for stale entries: If no correction exists (entry is just old/irrelevant, not wrong), only the curator can mark it obsolete without [✓ID].
Use N to save a pointer to a specific file, function, or code location so you don't have to search for it next session.
write_memory(
prefix="N",
content="Link-Auflösung beim read_memory-Aufruf
src/hmem-store.ts ~line 269 — read() method, ID branch
Guard: resolveLinks !== false prevents circular refs
Introduced in v1.4.0",
links=["E0069"]
)
L1: What it is — one sentence describing the concept/feature L2: Exact file path + line range + function/method name L3: Context, caveats, related patterns Links: Related entries (errors, decisions, lessons)
Your responsibility: Update your N entries whenever you notice code has moved or logic has changed. You don't need the curator for this — use update_memory directly. Stale pointers are worse than none. If you cannot verify whether the pointer is still valid, mark it obsolete: update_memory(id="N0012", content="...", obsolete=true).
Title: Short navigation label, ~50 chars (configurable via maxTitleChars). Think "chapter title in a book".
"hmem.py Performance: Bulk-Queries statt N+1", "Ghost Wakeup Bug in msg-router.ts""Fixed a bug", "Important lesson" (too vague)Body (after blank line): Detailed explanation — full sentences, multiline OK. Shown on drill-down, hidden in listings.
With title + body (recommended):
write_memory(prefix="L", content="hmem.py Performance: Bulk-Queries statt N+1\n\nAlle Nodes in 2 Bulk-Queries laden, nicht pro Entry einzeln.\nVorher: load_nodes() pro Entry = N+1 SQLite-Connections.\n\tImplementation detail\n\n\tChanged read() to batch-fetch all nodes for visible entries in one query")
Without body (simple entries, backward-compatible):
write_memory(prefix="E", content="SQLite connection failed due to wrong path in .mcp.json\n\tFix: use absolute path in env var")
Title auto-extracted: "SQLite connection failed due to wrong path in .mc"
write_memory(
prefix: "S",
store: "company",
content: "..."
)
Never write blindly. Before creating a new entry or appending, navigate the existing tree top-down to find the correct insertion point. New information almost always belongs inside an existing entry — not as a new root.
Step 1 — Check L1 summaries (already in context) Scan the root entries visible in your context. Is there a matching root for this topic?
write_memory() creates a new rootStep 2 — Read the matching root's children
read_memory(id="P0029") # shows root + all L2 titles
Do any L2 titles match the sub-topic?
append_memory(id="P0029", content="...") adds a new L2Step 3 — Drill into that L2
read_memory(id="P0029.15") # shows L2 node + all L3 titles
Does an L3 match even more specifically?
append_memory(id="P0029.15", content="...") adds a new L3read_memory(id="P0029.15.2"), etc.)Stop drilling when: no child matches, or you've reached the level of granularity that fits.
New insight about MCP server restart behavior after TypeScript compile:
# Step 1: L1 summaries show L0074 "MCP server muss nach Kompilierung neugestartet werden"
# Step 2: read_memory(id="L0074") → shows .1 "Fix: kill + auto-respawn", .2 "Context: Althing only"
# No L2 matches the new sub-case → append at L2
append_memory(id="L0074", content="Standalone hmem-mcp: npm restart required (no auto-respawn)")
# → adds L0074.3 (L3 under the existing lesson)
Instead of: write_memory(prefix="L", content="MCP restart needed after compile...") — which would duplicate L0074.
write_memory is correctOnly use write_memory when:
Rule: If in doubt, drill one level deeper before deciding to create a new root.
Checkpoint mode matters. Check checkpointMode in hmem.config.json:
"auto" (recommended): A background Haiku subagent handles checkpoints automatically every N exchanges. It reads recent O-entry exchanges, calls read_memory to avoid duplicates, and writes L/D/E entries + handoff via MCP tools. It also writes a rolling checkpoint summary ([CP] node tagged #checkpoint-summary) that compresses older exchanges for load_project. Skill-dialog exchanges are auto-tagged #skill-dialog and filtered from context injection. You do NOT need to write entries yourself unless the user explicitly asks you to save something specific.
"remind": You will receive a CHECKPOINT reminder every N messages. When you see it, save key learnings yourself using write_memory / append_memory.
In both modes: Only save what is still valuable in 6 months.
| Save | Don't save | |------|-----------| | New root cause + fix | Routine actions without learning value | | Insight that changes future work | What's already in the codebase | | Architecture decision + reasoning | Temporary debugging notes | | Unexpected tool/API behavior | What's in the documentation |
One write_memory call per category — entire hierarchy in one content string.
Use update_memory and append_memory to modify entries without deleting and recreating them.
Updates the text of a single node. Children are not touched.
update_memory(id="L0003", content="Corrected L1 summary — new wording")
update_memory(id="L0003.2", content="Fixed L2 detail")
update_memory(id="D0010", content="New L1", links=["E0042"]) # also update links
Use when: the wording is wrong, outdated, or needs clarification.
Appends new child nodes under an existing root or node. Existing children are preserved.
Content indentation is relative to the parent — 0 tabs = direct child of id.
Body works the same as in write_memory — blank line separates title from body.
Schema enforcement: For entries with a defined schema (e.g., P, I), appending to the root (e.g.,
id="P0029") is blocked unless the content's first line matches a defined section name. Use case: adding a missing section (e.g."Rules") to an entry created before that section existed. For arbitrary new content, target a specific section:append_memory(id="P0029.3", content="..."). For entries without a schema (L, D, E, etc. by default), root appends are always allowed.
append_memory(
id="L0003",
content="New finding discovered later\n\nDetailed explanation of what was found and why it matters.\nThis can span multiple lines.\n\tSub-detail about it"
)
# → adds L0003.N (L2 with title + body) and L0003.N.1 (L3)
# ↑ only works if L has no schema defined; use L0003.N for schema-constrained entries
append_memory(
id="P0029.3",
content="New detail in the Usage section"
)
# → adds P0029.3.M (L3 under section .3) — correct way for schema-constrained entries
append_memory(
id="L0003.2",
content="Extra note under L0003.2"
)
# → adds L0003.2.M (L3)
Use when: you have new context to add without replacing what's there.
| Situation | Tool |
|-----------|------|
| L1 wording is wrong/outdated | update_memory |
| A sub-node has wrong detail | update_memory |
| You have new info to add | append_memory |
| Entry is completely wrong | mark obsolete with [✓newId], then write_memory for the correction |
Access counts are managed automatically — every read_memory and append_memory call bumps the accessed entries. The ranking uses time-weighted scoring (access_count / log2(age_in_days + 2)) so newer entries with fewer accesses can outrank stale old ones. Entries with the highest weighted scores get [★] markers and expanded treatment in bulk reads. To explicitly mark an entry as important, use favorite: true on write_memory or update_memory.
Apply tags to multiple entries at once, or rename a tag everywhere:
# Add #bugfix to all E-prefix entries
tag_bulk(filter={prefix: "E"}, add_tags=["#bugfix"])
# Add tag to entries matching a search term
tag_bulk(filter={search: "FTS5"}, add_tags=["#search", "#sqlite"])
# Remove #old from all entries that have it
tag_bulk(filter={tag: "#old"}, remove_tags=["#old"])
# Add and remove simultaneously
tag_bulk(filter={prefix: "L", tag: "#draft"}, add_tags=["#stable"], remove_tags=["#draft"])
# Rename a tag everywhere
tag_rename(old_tag="#hmem-store", new_tag="#hmem")
Use tag_bulk when adding a new systematic tag to an existing category, or cleaning up after a tagging convention change. tag_rename handles typos or renames across the entire memory.
Actively track the user's expertise level across topics. This drives how you communicate — a coding expert doesn't need variable explanations, a beginner doesn't need jargon.
One H-entry per main topic, with sub-nodes per subtopic:
write_memory(prefix="H", content="User Skill: IT
Coding — Advanced: writes TypeScript fluently, debugs SQLite schemas, understands async/MCP
Terminal/CLI — Advanced: bash, git, systemctl, nvm, sqlite3 comfortable
Networking — Intermediate: HTTP/DNS solid, asked about WebSocket details
DevOps — Intermediate: systemd + nvm yes, Docker unfamiliar",
tags=["#skill-assessment", "#it"])
Levels: 1-10 scale (see user-assessment skill for full details). 1-2 = no experience, 5-6 = intermediate, 9-10 = expert. Half-points allowed.
Always include evidence (observed behavior, not assumptions).
Reference the O-entry (automatic session log) where the skill change was observed:
# User demonstrated new skill — link to the exchange that proves it
append_memory(id="H0010", content="Docker — Intermediate: configured docker-compose independently (see O0042.15)")
# User's skill improved
update_memory(id="H0010.3", content="Networking — Advanced: configures DNS, TLS, reverse proxies (see O0042.23)")
# User struggled — downgrade with evidence
update_memory(id="H0010.4", content="DevOps — Beginner: asked what systemd is, needed step-by-step (see O0042.8)")
The O-entry reference lets future agents verify the assessment by reading the original conversation.
Before explaining anything technical, check the relevant H-entry:
Example: If H0010.1 says "Coding — Advanced", don't explain what a Map is. If H0010.4 says "DevOps — Beginner", explain what a systemd service does before configuring one.
Not just IT — any domain the user works in:
Create new H-entries as topics emerge naturally from conversation.
Match the language of existing entries. Before writing, check what language the memory store uses (run read_memory() if unsure). If existing entries are in German, write in German. If English, write in English. Do not mix languages within a single store — it makes search and curation harder.
For L, D, and E entries with complex reasoning, do a quick read-back:
read_memory(id="X")
Ask: does this entry answer "why?" not just "what?"
Update if any of these are true:
Skip for: simple factual entries, routine appends, O-entry checkpoints.
| Wrong | Right |
|-------|-------|
| L1 too short: "Fixed bug" | Full sentence with root cause + blank line + body |
| Writing English when existing entries are German | Match the store's language |
| Tabs inside content text (e.g. code snippets) | Use spaces for indentation within content — tabs at line start always mean "go deeper in the hierarchy" |
| Mixed spaces and tabs for hierarchy | Stay consistent — either tabs or spaces as your depth marker |
| Everything flat, no indentation | Use hierarchy — L2/L3 for details |
| Save trivial things | Quality over quantity |
| Forget to write_memory | Always call BEFORE setting Status: Completed |
| Write to .hmem via sqlite3/SQL | ONLY use write_memory MCP tool — never raw SQL |
| MCP unavailable → skip saving | Reconnect MCP first (/mcp or restart tool) |
| update_memory(id="X", obsolete=true) without [✓ID] | Write correction first, then mark obsolete with [✓E0076] tag |
tools
Update flow for its-over-9k (hmem). Runs `npm update -g`, syncs skills, applies migrations, verifies hooks, shows the changelog. Use when the user asks to update/upgrade hmem, o9k, o9k-mcp, or its-over-9k (any language), or when the startup version-check flags a new release. Runs the npm update itself — don't assume it's already done.
development
Mandatory entry point for every Cortex session — invoke at conversation start, after /clear, and after any load_project call. All stable context (H-entries, projects, device, sync) is pre-injected by the hook — no read_memory(mode='essentials') needed. Surfaces pending git work, Next Steps + open T-tasks, and runs the O-entry routing check.
tools
Curate an .hmem file (your own or foreign) — mark obsolete/irrelevant, fix titles, consolidate duplicates, repair broken links. **Requires the `hmem-curate` MCP server** (skill prompts the user to enable it on entry). Use whenever the user says 'aufräumen', 'memory aufräumen', 'Speicher aufräumen', 'hmem aufräumen', 'clean up memory', 'tidy up hmem', 'curate memory', 'consolidate duplicates', 'merge duplicate entries', 'fix broken links', 'kümmer dich um die Memory', or invokes /o9k-curate. Also trigger when `memory_health()` flags BLOCKER/WARNING issues, when a P-entry's load_project output exceeds 4k tokens (session-start noise check defers to this skill), or before any batch cleanup of L, E, D, P entries. Skipping this skill and editing memory directly bypasses health checks, severity triage, and obsolete-chain integrity — never curate without it.
testing
Add a new rule and place it correctly — decide between a cross-project R-entry and a project-specific subnode under the active project's Rules section. Use whenever the user says 'neue Regel', 'Regel hinzufügen', 'new rule', 'add a rule', or invokes /o9k-new-rule. Critical safeguard: project-specific rules placed as R-entries pollute the session-start Rules listing across every project — get the scope right BEFORE writing.