<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MihaiBuilds</title>
    <description>The latest articles on DEV Community by MihaiBuilds (@mihaibuildsdev).</description>
    <link>https://dev.to/mihaibuildsdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3904583%2F67fee610-d560-45d8-a994-78991737033d.jpeg</url>
      <title>DEV Community: MihaiBuilds</title>
      <link>https://dev.to/mihaibuildsdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mihaibuildsdev"/>
    <language>en</language>
    <item>
      <title>I built the memory, now I'm building the brain</title>
      <dc:creator>MihaiBuilds</dc:creator>
      <pubDate>Thu, 28 May 2026 08:10:21 +0000</pubDate>
      <link>https://dev.to/mihaibuildsdev/i-built-the-memory-now-im-building-the-brain-2c9c</link>
      <guid>https://dev.to/mihaibuildsdev/i-built-the-memory-now-im-building-the-brain-2c9c</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://mihaibuilds.com/blog/i-built-the-memory-now-im-building-the-brain.html" rel="noopener noreferrer"&gt;mihaibuilds.com&lt;/a&gt;. Cross-posting here because dev.to is where I read a lot of work like this myself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three weeks ago I shipped Memory Vault v1.0 — an open-source, self-hosted AI memory layer you run yourself. Postgres + pgvector under the hood, hybrid search on top, an MCP server so Claude can read and write to it directly. The first product in a planned compounding stack.&lt;/p&gt;

&lt;p&gt;Today the second product in that stack exists too. It's called The Brain.&lt;/p&gt;

&lt;p&gt;I'll get to what it is in a second. First, the honest part: I didn't announce it the day I started. I built the first milestone in private, on my own, with no audience watching. Three days of focused work, ten merged PRs, then a clean stop. Build-in-public is the long-term plan for this project the same way it was for Memory Vault. But the first week was head-down, because the riskiest part of a new product isn't the announcement — it's whether the thing actually works. Now that it does, I can tell you about it without hedging.&lt;/p&gt;

&lt;h2&gt;
  
  
  What The Brain is
&lt;/h2&gt;

&lt;p&gt;The Brain is a &lt;strong&gt;workflow orchestrator&lt;/strong&gt;, not an AI agent. It runs Python-defined workflows you author, with full visibility into every step. The intelligence is in the workflow you write; The Brain is the runtime that makes it repeatable and observable. It calls LLMs as steps when needed; it doesn't replace them.&lt;/p&gt;

&lt;p&gt;Concretely: you write a Python file that describes a sequence of steps. Each step is a shell command, a Memory Vault query, or a local LLM call. The Brain runs them top to bottom, passes output forward between them with named placeholders, and persists every run to Postgres. You inspect runs from the CLI. Successful runs exit 0; failed runs exit 1. It drops straight into cron jobs or CI pipelines.&lt;/p&gt;

&lt;p&gt;That's the whole pitch. There's no autonomous decision-making, no agent loop, no self-direction. It runs what you tell it to run, and it records what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a workflow orchestrator, not an agent
&lt;/h2&gt;

&lt;p&gt;The orchestration layer is too load-bearing to depend on someone else's framework. When the framework changes, your workflows break — and these frameworks change constantly. LangChain, LangGraph, CrewAI, AutoGen: they're all moving targets, and "agent autonomy" is a moving definition. Owned runtime, owned database, owned LLM client, owned everything. Five years from now this still runs.&lt;/p&gt;

&lt;p&gt;The other reason: build-in-public projects have an honesty constraint that pure-agent products don't. If The Brain claims to "decide" or "reason," I'd have to explain in every blog post what that means, what model it uses, and why the decision quality is what it is. Calling it a workflow orchestrator collapses that ambiguity. The user writes the logic. The Brain runs it. The output is reproducible. The behavior is auditable. The audience this is for — solo developers who use AI seriously and want their tools to be transparent — is allergic to the alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  What M1 ships, today
&lt;/h2&gt;

