monk97.mewritingvault-hq

Keeping the notes, replacing the app.

Vault HQ · a front end for a second brain

2026-10-28 → 2026-11-02 not part of the home lab series

My notes live in one folder of plain markdown: projects, references, daily notes, and a steady stream of things that agents write there on my behalf. The files work well. Obsidian, the app I opened them in, stopped being the right place to use them. So I kept the files and replaced the front end with Vault HQ, a small local app that reads the folder and shows what needs me. This is how the folder is organised, what HQ does on top of it, and the parts that took some care.

Vault HQ's Home view in a dark terminal style: tabs for home, needs, days, tasks, inbox, notes and ask; an ask prompt with suggested questions; then panels for today's agent trail, tomorrow, items waiting on a decision, and project cards with progress bars.
Home. Every screenshot here is from a demo vault of made-up notes (a garden, learning Rust, a book club), not the real one.

WHY NOT JUST OBSIDIAN

The folder had grown into structured data. Each daily note carries metric properties and an agent log, tasks are kept in sync with Taskwarrior, and repeated jobs keep findings files. Obsidian shows all of that as files to open. Three things kept getting in the way:

The markdown itself was never the problem, so nothing about the storage changed. HQ is a different window onto the same files. Obsidian can still open every note, and HQ has a key that does exactly that.

THE BRAIN UNDERNEATH

The vault is one folder with a fixed layout. Every top-level folder has a purpose, and every folder carries its own AGENTS.md with the rules for writing there.

THE LAYOUT

00-Inbox/        # raw captures and session summaries; sorted weekly
01-Projects/     # one living note per project
03-Learning/     # guides and how-tos
04-Personal/     # mine alone
05-References/   # runbooks, designs, findings for repeated jobs
06-Memory/       # shared agent memory: session logs and atomic facts
07-Agents/       # how each agent is wired to the vault
08-Archive/      # sorted and done
09-Planning/     # daily notes, weekly plans, the tasks inbox
_templates/      # one template per kind of note
_tools/          # the automation, installed from here
AGENTS.md        # the contract every writer reads first

Several writers share this folder: me, Claude Code, Cursor, and a handful of scripts. That only works because they all follow one contract, the root AGENTS.md. Its central idea is zones, which decide whose words a note holds:

ZONEWHEREAGENTS MAY
human04-Personal, 09-Planning, any note with author: humanread, link, move or copy. Never write or edit. A change is proposed as a proposal- note in the inbox instead.
shared01-Projects, 03-Learning, 05-References, 07-Agentsedit in place, keeping my wording and decisions intact
machine00-Inbox, 06-Memory, _toolsanything, within the naming and frontmatter rules

The reason is plain. AI tends to smooth and summarise, and my own words should stay exactly as I wrote them. The contract adds a few rules any writer can follow: search for an existing note before creating one, copy a template rather than invent a structure, name files in lowercase kebab case, fill every frontmatter field including author, and never write a secret into the vault, because it syncs to iCloud. A lint script checks the result. After any meaningful action, an agent appends one line to that day's note, under a heading that belongs to machines:

## Agent log
- 14:05 claude@mac · site · drafted the vault-hq post → [[vault-hq-design]]

AUTOMATION, INSTALLED FROM THE VAULT ITSELF

The scripts live in the vault's _tools/automation/, and one installer wires them into the Mac. The vault the installer sits in is the vault that Mac serves. The installed copy is the one that runs, so an iCloud file that has been evicted can never break a hook. It sets up four things:

Because every writer uses the same folders, templates and log, knowledge lands in one place whichever tool produced it. Two conventions make that safe when writers overlap. Every script opens a vault file under the same exclusive file lock (flock), so two writers never interleave in one note. And tasks carry ids, so a task can be renamed, re-dated or moved without the sync losing track of it.

HQ ON TOP

HQ is a local web app with a terminal look: tabs on number keys, a legend at the bottom showing the current view's keys, monospace throughout. The type follows the zones. My own words are set in IBM Plex Mono and machine output in JetBrains Mono, so a glance tells you who wrote a line.

Ctrl K opens a palette to jump to any view, day or note, ask a question, or capture a thought into today's inbox note. ⌘, opens Settings.

