Eighteen Files And One Nix Module: How My Brain Works
The Obsidian vault I use as a system of record for every project, why the checkouts inside it are gitignored, and why half of it is generated from my NixOS flake.
I have a directory on my machine called brain. It is an Obsidian vault, it lives at /home/steve/brain, and it is the system of record for everything I am building, writing and recording. Thirteen commits, and at the time of writing, eighteen markdown files. That is the whole thing.
It sounds trivial written down like that. The interesting part is not the directory, it is the set of rules about what goes in it, because every one of those rules exists to solve a problem I actually hit. So let me walk through it.
The problem it solves
I work across several projects. This site, in Astro. A Go binary called rig that scaffolds projects from a blueprint and then re-applies that blueprint later. A Laravel forum. A Go CLI called conform that reviews an OpenAPI spec against the API design standard I keep. Each has its own repository, its own remote, its own history.
Each also has an agent session attached to it, and agent memory is keyed by working directory, which means it fragments across projects by design.
That fragmentation is correct for most facts. A build quirk in the forum is nobody else’s business. But there is a whole class of fact that has no home under that scheme: what exists, what state it is in, what is next. Ask “what am I working on” from inside a repo and the honest answer is “this, obviously”, which is not the question.
So the vault is the layer above. The test for whether something belongs in it is not importance, it is reach. A critical fact about one repository still belongs to that repository.
Notes are the index, repos are the content
This is the load-bearing idea, and everything else follows from it.
_meta/ conventions, current focus, machineprojects/ one note per projectcontent/ writing, draft through publishedvideos/ one note per videodaily/ dated notes, append-only
.repos/ project checkouts (gitignored).media/ footage and binaries (gitignored)The checkouts sit at .repos/<name>/ and are gitignored by the vault. Clone the vault and you get the index, not the contents.
That is deliberate, and worth being blunt about: the vault must never become a backup for the projects. Nesting repositories inside a repository gives you something that looks like a backup and is not one, which is strictly worse than having no backup, because you will stop worrying.
What the vault holds is the note about the project. Why it exists, what state it is in, what is next. Here is the frontmatter every project note carries:
---type: projectstatus: active # active | paused | shipped | abandonedclient: personal # personal, or the client's short namepath: /home/steve/brain/.repos/<name>repo: git@github.com:<owner>/<name>.giturl: https://... # if it is deployed somewhere---Four values in the status ladder. That is on purpose. A status field with fifteen values is a field nobody sets accurately, and a field nobody sets accurately is a field you cannot filter on, at which point why is it there?
Why the checkouts are dot-prefixed
Here is the bit that took me longest to arrive at, and it is pure Obsidian pragmatism.
Obsidian has no working ignore mechanism. The “Excluded files” setting in preferences reads like it will keep things out of the index. It does not. It de-ranks matches in search and nothing more, so the quick switcher, graph view and backlink pane still fill up with node_modules/**/README.md and every vendored LICENSE in every dependency you have ever installed. Once you have a Laravel app and a Next frontend in there, the vault stops working as a vault.
What Obsidian does honour, unconditionally, is skipping any directory whose name starts with a dot. So that is the mechanism. There is no second line of defence, which gives two rules:
- Nothing that is not prose goes in the visible tree. No checkouts, no build output, no video files.
- Anything bulky goes under a dot-directory.
There is a consequence worth knowing before it bites you: rg and fd skip hidden directories by default too. A search from the vault root will not see project code. That is usually exactly what I want from the vault root, and when it is not, --hidden fixes it, or I just work from inside the project.
Client is a field, not a folder
projects/ is flat. No clients/acme/ directory, no nesting at all. Client lives in frontmatter.
The reason is that Obsidian resolves [[name]] by filename rather than path. A folder therefore buys you nothing for navigation that the link graph does not already give you for free. All it adds is a second, competing hierarchy that you now have to keep consistent with the first.
A field also survives cases a folder cannot. A project with two clients. A project that changes hands. Personal work with no client at all. And it lets me group by client, or status, or stack, on the same day, where a folder forces me to pick one axis up front and then pay a move to change my mind.
When a client accrues enough projects to want a home, it gets a note, not a directory. It links out to each project, backlinks give me the reverse index for nothing, and it has somewhere to put the context a folder has no room for: contacts, rates, history.
The one real exception is isolation. Notes under NDA, or a set I might need to hand over intact. That is an access-control problem rather than an organisational one, and it argues for a separate vault rather than a subfolder in this one.
Most of it is generated
CLAUDE.md, _meta/conventions.md, _meta/machine.md and .gitignore are not written by hand. They are generated from a single file in my NixOS flake, users/features/vault.nix, and re-copied on every nixos-rebuild switch. Editing them in Obsidian appears to work and is silently reverted at the next rebuild.
The activation script is the whole trick:
run install -m 644 ${claudeMd} "$vault/CLAUDE.md"run install -m 644 ${conventionsMd} "$vault/_meta/conventions.md"run install -m 644 ${machineMd} "$vault/_meta/machine.md"
[ -e "$vault/_meta/current-focus.md" ] \ || run install -m 644 ${currentFocusMd} "$vault/_meta/current-focus.md"Note that these are real file copies rather than the home.file symlinks that manage every other dotfile on this box. Two reasons, both learned the irritating way.
First, the vault is a git repository with a remote, and a home.file entry is a symlink into /nix/store. Git would track the symlink, so every commit would carry a machine-local, garbage-collectable store path instead of the text, and the remote would be useless on any other machine.
Second, Obsidian opens notes read-write, and a store symlink is mode 444. Clicking into CLAUDE.md and typing gives you a permission error from inside the editor rather than anything explicable.
That last [ -e ... ] || guard matters as much as the copies do. It is the difference between “this file is authoritative in Nix and cannot drift” and “this file is seeded once and then belongs to me”.
_meta/machine.md is the clearest case for generating rather than writing. It records the host, the kernel, the compositor, and the global toolchain versions: PHP 8.5.9, Node 24.18.1, Go 1.26.5. Every one of those numbers moves the next time I bump my flake inputs. A hand-written note would be wrong within a week and I would not notice, because nothing about a stale note announces itself. A generated one cannot be wrong without the flake being wrong first.
The one file that has to be honest
_meta/current-focus.md is the exception. Seeded once, never overwritten, hand-maintained (and agent-maintained). It has exactly one job: answer “what is in flight” for someone arriving with no context.
Three rules keep it useful:
- Update it at the end of any session that changed what is in flight. Started something, finished something, parked something.
- Keep it under roughly fifteen lines. It is a snapshot, not a log.
- Delete freely. Finished work comes out.
That third one is the hard one, and it is the rule I expect to break first. The pull is always to leave the finished item in place, because it feels like progress made visible. Do that enough times and the file becomes a changelog, at which point nobody reads it, at which point it may as well not exist. History is what the git log is for.
A stale focus note is worse than an absent one, because it misdirects confidently. If I cannot keep it honest I should delete it rather than let it rot.
Dailies append, overviews get rewritten
Two directories run on the opposite discipline to everything else.
daily/ is append-only, one file per date. It is where a thing gets thought through before it has earned a project note. Both the forum and conform started life as sections in daily/2026-08-10.md, and the project notes came later, after the shape held up for a few hours. The daily entry is the record of what I believed that day, so it does not get tidied up afterwards.
research/ is where an agent gets sent on a mission, and it is the one area whose contents I expect to be generated in full, unsupervised, from a single instruction. The unit is the mission rather than the note, so “research X” produces a directory:
research/└── rest-api-design-patterns/ ├── overview.md ├── 2026-08-08-0948-resource-modeling-and-uris.md └── 2026-08-08-0949-http-semantics-and-idempotency.mdFindings append, the overview is rewritten. That split is the entire design.
A finding is a record of what was true when it was written, and it does not get edited later. If it turns out wrong, a later finding says so, and the overview reflects the correction. The overview is the only file allowed to change its mind, because its job is to hold the current best understanding rather than the history of how it got there. The history is the dated files, and the git log behind them.
One Obsidian gotcha to design around: every mission directory has a file called overview.md, so a bare [[overview]] is ambiguous across directories and Obsidian will guess. Linking to a specific mission from outside it takes the path form:
[[research/rest-api-design-patterns/overview]]Inside a mission, a bare [[overview]] resolves fine, because the ambiguity only bites across directories.
Does it work?
Well enough that I keep using it, which is the only test a personal system has to pass.
The clearest evidence is what the vault catches that nothing else does. Right now current-focus tells me the forum checkout still has no .git in it. That is a directory of Laravel rather than a repository, so nothing in it is recoverable, and it is the thing holding up everything else there.
Where would that fact live otherwise? Not in the repository, because there is no repository. It has nowhere else to be.
The honest weak point is the same one every system like this has. The generated layer cannot go stale, which is exactly why I generate it. The hand-written layer absolutely can, and no amount of convention prose protects current-focus from me not updating it on a Friday.
I also notice content/ and videos/ have been sitting empty since I created them, holding nothing but a .gitkeep. The shape exists before the contents do, which is either good design or optimism. This article is the first thing to land in one of them, so we will find out.
Keep Reading
Give your Laravel AI agent real-time web knowledge
The Laravel AI SDK is provider-agnostic, but its built-in web tools aren't. Here's how to give your agent real-time web knowledge without losing that independence.
Jul 2026 · 5 min read
DevToolsA Proper Look at Tabstack
A developer's review of Tabstack, the Mozilla-backed web API that gives AI agents clean extraction, browser automation, and cited research without a scraper.
Jun 2026 · 11 min read
DevToolsGiving your agents a terminal: a first look at the tabstack CLI
A hands-on look at Mozilla's tabstack CLI: a single Go binary that turns any URL into clean Markdown or JSON, runs browser automation, and scripts neatly.
Jun 2026 · 9 min read