The vault has 102 notes, 40 Claude Code skills, a custom CLI, a web viewer running on Unraid, automated health checks at 3AM, and a feedback loop that turns every debugging session into reusable knowledge. None of it was planned. It accumulated, one irritation at a time, into something that quietly makes everything else faster.
Update (March 14): Since this was written, vaultctl has grown to 8 packages, 496 tests, and 85 skills. The architecture described here is still accurate; the numbers have scaled.
This is two things: a philosophy about how knowledge should work, and the technical stack underneath it.
Part 1: The philosophy
Two methodologies do the heavy lifting: PARA for organisation, Zettelkasten for note structure. Everything else follows from those two choices.
PARA
Tiago Forte's system. Four categories: Projects (time-bound, has a deliverable), Areas (ongoing, no end date), Resources (reference material you look up), Archive (finished or abandoned work). Everything fits somewhere. The decision takes two seconds.
Then there's the inbox. The inbox is the whole trick. Fourteen notes sitting in mine right now. Some will get filed. Some will get deleted. The point is they exist, because the friction of "where does this go?" never stopped me from writing them down. An unfiled note is infinitely more useful than a thought you didn't write down.
Zettelkasten
PARA tells you where a note lives. Zettelkasten tells you how to write one: one note, one concept. Each note links to related notes with wiki-links ([[note-name]]). You don't build a hierarchy; you build a web. Six months later you open a note about one problem and find three related problems you'd forgotten about, already documented, already linked.
The two systems complement each other. PARA handles navigation (four folders, no decision paralysis). Zettelkasten handles discovery (atomic notes, connected by links). Without PARA, you drown in flat files. Without Zettelkasten, you end up with 50 huge notes that each cover seven topics and link to nothing.
Structured metadata
Every note gets YAML frontmatter: type, status, tags, timestamps. This turns the vault from a pile of markdown into a queryable database. Seven Dataview dashboards show active projects, deadlines, recent decisions, and knowledge gaps. Templates enforce consistency across note types. One note with missing metadata breaks a query, then you stop trusting the query, then you stop using the dashboards.
Two kinds of knowledge
Durable knowledge (facts, configurations, decisions) belongs in the vault. "The Unraid server is at 192.168.1.50." "Revenue search 28626 uses Amount, not Amount (Transaction Total)." Reference facts that get updated, not expired.
Procedural knowledge (how to debug X, what to do when Y breaks) belongs where the agent that needs it can find it. In this case, Claude Code skills that load automatically when the situation matches. A vault note about LazyLibrarian's config quirks is useful when I'm reading. A skill about the same quirks is useful when Claude is debugging the same problem six months later.
Extraction over documentation
After a two-hour debugging session, nobody opens a note to document what happened. So the knowledge evaporates. The fix: make extraction automatic. Two layers (one for procedural knowledge, one for durable facts) evaluate every session and extract anything worth keeping. No decision required. The details are in Part 2.
Claudeception, built by blader, handles the procedural side. I didn't build it. Forty skills in four months. Not because I sat down and wrote forty documents, but because the system extracts them from work I was already doing.
The weekly review
One session a week. The inbox gets triaged (filed or deleted, under five minutes). Dashboards show what's active. Stale notes surface. Gaps become visible. It's the one part that requires willpower, but the queries do the heavy lifting.
Part 2: The technical stack
The vault
Obsidian, with 102 notes across the PARA structure:
00_Inbox/ : 14 untriaged notes
01_Projects/ : 16 time-bound initiatives
02_Areas/ : 5 ongoing domains (work, home, learning)
03_Resources/ : tools, concepts, people, companies
04_Archive/ : completed/abandoned work
05_Journal/ : daily notes
_dashboards/ : 7 Dataview-powered query hubs
_templates/ : 13 Templater templates
_ai/ : shared context files Claude loads automatically
Every note has structured YAML frontmatter. Flat only. No nesting. Dataview can't query nested fields reliably, and the moment your frontmatter needs a schema document to understand, you've lost.
---
type: project
created: 2026-01-15
updated: 2026-02-18
status: active
tags: [netsuite, revops]
summary: Multi-tab analytics dashboard inside NetSuite
area_link: "[[02_Areas/work/my-company]]"
---
Thirteen Templater templates enforce consistency across note types. The dashboards are the payoff. dashboard-home shows active projects, upcoming deadlines, inbox count, and recently modified notes. dashboard-weekly-review runs a guided checklist with queries for decisions made, people contacted, and projects needing updates. All generated from frontmatter, no manual curation.
vaultctl
The Obsidian MCP server is broken in Claude Code CLI. BrokenPipeError kills it before it initialises. Zombie processes pile up. The 48-comment GitHub issue has no fix. So, vaultctl.
A TypeScript monorepo with three packages: @vaultctl/core (pure library), vaultctl (CLI wrapper), and @vaultctl/web (read-only vault browser). The CLI is stateless. It reads the vault filesystem and exits. No persistent process, no pipes to break.
# Search notes by content, type, or status
vaultctl search "tempo plans" --type project --status active
# Manage tags across frontmatter and inline
vaultctl tags rename status/active status/in-progress
# Check vault health
vaultctl health --check stale --days 90
# Read and update frontmatter
vaultctl meta set "01_Projects/vaultctl" status completed
The web viewer runs on Unraid at 192.168.1.50:3000, an Express 5 app with EJS templates, Basic Auth, and a retro dark theme. Accessible from my phone via Tailscale. The vault is synced to Unraid via Syncthing, so the web viewer always has current data.
102 tests. Published to npm. The MCP server it replaced had zero tests and crashed constantly.
Claude Code skills
Forty skills in ~/.claude/skills/, each a SKILL.md file that Claude loads when it matches the situation. Three do the heavy lifting:
claudeception, not mine, worth shouting out, extracts reusable knowledge from work sessions. A hook appends an evaluation prompt to every user request:
# claudeception-activator.sh (simplified)
# Appended to every user prompt submission
EVALUATE:
- Did this require non-obvious investigation?
- Was the solution something that helps in future situations?
- Did I discover something not in the documentation?
IF YES → Extract as a skill
IF NO → Skip
When extraction triggers, Claudeception checks quality criteria (is it reusable? non-trivial? verified?), searches for existing skills to avoid duplicates, and structures the output into a standard template:
# Skill: lazylibrarian-config-system
## Problem
LazyLibrarian silently drops config values on restart.
## Trigger Conditions
- config.ini values have no effect after restart
- a [SECTION] you added disappears
## Solution
1. Check ConfigBool: it uses getint(), not getboolean()
2. Container shutdown rewrites config from memory
3. ...
## Verification
docker exec lazylibrarian cat /config/config.ini | grep YOUR_KEY
update-codex handles the other side: durable facts. It audits sessions for knowledge worth persisting to the vault: IP addresses changed, config decisions made, architectural choices discovered. It builds a manifest (UPDATE this note, CREATE that note, SKIP this one), gets approval, then executes surgical edits. Frontmatter gets updated timestamps. Wiki-links get verified.
write-blog-post, the skill that produced the post you're reading, enforces voice, structure, frontmatter schema, and the build/deploy pipeline. It's a 200-line document that knows about gerund titles, understated closings, and the difference between builds and engineering as tags.
Hooks
Three hooks in ~/.claude/hooks/ form the safety and learning layer:
pre-tool-use.sh blocks five irreversible operations: force-push to main, rm -rf on source directories, DROP DATABASE, Unraid server shutdown, and docker system prune -a. Everything else is allowed. Claude runs in bypassPermissions mode, so the hook is the safety net, not the permission system.
claudeception-activator.sh appends the extraction evaluation prompt to every user request. This is what makes knowledge extraction automatic rather than opt-in.
block-obsidian-mcp.sh prevents broken MCP calls from hanging the session. Three lines of bash that save minutes of debugging.
Automation
gemini-jobs schedules tasks via cron and Gemini CLI's headless mode. Two jobs run on the Mac Mini:
Daily Briefing (6AM): reads calendar, vault projects, and recent notes to generate a morning summary.
Vault Health Check (3AM): scans for broken wiki-links, stale notes, missing frontmatter, and orphaned files. Writes results back into the vault as a daily note.
Both are bash scripts with metadata headers, run by a shared runner.sh that sets up the cron environment (PATH, Homebrew, API keys). Log rotation keeps the last 30 runs per job.
The feedback loop
Work session
↓
Discover something non-obvious
↓
claudeception extracts it → ~/.claude/skills/
↓
update-codex persists facts → Obsidian vault
↓
Dataview dashboards surface it
↓
Weekly review catches gaps
↓
Next work session is faster
The 2,300-line NetSuite Suitelet open in my editor right now (a full analytics platform with eight tabs, milestone reconciliation, revenue tracking, and a conference planner) exists partly because this system captured every NetSuite quirk along the way. forceLabel on loadSearchResults for formula fields. AMOUNT not AMOUNT_TRANSACTION_TOTAL for multi-line sales orders. Mutually exclusive parameters in the N/llm module. Each one discovered once, extracted once, available forever.
The system doesn't require discipline. It requires using the tools that are already running.