&lt;p&gt;M1 is called "Bare Runner." The name is the honest scope: it's the smallest thing that proves The Brain works end-to-end.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run Python-defined workflows from the CLI&lt;/strong&gt; with &lt;code&gt;brain run path/to/workflow.py&lt;/code&gt;. A workflow is a plain Python file exposing a module-level &lt;code&gt;workflow = Workflow(...)&lt;/code&gt;. Loaded with &lt;code&gt;importlib&lt;/code&gt; and validated at load time via Pydantic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three step types&lt;/strong&gt;: &lt;code&gt;ShellStep&lt;/code&gt; (subprocess + timeout), &lt;code&gt;MemoryVaultStep&lt;/code&gt; (Memory Vault REST), &lt;code&gt;LLMStep&lt;/code&gt; (OpenAI-compatible HTTP against LM Studio). Each lives in its own executor class; the runner dispatches by step type with no &lt;code&gt;isinstance&lt;/code&gt; chains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Placeholder substitution&lt;/strong&gt; — steps pass output forward with &lt;code&gt;{step_name}&lt;/code&gt; tokens in any string field (&lt;code&gt;prompt&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;query&lt;/code&gt;). Strict: a placeholder that names no prior completed step fails THAT step with a clear error. Fail fast; never pass literal braces downstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent run history in Postgres&lt;/strong&gt; — every run, every step, every output, every error. One &lt;code&gt;workflow_runs&lt;/code&gt; table; the run's full step-by-step output is stored as a JSONB array (not an object — JSONB doesn't preserve key order, and execution order is part of the data).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI introspection&lt;/strong&gt; — &lt;code&gt;brain history&lt;/code&gt; lists past runs with &lt;code&gt;--limit&lt;/code&gt;/&lt;code&gt;--workflow&lt;/code&gt;/&lt;code&gt;--status&lt;/code&gt; filters; &lt;code&gt;brain show &amp;lt;run_id&amp;gt;&lt;/code&gt; shows full step-by-step detail for one run. Run IDs match by prefix (Memory Vault's &lt;code&gt;token revoke&lt;/code&gt; precedent).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict failure semantics&lt;/strong&gt; — a workflow halts on the first failed step; the run row always lands in Postgres with a terminal status, even if an executor raises unexpectedly. The runner catches every executor exception and persists. A run that started always ends with a terminal DB row; no exception escapes unpersisted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-command Docker&lt;/strong&gt; — &lt;code&gt;docker compose up -d&lt;/code&gt; brings up Postgres and The Brain together, migrations run on boot via a hand-rolled migration runner in &lt;code&gt;src/db.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;46 hermetic tests&lt;/strong&gt; — pytest with a real Postgres test container, MV and LLM HTTP faked via &lt;code&gt;httpx.MockTransport&lt;/code&gt; (built-in, no &lt;code&gt;respx&lt;/code&gt; dependency). The suite is fast, deterministic, and runs anywhere with no external services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A run looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;brain brain run examples/hello.py
&lt;span class="go"&gt;Running workflow 'hello' (2 steps)
  ✓ greeting
  ✓ echo_it_back
Run c609f5e0 — success
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And inspecting it after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;brain brain show c609f5e0
&lt;span class="go"&gt;Run:      c609f5e0-a8d6-4221-84c0-58c0b5d0460d
Workflow: hello
Status:   success
Started:  2026-05-22 19:54:58
Duration: 0.0s

