Multi-agent · Local-first
Personal AI operating system
My own assistant: a Git-versioned Markdown vault as long-term memory, and a multi-agent runtime that asks before doing anything it cannot undo.
- Repo
- cortana + cortana-app
- Context
- Personal project
- Role
- Sole engineer, architect and only user
- Status
- In daily use since 2026
The problem
I wanted an assistant for my memory, knowledge, finances and calendar without renting my long-term memory from a vendor. Closed products keep it in a format you cannot read, diff or take with you.
Stack
- Python
- FastAPI
- Next.js
- Postgres
- Git as persistence
- Anthropic API
- Telegram
- Docker
What it does
Memory is plain text
A Markdown vault in Git is the source of truth: readable, diffable, portable
Approval gate
Nothing irreversible runs without me approving it first
Under $20/month
A hard spend ceiling every stack decision answers to
Restore-tested backups
Recovery rehearsed and verified, plus a guard against destructive operations
Try it yourself
InteractiveSay something to it
Where the line is drawn
Reversibility, not importance. Reads pass straight through. Anything that cannot be undone easily, such as a financial record, a sent message or a cancelled event, stops here and waits for me, whatever it is worth.
Pick one of the three on the left. Two of them want to write to the vault, so they will stop and show you the exact change before anything happens. The third only reads, so it does not.
The real assistant runs over Telegram against a Git-versioned Markdown vault, with Postgres for transactional state. The notes, balances and commit hashes below are invented, and nothing is written anywhere.
Context
Cortana is mine. Not a work project, not a product. My own assistant for memory, knowledge, personal finances and calendar, which I talk to over Telegram. It is the project where I make architectural decisions with nobody's constraints but my own, which is why it is worth reading closely.
The premise came from a specific irritation with assistants that advertise memory. They keep it in a store you cannot read, diff, grep or take with you. Your long-term memory becomes the most valuable thing you own inside the product and the least portable. I wanted the opposite, stated as a requirement: the memory has to outlive the runtime. Delete the application tomorrow and everything it knows should still open in a text editor.
Second constraint: a fixed budget under twenty dollars a month. That turns out to be a better design constraint than an unlimited one, because it forces every "just call the model" instinct to justify itself against a cheaper deterministic alternative.
Architecture
Everything follows from one split: memory and runtime are separate, and memory wins.
Long-term memory is a Markdown vault in Git. Plain text files with front-matter, linked to each other, in a repository with history. The application keeps a local clone and treats it like someone else's repo: pull before write, push after, never assume it owns the working copy. The vault can be edited by me in an editor, by a coding agent in a terminal, or by the application, and none of the three needs to know about the others. Git is the reconciliation mechanism, and it is one I already trust with everything else.
A runtime on top, FastAPI with Postgres for transactional state and a Next.js front end, dispatching to one lane per domain: memory, finance, calendar. Lanes are separate because their failure modes differ. A memory operation that goes wrong writes a bad note. A finance operation that goes wrong corrupts a record of real money.
Everything irreversible passes a human-in-the-loop gate. Not a confirmation dialog bolted on at the end, a stage every write path goes through, where the proposed action is described to me and waits. Sending a message, editing a financial record, cancelling an event: all gated. Reads are not. The line is drawn at reversibility rather than importance, because reversibility is the property you can evaluate mechanically.
The hard part
The best work here came from a near-miss.
Building the finance side meant the system held records of real transactions I rely on. Working through a schema change, I realised how close it sat to a specific bad outcome: a migration against the wrong target, or a test run pointed at the wrong database, could destroy financial history with nothing to restore from. Not a hypothetical, a concrete path from a plausible mistake to unrecoverable loss, in a system whose whole selling point is that I can trust what it remembers.
Three things went in, and the third is the one I argue for hardest:
- Automatic backups, on a schedule, off the machine holding the primary.
- A hard guard on destructive operations. The code refuses to truncate or drop unless the target database name marks it as a test database. Cheap, and it makes the dangerous case structurally impossible rather than discouraged. Same shape as the single-entry-point fix in my RAG project, arrived at independently.
- A restore drill. Backups were restored from scratch and verified. The difference between "we take backups" and "we have restored and know it works" is the difference between a belief and a fact. This is the one everybody skips.
A smaller story I like as much: CI has four required gates, lint, format, type check and
tests, and the type check is the one I kept forgetting to run. It had main red for three
commits before I made it a gate rather than a habit. A rule you have to remember is not a
rule.
Trade-offs
A hand-written scheduler instead of an orchestrator. I run Dagster at work and did not reach for it here. A handful of periodic jobs does not justify a tool built for much larger scale, whose own service, upgrade path and failure modes become a tax on a system operated by me on a weekend.
Markdown is slower to query than a database. Accepted deliberately, and tested rather
than assumed: I built a graph-based retrieval layer over the vault, ran it head to head
against plain grep on real queries, and it did not win. The graph layer was dropped.
Deleting your own more sophisticated implementation when the boring one measures the same is
the more useful habit.
Local-first means it is down when my machine is down. No cloud fallback. For a personal assistant that is not a real cost, and it is what keeps the bill under the ceiling.
One user means N=1 forever. Nothing is multi-tenant. That closed off a whole category of design work and made everything else simpler, and I made the call explicitly, in writing, rather than drifting into it.
Results
Four required CI gates before anything merges. Hundreds of tests that have caught real regressions on the way to production, including a batch of nineteen only the full suite surfaced because the individual files passed in isolation. Backups run automatically and have been restored under drill conditions. Cloud spend stays under twenty dollars a month by design.
Personal project, so unlike the four Mercaldas systems nothing here is held back.
Technical metrics
4 CI gates
Lint, format, type check and tests, all required
Hundreds
Tests, and they have caught real regressions
Pull before write
The runtime reconciles with the vault, never overwrites