Back to blog

I Built an AI That Doesn't Forget Me. The Whole Brain Is One SQLite File.

I built a self-hosted AI memory system I use every day. 122 tools, 479 commits, nine months -- and the entire brain is one SQLite file. The architecture, the real bugs, and why boring storage beat the fancy stack.

aiagentsmcpmemorysqliteguardianself-hosted

I Built an AI That Doesn't Forget Me. The Whole Brain Is One SQLite File.

Every general-purpose AI assistant has the same hole in it: it forgets you the second the conversation ends. You explain your goals, your constraints, the decision you're stuck on -- and next session you start over, re-typing the same context to the same model like you've never met. Everyone treats that as normal. I stopped treating it as normal about nine months ago and built the fix.

It's called Guardian. It's a self-hosted AI memory and decision system I built for myself and run every single day -- not a demo, not a startup, not a thing I'm selling you. It's the brain my AI assistant reads and writes on every call, so it actually remembers who I am, what I'm working on, and what we already decided. 479 commits. Nine months. And the entire brain is one SQLite file.

Rather see it than read it? There's an interactive walkthrough — the whole system as an instrument panel.

The bet I made

The industry's answer to "AI forgets" is bigger context windows. Cram more tokens into the prompt. A million-token window, and surely it'll remember everything.

That's the wrong fix. A bigger context window is a bigger short-term memory. It's still short-term memory. You still have to hand it everything up front, every time, and hope the important thing didn't fall off the edge.

Guardian's bet is different: an assistant with structured, queryable, persistent state -- not just a longer window -- is what turns an AI from a smart autocomplete into something that operates alongside you. Tasks don't get re-described every session. They have IDs, a status, and a comment thread that survives. Facts don't get re-asserted -- they live in a knowledge graph with dates attached. And you don't re-argue a decision you already made: it's written down, immutable, with the reasoning still bolted to it.

The context window is where the model thinks. Guardian is where it remembers. Those are two different jobs, and treating them as the same thing is why most "AI memory" ends up being a text file someone pastes back in.

What Guardian actually is

Under the hood it's a FastAPI backend, a Next.js web console, and -- the part that matters -- an MCP server that exposes 122 tools to my AI assistant.

MCP is the Model Context Protocol, the open standard Anthropic published for wiring an AI model to outside tools and data. Think of it as a USB port for a language model: any MCP-speaking client can plug into any MCP server and call its functions. Claude is my client. Guardian is the server. On every call, Claude can read my task board, query my memory, check a fact, record a decision -- through those 122 tools.

The whole thing runs 24/7 on a dedicated Mac mini sitting on my home network. Not the laptop. Not a cloud host. A little always-on box in my house, reachable over a private network, that holds my entire working memory in a single file.

The scale, concretely:

  • 122 MCP tools exposed to the AI
  • 124 backend service modules
  • 39 API route modules, JWT-authenticated
  • 26 pages in the Next.js console -- tasks, projects, facts, documents, goals, and more
  • 6,282 lines in the MCP server file alone
  • 56 direct dependencies -- not 150-plus
  • 18 database migrations, because the schema grew feature by feature
  • 479 commits over roughly nine months
  • One SQLite file that is the whole brain

That last line is the one people trip over.

The whole brain is one file, on purpose

Everyone reaches for the same stack when they build something like this: Postgres for the structured data, a hosted vector database like Pinecone for the semantic search, Redis for caching, maybe a separate service for full-text. Four moving parts before you've stored a single thought.

Guardian stores everything in one SQLite file. Tasks, facts, decisions, memories, the vector embeddings, the full-text index -- all of it, one file on disk.

SQLite has a full-text search engine built in (FTS5) for keyword matching. A small extension called sqlite-vec handles semantic similarity -- the "find me things that mean the same thing even if they don't share any words" kind of search -- and if that extension won't load on some machine, there's a plain-Python fallback that does the same math slower. Keyword search and meaning search, both, in the same file, with no external service running.

Which means my backup strategy is one line:

cp data/guardian.db backup.db

That's it. That copies my entire AI's memory -- every task, every fact, every decision, nine months of accumulated context -- into a second file I can stash anywhere. Try that with a four-service stack. You can't. You've got a database dump here, a vector index there, a cache you hope was disposable, and a bad afternoon reconciling them if anything ever tears.

