Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Curate the web-capture index. Use when the capture backlog grows, captures sit unprocessed at seedling/pending, or to surface stored research during work.
.claude/skills/athola-palace-index-curator/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 40% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 76% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 69% | 0% |
The web-research hooks auto-capture every WebFetch and WebSearch into hooks/memory-palace-index.yaml, storing each as a markdown file and an index entry. Captures land at the defaults routing_type: pending, maturity: seedling, importance_score: 50, and nothing advances them. Left alone, the index becomes a write-only graveyard: the majority of entries are never incorporated, analyzed, or surfaced.
This skill drains that backlog and keeps it drained. It wires the capture index to the corpus tooling the plugin already ships (decay_model, keyword_index, marginal_value) through three commands: a read-only report, a dry-run-first promotion engine, and a SessionStart surfacing hook.
pendingentries the drain held back.
pending.knowledge-intake.knowledge-locator.digital-garden-cultivator.bashuv run python scripts/memory_palace_cli.py index report
Reports total entries, the inert ratio, orphaned captures (entries whose backing file is gone), the largest topic clusters by domain, and the top promotion candidates. Writes nothing.
bash# Dry run: prints promote/archive proposals, writes nothing. uv run python scripts/memory_palace_cli.py index promote # Apply: backs up the index under data/backups/, then persists. uv run python scripts/memory_palace_cli.py index promote --apply
Each pending entry is classified into one action:
importance score, a routing type, and maturity seedling -> growing.
revisited. Marked archived rather than promoted, following the principle that unused captures should drain, not accumulate.
pending with no change.Applying is idempotent: promoted and archived entries are no longer pending, so a second run proposes nothing new. The dry-run diff is always shown before --apply writes.
Running these by hand is the exception. --apply runs on every commit from scripts/precommit_palace_maintenance.sh, so the backlog drains continuously rather than in occasional sweeps. Reach for the commands above when a commit is blocked, or when you want the dry-run diff before the hook decides for you.
The commit that carries the index must carry it with zero pending entries. scripts/check_capture_index_drained.py runs at the end of the maintenance hook and fails the commit otherwise, and tests/test_capture_index_artifact.py re-checks the same invariant in CI so a bypassed hook does not land a backlog.
Two things make that gate reachable rather than a standing block:
(hooks/shared/deduplication._stage_index). Without it, pre-commit reverts the unstaged write before any hook runs, so the drain reads a tree the fresh capture is missing from and converges on a fixed point that excludes exactly the entries it exists to process. That is how 47 captures accumulated behind a drain that reported nothing to do.
hold survivesit, so a blocked commit means a specific capture needs a person to score or archive it. The gate names the keys.
A SessionStart hook (hooks/index_surfacer.py) names the highest-value promoted captures at the start of a session. It is disabled by default. Enable it in memory-palace-config.yaml:
yamlfeature_flags: context_injection: true
The hook only speaks when promoted entries clear the importance floor, and it exits silently on any error so it can never block a session.
The three steps above all operate on the capture index at hooks/memory-palace-index.yaml. Retrieval reads a different file: data/indexes/keyword-index.yaml, built from the staging captures and consumed by cache_lookup. Curating one does nothing to the other.
That keyword index is derived data and is not tracked in git, so a fresh checkout has none at all. Rebuild it with:
bash# Report what would be indexed, writing nothing. uv run python scripts/build_indexes.py --dry-run # Write data/indexes/keyword-index.yaml. uv run python scripts/build_indexes.py
The builder refuses to write an empty index over a populated one. An empty corpus is reported with "wrote": false and any existing index is left untouched. Writing entries: {} over real data is how the corpus went dark in 1.5.0, and it stayed dark because the regeneration script named in that stub file had never been written.
A capture whose frontmatter will not parse contributes nothing to the keyword index. Body extraction is gated on that parse, so the whole document drops out rather than just its topic, and the drain above reports nothing wrong because the index entry itself looks ordinary.
The usual cause is a page title or search query holding a double quote, which closed the YAML scalar early when the capture was written. The capture hooks escape their scalars now, so this reaches captures written before that fix and no others.
bash# Report which captures cannot be parsed, writing nothing. uv run python scripts/repair_capture_frontmatter.py # Re-quote them, backing the originals up under data/backups/. uv run python scripts/repair_capture_frontmatter.py --apply
The repair rewrites the broken scalar and nothing else, and refuses any file it cannot re-parse afterward: turning an invisible capture into a subtly wrong one is worse than leaving it alone. Rebuild the keyword index once it has run, since retrieval reads the separate artifact described above.
cluster size). The decision logic is deterministic. No model call gates a transition.
constants. Wixted & Ebbesen (1997) and Murre & Dros (2015) show forgetting follows a power law. FSRS (Ye, Su & Cao, 2022) validates exponential decay only with a learned per-item half-life. Calibrate against reopen logs if usage data accrues.
cache_lookup / keyword_index), andembeddings are not required at the current corpus scale. BM25 is the workhorse up to ~5000 documents. Embeddings add value only for vocabulary-mismatch discovery.
content_hash) then MinHash with k-shingling for near-duplicates (Broder, 1997). SimHash is preferable only at tens of thousands of documents.
w3 usage. The plugin ships all three terms (graph_analyzer PageRank, decay_model, usage_tracker).
Triage decides whether a single capture drains or accumulates. When a whole body of work finishes, freeze it behind an index instead of deleting it or leaving it in the active listing.
See modules/archive-pattern.md for the structure, the closing-note requirement, and the two discoverability layers.
build_indexes.py --dry-run reports a non-zero entry count andleaves data/indexes/keyword-index.yaml byte-identical.
build_indexes.py against an empty corpus reports "wrote":false and leaves an existing populated index untouched.
index report runs and prints the inert ratio and orphan countfor the live index.
index promote (no flag) prints proposals and writes nothing(the index file is byte-identical afterward).
index promote --apply creates a timestamped backup underdata/backups/ before persisting, and a re-run proposes nothing.
context_injection: true, a SessionStart event surfaces thetop promoted captures, and with the flag off it stays silent.
are handled without raising: report degrades, promote holds, hook exits silently.
check_capture_index_drained.py exits 0 against the committedindex and exits 1 naming the keys when one is left pending.
repair_capture_frontmatter.py reports zero repairablecaptures against the committed corpus, and refuses a file it cannot re-parse after repair.
update_index appears in git diff --cachedwithout anyone staging it by hand.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 9,006 | 1,048 | -88% | 1 | 1 | 0% | 1,727 | 2,422 | +40% | 0 | 0 | — |
case-02 | fail→fail | 5,742 | 2,547 | -56% | 1 | 1 | 0% | 283 | 2,564 | +806% | 0 | 0 | — |
case-03 | fail→fail | 4,658 | 2,752 | -41% | 1 | 1 | 0% | 200 | 2,571 | +1186% | 0 | 0 | — |
case-04 | fail→pass | 9,924 | 1,972 | -80% | 1 | 1 | 0% | 1,517 | 2,537 | +67% | 0 | 0 | — |
case-05 | fail→pass | 10,737 | 5,353 | -50% | 1 | 1 | 0% | 1,737 | 3,058 | +76% | 0 | 0 | — |
case-06 | fail→pass | 8,962 | 2,338 | -74% | 1 | 1 | 0% | 1,391 | 2,651 | +91% | 0 | 0 | — |
case-07 | fail→pass | 9,988 | 2,051 | -79% | 1 | 1 | 0% | 1,509 | 2,544 | +69% | 0 | 0 | — |
case-08 | fail→pass | 7,063 | 3,099 | -56% | 1 | 1 | 0% | 1,136 | 2,748 | +142% | 0 | 0 | — |
case-09 | fail→pass | 11,010 | 2,766 | -75% | 1 | 1 | 0% | 1,967 | 2,596 | +32% | 0 | 0 | — |
case-10 | fail→pass | 14,211 | 2,000 | -86% | 1 | 1 | 0% | 2,267 | 2,526 | +11% | 0 | 0 | — |
case-11 | fail→pass | 7,232 | 1,690 | -77% | 1 | 1 | 0% | 1,189 | 2,441 | +105% | 0 | 0 | — |
case-12 | fail→pass | 7,293 | 1,832 | -75% | 1 | 1 | 0% | 1,178 | 2,538 | +115% | 0 | 0 | — |
case-13 | fail→pass | 7,709 | 2,232 | -71% | 1 | 1 | 0% | 1,252 | 2,591 | +107% | 0 | 0 | — |
case-14 | pass→pass | 12,410 | 3,385 | -73% | 1 | 1 | 0% | 1,892 | 2,810 | +49% | 0 | 0 | — |
case-15 | fail→pass | 13,622 | 1,923 | -86% | 1 | 1 | 0% | 1,884 | 2,566 | +36% | 0 | 0 | — |
case-16 | fail→pass | 11,152 | 1,991 | -82% | 1 | 1 | 0% | 1,834 | 2,576 | +40% | 0 | 0 | — |
case-17 | fail→pass | 18,469 | 1,635 | -91% | 1 | 1 | 0% | 3,040 | 2,462 | -19% | 0 | 0 | — |
case-18 | fail→pass | 15,324 | 1,604 | -90% | 1 | 1 | 0% | 1,331 | 2,475 | +86% | 0 | 0 | — |
case-19 | pass→pass | 13,232 | 2,606 | -80% | 1 | 1 | 0% | 2,076 | 2,602 | +25% | 0 | 0 | — |
case-20 | fail→pass | 12,847 | 5,855 | -54% | 1 | 1 | 0% | 2,082 | 3,151 | +51% | 0 | 0 | — |
case-21 | fail→pass | 11,202 | 2,171 | -81% | 1 | 1 | 0% | 1,713 | 2,597 | +52% | 0 | 0 | — |
case-22 | pass→pass | 14,798 | 4,743 | -68% | 1 | 1 | 0% | 2,420 | 2,999 | +24% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +77 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.