# 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
```
