skills/o9k-read/SKILL.md
Load long-term memory from hmem via read_memory or load_project. Use when starting fresh with no L1 summaries, after /compact, when the user asks 'what do you remember', 'continue where we left off', 'was war der letzte Stand', or about a project/topic visible in L1 summaries. Covers search, prefix filter, find_related, stale detection, and cross-project read_project().
npx skillsauth add Bumblebiber/hmem o9k-readInstall 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.
When this skill is invoked, pick the right path and execute a tool call immediately. Do NOT just read this document — act.
If read_memory is not available, tell the user:
"read_memory tool not found. Run hmem init to configure the MCP server."
Announcements: If read_memory or o9k-sync pull shows urgent announcements
(yellow warnings at the top), act on them immediately before doing anything else.
These are broadcast messages from the user or another device — typically config changes,
server migrations, or breaking updates that must be handled first.
Use load_project as the single entry point. It activates the project, returns the full
briefing (L2 content + L3 titles), and shows recent O-entries with full L4/L5 user/agent
exchanges — so you see exactly what happened in previous sessions, not just titles.
User mentioned a project → load it directly:
load_project(id="P0037")
This replaces the old pattern of update_memory(active=true) + read_memory().
One call gives you everything: project structure, open tasks, and recent session history.
Unclear which project → list projects first, then load:
read_memory(titles_only=true, prefix="P") # ~200 tokens overview
Then ask the user or pick the most relevant one, and call load_project(id="P00XX").
New project → create it first, then load:
write_memory(prefix="P", content="New project: ...") # → P00XX
load_project(id="P00XX")
Cross-project reference — need context from another project without switching active project:
read_project(id="P0048") # focused briefing, does NOT activate
Returns Overview, Codebase titles, Usage, Context, Requirements, Roadmap titles. Omits: session history (O-entries), Bugs, History, Ideas, Next Steps, Rules, R-injection. Use this when working on Project A but needing the API/architecture of Project B.
No specific project → load everything:
read_memory() # full bulk read, all projects
The response includes:
recentOEntries in config
(default: 10). Each O-entry contains the actual L4/L5 user/agent messages, not just
summaries. This is your continuity — read them to understand where the last session
left off.read_memory() shows a cross-project overview optimized for breadth. load_project
goes deep on one project — it activates it, filters related entries, and includes session
history. Use load_project when you know which project to work on; use read_memory
when you need orientation across all projects.
You still have the recent conversation — you do not need the newest entries again. Load the long-term knowledge that was lost during compression:
read_memory(mode="essentials")
Essentials mode prioritizes favorites, most-accessed, and pinned entries over newest. This is your "recover what matters" call — rules, decisions, error patterns, key references.
For a specific project's full context:
read_memory(context_for="P0029")
When the user asks about something visible in your L1 summaries:
# "Was weißt du über das hmem Projekt?"
read_memory(context_for="P0029")
# "Tell me about that Heimdall error"
read_memory(context_for="E0090")
context_for loads the entry expanded + all related entries (via links and weighted tag
scoring) in a single call. Raise min_tag_score for fewer results:
read_memory(context_for="P0029", min_tag_score=7) # stricter — only strong matches
read_memory() shows current context — newest entries, most-accessed favorites, open
tasks. It is not a full dump. Older entries with low access_count are intentionally omitted.
Expanded entries (newest, most-accessed, favorites): show L2 children + links.
Non-expanded entries: latest child + [+N more → ID] hint.
Active-prefix filtering: entries in active projects get full expansion, others title-only.
For older or broader knowledge:
read_memory(titles_only=true, prefix="L") # all lesson titles as table of contents
read_memory(search="SQLite corruption") # semantic search
read_memory(tag="#sqlite") # hashtag filter
Tags are hidden by default — use read_memory(curator=true) to see hashtags on entries.
Repeated bulk reads without a goal yield little new information after 3-4 iterations — use targeted search or a prefix overview instead.
Every node has a title (short navigation label) and an optional body (detail shown on drill-down). This affects what you see in different read modes:
| Mode | Shows |
|------|-------|
| titles_only=true | Title only — compact table of contents |
| Default (by ID) | Title + body for requested node, children as title-only |
| expand=true | Title + body recursively for all nodes |
| Bulk reads | Title only for L1, expanded entries show L2 titles |
Entries with a blank line between title and body have explicit title/body separation. Older entries without separation show auto-extracted titles (~50 chars) — the full text is still accessible on drill-down.
After the initial load, drill deeper with these patterns:
read_memory(prefix="E") # only errors
read_memory(store="company") # shared company knowledge
read_memory(id="E0042") # expand root → shows L2 children
read_memory(id="E0042.2") # expand L2 → shows L3 children
read_memory(id="E0042.2.1") # expand further (rarely needed)
Compact table of contents:
read_memory(titles_only=true) # all entries, one line each
read_memory(titles_only=true, prefix="L") # only lessons
Depth parameter — useful for listings (max 4), not for ID queries:
read_memory(depth=2) # all entries with L2 children
read_memory(prefix="L", depth=2) # all lessons with details
read_memory(depth=4) # deep dive L2+L3+L4 (large output, use sparingly)
For L5 detail, drill into a specific node ID instead of using depth.
Find entries created around a specific time or near another entry:
read_memory(time="14:30") # ±2h window around 14:30 today
read_memory(time="14:30", date="2026-02-20") # specific date + time
read_memory(time="14:30", period="-1h") # custom window: only 1h before
read_memory(time_around="P0001") # entries created near P0001
read_memory(time_around="P0001", period="+2h") # only after P0001
read_memory(search="Node.js startup crash")
read_memory(search="auth token")
search_memory(query="auth token", scope="memories")
Sub-node attribution (v6.6.1+): FTS hits inside sub-nodes are now surfaced explicitly. If the match is inside P0048.8.4, the output shows the root entry plus a ↳ matched in N sub-nodes: block with compact IDs and an 80-char preview. Tag-only or root-level matches keep the previous one-line format (no ↳ block).
O-entries store raw session context with progressive summarization. They are created automatically by the Stop hook — every user/agent exchange is recorded as an O-entry without manual intervention. When you switch projects, a new O-entry is started automatically (project-based O-entries).
O-entries are hidden from bulk reads but searchable. Use them when you need the original reasoning behind a decision or the full conversation that led to an entry:
read_memory(prefix="O") # browse recent context
read_memory(search="why did we choose per-node scoring") # search across raw context
read_memory(id="O0042", expand=true) # drill into specific entry
O-entries are linked to curated entries (P/L/D/E) via tags, so context_for will
surface relevant O-entries when their tags match.
v5 checkpoint integration: When checkpointMode is set to "auto", a Haiku subagent
reads recent O-entry exchanges at configurable intervals and automatically extracts
L/D/E entries + writes a rolling checkpoint summary ([CP] node tagged #checkpoint-summary).
What load_project shows: For the latest O-entry, it displays:
This keeps load_project compact even for long sessions. To see the full unfiltered
history, use read_memory(id="O0042", expand=true).
The recentOEntries config parameter (default: 10) controls how many recent O-entries
load_project includes.
By default, bulk reads hide most obsolete entries (top 3 by access count shown). To see all:
read_memory(show_obsolete=true)
Find entries not accessed in a while — useful for curation:
read_memory(stale_days=30) # sorted oldest-access first
read_memory(stale_days=60, prefix="L") # only stale lessons
Quick overview of your memory health:
memory_stats() # personal store
memory_stats(store="company") # company store
Output includes: total entries by prefix, total nodes, favorites count, pinned count, unique hashtags, stale count (>30d), oldest entry, and top 5 most-accessed entries.
Find entries similar to a given entry via FTS5 keyword matching — spots potential duplicates or thematic connections:
find_related(id="P0029") # up to 5 similar entries
find_related(id="L0042", limit=10)
Returns title-only list with overlapping keywords (different from relatedEntries in
ID reads which uses shared tags).
Check memory for structural issues before/after curation:
memory_health() # personal store
memory_health(store="company")
Checks: broken links, orphaned entries, stale favorites/pinned, broken obsolete chains, tag orphans.
After loading memory, read H-prefix entries to understand the user. The standard H-schema has four categories:
| Section | What it contains | |---------|-----------------| | Identity | Name, contact, GitHub, roles, background | | Work Style | Communication style, decision-making, agent behavior expectations | | Priorities | Current goals and focus areas | | Preferences | Likes/dislikes — what to do and what to avoid |
H-entries also contain Skill Assessments (1–10 per subtopic). Adapt language accordingly:
If no H-entries exist yet, create them from context clues in the conversation (see o9k-write skill for the H-prefix convention).
Your memory is your brain. If something is wrong, stale, or noisy — fix it NOW, don't list
problems for later. This applies after load_project AND after read_memory.
Every time you receive a load_project response, scan it for issues and fix them
immediately — before responding to the user. The load_project output IS the briefing
that every future session gets. If it contains noise, every future session starts with
noise. Treat it like your own resume: if something doesn't belong, remove it.
What to look for and how to fix it:
| Problem | Example | Action |
|---------|---------|--------|
| Resolved/done bugs | .6.2 E0101: O-entry root title never updated (fixed months ago) | update_memory(id="P0048.6.2", content="...", irrelevant=true) |
| Old protocol entries | .7.32 - windowsHide: true on all child_process spawns (fragment, not a session) | update_memory(id="P0048.7.32", content="...", irrelevant=true) |
| Stale version info | Overview says "v5.3.1" but current is v6.0.0 | update_memory(id="P0048.1.1", content="Current state: v6.0.0 on npm...") |
| Duplicate sections | .10 Bugs (duplicate of .6) | update_memory(id="P0048.10", content="...", irrelevant=true) |
| Completed open tasks | .8.2 ✓ DONE: O-Entry Session History Injection | Should be auto-filtered; if not, mark irrelevant |
| Wrong project's exchanges | O-entry exchanges from P0052 appearing in P0048 | Use move_nodes (see below) |
| Stale env/config references | Still references HMEM_PROJECT_DIR instead of HMEM_PATH | Update the node content |
Use update_many when marking multiple entries irrelevant in one go:
update_many(ids=["P0048.7.32", "P0048.6.2", "P0048.6.5"], irrelevant=true)
When exchanges or session nodes land in the wrong O-entry (e.g. project confusion during a session), move them to the correct location:
move_nodes(source_ids=["O0048.3.1.5", "O0048.3.1.6"], target_parent="O0052.1.1")
This preserves the exchange content while fixing the tree structure. Don't delete misplaced entries — move them to where they belong.
After any read_memory() call, scan for:
[✓ID]:
write_memory(prefix="E", content="Correct fix is...") → E0076
update_memory(id="E0042", content="Wrong — see [✓E0076]", obsolete=true)
update_memory(id="T0005", content="...", irrelevant=true)update_memory(id="S0001", content="...", favorite=true)read_memory call — only on the first bulk read
of a session and after load_projectFor a thorough deep-clean, use the /o9k-curate skill.
| Wrong | Right |
|-------|-------|
| read_memory() after /compact | read_memory(mode="essentials") — you already have recent context |
| update_memory(active=true) + read_memory() | load_project(id="P00XX") — one call does both |
| Blind bulk read at session start | read_memory(titles_only=true, prefix="P") first, then load_project |
| read_memory(id="E0042", depth=3) | read_memory(id="E0042.2") — branch by branch |
| Load everything without purpose | Check L1 first, then expand selectively |
| Read .hmem file directly | Always use MCP tools — it is a SQLite binary |
| Just display this skill text | Call a read_memory or load_project variant immediately |
| load_project on a secondary reference project | read_project(id="P00XX") — doesn't switch active project |
| update_memory(id="X", obsolete=true) without [✓ID] | Write correction first, then mark obsolete with [✓ID] tag |
| Repeated read_memory() to find old entries | read_memory(titles_only=true, prefix="L") or read_memory(search="...") |
| Listing noise without fixing it | Fix it NOW: update_memory(id, content, irrelevant=true) |
| Deleting misplaced O-entries | Move them: move_nodes(source_ids=[...], target_parent="O00XX.Y") |
| Manually creating O-entries | O-entries are auto-logged by the Stop hook — do not create them manually |
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.