The Days view in week mode: a calendar strip, sparklines for open tasks, tasks done and inbox size, and one column per day with Quick Wins, Work, Learning, Tomorrow and the agent log.
Days, week view: my own lines and the agents' log, side by side.
The Ctrl K palette over the Home view, with 'garden' typed: two matching notes, then ask, search notes and capture actions for the same word.
Ctrl K: one box to open a note, ask, search or capture.
The Notes view: notes listed by folder on the left; on the right a reference note rendered with its frontmatter, a table, a callout and a followed link, with 'edit' and 'open in obsidian' buttons and the backlinks section at the bottom.
Notes: frontmatter, tables, callouts and links rendered, with backlinks underneath.

Needs you is the view I wanted most, and it has no model in it. What lands in the queue is decided by fixed rules in one file:

The order comes from the same rules (the later a task, the higher it sits), so it can be explained and does not shift between visits. Every answer, whether done, move to next week, drop, accept, decline or pause, is appended to a decision log in my zone with the outcome and a "why" in my own words. Snoozing is the one answer that skips the decision log.

The Needs you view: a queue of four items (overdue, proposal, due in 2 days, quiet project) and the overdue task open on the right with four outcomes, a box for the reasoning, and the evidence note.
Needs you: the queue on the left, one decision open, with a box for the "why".

Underneath, the server is Node 25 running TypeScript directly, with no build step. It listens on localhost only and keeps no database: the vault and Taskwarrior are the only stores. The two exceptions are snoozes and the search index's embedding cache, kept beside the automation's state. Every task change goes through the task command, so Taskwarrior's own hook carries it back into the note, and there is one write path for tasks. Every write request must carry a custom header and come from the same origin, so no other website open in the browser can drive it. Every vault write HQ makes lives in one file, so the whole write surface can be read in one sitting.

The Tasks view: an add-task bar with project, date and priority, filters for all, due this week and high priority, and open tasks grouped by project note, each with a priority, a due date and a drop link.
Tasks, grouped by the project note they live in. Every change here goes through Taskwarrior.

LOCAL MODELS ONLY

Ask is the meaning-based search Obsidian lacked, and it runs on Ollama on the Mac. Search works in three steps:

Small models sometimes write "Excerpt 3" despite being told to cite, so those references are rewritten into the matching note link before the answer is shown. The chunks the model read are listed under every answer, and Search notes skips the model and returns the ranked chunks alone. Embeddings are cached by a hash of each chunk, so only new or changed text is embedded again.

The Ask view: 'ollama ready, semantic search on'; the question 'How long should zone 2 run, and why?' answered in one sentence citing a note, and below it the chunks it read from, each with its note, heading and score.
Ask: a one-line answer that cites its note, and every chunk it read, with scores.

Settings lets me change the model server and the models, but it accepts only an address on this Mac or the home network: plain http://, a loopback or private-range address or a .local name, and no path. Nothing in HQ talks to a cloud model, so the notes never leave the house to be searched or answered. (The wider setup does use a hosted model in one place: the short session summary that the Claude Code hook writes, about a session that already ran in Claude.)

ON THE MAC: THE TWO MODELS

ollama pull llama3.1:8b        # answers
ollama pull nomic-embed-text   # meaning, for search

THE PARTS THAT TOOK CARE

SAVING A NOTE WITHOUT CLOBBERING ANOTHER WRITER

The first design note said HQ would not be an editor; long writing would stay in Obsidian. That lasted five days. Once notes could be read in HQ, going elsewhere to fix a typo was the friction HQ existed to remove. But a note can change underneath an open editor at any moment: an agent appends a line, the task sync ticks a box, iCloud brings in an edit from another device.

So the editor starts from the file's exact text and its SHA-256. A save sends both back. Under the same file lock every other vault writer uses, the server reads the file again, and refuses the save if the hash no longer matches. HQ then shows what changed and lets me choose. Unsaved text is kept as a draft per note in the meantime.

A note open for editing in HQ with a red notice: 'This note changed on disk while you were editing. Nothing was overwritten.' It shows the line an agent added, and three choices: use the disk version, copy mine to the clipboard, or overwrite with mine.
A refused save: an agent added a line while the note was open, so HQ shows the change and asks.

THE SAVE, SIMPLIFIED (THE REAL ONE ALSO BUMPS updated:)

with open(path, "r+") as f:
    fcntl.flock(f, fcntl.LOCK_EX)          # the lock every vault writer takes
    cur = f.read()
    if sha256(cur) != base:                # changed since the editor read it
        return conflict(cur)               # show it; never overwrite blind
    f.seek(0); f.truncate(); f.write(new)  # in place, not by rename

