Files
김경종 72dad72703
Tests / Hermetic test suite (push) Has been cancelled
Tests / Skill frontmatter validation (push) Has been cancelled
add claude-obsidian
2026-05-28 10:57:16 +09:00

566 lines
20 KiB
Markdown

# DragonScale Memory Guide
DragonScale Memory is an optional extension for `claude-obsidian`. It adds conservative helpers for log rollups, stable page addresses, duplicate-page linting, and frontier topic suggestion. Start with [docs/install-guide.md](./install-guide.md). For the design spec and rationale, read [wiki/concepts/DragonScale Memory.md](../wiki/concepts/DragonScale%20Memory.md).
This page stays close to shipped behavior in `v1.6.0`. It explains what setup creates, what each mechanism actually does, what it needs, and how to turn it off safely without uninstalling the repo.
## What DragonScale Is
### Scope and opt-in status
DragonScale is a memory-layer extension for the wiki. It covers rollups, deterministic page IDs, duplicate detection, and one opt-in topic-selection path for `/autoresearch`. It is not required for the base vault.
If you never run `bash bin/setup-dragonscale.sh`, the base install and the original skill behavior remain in place. The repo uses feature detection so DragonScale can stay optional instead of becoming a hard dependency.
The concept page is broader than this guide. This guide is operational. When the spec and implementation differ in detail, prefer the shipped scripts and skills for day-to-day behavior.
### What ships in 1.6.0
Version `1.6.0` ships all four DragonScale mechanisms as opt-in features:
- Mechanism 1, Fold Operator: `skills/wiki-fold/`
- Mechanism 2, Deterministic Page Addresses: `scripts/allocate-address.sh` plus `wiki-ingest` and `wiki-lint` integration
- Mechanism 3, Semantic Tiling Lint: `scripts/tiling-check.py` plus `wiki-lint` integration
- Mechanism 4, Boundary-First Autoresearch: `scripts/boundary-score.py` plus `skills/autoresearch/SKILL.md` Topic Selection logic
Use `CHANGELOG.md` for the release trail, [docs/install-guide.md](./install-guide.md) for the quick-start view, and [wiki/concepts/DragonScale Memory.md](../wiki/concepts/DragonScale%20Memory.md) for the full design context.
## Before You Enable It
### Base install requirements
DragonScale is an add-on, not a replacement for base setup. Do the normal vault install first by following [docs/install-guide.md](./install-guide.md).
At minimum:
- clone the repo or install the plugin
- run `bash bin/setup-vault.sh`
- open the folder as an Obsidian vault
- use `/wiki` to scaffold or continue setup
The DragonScale setup script accepts one optional argument, the vault path:
```bash
bash bin/setup-dragonscale.sh
```
```bash
bash bin/setup-dragonscale.sh /path/to/vault
```
If you omit the path, it uses the repo root inferred from `bin/`.
### Universal prerequisite: flock
`flock` is the universal prerequisite. Mechanism 2 uses it directly in `scripts/allocate-address.sh` to guard `.vault-meta/.address.lock`. Mechanism 3 uses flock from Python to guard `.vault-meta/.tiling.lock` around cache I/O.
Quick check:
```bash
command -v flock
```
If that prints nothing, install `flock` before relying on DragonScale. On Linux it is usually already present. On macOS it commonly comes from `util-linux`.
If `flock` is missing, setup can still create files, but the address allocator and tiling cache path are not reliable. Treat that as a blocker.
### Mechanism 3 extra prerequisites: python3, ollama, nomic-embed-text
Mechanism 3 is the only mechanism with the full local embeddings stack. You need `python3`, `ollama`, and the `nomic-embed-text` model pulled into ollama.
Useful checks:
```bash
command -v python3
```
```bash
curl -sS http://127.0.0.1:11434/api/version
```
```bash
ollama pull nomic-embed-text
```
The setup script does not install any of those. It only checks and reports status. Mechanism 4 needs `python3`, but not ollama. Mechanisms 1 and 2 do not need either.
### What happens when optional deps are missing
DragonScale is meant to fail closed or no-op cleanly.
If `python3` is missing:
- Mechanism 3 cannot run
- Mechanism 4 cannot run
- Mechanisms 1 and 2 still work
If ollama is unreachable, `scripts/tiling-check.py` exits `10`. If ollama is reachable but `nomic-embed-text` is not installed, it exits `11`. `wiki-lint` is expected to treat those as skip conditions for semantic tiling, not as a reason to break the rest of the lint flow.
If the boundary helper fails, `/autoresearch` falls back to the normal ask-the-user topic path. It does not force a candidate list and it does not improvise a topic.
If DragonScale setup has never been run, `wiki-ingest` and `wiki-lint` keep their non-DragonScale behavior.
## Setup
### Run bin/setup-dragonscale.sh
Run:
```bash
bash bin/setup-dragonscale.sh
```
The script is idempotent. It is safe to re-run and it does not overwrite the runtime files it already created.
Before provisioning state, it verifies:
- `scripts/allocate-address.sh`
- `scripts/tiling-check.py`
- `skills/wiki-fold/SKILL.md`
If any of those are missing, setup stops and tells you to reinstall the plugin.
What setup does:
- makes `scripts/allocate-address.sh` executable
- makes `scripts/tiling-check.py` executable
- creates `.vault-meta/` if needed
- creates address, tiling, and legacy-baseline state files if missing
- creates `.raw/.manifest.json` if missing
- runs sanity checks at the end
What setup does not do:
- install ollama
- pull `nomic-embed-text`
- backfill addresses onto old pages
- run a fold
- run semantic tiling
- rewrite your existing wiki pages
### What files and state it creates
Setup provisions a small amount of runtime state.
In `.vault-meta/` it creates:
- `address-counter.txt`
- `tiling-thresholds.json`
- `legacy-pages.txt`
In `.raw/` it creates:
- `.manifest.json`
`address-counter.txt` starts at `1`, so the next reserved page address in a brand-new vault will be `c-000001`.
`tiling-thresholds.json` is seeded with `error: 0.90`, `review: 0.80`, and `calibrated: false`. These are conservative seed bands, not calibrated truth for your vault.
`legacy-pages.txt` gets a rollout marker comment:
```text
# rollout: YYYY-MM-DD
```
`wiki-lint` uses that baseline to separate legacy pages from post-rollout pages for address enforcement.
`.raw/.manifest.json` starts with empty `sources` and `address_map` objects. The ingest skill maintains that file. The source documents under `.raw/` remain immutable.
### How to verify setup
The setup script already performs sanity checks, but it is useful to verify a few things yourself.
Check the next address without reserving one:
```bash
./scripts/allocate-address.sh --peek
```
Check that runtime state exists:
```bash
ls -1 .vault-meta
```
Check tiling readiness without computing embeddings:
```bash
python3 ./scripts/tiling-check.py --peek
```
Check the boundary helper:
```bash
python3 ./scripts/boundary-score.py --top 5
```
If your vault is small or tightly integrated, the boundary helper may report no positive-score frontier pages. That is still a valid run.
## Mechanism 1: Fold Operator
### What it does
The fold operator is a log rollup. It reads the most recent `2^k` entries from `wiki/log.md` and produces an extractive fold page under `wiki/folds/`.
The fold is additive. It does not delete, move, or rewrite the child entries. The fold is extractive. Every outcome and theme in the output must be traceable to a child log entry.
The current shipped skill is intentionally narrow. It supports a flat fold over raw log entries. Hierarchical fold-of-folds behavior remains outside the scope of the current skill even though the concept spec discusses stacked folds.
The fold ID is deterministic for a given range:
```text
fold-k{K}-from-{EARLIEST-DATE}-to-{LATEST-DATE}-n{COUNT}
```
That gives structural idempotency. If the exact fold already exists, the skill should stop instead of writing a duplicate.
### When to use it
Use a fold when the log has accumulated a coherent batch of work and you want a checkpoint page that is easier to scan than a raw run of entries.
Typical cases:
- after several ingests on one theme
- after a burst of research sessions
- before a long flat `wiki/log.md` gets harder to use
Do not treat folds as garbage collection. They summarize. They do not compact by deletion.
Example command:
```text
fold the log, dry-run k=3
```
That asks for a dry-run over `2^3 = 8` entries.
### Dry-run vs commit mode
Dry-run is the default and it is stdout-only. That matters because the repo has a PostToolUse hook for writes.
In dry-run mode:
- no file is written
- no auto-commit hook is triggered
- you get the proposed fold content in the terminal output
In commit mode:
- the fold page is written to `wiki/folds/`
- `wiki/index.md` is updated
- `wiki/log.md` gets a new fold entry
The skill docs expect three separate write operations in commit mode, so three auto-commits from the hook are normal.
Example commit command:
```text
fold the log, commit k=3
```
Run a dry-run first. Commit only if the fold content looks right.
To disable Mechanism 1 without uninstalling DragonScale, stop invoking `wiki-fold`. Existing fold pages can remain in the vault, or you can remove them manually if you no longer want them.
## Mechanism 2: Deterministic Page Addresses
### Address format and rollout policy
Mechanism 2 assigns stable frontmatter addresses. The shipped format is:
```yaml
address: c-000042
```
`c-` means creation-order counter. The numeric part is zero-padded to six digits. This is not a content hash. The spec is explicit that the shipped address is deterministic and stable, but not content-addressable.
The rollout baseline is `2026-04-23`. After DragonScale adoption, post-rollout non-meta pages are expected to have addresses. Legacy pages are exempt until you do a deliberate backfill.
The helper has three real modes:
```bash
./scripts/allocate-address.sh
```
```bash
./scripts/allocate-address.sh --peek
```
```bash
./scripts/allocate-address.sh --rebuild
```
The default mode reserves and prints the next address. `--peek` is read-only. `--rebuild` recomputes the counter from the highest observed `c-NNNNNN`.
Example command:
```bash
./scripts/allocate-address.sh --peek
```
### How ingest and lint use it
`wiki-ingest` enables address assignment only when `./scripts/allocate-address.sh` is executable and `./.vault-meta` exists. If both conditions are true, new non-meta pages get `address:` in frontmatter. If not, ingest proceeds without addresses.
`wiki-lint` enables address validation only when `./scripts/allocate-address.sh` is executable and `./.vault-meta/address-counter.txt` exists. If those conditions are true, lint checks address format, uniqueness, counter consistency against `--peek`, missing addresses on post-rollout pages, and `address_map` consistency in `.raw/.manifest.json`.
The single-writer rule matters here. The allocator uses `flock`, but the ingest skill still says Phase 2 is single-writer only. Do not run parallel ingests from multiple sessions or sub-agents that assign addresses.
One hard rule from the skill docs is worth repeating. Never edit `.vault-meta/address-counter.txt` directly. Only mutate it through `scripts/allocate-address.sh`.
To disable Mechanism 2 without uninstalling:
1. stop running ingests that depend on address assignment
2. remove `.vault-meta/` if you want feature detection to turn off
3. stop using `./scripts/allocate-address.sh`
Existing `address:` fields can stay on pages. They become inert metadata if the feature is disabled.
## Mechanism 3: Semantic Tiling Lint
### What it checks
Mechanism 3 is an embedding-based duplicate-page detector. It scans markdown files under `wiki/` and excludes:
- `wiki/folds/`
- `wiki/meta/`
- common meta filenames such as `index.md`, `log.md`, `hot.md`, `overview.md`, `dashboard.md`, `Wiki Map.md`, and `getting-started.md`
- files with `type: meta`
- files with `type: fold`
- symlinks or paths that escape the vault root
It computes one embedding per included page, compares pairs by cosine similarity, and emits candidate overlap in bands.
Default bands:
- `>= 0.90` as error
- `0.80 - 0.90` as review
- `< 0.80` as pass
The helper never auto-merges pages. It only reports candidates for review.
Example command:
```bash
python3 ./scripts/tiling-check.py --peek
```
That gives structured diagnostics without computing embeddings.
### Local embeddings requirement
By default, the helper only trusts a local ollama endpoint at `http://127.0.0.1:11434`. Remote ollama endpoints require an explicit override flag because page bodies are sent as embedding input.
Remote override example:
```bash
python3 ./scripts/tiling-check.py --allow-remote-ollama --peek
```
The normal ready path is local:
1. `python3` is installed
2. ollama is reachable on localhost
3. `nomic-embed-text` is installed in ollama
Important exit codes:
- `0` success
- `10` ollama unreachable
- `11` model missing
`wiki-lint` is written to treat those as skip conditions.
### Calibration and no-op behavior
The shipped thresholds are conservative seeds, not calibrated truth. The skill docs call for a manual one-time calibration pass per vault. Until you do that, expect both false negatives and false positives.
The helper also has intentional no-op behavior. If ollama or the model is missing, it exits with the skip code. It does not fake results.
Useful commands:
```bash
python3 ./scripts/tiling-check.py --peek
```
```bash
python3 ./scripts/tiling-check.py --rebuild-cache
```
```bash
python3 ./scripts/tiling-check.py --report wiki/meta/tiling-report-YYYY-MM-DD.md
```
`--report` is real and path-confined to the vault. Use it when you want a saved report. Use `--peek` when you only want readiness and diagnostics.
To disable Mechanism 3 without uninstalling:
1. stop running `python3 ./scripts/tiling-check.py`
2. stop using the semantic-tiling path in `wiki-lint`
3. do not provision ollama or the model if you do not need them
Note that `.vault-meta/` is a shared gate for Mechanisms 2, 3, and 4. Do not remove it to disable Mechanism 3 alone, or you will also turn off address allocation and boundary-first autoresearch. The tiling cache lives under `.vault-meta/` but is inert when the helper is not invoked.
## Mechanism 4: Boundary-First Autoresearch
### What it does
Mechanism 4 scores frontier pages in the wiki graph. The shipped formula is:
```text
boundary_score(p) = (out_degree(p) - in_degree(p)) * recency_weight(p)
```
In practice, high-score pages point outward to many scoreable pages, receive relatively fewer inbound links, and were updated recently enough to still be frontier-like.
The helper reads `wiki/**/*.md`, builds a wikilink graph, and emits ranked results to stdout or JSON. It is intentionally stdout-only. Unlike the tiling helper, it has no `--report PATH` mode.
Example command:
```bash
python3 ./scripts/boundary-score.py --json --top 5
```
That is the exact command the autoresearch skill uses for candidate generation.
### Agenda-control caveat
This caveat is explicit in both the spec and the skill docs.
This is agenda control, not pure memory.
Mechanism 4 does not just describe the vault. It influences what the agent is likely to research next. That crosses the memory and planning boundary.
The project keeps it opt-in and labels it honestly. If you want the strict memory-layer subset only, omit this path. Do not use `/autoresearch` without a topic, or do not set up and invoke the boundary scorer.
### How /autoresearch behaves with and without it
With Mechanism 4 available, and only when `/autoresearch` is invoked without a topic, the skill:
1. checks for `scripts/boundary-score.py`
2. checks for `./.vault-meta`
3. checks for `python3`
4. runs `./scripts/boundary-score.py --json --top 5`
5. presents the top frontier pages as candidate topics
6. lets the user pick, override with free text, or decline
If the helper exits non-zero, returns invalid JSON, or returns an empty `results` array, the skill falls back.
Without Mechanism 4, or after fallback, `/autoresearch` simply asks:
```text
What topic should I research?
```
The helper suggests. The user still decides.
To disable Mechanism 4 without uninstalling:
1. stop running `python3 ./scripts/boundary-score.py`
2. use `/autoresearch [topic]` with an explicit topic
3. avoid the no-topic `/autoresearch` path if you do not want frontier suggestions
Note that `.vault-meta/` is a shared gate for Mechanisms 2, 3, and 4. Do not remove it to disable Mechanism 4 alone. The scorer itself is read-only and uses no shared state; disabling it just means not invoking it.
## Operational Policies
### Single-writer rule
DragonScale assumes a single writer for the address-assignment path. The allocator is flock-guarded, which protects the counter from simple races. It does not turn the whole wiki into a safe multi-writer system.
The ingest skill is explicit here. Do not run parallel ingests from multiple Claude sessions or sub-agents that assign addresses.
The safe operating policy is:
- one active ingest writer at a time
- one address allocator path at a time
- no direct manual edits to counter state
Mechanism 1 is human-invoked and easy to serialize. Mechanism 3 uses a lock for cache I/O. Mechanism 4 is read-only.
### Feature detection and graceful fallback
DragonScale is meant to be feature-detected, not assumed.
`wiki-ingest` only assigns addresses when the allocator is executable and `.vault-meta/` exists.
`wiki-lint` only validates addresses when the allocator exists and `.vault-meta/address-counter.txt` exists.
`wiki-lint` only runs semantic tiling when the helper exists and `python3` is available, then interprets readiness from `--peek`.
`autoresearch` only uses boundary-first selection when the helper exists, `.vault-meta/` exists, and `python3` is present.
When those conditions are not met, the repo falls back to earlier behavior. That is the intended operational posture.
## Troubleshooting
### Missing flock
If `flock` is missing, fix that first. Symptoms can include an unsafe address-allocation path or a tiling cache path that cannot lock correctly.
Check:
```bash
command -v flock
```
If it is absent, install the package that provides it for your system, then rerun:
```bash
bash bin/setup-dragonscale.sh
```
Do not work around this by editing `.vault-meta/address-counter.txt` directly.
### Missing ollama or model
This only blocks Mechanism 3. It does not block the rest of DragonScale.
Check ollama reachability:
```bash
curl -sS http://127.0.0.1:11434/api/version
```
Check tiling readiness:
```bash
python3 ./scripts/tiling-check.py --peek
```
If the helper exits `10`, ollama is not reachable. If it exits `11`, pull the model:
```bash
ollama pull nomic-embed-text
```
Then rerun:
```bash
python3 ./scripts/tiling-check.py --peek
```
Remember that Mechanism 4 does not need ollama. If you only want boundary-first autoresearch, `python3` is enough.
### Safe rollback / disable path
You do not need to uninstall the repo to turn DragonScale off. Use the smallest rollback that fits what you want:
- Mechanism 1: stop invoking `wiki-fold`. It uses no shared state.
- Mechanism 2: stop using `./scripts/allocate-address.sh`. Existing `address:` frontmatter fields remain as plain content.
- Mechanism 3: stop running `python3 ./scripts/tiling-check.py` and stop invoking the semantic-tiling path in `wiki-lint`. Cache under `.vault-meta/` is inert when not used.
- Mechanism 4: stop running `python3 ./scripts/boundary-score.py` and avoid the no-topic `/autoresearch` path. The scorer is read-only; disabling is not invoking it.
`.vault-meta/` is a shared gate for Mechanisms 2, 3, and 4. Removing it disables all three together, not just one.
If you want to disable DragonScale feature detection across the setup-based mechanisms at once, remove `.vault-meta/`:
```bash
rm -rf .vault-meta
```
Then stop invoking the DragonScale-specific helpers and skills. This leaves your normal wiki content intact. It does not remove fold pages, and it does not strip existing `address:` fields from frontmatter. Those remain as plain content unless you choose to clean them up manually.
If you later want DragonScale back, rerun:
```bash
bash bin/setup-dragonscale.sh
```