This is a boring-tech decision, and boring tech wins. A single-file database that works completely offline, backs up with cp, and needs zero services running is not a beginner's shortcut. It's the considered choice for a system that has exactly one user and needs to be bulletproof, not web-scale. I made that call deliberately, and nine months in I have not once wished for Postgres.

The 122 tools are the actual product

The architecture is interesting. The tool surface is the point.

Anyone can stand up a database. What makes Guardian useful is that my AI has 122 specific, well-documented verbs it can perform against my life, and it groups into subsystems that each solve a real problem:

Memory and the knowledge graph. Not a pile of text -- a temporal fact store. Facts are triples: subject, predicate, object. "Kevin / is building / Guardian." Every triple carries a valid-from and a valid-to date, so the system knows not just what's true but when it was true. I can ask "what was true as of March" and get the right answer, not today's answer. When a fact changes, the old one gets an end date instead of getting deleted -- so the history survives and never masquerades as the present.

Tasks. A full kanban board -- the columns a project moves through, triage to ready to in-progress to review to done -- with comment threads, file attachments, per-task change history, and real time tracking that records estimate versus actual so I can see where I'm consistently wrong about how long things take.

Intel. A separate "things to read" queue with its own lifecycle, kept deliberately distinct from tasks, plus a one-call bridge to promote a piece of reading into an action when it turns out to imply one.

Projects. Long-running containers that are themselves measurable goals, each with an explicit definition of what "done" actually means, testably -- so a project can't quietly drift into "we've been at this forever and I'm not sure what finished looks like."

Compass. Goal alignment, built on Daniel Miessler's TELOS framework. Every recommendation my assistant makes can be scored against my actual stated goals instead of generic best-practice advice.

The MCP server file is 6,282 lines, and most of that isn't plumbing -- it's docstrings that teach the AI the house rules. One of them: any task assigned to me has to be action-ready, meaning the first line is the physical thing to do, not homework. The tools don't just move data. They enforce how the system is supposed to be used.

The subsystems worth knowing about

A few pieces are genuinely more than CRUD, and they're where I actually had to solve something.

The Dreamer. Every night at 3am, a job wakes up, re-reads the last week of memories, and derives new facts from them two ways. Deductive: something logically implied by what's already there, requiring at least two source memories, starting at 70% confidence. Inductive: a pattern noticed across at least three memories, starting at 50%. Every derived fact keeps a pointer back to the exact memories it came from, so if a source memory turns out to be wrong, I can find every conclusion that was built on it and pull them. When the same fact gets derived again on a later night, its confidence ticks up. It's a clean-room take on a pattern from a project called Honcho -- I rebuilt it from the idea rather than copying their code, because their license would have forced me to relicense my whole system.

The Council. For a genuinely hard decision, Guardian doesn't average a few model outputs together and call it consensus. It runs several distinct personas on one strong model and forces them through three rounds: each stakes out a position independently, then each has to name the others and say specifically where they disagree, then a chair writes a synthesis into three buckets -- what we converged on, what's still contested, and the recommendation. The synthesis step is explicitly instructed to report the disagreement that remains, not paper over it. Fake consensus is worse than an honest split, so the code refuses to manufacture it.

Recurring tasks, and the bug that taught me the most. A recurring task in Guardian carries a raw recurrence rule -- RFC 5545 RRULE, the same format your calendar app uses under the hood to say "every second Tuesday" -- and nothing else. No separate scheduler process, no cron job ticking in the background. When you complete the task, a single function computes the next occurrence and spawns it. Completion drives recurrence. A task only ever comes back because a human actually finished the last one.

That design is simple, and it hides three genuinely nasty correctness bugs I had to think through, all of them now pinned by tests:

Sub-daily rules -- "every 30 seconds" -- get rejected outright, because asking the date library for the next occurrence starting from a far-past anchor makes it iterate through millions of intervals and hang the whole API process. Ask for a hang, get a hang.

Validation doesn't just parse the rule, it probes it -- actually asks for the first occurrence -- because some rules parse perfectly and then explode the moment you request a date from them. Parse-OK-explode-later is the worst kind of input, because it passes the obvious check and fails in production.

And the one I'm fondest of: a recurrence limit of "do this five times" -- COUNT=5 in the rule -- would silently reset to five on every single respawn and run forever, because each new copy re-anchors its own start date and the count comes along for the ride untouched. The fix is to decrement the count by hand on each child. Miss that one line and you've built a task that promises to happen five times and instead runs forever.

That's the real engineering. Not the AI call. The state machine around it.

The bugs I'm actually proud of