Steps:
  ✓ greeting
      Hello from The Brain
  ✓ echo_it_back
      The previous step said: Hello from The Brain
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Architectural decisions worth naming
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Functional/declarative workflow files, not class + decorator.&lt;/strong&gt; A workflow is a data structure: &lt;code&gt;workflow = Workflow(name=..., steps=[Step(...), Step(...)])&lt;/code&gt;. Easiest to introspect, easiest to serialize, easiest to register for cron in the next milestone. Class-with-decorators looks ergonomic at first and gets in the way the moment you try to load workflows dynamically. The declarative form is what every workflow tool I respect converges on for a reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single &lt;code&gt;workflow_runs&lt;/code&gt; table for M1, per-step granularity deferred to M2.&lt;/strong&gt; The whole run's step-by-step output goes in one JSONB column. Yes, a per-step table is the "right" long-term schema. But M2 is where state-between-runs lands, and that's the milestone where it actually pays for itself. Shipping the right table in M1 would be carrying schema complexity for a feature M1 doesn't have. Defer it; revisit when the use case lands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thin in-repo Memory Vault REST client (~30 LOC), no shared library.&lt;/strong&gt; The Brain talks to Memory Vault over HTTP. I could extract a shared &lt;code&gt;mihaibuilds-clients&lt;/code&gt; library now. I'd be over-engineering for a future I haven't reached. The right time to extract a client library is when there are three or more callers — not when there's one. Right now the entire client is &lt;code&gt;httpx.post(...)&lt;/code&gt;. When The Brain plus two or three addons all talk to Memory Vault, the duplication will tell me it's time to extract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LM Studio only in v1.0, not LM Studio + Ollama.&lt;/strong&gt; This is the explicit lesson I'm carrying from Memory Vault. Memory Vault's marketing claimed both LM Studio and Ollama support; only LM Studio was end-to-end tested. The Brain ships LM Studio only in v1.0. Ollama probably works through the same OpenAI-compatible client shape, but "probably works" isn't a release guarantee. Only claim providers you've actually tested. This rule survives every product I build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Owned runtime, not LangChain/LangGraph/CrewAI wrapper.&lt;/strong&gt; Already covered above — but worth re-stating in the architecture section because it's the decision the rest of the codebase shape derives from. The Brain is ~1,500 lines of Python. A LangChain wrapper would be more code, more dependencies, and a runtime that breaks every time the upstream framework changes its API. Owned runtime is the simpler answer, not the more ambitious one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What v1.0 won't do, on purpose
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No autonomous decision-making.&lt;/strong&gt; The Brain runs the workflow you defined. It doesn't pick a different step at runtime. If you want branching, you write a workflow that branches. Rich conditional logic is in the v1.0-out section deliberately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No multi-user / team workflows.&lt;/strong&gt; Single-tenant by design. Multi-user activation lives behind a PRO tier later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No managed cloud.&lt;/strong&gt; Self-hosted, MIT-licensed, runs on your laptop or your VPS. Always.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No visual workflow builder.&lt;/strong&gt; The workflow file is the source of truth. You read it like Python, you diff it like Python, you grep it like Python. Visual builders are a PRO concern, not a v1.0 concern.&lt;/p&gt;

&lt;p&gt;These are deliberate trade-offs. The Brain v1.0 is the smallest correct version, not the most ambitious one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;Developers who run real workflows on their own machines and want LLMs as a step inside those workflows — not as the thing in charge. Solo builders stitching together memory, models, and shell tools who are tired of agent frameworks that change their API every quarter. Anyone who wants every run to be inspectable, every output persisted, and every decision their own to make.&lt;/p&gt;

&lt;p&gt;If you've ever written a Python script that calls an LLM, then bolted on a cron entry, then realized you have no record of what it did yesterday — this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Milestone 2 is triggers and state — cron schedules, a long-running scheduler daemon, and workflows that read the previous run's output. M2 is the milestone where The Brain becomes worth running unattended.&lt;/p&gt;

