# Parsidion

> Use first when a request references past knowledge or future memory: "have we seen this before", "what do we know", "check notes", "prior art", "save to the vault", "remember this", "don't forget", "capture this", "the vault", "ParsidionVault", or "ClaudeVault". Also for vault-specific queries and upkeep: "what changed in the vault since <date>" or "what did the vault know as of <date>" (temporal search), "do my notes contradict each other" or "find conflicts in the vault" (vault-conflicts), and "connect parsidion to codex or gemini" or "wire parsidion into another agent" (multi-agent install). Parsidion owns the persistent markdown vault (default ~/ParsidionVault, legacy ~/ClaudeVault fallback) for debugging fixes, reusable patterns, research, and cross-project context. Use it to search existing knowledge, save new discoveries, rebuild the index, run the session summarizer, detect contradictions between notes, or configure vault hooks and agent integrations. Do not invoke for pure coding/debugging with no me

- Skill: `paulrobello/parsidion` (Agent Skill, multi-file: 158 files)
- Install (CLI): `npx skillmds@latest add paulrobello/parsidion`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulrobello/parsidion/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: paulrobello (https://skillmd.com/u/paulrobello)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulrobello/parsidion

---


# Parsidion vault - Knowledge Management System

> A richly organized Obsidian vault (default `~/ParsidionVault/`, with legacy `~/ClaudeVault/` fallback) that replaces built-in auto memory with structured, searchable, cross-linked knowledge.

## Philosophy

- **Search before create.** Always search the vault before creating a new note. Duplicate knowledge fragments rot.
- **Atomic notes.** One concept per note. If a note covers two distinct ideas, split it.
- **Frontmatter is mandatory.** Every note must have a valid YAML frontmatter block. No exceptions.
- **Knowledge compounds.** Individual notes gain value through links. An unlinked note is a dead note.
- **Confidence matters.** Tag what you know vs. what you suspect. Update confidence as understanding deepens.

## Vault-First Consultation

The vault is your **first stop** — not your last resort. Before web search, before documentation, before experimentation, check whether the vault already holds a solution.

### Debugging: Search Before You Diagnose

When you encounter an error, exception, or unexpected behavior:

1. **Extract the key signal**: the exception type, package name, or the most distinctive phrase from the error message
2. **Dispatch the `vault-explorer` agent** with the signal as the query.
3. **Answer returned?** Apply the documented solution; update the note if you learn something new about the problem. `Read` specific files from the Sources section only if you need more depth.
4. **"No relevant vault notes found"?** Diagnose, solve, then save the solution so future sessions benefit.

### Implementation: Check for Prior Art

When implementing any feature, pattern, or integration:

1. **Dispatch the `vault-explorer` agent** with the feature keyword as the query.
2. **Answer returned?** Reuse and adapt — a proven implementation beats a fresh one every time. `Read` specific files from the Sources section for details; check the `sources` frontmatter field for code references.
3. **"No relevant vault notes found"?** Implement it, then save the pattern so future sessions benefit.

### Efficient Vault Search

Dispatch the **`vault-explorer` agent** with a natural language query. The agent
searches all relevant vault folders using the appropriate Grep strategy, ranks
matches, reads the top files, and returns:

- **`## Answer`** — synthesized answer ready to use
- **`## Sources`** — absolute paths for targeted `Read` calls if deeper context is needed

**No results?** Dispatch the `research-agent` to research externally
and save findings to the vault.

### The Vault-First Loop

```
Error or implementation question
  → Dispatch vault-explorer agent
    → Found? Apply / adapt solution → Update note with new learnings
    → Not found? Solve it → Dispatch research-agent to save
```

Saving after a successful solve is as important as searching before. Every unsaved solution is a missed opportunity for every future session.

---

## Vault Structure

```
~/ParsidionVault/        # Or legacy ~/ClaudeVault/ when upgrading
├── CLAUDE.md            # Auto-generated lean index (stats, conventions, recent activity, folder pointers)
├── TAGS.md              # Auto-generated full tag cloud + tag list (for summarizer tag reuse)
├── config.yaml          # Optional — hook/summarizer settings (see Configuration)
├── Daily/               # Session summaries (Daily/YYYY-MM/DD-{username}.md)
├── Projects/            # Per-project context and decisions
├── Languages/           # Python, Rust, TypeScript, Swift, etc.
├── Frameworks/          # Next.js, FastAPI, Textual, Rich, etc.
├── Patterns/            # Design patterns, architectural solutions
├── Debugging/           # Error patterns, diagnostic steps, fixes
├── Tools/               # CLI tools, libraries, packages
├── Research/            # Deep-dive research documents
├── Knowledge/           # General knowledge, concepts, reference material
├── Rules/               # Trigger-scoped behavioral rules (auto-injected on match)
├── History/             # Historical notes
└── Templates/           # Symlinked to skill templates (read-only)
```

## Conventions

### Filenames
- **Kebab-case only**: `python-async-patterns.md`, `nextjs-app-router-caching.md`
- Descriptive but concise -- aim for 3-5 words

### Subfolders for Related Note Clusters

When **3 or more notes share a common subject**, group them into a subfolder named after that subject:

```
# Before (scattered)
Research/
  fastapi-middleware-basics.md
  fastapi-middleware-auth.md
  fastapi-middleware-cors.md

# After (grouped)
Research/
  fastapi-middleware/
    basics.md
    auth.md
    cors.md
```

**Rules:**
- The subfolder name is the shared subject prefix in kebab-case (e.g., `fastapi-middleware/`)
- Note filenames inside the subfolder drop the redundant prefix (e.g., `auth.md` not `fastapi-middleware-auth.md`)
- This applies both **proactively** (when creating a 3rd note on a subject) and **retroactively** (reorganize existing notes when a 3rd sibling appears)
- Update all `[[wikilinks]]` in `related` fields after moving notes
- Run `update_index.py` after any reorganization

### Cross-References
- Use **wikilinks** for all internal references: `[[python-async-patterns]]`
- Place wikilinks in the `related` frontmatter field AND inline in the note body where contextually relevant

### Folder Placement
| Content Type | Folder |
|---|---|
| Language-specific knowledge (syntax, idioms, stdlib) | `Languages/` |
| Framework-specific knowledge (config, APIs, gotchas) | `Frameworks/` |
| Reusable design patterns and architectural solutions | `Patterns/` |
| Error patterns, diagnostic steps, bug fixes | `Debugging/` |
| CLI tools, libraries, package notes | `Tools/` |
| Long-form research and analysis | `Research/` |
| General knowledge, concepts, reference material | `Knowledge/` |
| Behavioral rules auto-injected when a prompt/file trigger matches | `Rules/` |
| Per-project decisions, architecture, key paths | `Projects/` |
| Daily session summaries | `Daily/YYYY-MM/DD-{username}.md` (e.g. `Daily/2026-03/13-probello.md`; username from `vault.username`, default `$USER`) |

### Frontmatter Standard

Every note must begin with this YAML block:

```yaml
---
date: YYYY-MM-DD
type: pattern|debugging|research|project|daily|tool|language|framework|knowledge
tags: [tag1, tag2]
project: project-name        # optional - omit if not project-specific
confidence: high|medium|low
provenance: inferred         # optional - explicit|inferred|corrected|observed|imported (default: inferred)
sources: []                  # URLs, file paths, or references
related: []                  # [[wikilinks]] to other vault notes
---
```

**Field rules:**
- `date`: The date the note was created or last substantially updated.
- `type`: Must be one of the enumerated values. Drives folder placement.
- `tags`: Freeform but prefer existing tags. Check the vault index first. Use kebab-case, never underscores — mixed underscore/hyphen tags fragment search and dedup across the vault. Convert repo names like `par_ai_core` to `par-ai-core`. Prefer short singular tags — e.g. `voxel` not `voxel-engine`, `hook` not `hooks`, `fractal` not `fractals`. Longer compound tags are acceptable only when the shorter form would be ambiguous.
- `project`: Optional. Must also be kebab-case (no underscores). Convert repo names: `par_ai_core` → `par-ai-core`.
- `confidence`: `high` = verified across multiple interactions or sources. `medium` = likely correct, single source. `low` = hypothesis or unverified.
- `provenance` (optional): Origin of the note's content. `explicit` = user directly stated it; `inferred` (default) = derived from transcript/behavior or distilled by an agent; `corrected` = edited from a prior incorrect version; `observed` = directly witnessed at runtime (e.g. daily notes); `imported` = brought in from an external source. Omit when unsure — defaults to `inferred`.
- `related`: Must contain at least one wikilink in inline quoted array format: `["[[note-one]]", "[[note-two]]"]`. No orphan notes.

## When to Save Knowledge

Save a note when you encounter:

- **Stable patterns** confirmed across multiple interactions or projects
- **Key architectural decisions** and the reasoning behind them
- **Important file paths** and project structure insights
- **Solutions to recurring problems** -- especially if the fix was non-obvious
- **Debugging insights** -- error messages mapped to root causes and fixes
- **User preferences** for workflow, tools, coding style, communication
- **Explicit requests** -- when the user says "remember this" or equivalent

## When NOT to Save

Do not create notes for:

- **Session-specific context** -- current task details, temporary variables, in-progress work
- **Incomplete or unverified information** -- wait until confidence is at least `medium`
- **Anything already in CLAUDE.md** -- the vault supplements project instructions, it does not duplicate them
- **Raw code dumps** -- code without explanation, context, or the "why" has no vault value
- **Transient state** -- things that will be irrelevant by next session

## Anti-Patterns

| Anti-Pattern | Correct Approach |
|---|---|
| Creating a note without frontmatter | Always include the full frontmatter block |
| Creating a duplicate note | Search the vault first; update the existing note instead |
| Dumping code without context | Explain what the code does, why it matters, and when to use it |
| Orphan notes (no links) | Every note must link to at least one other note via `related` |
| Monolithic notes (multiple concepts) | Split into atomic notes, one concept each, linked together |
| Modifying files in `Templates/` | Templates are managed by the skill -- never edit directly |
| 3+ notes on the same subject left flat | Move them into a named subfolder; update all wikilinks |

## Workflow Integration

### Hooks

All hooks read `<resolved vault>/config.yaml` for settings. CLI args override config values.

Claude Code installs the full hook set below. Codex runtime hooks are session lifecycle only: native Codex `SessionStart` and `Stop` hooks registered in `~/.codex/hooks.json` when `hooks = true` is enabled in `~/.codex/config.toml`. Gemini runtime hooks are also lifecycle only: `SessionStart` and `SessionEnd` commands registered in `~/.gemini/settings.json` via `--runtime gemini` or `--runtime all`. Gemini runtime hooks are separate from prompt AI backend selection and do not add a Gemini prompt backend; Gemini has no native subagent lifecycle capture in this first pass.

#### Connecting other agents

The `connect` / `disconnect` installer verbs wire parsidion into a coding agent's hooks and instructions file in one command. `connect` writes the agent instructions file (`~/.codex/AGENTS.md` for codex, `~/.gemini/GEMINI.md` for gemini) and enables the agent's runtime hooks; `disconnect` removes only that agent's integration:

```bash
uv run install.py connect codex      # wire codex: AGENTS.md + codex hooks
uv run install.py connect gemini     # wire gemini: GEMINI.md + gemini hooks
uv run install.py disconnect codex   # remove the codex integration only
```

`connect`/`disconnect` are a friendlier front-end for the existing `--runtime` flag; they also inject the agent instructions file that `--runtime` alone does not.

| Hook | Behavior | Config section |
|---|---|---|
| **SessionStart** | Loads relevant vault notes as a **compact one-line-per-note index** (title + tags) by default — minimal token usage. `--verbose` flag or `verbose_mode: true` config switches to full note summaries. Optional AI selection via `--ai [MODEL]` or `session_start_hook.ai_model` uses the configured prompt AI backend. | `session_start_hook` |
| **SessionEnd** | Captures learnings from the session transcript (fires once at session end); auto-launches the summarizer when pending entries exist | `session_stop_hook` |
| **PreCompact** | Snapshots current working state so context survives compaction | `pre_compact_hook` |

### Rebuilding the Index

The `CLAUDE.md` at the vault root is auto-generated. Rebuild it when:
- The user requests it ("rebuild vault index", "update vault index", "refresh vault index", "refresh the vault", "sync the vault")
- After creating, renaming, moving, or deleting notes
- The index timestamp is older than 24 hours

```bash
uv run ~/.claude/skills/parsidion/scripts/update_index.py
```

This scans all vault folders, reads frontmatter, and produces:
- A lean root `CLAUDE.md` index with quick stats, conventions, recent activity, top-20 tags, and folder counts with MANIFEST pointers
- A `TAGS.md` file with the full tag cloud and machine-readable tag list (used by the summarizer to avoid duplicating tags)
- **Per-folder `MANIFEST.md` files** — table-format indexes (Note | Tags | Summary) written inside each subfolder for quick orientation without loading the full root index
- **Staleness markers** — notes with zero incoming wikilinks AND older than 30 days are flagged `[STALE?]` in the index (surfaced for review, never auto-deleted)

When `git.auto_commit` is enabled, the rebuild commits only the generated
`CLAUDE.md`, `TAGS.md`, and `MANIFEST.md` files. Any unrelated work that was
already staged remains staged and is not included in the index commit.

Confirm to the user when the rebuild is complete.

## Summarizing Pending Sessions

The stop hook queues session transcript paths when it detects learnable content.
Run the summarizer on demand to generate structured vault notes.

Supported transcript locations:
- Claude Code: `~/.claude/projects/**/*.jsonl`
- Codex CLI: `~/.codex/sessions/**/*.jsonl`
- Gemini CLI: `~/.gemini/**/*.jsonl` and `<project>/.gemini/**/*.jsonl`
- pi (global): `~/.pi/agent/sessions/**/*.jsonl`
- pi (project-local): `<project>/.pi/agent-sessions/**/*.jsonl`

### Running the Summarizer

**From a terminal outside Claude Code** (normal usage):
```bash
uv run ~/.claude/skills/parsidion/scripts/summarize_sessions.py
```

**From inside a Claude Code session** (testing/debugging only):
Claude Code sets the `CLAUDECODE` env var which blocks nested Claude sessions.
Unset it for the subprocess:
```bash
env -u CLAUDECODE uv run ~/.claude/skills/parsidion/scripts/summarize_sessions.py
```

**Process a single explicit file** (useful for testing one entry):
```bash
env -u CLAUDECODE uv run ~/.claude/skills/parsidion/scripts/summarize_sessions.py \
  --sessions /path/to/file.jsonl
```

### Options

| Flag | Description | Config key |
|---|---|---|
| `--sessions FILE` | Process an explicit JSONL file instead of the default pending queue | — |
| `--dry-run, -n` | Preview what would be written without creating notes | — |
| `--model MODEL` | Explicit large-model override (default: backend large model) | `summarizer.model` |
| `--persist` | Accepted for backwards compatibility; currently unused | `summarizer.persist` |
| `--retry-dead-letters` | Re-queue retryable dead-lettered sessions into the pending queue, then summarize. Removes them from `dead_letters.jsonl` so they are actually re-processed (the `_dead_lettered_ids` guard otherwise skips them). Most `no_result` dead-letters are transient and succeed on a later retry; the 3-strike graveyard otherwise loses them | — |
| `--reason PREFIX` | With `--retry-dead-letters`: re-queue entries whose `last_failure` starts with this prefix (default `no_result` covers the legacy opaque kind and `no_result_timeout`/`empty`/`backend`) | — |
| `--min-age-days N` | With `--retry-dead-letters`: only re-queue entries dead-lettered at least N days ago (cooldown; default 0 = all matching) | — |
| `--max-count N` | With `--retry-dead-letters`: cap the number re-queued (0 = no cap) | — |

Uses the configured prompt AI backend: Claude runs through `claude -p`, Codex runs through `codex exec`, and no Claude Agent SDK or Codex SDK is required for this summarizer path.
Processes up to 5 sessions in parallel with `anyio` (configurable via `summarizer.max_parallel`).
Rebuilds the vault index automatically when done.
Sessions from today whose generated note type is `daily` are skipped (today's daily note
is still being built by the stop hook).

### Write-Gate Filter

Before generating a note, the summarizer asks Claude to evaluate whether the session contains
reusable insight. Transient sessions (failed experiments, routine builds, dead-ends with no
generalizable lesson) are skipped rather than saved. Skipped sessions are reported separately
from failures in the output summary.

### Hierarchical Summarization

For sessions whose cleaned transcript exceeds `max_cleaned_chars` (default 12,000), the
summarizer splits the transcript into chunks and uses the backend small model by default
(configurable via `summarizer.cluster_model`) to summarize each chunk first. The chunk summaries
are then fed to the main backend large-model note generator. This preserves context from very
long sessions that would otherwise be truncated.

### Automated Backlinks

After writing a new note, the summarizer scans existing vault notes for tag overlap and
injects bidirectional `[[wikilinks]]` — updating both the new note's `related` field and
adding back-references in matching existing notes. This builds the link graph automatically
rather than requiring manual maintenance.

## Vault Merge

`vault-merge` is a global CLI command for backend-aware AI-assisted note merging. It detects near-duplicate notes, merges their content via the configured prompt AI backend, and updates all bidirectional backlinks.

### Usage

```bash
# Find near-duplicate pairs using embedding similarity (scan only, no merge)
vault-merge --scan

# Merge two specific notes (NOTE_A survives, NOTE_B is trashed)
vault-merge NOTE_A NOTE_B --execute

# Merge without rebuilding the index (for batch merges — rebuild once at the end)
vault-merge NOTE_A NOTE_B --no-index --execute
```

### Key Flags

| Flag | Description |
|---|---|
| `--scan` | List near-duplicate pairs sorted by cosine similarity; no merge performed |
| `--execute` | Actually perform the merge (default is dry-run preview) |
| `--no-index` | Skip rebuilding the vault index after the merge; use during batch operations and run `update_index.py` once when all merges are done |

> Use `--no-index` on every individual merge during a batch deduplication run; run `update_index.py` once at the end. The `vault-deduplicator` agent handles this automatically.

## Vault Conflicts

`vault-conflicts` is a global CLI command (companion to `vault-merge`) that detects contradictions between similar notes and resolves them via the configured prompt AI backend. Where `vault-merge` collapses near-duplicate notes saying the *same* thing, `vault-conflicts` surfaces pairs that say *opposite* things.

### Usage

```bash
# List candidate conflict pairs only (no AI, no writes)
vault-conflicts --scan-only

# Machine-readable output for scripting
vault-conflicts --json

# Interactive resolution (default): review each pair, resolve via prompt AI backend
vault-conflicts

# List pairs without invoking the AI backend
vault-conflicts --no-ai
```

### Key Flags

| Flag | Description |
|---|---|
| `--scan-only` | List candidate conflict pairs (embedding-similarity candidates); no AI call, no writes |
| `--json` | Emit machine-readable JSON instead of interactive output |
| `--no-ai` | List pairs without invoking the prompt AI backend |
| `--threshold FLOAT` | Cosine similarity threshold for candidate pairs |
| `--top N` | Limit to the top N candidate pairs |
| `--vault PATH` | Target vault (path or name from `~/.config/parsidion/vaults.yaml`) |

## Temporal Search

`vault-search` accepts two temporal filters (used with metadata mode, no positional query):

- `--changed-since YYYY-MM-DD` — notes whose file `mtime` is on/after the date (catches edits).
- `--as-of YYYY-MM-DD` — notes whose frontmatter `date` is on/before the date (creation-time bound).

```bash
vault-search --changed-since 2026-06-01   # everything touched since June 1
vault-search --as-of 2026-01-01           # everything dated Jan 1 or earlier
vault-search --changed-since 2026-06-01 --tag python   # combine with other filters
```

## Vault Doctor

`vault_doctor.py` scans all vault notes for structural issues and repairs repairable issues via the configured prompt AI backend.

### Issues detected

| Code | Severity | Description |
|---|---|---|
| `MISSING_FRONTMATTER` | error | No YAML frontmatter block |
| `MISSING_FIELD` | error | `date`/`type` missing (all notes); `confidence`/`related` missing (non-daily) |
| `INVALID_TYPE` | error | `type` not in allowed set |
| `INVALID_DATE` | warning | `date` not in YYYY-MM-DD format |
| `ORPHAN_NOTE` | warning | No `[[wikilinks]]` in `related` field |
| `BROKEN_WIKILINK` | warning | Link target not found in vault |
| `HEADING_MISMATCH` | warning | No `#` heading found; first `##` heading should be promoted to `#` (skips daily notes) |
| `FLAT_DAILY` | warning | `Daily/YYYY-MM-DD.md` instead of `Daily/YYYY-MM/DD-{username}.md` |
| `PREFIX_CLUSTER` | warning | 3+ flat notes share a kebab prefix — should be moved into a subfolder |
| `NESTED_FM_KEY` | warning | Indented mapping key; the parser reads it as a top-level scalar |
| `UNTERMINATED_FM_LIST` | error | Inline list opens with `[` but never closes on the same line |
| `ORPHAN_FM_BRACKET` | warning | Stray `]` left as the first body line |
| `SCALAR_LIST_FIELD` | warning | `tags`/`related`/`sources` holds a bare scalar, so its entries are lost |
| `DUPLICATE_FM_KEY` | warning | Same top-level key twice; the parser is last-wins and drops the earlier value |

Daily notes (`type: daily` or path under `Daily/`) are exempt from `confidence`, `related`, and orphan checks.

The last five codes are **frontmatter-syntax** checks. `parse_frontmatter` implements a
deliberately small YAML subset and never raises, so a note using a shape outside it
silently gets the wrong value — these read the raw frontmatter text, the only place the
damage is still visible.

### Running the doctor

```bash
# Scan and report only (no writes)
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --dry-run

# Fix everything in one pass (frontmatter + tags + subfolders) — used by nightly cron
env -u CLAUDECODE uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --fix-all

# Individual fix modes:
# Repair frontmatter issues via the configured prompt AI backend (3 parallel workers by default)
env -u CLAUDECODE uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --fix-frontmatter
env -u CLAUDECODE uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --fix-frontmatter --jobs 5 --timeout 180

# Detect and merge duplicate tags (plural/singular, hyphen/underscore, collapsed)
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --fix-tags           # dry-run
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --fix-tags --execute # apply

# Detect and migrate prefix clusters into subfolders
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --migrate-subfolders           # dry-run
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --migrate-subfolders --execute # apply

# Repair up to 20 notes at a time
env -u CLAUDECODE uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --fix-frontmatter --limit 20

# Errors only (skip warnings)
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --errors-only --dry-run

# List every selectable rule with its risk column
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --list-rules

# Safe bulk invocation: fix-all without the risky bulk rules (ENH-015)
uv run --no-project ~/.claude/skills/parsidion/scripts/vault_doctor.py --fix-all --skip strip-prefixes --skip subfolder-prefix
```

For Claude CLI backend calls launched from inside Claude Code, unset `CLAUDECODE` as shown above. Codex backend calls use `codex exec` with an internal recursion guard.

`--fix-all` is equivalent to `--fix-frontmatter --fix-tags --strip-prefixes --migrate-subfolders --migrate-daily-notes --fix-permissions --execute`. Note that `--strip-prefixes` performs a vault-wide bulk file rename (rewriting wikilinks across every note) and `--migrate-daily-notes` renames legacy flat dailies to `DD-{username}.md` — run `--fix-all` on a clean git tree so a rename can be reverted. The nightly cron via `summarize_sessions.py --run-doctor` uses `--fix-all`.

**Per-rule selection (ENH-015).** `--only RULE` / `--skip RULE` (repeatable, mutually exclusive) select rules by name from the `--list-rules` catalog, covering both the per-note checks and the fix modes: `frontmatter-syntax`, `required-fields`, `valid-type`, `date-format`, `related-links`, `self-ref`, `headings`, `broken-wikilinks`, `flat-daily` (checks); `frontmatter-repair` (the AI repair stage), `tags`, `strip-prefixes`, `subfolder-prefix`, `daily-namespace`, `permissions` (bulk/safe modes). Selection gates the fix-mode dispatch, the scan checks, and the in-scan prefix-cluster stage alike. Every scan run ends with a per-rule `rule | found | fixed | skipped` report; `skipped` counts issues left unprocessed (under `--dry-run`, all of them). The `bulk` risk label marks the historically regression-prone rules (bulk renames, tag merges, AI body rewrites); the recommended safe bulk invocation is `--fix-all --skip strip-prefixes --skip subfolder-prefix`.

Repairs run in parallel (`--jobs N`, default 3). Each prompt AI subprocess (`claude -p` or `codex exec`) is independent so parallelism is safe; state updates and console output are guarded by a lock so lines are never interleaved. The per-call timeout (`--timeout SECS`) defaults to 120s — increase it when running many parallel workers to avoid spurious timeouts.

Repairable codes (prompt AI backend can fix): `MISSING_FRONTMATTER`, `MISSING_FIELD`, `INVALID_TYPE`, `INVALID_DATE`, `ORPHAN_NOTE`.
Auto-repairable without prompt AI (Python-only): `BROKEN_WIKILINK` (exact stem match, prefix-strip match, then a `vault-search` semantic fallback that never returns a `Daily/` note — a journal page matches almost any topic, so substituting one satisfies "the link resolves" while destroying what the link meant; when nothing suitable is found the link is dropped instead), `DUPLICATE_TAG` (merge via `--fix-tags`), `HEADING_MISMATCH` (promotes first `##` to `#`; enabled by default, disable with `--no-fix-headings`).
Auto-repairable via Python + prompt AI filter: `PREFIX_CLUSTER` — candidates are detected by Python; generic modifier prefixes that would split a compound slug are blocked deterministically by `_GENERIC_PREFIX_DENYLIST` (`client`/`code`/`env`/`id`/`admin`/`asset` + common modifiers); then the configured small model filters remaining generic-word false positives (e.g. 'fixing', 'missing'), keeping only specific subject names (project, library, OS, tool). Files are then moved and wikilinks patched by Python.
Auto-repairable without prompt AI (Python-only deterministic pre-pass): `NESTED_FM_KEY` (the `metadata:` mapping-wrapper shape — flattened to top level; other indented-key shapes still report) and `SCALAR_LIST_FIELD` (scalar `tags`/`sources` whitespace-split into a list; scalar `related` containing `[[wikilinks]]` re-wrapped). These run before issue classification; no AI backend call.
Not auto-repairable (require manual fix): `FLAT_DAILY`, and the three remaining frontmatter-syntax
codes (`UNTERMINATED_FM_LIST`, `ORPHAN_FM_BRACKET`, `DUPLICATE_FM_KEY`). Those syntax codes are
detection-only by design — the AI repair path is deliberately not turned loose on structurally
broken frontmatter. A note carrying one is left out of `doctor_state.json` entirely so every run
re-reports it until it is fixed (a `skipped` state entry is permanent, unlike `ok`, which expires
after `STATE_STALE_DAYS`).

### Singleton guard

Only one doctor may run at a time. The doctor claims an `flock` on `<vault>/.doctor.lock` (SEC-016); a second doctor finds the lock held, prints an error, and exits. The kernel releases the lock when the holder exits — including on SIGKILL — so there is no stale-PID state to recover from.

### Auto-commit stale files

Before scanning, the doctor runs `git status --porcelain` on the vault and commits any tracked or untracked files whose mtime is ≥ 15 minutes old. Deletions are skipped (no mtime to check). The commit message is `chore(vault): auto-commit N stale file(s) via vault_doctor`. This is a no-op when the vault has no `.git` directory or `git.auto_commit` is `false` in config. With `--dry-run` the files are listed but not committed.

### State tracking

The doctor writes `<resolved vault>/doctor_state.json` to avoid reprocessing notes unnecessarily:

| Status | Meaning | Next run behaviour |
|---|---|---|
| `ok` | No issues found | Skipped for 7 days |
| `fixed` | Prompt AI backend repaired it | Re-checked to confirm |
| `failed` | Prompt AI backend returned no output | Retried |
| `timeout` | Prompt AI backend timed out once | Retried once more |
| `needs_review` | Timed out on retry | Skipped — user must fix manually |
| `skipped` | Only non-repairable issues | Skipped indefinitely |

Use `--no-state` to ignore the state file and rescan all notes.

Run `update_index.py` after repairs — it reads `doctor_state.json` and adds a vault health line to `CLAUDE.md`'s Quick Stats section.

## Configuration

All hooks and the summarizer read `<resolved vault>/config.yaml` for settings.
Precedence: **hardcoded defaults → config.yaml → CLI args** (last one wins).

A template with all options lives at `~/.claude/skills/parsidion/templates/config.yaml`.
Copy it to the vault root to get started:

```bash
cp ~/.claude/skills/parsidion/templates/config.yaml ~/ParsidionVault/config.yaml
```

### Config Sections

> **📝 Note:** Model IDs shown in the config block below (e.g. `claude-sonnet-4-6`,
> `claude-haiku-4-5-20251001`, `gpt-5.6-luna`/`gpt-5.6-terra`, `BAAI/bge-small-en-v1.5`) are the hardcoded defaults.
> They can be changed via the corresponding keys in `<resolved vault>/config.yaml` without
> modifying any scripts. See the template at
> `~/.claude/skills/parsidion/templates/config.yaml` for all available keys.

```yaml
session_start_hook:
  ai_model: null           # Model for AI note selection (null = disabled)
  max_chars: 4000          # Max context injection characters
  ai_timeout: 25           # AI call timeout in seconds
  recent_days: 3           # Days to look back for recent notes
  debug: false             # Append injected context to debug log in $TMPDIR
  verbose_mode: false      # If true, inject full note summaries instead of compact one-line index
  use_embeddings: true     # Blend semantic matches into context; graceful fallback if db absent
  show_dead_letter_notice: false  # Include the dead-letter queue warning in SessionStart context

session_stop_hook:
  ai_model: null           # Model for AI classification (null = disabled)
  ai_timeout: 25           # AI call timeout in seconds
  auto_summarize: true     # Auto-launch summarizer when pending entries exist
  transcript_tail_lines: 200      # Lines to inspect by default
  pi_transcript_tail_lines: 1000  # Deeper fallback tail for pi transcripts when needed

subagent_stop_hook:
  enabled: true            # Set false to disable subagent transcript capture entirely
  min_messages: 3          # Minimum assistant message count (pi transcripts default to 1 when unset)
  excluded_agents: "vault-explorer,research-agent"  # Never capture these

pre_compact_hook:
  lines: 200               # Transcript lines to analyse

summarizer:
  model: null          # null = ai_models.<backend>.large
  max_parallel: 5          # Concurrent summarization tasks
  transcript_tail_lines: 400
  max_cleaned_chars: 12000
  persist: false           # Legacy no-op; retained for CLI compatibility
  cluster_model: null  # null = ai_models.<backend>.small
  dead_letter_retention_days: 7  # Prune dead_letters.jsonl entries older than N days each run (<=0 disables)

ai:
  backend: auto            # auto | claude-cli | codex-cli | none

ai_models:
  claude:
    small: claude-haiku-4-5-20251001
    large: claude-sonnet-4-6
  codex:
    small: gpt-5.6-luna
    large: gpt-5.6-terra

codex_cli:
  command: codex
  timeout: 60
  sandbox: read-only
  ephemeral: true
  skip_git_repo_check: true
  suppress_notify: true

embeddings:
  model: BAAI/bge-small-en-v1.5  # fastembed model ID; ~67 MB ONNX model, cached after first run
  min_score: 0.45                 # Minimum cosine similarity for search results
  top_k: 10                       # Default result count

git:
  auto_commit: true        # Auto-commit vault changes after writes
```

- `auto` prefers the active runtime when detectable and falls back to Claude CLI when ambiguous.
- Codex backend uses `codex exec` with default ephemeral/read-only/skip-git-repo-check/output-last-message prompt calls and the normal Codex CLI auth path; Parsidion does not manage Codex auth files such as `~/.codex/auth.json`. Internal calls also pass `--config notify=[]` by default so user-configured Codex turn-complete notifications do not fire for vault summarization. This is not OpenAI API-key provider support. Gemini runtime hooks do not imply `ai.backend: gemini`; no Gemini prompt backend is provided yet.
- `summarize_sessions.py` also uses the configured prompt AI backend. Claude uses `claude -p`, Codex uses `codex exec`, and no Claude Agent SDK or Codex SDK is required for this path.

### Programmatic Access

```python
import vault_common

# Load all config (cached per process)
config = vault_common.load_config()

# Get a single value with fallback
max_chars = vault_common.get_config("session_start_hook", "max_chars", 4000)
```

If `config.yaml` is missing or unreadable, all `get_config()` calls return the default.

## Manual Usage

### Creating a Note

1. Search the vault to confirm the note does not already exist.
2. Create the file in the appropriate folder with kebab-case naming.
3. Include the full frontmatter block.
4. Write the note body with inline wikilinks to related notes.
5. Ensure the `related` frontmatter field is populated.
6. Run `update_index.py` to rebuild the index.

### Searching the Vault

- Dispatch the **`vault-explorer` agent** for natural language queries — it searches priority folders, ranks matches, and returns a synthesized answer with source paths.
- Use `vault_common.py` functions for programmatic search from scripts.
- Use Obsidian's built-in search for interactive visual exploration.
- Use `Grep` or `Glob` tools only for targeted investigation when you know the specific file or exact pattern you need.

### Agents

| Agent | Trigger | Description |
|---|---|---|
| `vault-explorer` | "what do we know about X", "check our notes", "prior art" | Semantic vault search — returns a synthesized answer with source paths |
| `research-agent` | "research X and save to vault", no vault results found | External research via Brave Search + Web Fetch; saves findings to vault |
| `vault-deduplicator` | "deduplicate the vault", "find duplicate notes", "merge duplicate vault notes" | Scans for near-duplicate note pairs by embedding similarity, evaluates each pair, merges confirmed duplicates, and rebuilds the index |

### Linking Notes

- Use `[[wikilinks]]` in both the `related` frontmatter field and the note body.
- When a new note references an existing note, consider updating the existing note's `related` field to create a bidirectional link.

### Updating Existing Notes

- Prefer updating an existing note over creating a new one on the same topic.
- Update the `date` field when making substantial changes.
- Adjust `confidence` as understanding evolves.

## Complementary Tools

### NotebookLM

Vault notes capture research as structured text. The `notebooklm` skill transforms the same
source material into audio overviews, podcasts, quizzes, flashcards, mind maps, slide decks,
and reports — formats the vault cannot produce.

Common patterns after vault research:
- **Audio consumption**: Generate a podcast from the URLs in a vault note's `sources` field.
- **Study materials**: Create quizzes or flashcards from a `Research/` note.
- **Briefing documents**: Generate a briefing-doc from a cluster of related notes.
- **Mind maps**: Visual overview of a notebook containing the same sources as a vault cluster.

**Workflow**: Add the `sources` URLs from your vault note(s) as NotebookLM sources, then use
`notebooklm generate` for the desired artifact. The research-agent does this
automatically when NotebookLM is available.

**Requirement**: `pip install notebooklm-py` then `notebooklm login`. Run `notebooklm status`
to check. If unavailable, the vault workflow is completely unaffected.

## Graph Color Groups

The Obsidian graph at `<resolved vault>/.obsidian/graph.json` uses color groups to visually categorize nodes by tag.

### Current Color Groups

| Group | Tags | RGB (decimal) |
|---|---|---|
| Projects | `#synknot`, `#parvitar`, `#parsistant`, `#termflix`, `#parvault`, `#parsidion`, `#pkm`, `#vault`, `#par-cc-bot`, `#parllama`, `#par-dc-bot`, `#par-particle-life`, `#par-shape-2d`, `#par_qr_3d`, `#par_scrape`, `#par-gpt`, `#par-ocr`, `#par_infini_sweeper`, `#parsplat`, `#pim-leaderboard`, `#par-ai-core`, `#cctmux` | 48340 |
| Debugging | `#debugging` | 16733986 |
| Patterns | `#memory`, `#migration`, `#sync`, `#architecture`, `#pattern`, `#automation`, `#config`, `#event-driven`, `#pipeline`, `#subprocess`, `#crdt`, `#layered-config`, `#rate-limiting`, `#semantic-search`, `#vector-search`, `#codesign`, `#state-machine`, `#concurrency` | 5025616 |
| Research | `#research`, `#qdrant`, `#embeddings`, `#vector-database`, `#ocr` | 10233776 |
| Tools & SDKs | `#claude-code`, `#claude-sdk`, `#claude`, `#rich`, `#mcp`, `#ollama`, `#maturin`, `#redis`, `#websockets`, `#sentry`, `#mermaid`, `#mermaid-cli`, `#custom-tools`, `#acp-protocol`, `#tool`, `#api`, `#encryption`, `#discord`, `#security`, `#git`, `#performance`, `#plugin`, `#cli`, `#llm`, `#hooks`, `#sqlite`, `#agent`, `#pydantic`, `#langchain`, `#multi-provider`, `#docker`, `#xdg`, `#image`, `#textual`, `#typer`, `#fastapi`, `#websocket`, `#image-processing`, `#json`, `#yaml`, `#ai` | 2201331 |
| Languages | `#rust`, `#python`, `#swift`, `#swiftui`, `#typescript`, `#nextjs`, `#react`, `#macos`, `#macos-26`, `#rust-packages`, `#ui`, `#ux`, `#documentation`, `#sql`, `#ios`, `#python-bindings` | 16761095 |
| Terminal | `#terminal`, `#tmux`, `#par-term`, `#par-term-emu-core-rust`, `#tui`, `#vt100`, `#vte`, `#pty`, `#ansi`, `#crossterm`, `#svt` | 38536 |
| Graphics / 3D | `#wgpu`, `#sdf`, `#sdf-terrain`, `#voxel`, `#fractals`, `#mandel`, `#vrm`, `#avatar`, `#face-tracking`, `#arkit`, `#graphics`, `#fractal`, `#gpu-rendering`, `#animation`, `#gaussian-splatting`, `#physics`, `#rendering`, `#simulation`, `#glsl`, `#shader`, `#shaders`, `#3d-printing`, `#par-fractal`, `#voxel-world`, `#sparse-voxel-tree`, `#sub-voxel`, `#wgsl`, `#vulkan`, `#game` | 15277667 |
| Daily | `#daily` | 16744448 |

### When to Update

Update `graph.json` when:
- A new project tag is introduced (add to the Projects group)
- A new language or framework tag appears frequently (add to Languages or Tools/AI)
- A new topic cluster emerges that warrants its own color group
- An existing tag is renamed or removed

### How to Update

1. Read `<resolved vault>/.obsidian/graph.json`
2. Locate the matching `colorGroups` entry by its query pattern
3. To **add a tag to an existing group**: append `OR tag:#newtag` to the query string
4. To **create a new group**: append a new object to the `colorGroups` array:
   ```json
   {
     "query": "tag:#newtag",
     "color": { "a": 1, "rgb": <decimal_rgb> }
   }
   ```
5. Write the updated file — Obsidian picks up changes automatically

### RGB Format

Colors are stored as a single decimal integer (e.g., `

…(truncated)