Four production incidents taught me more than any feature I shipped. All four are the kind of failure you only understand by living through it.

The writes that said "saved" and lied. For months, tasks in Guardian would quietly revert. I'd reassign a task from a background agent back to myself, the tool would confirm it, I'd read the task back and see my own name on it -- and hours later it was back on the agent. Twenty-six tasks piled up on one queue that I had personally moved off it, over and over. Every reassign reported success. None of them stuck.

The cause was one line of database setup I wrote on day one and never looked at again. Guardian opened SQLite with a shared connection pool -- literally one connection, reused across every thread in the process. Under load, several requests hit that single connection at once, it wedged mid-transaction, and every write layered on top of the wedge lived only in memory. The connection could see its own uncommitted changes, so a read on that same connection returned the new value and everything looked fine. Then the process restarted, the uncommitted transaction rolled back, and the write was simply gone. It had never touched the disk.

This is the worst kind of bug there is. The system tells you it saved, shows you the saved value, and lies the whole time. The fix was to stop sharing the connection -- a fresh one per unit of work, so a poisoned connection can't outlive the request that broke it -- plus write-ahead logging so readers stop fighting the writer for the lock. Two settings. Months of phantom reverts, gone. The lesson: never trust a system's own report that it saved. Check the disk. A success message and a correct read-back can both be theater when they come from the same broken connection that never committed.

The two weeks my AI's self-summary silently froze. Guardian keeps a "dossier" -- a synthesized brief of who I am and what I'm focused on -- that regenerates nightly. One day I noticed it hadn't updated in two weeks. No error. No alert. Nothing in the logs screaming. The cause: the synthesis step had a cap of 8,000 tokens on its output. My dossier had quietly grown past that. When the model hit the cap, it stopped mid-thought, and a guardrail I'd written -- correctly -- refused to store a truncated result rather than save garbage. So every night, for two weeks, it generated a too-long summary, refused to save it, and failed silently, leaving the old one in place. The guardrail worked exactly as designed. The silence was the bug. Now a truncated synthesis is loud.

The day a model update broke every summary I generate. A newer version of the model I use started hard-rejecting a parameter I'd been passing on every synthesis call for months -- temperature, the knob that controls randomness. Overnight, a routine model bump turned a parameter that had always worked into a 400 error, and it broke every code path in the system that generated a summary. The fix was mechanical. The lesson wasn't: when you depend on a model you don't control, a silent contract change on their side is a production outage on yours, and you find out at 3am.

The recurrence count bug from the last section -- the five-times task that runs forever. I'll take a bug I can explain in one sentence over a mysterious one any day, and this one's a keeper.

None of these are the AI being dumb. Every one is ordinary systems engineering -- token limits, API contracts, a shared connection that lied, state that re-anchors when you're not looking. Which is the whole point. The hard part of building on top of an LLM was never the LLM. It's everything around it.

What I'm honest about

Where it's thin:

The public README is stale. It still describes the storage layer as Postgres and a memory library called Mem0, which is what I started with. The live system is SQLite, full stop -- I traced the actual memory path end to end to be sure. A few old files still import the Mem0-era code, but none of them sit on the path my AI actually uses. The architecture is settled. The docs just haven't caught up, and that's on me.

The tests are targeted, not comprehensive. Nine test files, aimed dead-on at the logic that's genuinely hard to get right by reading it -- the recurrence date math across daylight saving, the task rules, the time tracking. That's not blanket coverage of 124 service modules, and I'm not going to pretend it is. For a system with one user, testing the parts that are hard to verify by eye and leaning on daily use for the rest is a defensible trade. But "targeted" is the honest word, not "comprehensive."

It has exactly one user: me. The database has user isolation baked in from the start -- a real design choice, not an afterthought -- but it's been proven at n=1. "Designed for isolation, run by one person" is the true sentence. "Multi-user" would be a lie.

I'd rather say the true thing plainly than oversell a side of it. The system is real -- this document was written by an AI using Guardian's own tools against Guardian's own live task board.

Why this matters

The frontier in AI worth caring about isn't a smarter model. The models are already smarter than the scaffolding we put around them. It's memory and state -- the boring machinery that lets a model operate over time instead of resetting every conversation. Almost none of the hard parts of Guardian are AI. They're ordinary systems work pointed at a new problem, and that's where I'd tell anyone building on an LLM to spend their time. The model is the easy part now.

Guardian runs. I use it every day. It remembers me.