&lt;p&gt;The full roadmap and milestone progress table live in the repo's README. Each milestone gets a dev-log post here as it ships — one of four dev.to posts across the build period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/MihaiBuilds/the-brain
&lt;span class="nb"&gt;cd &lt;/span&gt;the-brain
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;brain brain run examples/hello.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo has the full quickstart with configuration, Memory Vault wiring, and the real-world digest example (recent memories → local LLM summary → markdown file, all in one Python file).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MihaiBuilds/the-brain" rel="noopener noreferrer"&gt;GitHub — The Brain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MihaiBuilds/memory-vault" rel="noopener noreferrer"&gt;Memory Vault — the layer underneath&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Follow along
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Twitter / X: &lt;a href="https://x.com/mihaibuilds" rel="noopener noreferrer"&gt;@mihaibuilds&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Blog: &lt;a href="https://mihaibuilds.com" rel="noopener noreferrer"&gt;mihaibuilds.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/MihaiBuilds/the-brain" rel="noopener noreferrer"&gt;github.com/MihaiBuilds/the-brain&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>postgres</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Memory Vault v1.0 — building open-source AI memory the boring way</title>
      <dc:creator>MihaiBuilds</dc:creator>
      <pubDate>Sat, 09 May 2026 13:15:56 +0000</pubDate>
      <link>https://dev.to/mihaibuildsdev/memory-vault-v10-building-open-source-ai-memory-the-boring-way-33ej</link>
      <guid>https://dev.to/mihaibuildsdev/memory-vault-v10-building-open-source-ai-memory-the-boring-way-33ej</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://mihaibuilds.com/blog/memory-vault-v1-0-released.html" rel="noopener noreferrer"&gt;mihaibuilds.com&lt;/a&gt;. Cross-posting here because dev.to is where I find a lot of this kind of work myself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the past year I kept hitting the same wall. I'd have a real conversation with Claude — work through a database design, debug something gnarly, agree on a convention I wanted to keep — and the next morning it was gone. Not summarized. Not searchable. Just gone. ChatGPT was the same. Every assistant I used had the long-term memory of a goldfish, and the workaround the industry settled on was "paste the relevant context back in every time." That's not memory. That's me being the memory.&lt;/p&gt;

