{
  "version": "https://jsonfeed.org/version/1",
  "title": "Local-first on LLBBL Blog",
  "icon": "https://avatars.micro.blog/avatars/2023/40/125738.jpg",
  "home_page_url": "https://llbbl.blog/",
  "feed_url": "https://llbbl.blog/feed.json",
  "items": [
      {
        "id": "http://llbbl.micro.blog/2026/08/01/run-your-whole-agent-stack.html",
        "title": "Run Your Whole Agent Stack on a $5 Box",
        "content_html": "<p>I SSH&rsquo;d into my home server this afternoon and ran <code>docker stats</code> on the memory layer that every one of my coding agent sessions talks to. Here&rsquo;s what came back:</p>\n<pre tabindex=\"0\"><code>mem0-qdrant    28.09MiB / 60.75GiB    2.13%\nmem0-neo4j     612.7MiB / 60.75GiB    0.77%\n</code></pre><p><strong>640 megabytes.</strong> Vector store and graph store, both up for three weeks straight, serving every <code>remember</code> and <code>recall</code> call my agents make. The entire persistent memory for my AI tooling uses less RAM than one Chrome tab with Figma open.</p>\n<p>So let&rsquo;s talk about why you&rsquo;re paying a monthly subscription for this.</p>\n<h2 id=\"local-first-not-local-only\">Local-First, Not Local-Only</h2>\n<p>I want to be precise here, because &ldquo;self-hosted AI&rdquo; has become a phrase people use to mean nine different things.</p>\n<p>My setup is local-first, not local-only. The <em>state</em> lives on my hardware. The memories, the embeddings, the graph relationships, everything my agents have learned about my projects, all of it sits on a box I own, in a Docker volume I can <code>tar</code> and carry away. Nobody can deprecate it, price-hike it, or sunset it.</p>\n<p>The inference does not. My embedder points at Mistral&rsquo;s managed API. I&rsquo;ll get to why, and how to swap it, but I&rsquo;m not going to pretend otherwise in a post about self-hosting.</p>\n<p>That embedder is the only thing that leaves my network, and only when something actually gets embedded, so when writing a memory and searching for one. Listing, deleting, and every graph operation are local with zero API calls.</p>\n<p><strong>State is what you can&rsquo;t get back. Compute is a commodity you rent by the token.</strong> Losing access to an API means switching providers. Losing two years of accumulated project context means starting over.</p>\n<h2 id=\"the-four-pieces\">The Four Pieces</h2>\n<p><strong>Qdrant</strong> is semantic search. When I ask what it remembers about my package manager preferences, Qdrant turns that into a similarity query and hands back the relevant memories. It&rsquo;s Rust, it&rsquo;s fast, and at <strong>28MB resident</strong> it&rsquo;s essentially free to run. One gotcha: vector dimensions are fixed when the collection is created. Swap embedding models and you need a new collection, not a migration. I learned that the annoying way.</p>\n<p><strong>Neo4j</strong> is the graph store. Vectors are great at &ldquo;find me things that sound like this&rdquo; and bad at &ldquo;what depends on what.&rdquo; The graph holds explicit subject-predicate-object facts, so <code>project X</code> <code>built_with</code> <code>Python 3.13</code> is a traversable edge instead of a fuzzy match. It&rsquo;s the heavy one at <strong>613MB</strong>, but it&rsquo;s a JVM, so that&rsquo;s mostly heap floor rather than working set. If you&rsquo;re squeezing onto the smallest possible VPS, interrogate this one first.</p>\n<p><strong>mem0</strong> is the orchestration on top: what gets extracted from a conversation, what gets deduped against existing memories, what gets written where. That&rsquo;s the difference between a database and a memory system.</p>\n<p><strong>The MCP server</strong> is what makes any of it useful. A small Go binary that speaks Model Context Protocol over stdio to Claude Code, exposing eight tools: <code>remember</code>, <code>recall</code>, <code>list_memories</code>, <code>forget</code>, <code>memory_stats</code>, <code>add_relation</code>, <code>recall_related</code>, <code>forget_relation</code>.</p>\n<p>The topology is deliberately boring:</p>\n<pre tabindex=\"0\"><code>Mac                                    Home server\n┌──────────────┐   ┌──────────┐        ┌─────────────────┐\n│ Claude Code  │◄─►│ mem0-mcp │  LAN   │ Qdrant + Neo4j  │\n│              │   │ (Go)     │───────►│                 │\n└──────────────┘   └──────────┘        └─────────────────┘\n       stdio                    HTTP + bolt\n</code></pre><p>Client binary on my laptop, containers on a box. No cloud in the middle, no account, no dashboard, no seat license.</p>\n<hr>\n<h2 id=\"the-ansible-role-is-the-whole-argument\">The Ansible Role Is the Whole Argument</h2>\n<p>Anyone can <code>docker compose up</code> a stack once. That&rsquo;s a weekend, not infrastructure. What makes this real is that it&rsquo;s a role in a repo, and rebuilding it on a fresh box is one command:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">ansible-playbook -i common_hosts home.yml --tags mem0\n</code></pre></div><p>That role does the unglamorous work:</p>\n<ul>\n<li>Installs a read-only deploy key scoped to exactly one repo, with an SSH <code>Host</code> alias so it can&rsquo;t collide with my personal GitHub key</li>\n<li>Clones and updates the source at a pinned branch</li>\n<li>Templates a <code>.env</code> with secrets pulled from Ansible Vault, <code>no_log: true</code> so nothing leaks into terminal output on a <code>--diff</code> run</li>\n<li>Brings up the compose stack with <code>remove_orphans: true</code>, so when I dropped a service upstream, the stale container went with it instead of lingering forever</li>\n</ul>\n<p>Be careful with your secrets and how you are creating your .env files!</p>\n<h2 id=\"the-honest-part-about-the-api-key\">The Honest Part About the API Key</h2>\n<p>I self-host the state and rent the inference. Two reasons.</p>\n<p>The first reason is speed. I ran embeddings locally before this, on CPU, and it was miserable: roughly 87 seconds to embed 32 memories, against about 2 seconds through a hosted API. That is a 45x difference on an operation sitting directly in the path of every <code>remember</code> and <code>recall</code>. A good model on a CPU is still a slow model, and this was never a quality problem.</p>\n<p>The second is that embeddings have gotten cheap enough that not worth the time to setup your own embedding service. <a href=\"https://mistral.ai/pricing/api\">Mistral</a> charges <strong>$0.10 per million tokens</strong> for <code>mistral-embed</code>. Google&rsquo;s <a href=\"https://ai.google.dev/gemini-api/docs/pricing\"><code>gemini-embedding-001</code></a> is <strong>$0.15 per million</strong>, halved on their batch API. Both are good models. Both bill you.</p>\n<p><a href=\"https://developers.cloudflare.com/workers-ai/platform/pricing/\">Cloudflare</a> is the worth knowing about if you&rsquo;d rather not pay at all. Workers AI includes <strong>10,000 neurons per day free</strong>, on the free plan as well as the paid one. Neurons are their normalized compute unit, and <code>bge-m3</code> costs 1,075 of them per million input tokens — so that daily allowance is roughly <strong>nine million tokens a day, at no cost.</strong> Past it you&rsquo;re at $0.012 per million, which is an order of magnitude under the paid competition. For a personal memory layer, nine million tokens a day is not a trial. It&rsquo;s just free.</p>\n<p>One detail if you&rsquo;re swapping: <code>bge-m3</code> emits 1024-dimension vectors, the same as <code>mistral-embed</code>. Go back to that Qdrant gotcha — matching dimensions means your existing collection still works. Mismatched ones mean starting over.</p>\n<p>And the escape hatch is already built. The env vars in my role are <code>TEI_BASE_URL</code>, <code>TEI_MODEL</code>, <code>TEI_DIMENSIONS</code>. Generic OpenAI-compatible embedder knobs, named after Text Embeddings Inference for historical reasons and pointed at Mistral today. Aim them at a self-hosted TEI container, at Ollama, at anything speaking that shape, and the rest of the stack doesn&rsquo;t notice.</p>\n<p>That&rsquo;s what local-first buys you. Not purity. <strong>Optionality.</strong></p>\n<h2 id=\"so-the-5-box\">So, the $5 Box</h2>\n<p>My server has 60GB of RAM, which is absurd overkill and exists because it does a dozen other things. The stack itself measured <strong>640MB with three weeks of uptime</strong>, essentially zero CPU at idle.</p>\n<p>That fits comfortably on a small cloud VPS in the few-dollars-a-month range. Check current pricing yourself rather than trusting a number in a blog post, but the shape is: a 2 vCPU / 4GB instance from Hetzner or similar costs less per month than one seat of most AI memory SaaS products, and you get to run everything else on it too.</p>\n<p>Your real constraint is RAM, specifically Neo4j&rsquo;s JVM floor. On a 1GB instance you&rsquo;d be fighting it. At 2GB you&rsquo;re fine. At 4GB you&rsquo;ll forget it&rsquo;s running.</p>\n<h2 id=\"why-i-care\">Why I Care</h2>\n<p>The indie web ethos is about noticing that renting your identity from a platform means the platform decides what happens to it.</p>\n<p>We&rsquo;re about to make the same mistake with agent memory, except worse, because the thing being accumulated this time is a working model of how you think and what you&rsquo;re building. Every &ldquo;our AI remembers you across sessions&rdquo; product is a proposal that you deposit that into someone else&rsquo;s database and hope the pricing page stays reasonable.</p>\n<p>Qdrant is Apache 2.0. Neo4j Community is GPL. Docker Compose is a YAML file. Ansible is idempotent YAML. Nothing in this stack is exotic. The barrier to owning your agent memory is <strong>an afternoon and 640 megabytes.</strong></p>\n<p>Not everyone needs this, and I&rsquo;m not going to pretend a solo dev with three side projects is being exploited by a $20 subscription. But if you&rsquo;re accumulating context you&rsquo;d be genuinely sad to lose, the math changes. Own the state, rent the compute, and keep the role in version control so the whole thing is reproducible on a box you haven&rsquo;t bought yet.</p>\n<p>Moving the embedder onto Cloudflare&rsquo;s free tier is next on my list, what&rsquo;s on yours?</p>\n<hr>\n<blockquote>\n<p>I&rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at <a href=\"https://micro.blog/llbbl?remote_follow=1\">@logan@llbbl.blog</a>.</p>\n</blockquote>\n",
        "date_published": "2026-08-01T10:00:00-05:00",
        "url": "https://llbbl.blog/2026/08/01/run-your-whole-agent-stack.html",
        "tags": ["DevOps","AI","Ansible","Self-hosting","Local-first"]
      }
  ]
}