The last line goes against the usual advice. The safe way to replace a file is normally to write a new one and rename it over the old. Here that would be wrong. The lock belongs to the open file, not its name. A writer that already holds the lock on the old file would keep writing into a file that no longer has a name, and its change would be lost. So the save truncates and writes the same file, under the same lock. Saves can also only reach notes the vault scan already found. The path is never built from the request, so an edit can't escape the folder.

WHEN macOS REFUSED TO READ THE NOTES

HQ started in a terminal, and then moved into a LaunchAgent so it would always be running. Under launchd it is a background process, and macOS refused it reads of iCloud files that were not stored locally. The read failed with Unknown system error -11 (EDEADLK), and one failed read blanked every view.

The fix was a Finder setting: right-click the vault folder and choose Keep Downloaded, which keeps every note inside stored on the Mac. The lasting change was in the code. The scan now skips a note it can't read, and the top bar says "N notes unreadable", with the list on hover. One bad file can no longer take the whole page down with it. The automation's settings file is now read from the installer's local copy, so the lab and NAS cards don't depend on iCloud being reachable.

A 4.6 MB NATIVE APP AROUND THE SAME PAGE

A browser tab is easy to lose, so HQ also has a native Mac app, built with Tauri. It is a Rust shell around the same interface, which it loads from the local server. On disk it is 4.6 MB (du -sh on the installed app, with a release build that is size-optimised, link-time optimised and stripped). It adds what a tab can't do:

The page gets no native access at all. Tauri can expose native functions to the page through its IPC bridge, and HQ grants none. Any link that isn't the local server is handed to macOS, so the web opens in the browser and a note link opens in Obsidian. The window can only ever show HQ.

PERSONAL CODE IN ONE FILE

My work notes live on a separate machine, in a separate vault, and never mix with these. That machine runs its own copy of HQ, and each copy reads exactly one vault, the one its installer recorded. The few features that only make sense at home, such as the lab and NAS cards, live in a single file that HQ loads only if it exists. Each one also stays off unless the vault's own settings list it. The export script copies HQ without that file, without git history, and without builds, and it fails if a personal term is still in the copy. It is the only supported way to move HQ to another machine. Settings can also refuse to open a vault before reading anything inside it.

BY THE NUMBERS

Everything here comes from the repository or a command run against it on 2026-11-02.

WHATVALUEHOW IT WAS COUNTED
first commit2026-10-28git log
commits9, the last on 2026-11-02git log
server2,097 lines of TypeScriptwc -l, the personal file left out
interface4,807 lines of TypeScript, TSX and CSSwc -l
native shell237 lines of Rustwc -l
native app on disk4.6 MBdu -sh on the installed app
runtime dependencies3: React, React DOM, Zodpackage.json
chunks sent to the model8the search code

The design note's first check, on 2026-10-28, recorded a task added in HQ and then dropped, which reached Taskwarrior and the note in about 5 seconds, and a cited answer from the vault in about 50 seconds on the default 8B model.

LIMITS, AND WHAT IS NEXT

More is coming. HQ shows one place today, but the same pattern can take in other streams: a bookmark importer, for one, and a quick-capture bar where I type something to remember and it lands as a clean note in the right folder, following the same contract every other writer does.

The whole thing started from the idea of building a second brain: one place that people and tools both write to, and that gives back what matters when it matters. Vault HQ is my version of the part you look at.

ASK AN AGENT

PROMPT: AN AGENT CONTRACT FOR MY NOTES FOLDER

My notes are a folder of markdown files that I and several AI agents write to. Read the folder's structure (not the content of personal notes) and draft an AGENTS.md contract for it.

- Propose zones: folders and notes that are mine alone, ones agents may edit in place, and ones that belong to machines. Agents must never edit my zone; they propose changes as a new note in the inbox instead.
- Naming and frontmatter rules (title, type, created, updated, author, tags), and a template per kind of note.
- One log line per meaningful action in that day's note, in a fixed format.
- How writers avoid clobbering each other: one shared file lock, and a refusal to save over a file that changed since it was read.
Show me the draft and a list of folders you were unsure about. Write nothing until I agree.
← writing Building the lab with an agent in the loop → press / for a prompt