&lt;p&gt;So I built one. Memory Vault is an open-source, self-hosted AI memory system you run yourself: Postgres with pgvector underneath, hybrid search on top, an MCP server so Claude can read and write to it directly, a knowledge graph that extracts entities without an LLM bill, a local LLM chat with retrieved-source citations, and a one-command Docker setup. Two days ago it crossed the line from "build-in-public project" to "v1.0 stable release." (v1.0.2 yesterday closed two security findings I caught after enabling branch protection — path-traversal + info-exposure on an internal stream handler.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What Memory Vault is
&lt;/h2&gt;

&lt;p&gt;A long-term memory layer for AI assistants and the apps you build on top of them. You ingest text — markdown notes, conversation logs, anything plain — and it gets chunked, embedded, full-text indexed, and stored in a single Postgres database. Hybrid search (vector similarity + keyword tsvector + Reciprocal Rank Fusion) returns the right chunks back when you query. An MCP server exposes four tools (&lt;code&gt;recall&lt;/code&gt;, &lt;code&gt;remember&lt;/code&gt;, &lt;code&gt;forget&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;) that Claude Desktop or Claude Code can call directly, which means Claude can read and write to your memory inside any conversation without you copy-pasting context. A REST API exposes the same operations for any app you build. A dashboard gives you a Search, Browse, Graph, Ingest, Stats, and Chat page. A local LLM chat (LM Studio in v1.0) lets you talk to your memories with full source citations — every response shows which chunks it pulled from, clickable.&lt;/p&gt;

&lt;p&gt;It runs entirely on your machine. No API keys. No cloud. No telemetry. Postgres on port 5432, the API on port 8000, dashboard on the same port. &lt;code&gt;docker compose up&lt;/code&gt; and it's running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fls3i6vlk1ep80b08npnf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fls3i6vlk1ep80b08npnf.png" alt="Memory Vault dashboard Chat page answering a question with the sources panel expanded showing retrieved chunks" width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What v1.0 actually does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid search&lt;/strong&gt; — pgvector HNSW for semantic + tsvector GIN for keyword + Reciprocal Rank Fusion to merge them. Vector-only search misses exact terms; keyword-only misses paraphrases. RRF gets both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP server&lt;/strong&gt; — four tools (&lt;code&gt;recall&lt;/code&gt;, &lt;code&gt;remember&lt;/code&gt;, &lt;code&gt;forget&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;) callable from Claude Desktop, Claude Code, or any MCP client. Claude reads and writes your memory in-conversation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge graph&lt;/strong&gt; — spaCy NER plus co-occurrence extracts entities (Person, Project, Tool, Concept) and &lt;code&gt;related_to&lt;/code&gt; relationships from every ingested chunk. No LLM, no per-token cost, rendered as an interactive Cytoscape force-directed graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory spaces&lt;/strong&gt; — namespacing for different contexts (work, personal, projects). Per-space dedup; cross-space isolation by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local LLM chat&lt;/strong&gt; — LM Studio native API with sources panel showing retrieved chunks for every answer. Every response is grounded and the grounding is visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API&lt;/strong&gt; — bearer-auth-protected, OpenAPI-documented at &lt;code&gt;/docs&lt;/code&gt;, every operation the dashboard does is also a documented endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-command Docker&lt;/strong&gt; — &lt;code&gt;docker compose up&lt;/code&gt;. Postgres, the app, and the spaCy model bundled into a single image at build time, no first-run download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted, MIT-licensed&lt;/strong&gt; — your data stays on your machine. The whole thing is yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;170 tests passing&lt;/strong&gt; — pytest with a real Postgres + pgvector service container, no mocks of the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architectural decisions worth naming
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Postgres + pgvector instead of a dedicated vector database.&lt;/strong&gt; I run one database, not two. Operationally this matters more than the marginal performance of a purpose-built vector store at small scale. You already know how to back up Postgres. You already know how to monitor it. HNSW indexes plus tuned &lt;code&gt;maintenance_work_mem&lt;/code&gt; and &lt;code&gt;ef_search&lt;/code&gt; get you to "fast enough for hundreds of thousands of chunks on a laptop." When that stops being true, the migration path is sane. Until then, one database is the right answer for a self-hosted personal-memory tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid search instead of vector-only.&lt;/strong&gt; Pure vector search is great at paraphrase and concept. It's bad at exact terms — model names, error codes, file paths, anything where the literal string is the signal. Memory Vault stores both an embedding and a tsvector for every chunk and merges the two ranked result sets with Reciprocal Rank Fusion. RRF is parameter-free, doesn't require score normalization, and consistently beats either approach alone on the kind of mixed queries real users actually type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;spaCy + co-occurrence for the knowledge graph, not an LLM.&lt;/strong&gt; The default move in this space is to feed every chunk through an LLM and ask it for entities and relationships. It works. It also costs money on every ingest, couples your graph quality to whichever model you happened to pick, and requires API keys for a tool whose entire pitch is no API keys. spaCy's &lt;code&gt;en_core_web_sm&lt;/code&gt; model plus a co-occurrence rule (two entities in the same chunk = a &lt;code&gt;related_to&lt;/code&gt; edge, weighted by frequency) gets you a useful graph for zero per-ingest cost. The honest limits — English only, context-dependent NER, no fuzzy matching — are documented up front rather than masked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP-first, not REST-first.&lt;/strong&gt; Memory Vault was designed around the assumption that the primary user of this database is going to be Claude, not me. The MCP server isn't a wrapper around a REST API — it's a direct path into the same code that the REST API uses. Both are first-class. But the design starting point was "what does Claude need to call to make memory feel native," and then the REST API was the same operations exposed for human-driven apps. That ordering changes which tradeoffs are interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PoolClosed story
&lt;/h2&gt;

&lt;p&gt;About a week before tag day, I added a CLI command called &lt;code&gt;memory-vault diagnose&lt;/code&gt;. It bundles app logs, database logs, status output, OS info, and redacted environment into a zip file users can attach to bug reports. Foundation work. Paid for once. The kind of thing that makes every future bug report ten times higher signal-to-noise.&lt;/p&gt;

&lt;p&gt;I shipped it. Then I ran the test suite. 163 passed, 52 errored. Every error was &lt;code&gt;psycopg_pool.PoolClosed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First instinct: probably an &lt;code&gt;httpx&lt;/code&gt; lifespan thing. Modern &lt;code&gt;httpx&lt;/code&gt; has changed how it handles ASGI lifespan events between minor versions. The test suite uses &lt;code&gt;httpx.ASGITransport&lt;/code&gt; to drive the FastAPI app in-process, sharing a session-wide connection pool fixture. If the transport was firing shutdown events between tests, the pool would close mid-suite. There's a kwarg for this. I added &lt;code&gt;lifespan="off"&lt;/code&gt; to the transport. &lt;code&gt;TypeError: ASGITransport.__init__() got an unexpected keyword argument 'lifespan'&lt;/code&gt;. The kwarg doesn't exist in 0.28.x. Reverted.&lt;/p&gt;

&lt;p&gt;Second instinct: walk the call graph. &lt;code&gt;memory-vault diagnose&lt;/code&gt; calls into the CLI's &lt;code&gt;_run_status&lt;/code&gt; helper to capture status output for the bundle. &lt;code&gt;_run_status&lt;/code&gt; was implemented as &lt;code&gt;asyncio.run(_cmd_status())&lt;/code&gt; — directly calling the CLI's status function in-process. &lt;code&gt;_cmd_status&lt;/code&gt; initializes a connection pool at the top of the function and closes it via a &lt;code&gt;finally&lt;/code&gt; block at the end. Which is correct behavior for the CLI. It's also exactly what you don't want when something else in the same process — like a session-wide test fixture — already owns a pool that's mid-flight.&lt;/p&gt;

&lt;p&gt;The fix was four lines. Replace the in-process &lt;code&gt;asyncio.run&lt;/code&gt; with &lt;code&gt;subprocess.run(["memory-vault", "status"])&lt;/code&gt;. The subprocess gets its own pool, lives its own lifecycle, exits cleanly, and the parent process's pool is never touched. 163 passed, 0 errored.&lt;/p&gt;

&lt;p&gt;The lesson isn't about pools or fixtures specifically. It's that "obvious" fixes (changing the test transport config) and root causes (one function quietly tearing down state owned by a different function) live in different parts of the code. The &lt;code&gt;lifespan="off"&lt;/code&gt; move would have masked the symptom in the tests and left the actual bug in the CLI, where users would have hit it. Almost the entire week's gap between "all my sub-steps look done" and "v1.0 is actually shippable" was the discipline of not bypassing this kind of thing when bypassing was easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What v1.0 doesn't do, on purpose
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;English-only NER.&lt;/strong&gt; The bundled spaCy model is &lt;code&gt;en_core_web_sm&lt;/code&gt;. Non-English content gets little to no useful entity extraction. Multilingual models exist; they're heavier and slower; they're a question driven by real user demand, not a v1.0 must-have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No fuzzy entity matching.&lt;/strong&gt; "PostgreSQL" and "Postgres" are separate entities in the graph. No alias merging in v1.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No re-extraction on edit.&lt;/strong&gt; If you re-ingest a corrected version of a chunk, the new entities are added but the old ones aren't cleaned up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single-user.&lt;/strong&gt; v1.0 has bearer auth and one user behind it. The schema has &lt;code&gt;owner_id&lt;/code&gt; and &lt;code&gt;access_level&lt;/code&gt; columns from day one, but multi-user activation is part of the PRO tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LM Studio only for chat.&lt;/strong&gt; Ollama and llama.cpp use the same OpenAI-compatible client architecture under the hood, but the only end-to-end-tested path in v1.0 is LM Studio. Ollama support is not in v1.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No multi-conversation history in chat.&lt;/strong&gt; Single-thread chat. Driven by whether real users ask for it.&lt;/p&gt;

&lt;p&gt;These are deliberate trade-offs. Honest gaps documented up front build more trust than feature bullets that fall apart when someone actually tries them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The open-core model
&lt;/h2&gt;

&lt;p&gt;Memory Vault is and will always be MIT-licensed. The whole thing — search, MCP, graph, REST API, dashboard, local LLM chat, ingestion pipeline, the database schema, the Docker setup. You can run it on your machine. You can fork it. You can use it inside a commercial product. The free tier is genuinely useful — not a crippled demo of the paid tier.&lt;/p&gt;

&lt;p&gt;A paid PRO tier is on the roadmap for teams: dedup with importance decay, conflict resolution and supersede chains, multi-user activation, additional adapters (PDF, web pages), automated encrypted backups, and a fuller dashboard with analytics. The PRO tier is genuinely paid features — operational tools that solo users on a laptop don't strictly need, and teams running shared knowledge bases really do. The split is honest by design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this took to build
&lt;/h2&gt;

&lt;p&gt;Seven weeks of evenings and weekends across nine locked milestones, scope frozen on March 27. M1 was the announcement. M2 the core hybrid search. M3 the one-command Docker. M4 the MCP server. M5 the REST API. M6 the dashboard. M7 the knowledge graph. M8 — this one — was local LLM chat plus the polish, CI/CD, security review, and release engineering that turn a build-in-public project into something other people can actually use.&lt;/p&gt;

&lt;p&gt;Two of those weeks were the kind of work nobody sees: structured JSON logging with request ID propagation, a diagnostic CLI that produces a redacted bundle for bug reports, GitHub Actions for lint and test and multi-arch Docker release, security audit (bandit, npm audit, Dependabot, CodeQL, plus a 15-test pentest pass with curl), Contributor Covenant Code of Conduct, threat model in SECURITY.md, branch protection rules, and the discipline to fix the actual root cause of a test failure instead of bypassing it. Unglamorous. Also the difference between v0.7 and v1.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Beyond.&lt;/strong&gt; Memory Vault is the first product in a planned compounding stack — The Brain is the next layer, building agents on top of this memory infrastructure. The memory layer is the one that has to be solid first. Today it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/MihaiBuilds/memory-vault
&lt;span class="nb"&gt;cd &lt;/span&gt;memory-vault
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8000&lt;/code&gt; and you're running.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/MihaiBuilds/memory-vault/releases/latest" rel="noopener noreferrer"&gt;GitHub — latest release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MihaiBuilds/memory-vault#readme" rel="noopener noreferrer"&gt;README and quick start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MihaiBuilds/memory-vault#mcp-integration" rel="noopener noreferrer"&gt;MCP setup for Claude Desktop / Claude Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Questions and bug reports: &lt;a href="https://github.com/MihaiBuilds/memory-vault/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;General discussion: &lt;a href="https://github.com/MihaiBuilds/memory-vault/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Three Postgres tuning tips landed during M6 and M7 that materially improved Memory Vault: &lt;a href="https://x.com/rivestack" rel="noopener noreferrer"&gt;@rivestack&lt;/a&gt; on &lt;code&gt;maintenance_work_mem&lt;/code&gt;, &lt;code&gt;ef_search&lt;/code&gt; as a runtime knob, and post-deploy cache warmup for HNSW indexes. The first ships in v1.0; we'll use the others when we get to them. Public credit, fair credit. Build-in-public works because builders with deeper expertise see what you're shipping and tell you what's wrong before production does.&lt;/p&gt;

&lt;p&gt;Beta tester Inevitable-Way-3916 ran the dashboard early, asked the architecture questions that forced the ARCHITECTURE.md doc to exist, and put bulk ingest on the list. Thanks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow along
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Twitter / X: &lt;a href="https://x.com/mihaibuilds" rel="noopener noreferrer"&gt;@mihaibuilds&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Blog: &lt;a href="https://mihaibuilds.com" rel="noopener noreferrer"&gt;mihaibuilds.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/MihaiBuilds/memory-vault" rel="noopener noreferrer"&gt;github.com/MihaiBuilds/memory-vault&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>postgres</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
