<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Knowledge-Management on hippotion</title><link>https://blog.hippotion.com/tags/knowledge-management/</link><description>Recent content in Knowledge-Management on hippotion</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 05 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.hippotion.com/tags/knowledge-management/index.xml" rel="self" type="application/rss+xml"/><item><title>I Added a Knowledge Graph to My Search. It Made It Worse.</title><link>https://blog.hippotion.com/posts/graph-hurt-my-search/</link><pubDate>Fri, 05 Jun 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/graph-hurt-my-search/</guid><description>My second brain searches over a vault of markdown using BM25 + vectors + graph expansion. I&amp;rsquo;d been telling people the graph improved recall. Then I finally benchmarked it, and plain keyword search beat my fancy hybrid — the graph was actively dragging the right answers out of the results. Here&amp;rsquo;s the scorecard and what it taught me about where graphs actually belong.</description><content:encoded><![CDATA[<p>I have a note in my second brain that I wrote months ago. It says, with the
confidence of someone who hadn&rsquo;t measured anything:</p>
<blockquote>
<p>Combining lexical search (BM25) with vector similarity and graph expansion
produces more robust recall than embeddings alone.</p>
</blockquote>
<p>That sentence shipped into production. My <a href="/posts/a-second-brain-you-can-git-clone/">vault of markdown notes</a>
gets indexed into a search database, and the search function fuses three
signals: BM25 (classic keyword ranking), vector similarity (embeddings), and
<strong>graph expansion</strong> — when a note matches, pull in its linked neighbours too,
on the theory that the thing you want is often <em>next to</em> the thing you typed.</p>
<p>It sounds right. Graphs are having a moment in RAG. &ldquo;Add a knowledge graph to
your retrieval&rdquo; is the kind of thing you can put on a slide and nobody pushes
back. I believed it enough to make graph expansion a first-class signal with a
weight of <code>0.5</code> — equal footing with keyword matching.</p>
<p>This week I finally wrote a benchmark. The graph wasn&rsquo;t helping. It was the
single biggest thing <em>hurting</em> my search.</p>
<h2 id="the-setup">The setup</h2>
<p>30 gold queries against the live vault (63 notes), borrowing the harness shape
from an eval framework I&rsquo;d been reading. Each query has a hand-labelled &ldquo;correct&rdquo;
note. I measured recall@5 (did the right note land in the top 5?) and MRR (how
high did it rank?), across three retrievers:</p>
<ul>
<li><strong>grep</strong> — naive substring term-count. The dumb floor.</li>
<li><strong>bm25</strong> — pure keyword ranking, FTS5&rsquo;s BM25. The honest baseline.</li>
<li><strong>live</strong> — my production hybrid (BM25 + vector + graph).</li>
</ul>
<p>I expected a clean staircase: grep at the bottom, bm25 in the middle, my
clever hybrid on top. That&rsquo;s the whole reason you build the clever thing.</p>
<h2 id="the-scorecard">The scorecard</h2>
<table>
	<thead>
			<tr>
					<th>retriever</th>
					<th>recall@5</th>
					<th>MRR</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>grep</td>
					<td>0.467</td>
					<td>0.307</td>
			</tr>
			<tr>
					<td>bm25</td>
					<td><strong>0.950</strong></td>
					<td><strong>0.826</strong></td>
			</tr>
			<tr>
					<td>live (hybrid, <code>w_graph=0.5</code>)</td>
					<td>0.650</td>
					<td>0.520</td>
			</tr>
	</tbody>
