Skip to content
Nicolás Forero

Case study, personal project

brain, a personal agent system on my own server

It turns the small facts of my days, meals, training, money and a weight, into a morning brief and a weekly review on my phone. Claude agents do the work on a schedule; plain files are the database.

Role
Everything: product, design, code and operations
Since
November 2026, in daily use
Built with
Next.js, TypeScript, Zod, Claude Code, Ubuntu, systemd, Tailscale
brain’s Today page: a week of progress rings and the day’s tasks as cards, the weigh-in card first, with a weight field.brain’s Health page: protein, training and bodyweight for the last 28 days, and a four-week calendar of training days.brain’s meal-prep page: the week’s plan in Spanish, approved, with a shopping list to tick.

The problem

My health, what I’m learning and my money lived in five apps that never talked to each other. I wanted one place where they meet, and something that reads all of it every morning and tells me what the day needs.

I also had two things to learn. I wanted to run real infrastructure myself, a server, its services and their monitoring, instead of renting it all as a platform. And I wanted to ship AI features I’d trust with my own health and money, which means knowing exactly what each agent can touch.

How it works

Two repositories

The code can become public. My data is a private vault I also open in Obsidian, split into what I write and what the agents write.

Two routines

A daily brief at 06:30, and on Saturdays at 17:00 a weekly review with the meal-prep plan and its shopping list. If a run fails, my phone gets a push.

One small server

One virtual CPU and 4 GB of memory, hardened by script, with every service run and restarted by systemd. No containers: nothing here needs them yet.

One morning, replayed

Every morning at 06:30 an agent reads what I logged and writes the day’s brief onto my phone. Here is that run, step by step, against the sample vault the project ships with.

11 steps, about 20 seconds. Sample data.

Press play to watch the runner and the agent write this morning’s brief.

Today

  • Weigh in06:45

  • Breakfast07:00

  • Gym session18:00

  • Coaching, 15 min21:00

Decisions I’d defend

Each one is a short decision record in the repository, written when I made it.

Files are the database

Problem
One person, a few thousand records a year, written by agents, by Obsidian on my phone and by git. A database would be one more thing to run, back up and keep in sync with all three.
Decision
Append-only JSONL for facts and markdown for everything I write. A line is never edited; a correction is a new line with the same id that supersedes the old one.
Tradeoff
Every view reads and adds up the files in memory, and there are no joins.
Outcome
Everything is diffable, works offline in Obsidian, and an agent can read it with no driver. I set the point to revisit in advance: when a read takes longer than 200 ms, the first step is a cache built from the files, not a migration.

Agents get file tools, and nothing else

Problem
The routines run unattended at 06:30, with nobody watching. A confused or prompt-injected run must not be able to do real damage.
Decision
Scheduled runs get read, write, edit and search on the vault, and a turn limit. The runner script around them, not the model, does git, validation and rendering. Their credential lives only in a root-owned file systemd loads.
Tradeoff
The model can’t run a check itself. Anything it needs computed, the runner has to compute first and hand over.
Outcome
The worst a run can do is write wrong files, and validation and git history catch that. Every run is also logged as a record, so the dashboard shows what each run did.

My private network is the login

Problem
The dashboard holds my health and money, and only I use it, on my own devices. A login system is code I’d have to get right and keep patched.
Decision
The server has no public ports. The dashboard is only reachable over my private network (Tailscale), on my own phone and laptop. There is no login.
Tradeoff
Anything on that network can read the data, so the network has to stay mine alone. A public demo has to run separately, against fake data.
Outcome
No passwords or sessions to manage. The demo you see on this site comes from the sample vault the project ships with.

The dashboard writes plain facts only

Problem
Logging a weight or ticking a shopping item through a chat with an agent was slow, and the shopping ticks lived in one browser.
Decision
Forms may write a fact I read off the world: a weight, a cash expense, a tick. Each one is validated against the same schema and appended under the same lock the git jobs use. Anything that needs judgment, like estimating a meal or changing a plan, stays in chat.
Tradeoff
A fact typed on the web reaches git within about five minutes, not instantly, and the web process can now write to one folder of the vault.
Outcome
Simple facts take one tap, and the rule for what a form may write is in the decision record, so it doesn’t drift with each new form.

Where it stands

I started brain on 5 November 2026. The server, the agent and the dashboard were running the next day. On Saturday 7 November the weekly routine wrote the first real meal-prep plan, and I approved it in a chat from my phone the day after.

On 8 November the daily run failed: it and the sync job pulled from git at the same moment. My phone got the push, the run record showed the error, and I put both jobs behind one lock. That is the loop I wanted: a failure I hear about, can read, and fix once.

I haven’t measured anything yet that I’d put a number on. After two weeks of routines I’ll review what the agents cost and how often I actually act on the brief, and write that here with the dates.