</table>
<p>Read that bottom row twice. My production &ldquo;smart&rdquo; search found the right note
<strong>65%</strong> of the time. Plain keyword search found it <strong>95%</strong> of the time. The
hybrid I&rsquo;d been quietly proud of was <em>worse than its own baseline</em> — it broke
<strong>9 of 30 queries that BM25 got right</strong>. BM25 alone whiffed on exactly one.</p>
<p>The clever layer wasn&rsquo;t adding intelligence. It was adding noise, confidently.</p>
<h2 id="why-the-graph-backfired">Why the graph backfired</h2>
<p>Here&rsquo;s the mechanism, and it&rsquo;s almost funny once you see it.</p>
<p>Graph expansion pulls in a matched note&rsquo;s neighbours. But in a real knowledge
base, the most <em>connected</em> notes are hubs — my inbox of ideas, my project radar,
my &ldquo;things Claude noticed&rdquo; log. Everything links to them, so they&rsquo;re everyone&rsquo;s
neighbour. When I searched for something specific, the graph helpfully dragged
these popularity-contest winners into the candidate set, and they elbowed the
genuinely relevant note clean out of the top 5.</p>
<p>Concrete example. Query: <em>&ldquo;who owns this knowledge system?&rdquo;</em> The correct answer
is my personal note. BM25 ranked it #5 — just barely in. The hybrid, drunk on
graph neighbours, pushed it off the list entirely. The graph didn&rsquo;t find a
better answer. It buried a good one under hubs.</p>
<p>I swept the graph weight to confirm it wasn&rsquo;t a fluke. It was perfectly
monotonic — <strong>every</strong> increment of graph made search worse:</p>
<table>
	<thead>
			<tr>
					<th>graph weight</th>
					<th>recall@5</th>
					<th>MRR</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>0.0 (off)</td>
					<td>0.950</td>
					<td>0.826</td>
			</tr>
			<tr>
					<td>0.1</td>
					<td>0.950</td>
					<td>0.737</td>
			</tr>
			<tr>
					<td>0.25</td>
					<td>0.817</td>
					<td>0.564</td>
			</tr>
			<tr>
					<td>0.5 (what I shipped)</td>
					<td>0.650</td>
					<td>0.520</td>
			</tr>
	</tbody>
</table>
<p>There&rsquo;s no ambiguity to argue with. More graph, more harm, no exceptions. The
value I&rsquo;d been <em>claiming</em> in that confident note — I finally measured it, and
it was negative.</p>
<h2 id="the-fix-and-the-actual-lesson">The fix, and the actual lesson</h2>
<p>The fix was one line: drop the default graph weight from <code>0.5</code> to <code>0.1</code>. Recall
snapped back to 0.95, tying pure BM25. (Turning the graph fully off is
marginally better still on MRR; I kept a whisper of it as a tiebreaker, which is
a taste call, not a data-driven one.)</p>
<p>But the one-line fix isn&rsquo;t the point. The point is <em>where graphs belong</em>.</p>
<p>Graph expansion isn&rsquo;t a bad idea — I aimed it at the wrong job. <strong>Precision
retrieval</strong> (&ldquo;find me the one note that answers this&rdquo;) wants to be narrow and
literal. Pulling in neighbours is the opposite of what you want; every neighbour
is a chance to be wrong. But I have a <em>different</em> feature in this same system —
a discovery mode that deliberately collides distant notes to surface unexpected
connections. There, neighbour-pulling isn&rsquo;t noise, it&rsquo;s the entire product.</p>
<p>Same mechanism. One context it&rsquo;s poison, the other it&rsquo;s the point. I&rsquo;d been
running my discovery tool inside my lookup tool and calling it a hybrid.</p>
<p>A few honest caveats, because a benchmark you can&rsquo;t poke holes in is usually
lying: my gold set is self-authored v1, the corpus is small (63 notes), and the
vector signal was actually <em>dark</em> during this run — I hadn&rsquo;t built the
embeddings yet, so &ldquo;hybrid&rdquo; here was really &ldquo;BM25 + graph.&rdquo; The vector half of
my original claim is still untested. This is directional, not gospel.</p>
<p>But directional was enough. I&rsquo;d shipped a claim, the claim got measured, and it
didn&rsquo;t survive contact with 30 queries. That&rsquo;s the whole reason I <a href="/posts/gitops-for-my-brain/">keep my
brain in git with everything reproducible</a>: so the
day I bother to measure, the measurement can actually win the argument against
my own confident prose.</p>
<p>The slide-deck version of RAG says <em>add a graph</em>. The benchmark says <em>know which
question you&rsquo;re answering first</em>. I&rsquo;ll take the benchmark.</p>
]]></content:encoded></item><item><title>I Run GitOps for My Brain</title><link>https://blog.hippotion.com/posts/gitops-for-my-brain/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/gitops-for-my-brain/</guid><description>An AI agent on a scheduled idle walk through my notes pointed out that I&amp;rsquo;d built the same architecture three times — at work, in my homelab, and in my second brain — and that the third copy was missing the part that makes GitOps work. It was right. So we shipped the missing piece the same day.</description><content:encoded><![CDATA[<h2 id="the-pattern-i-didnt-know-i-had">The pattern I didn&rsquo;t know I had</h2>
<p>This week an AI agent told me something about my own systems that I&rsquo;d never
noticed, and it was correct: I have one favorite architecture, and I&rsquo;ve built
it three times.</p>
<ul>
<li><strong>At work</strong>: git holds Terraform code → Terraform derives the S3 buckets.
Nobody clicks around in the AWS console; the repo is the truth.</li>
<li><strong>In the homelab</strong>: git holds Kubernetes manifests → ArgoCD derives the
cluster. Every app on my rack is a folder in a repo.</li>
<li><strong>In my second brain</strong>: a vault of markdown notes → an indexer derives the
search database (SQLite FTS + a link graph) that my AI tools query.</li>
</ul>
<p>Same shape everywhere: a plain-text source of truth in git, and a machine that
builds the real thing from it. Master copy, derived state. I never decided
this consciously — it&rsquo;s just how my hands build things now.</p>
<h2 id="gitops-isnt-the-git-part">GitOps isn&rsquo;t the git part</h2>
<p>Here&rsquo;s the thing that the third copy got wrong, and it took me embarrassingly
long to see because I <em>teach</em> this pattern at the infrastructure layer.</p>
<p>&ldquo;Configuration in git&rdquo; existed long before GitOps. What made GitOps an actual
shift was the <strong>reconciler</strong>: ArgoCD doesn&rsquo;t apply your manifests once and
wish you luck. It watches, continuously. When the cluster drifts from the
repo, you get an <code>OutOfSync</code> badge, and with <code>selfHeal</code> enabled it puts
reality back where the repo says it should be. The loop is the product. Git
is just where the loop points.</p>
<p>My vault had no loop. If I edited a note and forgot to rebuild the index, the
search results my AI agents rely on were silently stale — no badge, no error,
nothing. The only protection was a rule in the repo&rsquo;s agent instructions:
<em>&ldquo;if files and index disagree, the files win — run the indexer.&rdquo;</em></p>
<p>A policy that agents must remember. In other words: I was running Kubernetes
with a sticky note on the monitor that says <em>please redeploy after editing
the YAML</em>. I would never accept that on my cluster. My brain ran on it for
months.</p>
<h2 id="the-fix-took-an-afternoon">The fix took an afternoon</h2>
<p>Two pieces, both boring on purpose.</p>
<p><strong><code>exo status</code></strong> — the OutOfSync badge. The indexer now stores a content hash
per note; <code>status</code> re-hashes the vault and diffs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;status&#34;</span><span class="p">:</span> <span class="s2">&#34;OutOfSync&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;modified&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;vault/10-notes/interests-themes.md&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;new&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;deleted&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;repair&#34;</span><span class="p">:</span> <span class="s2">&#34;exo index&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Exit code 0 when synced, 1 when not — so scripts and CI can ask the question
too, exactly like <code>argocd app get</code>.</p>
<p><strong>Git hooks</strong> — the selfHeal. Versioned hooks (<code>core.hooksPath .githooks</code>) on
<code>post-commit</code> and <code>post-merge</code> rebuild the index after every commit and pull:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nb">command</span> -v exo &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">||</span> <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl"><span class="nv">EXO_ROOT</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>git rev-parse --show-toplevel<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">exo index &gt;/dev/null 2&gt;<span class="p">&amp;</span><span class="m">1</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">&#34;exo: index reconciled (Synced)&#34;</span>
</span></span></code></pre></div><p>Now every <code>git commit</code> in the vault prints <code>exo: index reconciled (Synced)</code>
on its way out. The rule didn&rsquo;t change — <em>files win</em> — but it stopped being
something agents must remember and became something a machine enforces.
That&rsquo;s the entire difference between configuration management and GitOps,
replayed at the knowledge layer.</p>
<h2 id="the-part-where-it-gets-a-little-strange">The part where it gets a little strange</h2>
<p>The reason I&rsquo;m writing this post at all: I didn&rsquo;t have this idea. A scheduled
agent did, on what I can only describe as an idle walk.</p>
<p>My vault has a weekly cron job — we call it the Wanderer — that samples pairs
of notes that are <em>far apart</em>: different folders, different months, almost no
shared vocabulary. A headless Claude gets the pairs with exactly one task:
<em>read both notes in full and say whether anything genuinely connects. &ldquo;Nothing
connects&rdquo; is a successful run.</em> That last sentence is load-bearing — the run
always reports its result either way, so the agent never needs to manufacture
a finding to have done its job.</p>
<p>On its very first walk, it collided a work note about Terraform-driven S3
provisioning with the architecture map of the vault itself, and wrote: <em>same
sentence in different clothes — and the brain copy is missing its
reconciler.</em> Then it listed the two fixes you just read about.</p>
<p>Retrieval answers the questions you ask. Distant collisions surface the
questions you didn&rsquo;t know you had. It turns out my second brain didn&rsquo;t need
to get better at remembering — it needed to occasionally interrupt me.</p>
<h2 id="if-you-keep-a-vault">If you keep a vault</h2>
<p>Whatever your stack — Obsidian, org-mode, a folder of markdown — if anything
<em>derives</em> from your notes (an index, embeddings, a published site), then you
have source of truth and derived state, and the GitOps question applies: <strong>who
notices when they drift?</strong> If the answer is &ldquo;I do, hopefully,&rdquo; you&rsquo;re running
the sticky-note era. Give it a badge and a loop. It&rsquo;s an afternoon.</p>
]]></content:encoded></item><item><title>🌱 My Second Brain Weeds Itself Now</title><link>https://blog.hippotion.com/posts/an-ai-gardener-for-your-second-brain/</link><pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/an-ai-gardener-for-your-second-brain/</guid><description>I gave my markdown knowledge base a nightly gardener — an AI that finds orphan notes and missing links and fixes them, every change a reviewable git commit. The fun part was the Kubernetes wall I hit on the way.</description><content:encoded><![CDATA[<p>A few weeks ago I <a href="/posts/a-second-brain-you-can-git-clone/">rebuilt my second brain as a folder of markdown in git</a> — vault is the source of truth, everything else (search index, graph, 3D viewer) is a derived layer I can delete and rebuild. I love it. But a knowledge base has a dirty secret: <strong>it rots.</strong></p>
<p>Not the files — those are fine. The <em>connections</em> rot. You capture a note at 11pm and never link it to anything, so it becomes an orphan floating off the graph. A project note&rsquo;s one-line summary describes what the project was three weeks ago. Two notes are obviously about the same thing and neither knows the other exists. Do this for a few months and you don&rsquo;t have a second brain, you have a junk drawer with good search.</p>
<p>The honest fix is to weed the garden regularly. The honest truth is that nobody does, including me.</p>
<p>So I stopped relying on myself and built a gardener.</p>
<h2 id="what-it-actually-does">What it actually does</h2>
<p>Every night at 3am, on my homelab box, a script runs:</p>
<ol>
<li><strong>Detect</strong> — <code>exo garden</code>, a plain query over the index, produces a report: here are the orphans, here are notes that should probably link to each other, here are summaries that look stale. <strong>No AI in this step.</strong> It&rsquo;s SQL and graph traversal. Deterministic, boring, trustworthy.</li>
<li><strong>Decide and write</strong> — that report gets piped to <code>claude -p</code> (Claude Code in headless mode). Claude reads the vault&rsquo;s operating contract, makes <em>only high-confidence</em> edits — add a <code>[[wikilink]]</code> between two genuinely related notes, refresh a stale summary — caps itself at ~10 notes a night, and writes a dated log note explaining exactly what it changed and what it deliberately skipped.</li>
<li><strong>Commit</strong> — the wrapper reindexes and lands everything as a single <code>garden: 2026-06-09 …</code> git commit, then pushes. My 3D graph viewer picks it up on the next sync.</li>
</ol>
<p>The first real run, it found one orphan (<code>90-meta/README</code>), linked it into the notes it actually indexes, and then — this is the part I liked — <em>declined</em> to touch the 12 &ldquo;stale summary&rdquo; candidates because, on inspection, every one of them was already accurate. It wrote: <em>&ldquo;flagged by length, not staleness; churning them would add noise.&rdquo;</em> A gardener that knows when <strong>not</strong> to prune is the one you can leave alone.</p>
<h2 id="isnt-this-a-solved-problem">&ldquo;Isn&rsquo;t this a solved problem?&rdquo;</h2>
<p>Mostly, no — but partly, yes, and I want to be straight about it. AI-assisted note-linking exists: Obsidian plugins like Smart Connections suggest related notes, and apps like Mem and Reflect auto-organize as you write. They&rsquo;re good.</p>
<p>Three things make this different enough to build:</p>
<ul>
<li><strong>Every change is a reviewable git diff, authored by a named agent.</strong> Not silent magic that rearranges your notes while you&rsquo;re not looking. <code>git log -p</code> shows you exactly what the gardener did last night; <code>git revert</code> undoes a bad night in one command. For something as personal as a knowledge base, &ldquo;show me the diff&rdquo; beats &ldquo;trust me.&rdquo;</li>
<li><strong>It&rsquo;s mine, end to end.</strong> Runs on my hardware, on my schedule, with a model I point at. No SaaS holds my brain hostage.</li>
<li><strong>The detection is deterministic; the model only acts.</strong> The LLM never decides <em>what&rsquo;s wrong</em> — a boring query does that. The model only decides <em>how to fix the things already found</em>. That split keeps the whole thing auditable and cheap.</li>
</ul>
<p>If you already live in a tool that does this and you trust it, great. I wanted the git-diff trail and the local control.</p>
<h2 id="the-part-i-actually-want-to-tell-you-about">The part I actually want to tell you about</h2>
<p>The plan was tidy: I run n8n on the same cluster, so n8n would be the scheduler — fire nightly, <strong>SSH into the node</strong>, run the gardener. Clean, visual, one workflow.</p>
<p>n8n could not reach the node. At all. Every port: <code>ECONNREFUSED</code>.</p>
<p>This sent me down a genuinely interesting hole, because the homelab runs <strong>Cilium</strong> for networking, and Cilium has opinions about your own node that plain Kubernetes does not.</p>
<p>First instinct: a NetworkPolicy allowing egress to the node&rsquo;s IP. Wrote it, synced it, still refused. The reason is a Cilium subtlety worth knowing: <strong>the node isn&rsquo;t a CIDR, it&rsquo;s an identity.</strong> Cilium classifies your cluster&rsquo;s own node as the special <code>host</code> identity, and ordinary <code>ipBlock</code> CIDR rules <em>do not match it</em> unless you flip a cluster-wide setting (<code>policy-cidr-match-mode: nodes</code>). My <code>192.168.0.109/32</code> rule was a no-op.</p>
<p>So I switched to the Cilium-native tool: a <code>CiliumNetworkPolicy</code> with <code>toEntities: [host]</code>. Confirmed it applied — I could see <code>reserved:host</code> allowed right there in the datapath&rsquo;s BPF policy map. I confirmed the node&rsquo;s IP really does resolve to identity <code>1</code> (host). I confirmed the host firewall was <em>disabled</em>. Everything said &ldquo;allowed.&rdquo;</p>
<p>Still <code>ECONNREFUSED</code>.</p>
<p>That&rsquo;s the wall. The packet leaves the pod with Cilium&rsquo;s blessing, hits the host&rsquo;s own network stack, and <em>something there</em> sends a reset — and I couldn&rsquo;t see what, because inspecting the host firewall needs root, and this automation deliberately doesn&rsquo;t have it. I could have kept digging with a password. But I stopped and asked a better question: <strong>why am I making a pod reach back into the host it&rsquo;s running on at all?</strong></p>
<p>That&rsquo;s an awkward direction. The work has to happen <em>on</em> the host (that&rsquo;s where the vault, git creds, and Claude live). A pod straining to SSH into its own node is fighting the grain of the platform.</p>
<p>So I inverted it. <strong>The node schedules itself</strong> — a plain cron entry, rock-solid, no network gymnastics. And n8n, instead of <em>triggering</em> the job, <em>receives</em> it: at the end of each run the node POSTs a summary to an n8n webhook. Node→n8n works perfectly (it&rsquo;s just an outbound HTTPS call to a URL). n8n keeps the run history and is the place I&rsquo;ll later wire a phone notification.</p>
<p>I lost nothing that mattered. n8n is still my dashboard; the schedule just lives where the work lives. And I deleted the SSH key and the network-policy hole I&rsquo;d opened — the cleanup felt better than the original plan would have.</p>
<h2 id="the-lesson-such-as-it-is">The lesson, such as it is</h2>
<p>Two, actually.</p>
<p><strong>One:</strong> when you&rsquo;re automating something to run unattended, the bug you want to find is the one that shows up in a <em>dry run at 2pm</em>, not at <em>3am three weeks from now</em>. I almost shipped a version where a brand-new note (untracked by git) was invisible to my change-detection and would&rsquo;ve been silently wiped each night. The dry run caught it. Always build the dry run.</p>
<p><strong>Two, the bigger one:</strong> I spent an hour trying to make a pod punch into its host because that was <em>my</em> plan, and the platform kept saying no in increasingly specific ways. The fix wasn&rsquo;t a cleverer NetworkPolicy. It was noticing I was pushing against the design and turning around. The node scheduling itself and <em>reporting up</em> to n8n is simpler, safer, and more honest about where the work actually lives.</p>
<p>My brain weeds itself now. Every morning there&rsquo;s maybe one small, sensible commit waiting — a link I&rsquo;d have never made, a summary nudged back to true — and I can read exactly what changed before my coffee&rsquo;s done. That&rsquo;s the whole dream of a second brain that isn&rsquo;t a junk drawer: it stays a garden, and I barely have to touch it.</p>
]]></content:encoded></item><item><title>🧠 A Second Brain You Can `git clone`</title><link>https://blog.hippotion.com/posts/a-second-brain-you-can-git-clone/</link><pubDate>Fri, 16 Jan 2026 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/a-second-brain-you-can-git-clone/</guid><description>My first second brain died the way most do — on multi-device sync. The rebuild: plain markdown as the source of truth, every clever layer derived and disposable, and an AI that tends it through reviewable git diffs.</description><content:encoded><![CDATA[<h2 id="the-graveyard-of-second-brains">The graveyard of second brains</h2>
<p>I had a second brain once. Obsidian vault, a CouchDB LiveSync backend, even a
weekly agent that summarised my notes. It worked — for a while. Then the sync
started fighting itself across my laptop, the homelab, and my phone, and the day
syncing becomes a chore is the day you stop opening the thing. The notes were
still there. I just never looked at them again.</p>
<p>That&rsquo;s how most second brains die. Not from bad notes — from the <em>plumbing</em>. The
sync breaks, or the upkeep outpaces the payoff, or the whole thing is trapped in
one app&rsquo;s database and moving it feels like surgery. The knowledge was never the
problem. The container was.</p>
<p>So when I rebuilt it, I started from the failure modes, not the features.</p>
<h2 id="what-i-actually-wanted">What I actually wanted</h2>
<p>Three things, none of them &ldquo;more notes&rdquo;:</p>
<ol>
<li><strong>Memory I share with my AIs.</strong> Every time I open a fresh Claude session, it
starts from zero — I re-explain my homelab, my projects, what we decided last
week. I wanted a place both of us read <em>and</em> write, so the context survives the
session.</li>
<li><strong>Something that outlives any tool.</strong> No lock-in. If the app of the month dies,
my brain shouldn&rsquo;t die with it.</li>
<li><strong>Sync that can&rsquo;t rot.</strong> The thing that killed v1.</li>
</ol>
<h2 id="the-one-decision-that-matters">The one decision that matters</h2>
<p><strong>The store and the intelligence are different layers, and only the store is
sacred.</strong></p>
<p>The store is a folder of plain markdown in git. That&rsquo;s it. Human-readable, diffable,
greppable, yours. Everything clever sits <em>above</em> it and is fully rebuildable:</p>
<pre tabindex="0"><code>L5  Visualisation   3D graph, Obsidian, whatever reads markdown
L4  Automation      scheduled &#34;gardener&#34; runs
L3  Agent interface MCP servers — search, graph, note CRUD
L2  Index           SQLite: full-text + vectors + materialised edges
L1  Structure       typed frontmatter + [[wikilinks]]
L0  Substrate       markdown files in git   ← the only thing that&#39;s truth
</code></pre><p>Delete L1–L5 and nothing is lost — you rebuild them from L0 with one command.
That property is the whole design. The index can corrupt, the embedding model can
change, the viewer can break (mine did, spectacularly — that&rsquo;s another post), and
the knowledge doesn&rsquo;t care. It&rsquo;s text in git.</p>
<p>And <strong>sync is just <code>git pull</code>.</strong> No LiveSync daemon to wedge itself, no proprietary
replication. The exact thing that killed v1 is now the most boring, battle-tested
part of the stack. Three devices, one <code>git pull</code>, done.</p>
<h2 id="search-that-explains-itself">Search that explains itself</h2>
<p>The retrieval layer is deliberately not &ldquo;throw it all at embeddings.&rdquo; It fuses
three signals — keyword (BM25), vector similarity, and graph expansion (pull in
the neighbours of strong hits) — and every result reports <em>which signals fired</em>.</p>
<pre tabindex="0"><code>exo search &#34;hybrid retrieval&#34;
→ hybrid-retrieval   matched_on: [bm25, graph]
</code></pre><p>That <code>matched_on</code> matters more than it looks. An embeddings-only system gives you
a ranked list and no reason — you can&rsquo;t tell a real match from a vibe. For a brain
I&rsquo;m supposed to trust over years, &ldquo;why did this surface?&rdquo; is a feature, not a
nicety.</p>
<h2 id="the-ai-is-a-librarian-not-a-hoarder">The AI is a librarian, not a hoarder</h2>
<p>Here&rsquo;s the part I care about most. The AI doesn&rsquo;t just <em>read</em> the brain — it
writes to it. Through an MCP server it can search, walk the graph, and author
notes. But under a hard rule: <strong>every write is a reviewable git diff.</strong></p>
<p>It searches before it writes (extend a note, don&rsquo;t spawn a duplicate). It links
instead of piling. A scheduled &ldquo;gardener&rdquo; pass finds orphaned notes and stale
summaries and proposes fixes — as commits I can read and <code>git revert</code> if it gets
something wrong. No black-box mutation of my memory. Just a librarian that files
things while I&rsquo;m asleep and leaves a paper trail.</p>
<p>So now &ldquo;what am I building?&rdquo; is a question with an instant, honest answer: a single
map note, kept current, that every project links into. I ask, the AI pulls it, and
neither of us has to remember.</p>
<h2 id="why-not-just">Why not just…</h2>
<ul>
<li><strong>Obsidian alone?</strong> It&rsquo;s a lovely <em>viewer</em> — and I still use it as one. But it
can&rsquo;t give an agent structured read/write or explainable retrieval, and its sync
is what burned me. Here Obsidian reads the same markdown; it&rsquo;s a window, not the
house.</li>
<li><strong>Embeddings RAG?</strong> Opaque and one-directional. It can rank, but it can&rsquo;t tell
you why, and it can&rsquo;t write back. This is transparent and bidirectional.</li>
<li><strong>Notion / a SaaS brain?</strong> Lock-in by design. <code>git clone</code> is my backup and any
text editor is my fallback.</li>
<li><strong>A graph database?</strong> Unnecessary infra. The graph lives in the wikilinks; SQLite
just materialises it. I&rsquo;ll add Neo4j the day my queries actually outgrow a single
file, and not a day sooner.</li>
</ul>
<h2 id="what-it-changes">What it changes</h2>
<p>The vault is small still — that&rsquo;s fine; it grows by use. But the loop already
pays off: I work, the AI checkpoints decisions into markdown, and the <em>next</em>
session — fresh model, no memory of its own — searches the brain and is caught up
in seconds. The knowledge stopped living only in my head and in dead chat logs.</p>
<p>I&rsquo;m a team of one. There&rsquo;s no colleague who remembers why I made a call six months
ago, no handover doc someone else maintains. Continuity isn&rsquo;t a nice-to-have; it&rsquo;s
the whole job. A second brain that the AI helps keep alive — and that I can
<code>git clone</code> onto any machine in thirty seconds — is the first version of this idea
that I actually trust to still be here in five years.</p>
<p>The notes from v1? They&rsquo;re sitting in a folder, waiting to be triaged into v2. This
time I&rsquo;ll still be opening it.</p>
]]></content:encoded></item></channel></rss>