← Back to the blog

My own data in the second brain

Series: How I built a second brain · part 6

The first parts of this series were about what the AI remembers from its conversations with me. But a conversation is only what I recall and choose to say. So between July and the end of August I connected five sources to the second brain that know more about me than my memory does: my watch, my car, my bank, my photos and my mail. Each turned out to be a different problem. But they all failed the same way: silently.

One shape for everything

I set the pattern with the first source and then simply repeated it. Data arrives as a file in its own folder and is never changed there again. From that folder a program generates pages in the wiki, in full, on every run. Nobody edits a page by hand.

A generated page is a pure function of the raw data. Delete it, run the program again, and it must come out exactly the same.

It sounds like an academic remark, but it is a safeguard. When I fix a bug in the program, the whole history is fixed by one regeneration, without hand-editing hundreds of pages. And it all runs on my own machine: no cloud service, no database, just files.

The watch: "ran fine, wrote nothing"

The watch sends its data through the phone to my server. In July, nothing arrived for three nights in a row, and every night the program reported that it had run fine. It had. It just wrote nothing, and that cannot be told apart from a quiet day. The cause was mundane: the VPN connection the data travels over was not running on the phone.

The fix was not a better transfer but an alarm. Every run now first checks how old the newest data is, before it does anything else. If nothing has arrived for long enough, it speaks up, and while the data can still be rescued from the phone, it says how many hours are left to do it.

Then I checked the alarm against its own log and found that it would never have gone off on the night that mattered. Data arrives once a day, so its age doesn't grow smoothly but jumps by whole days. The threshold sat just above the first missed night. So the warning came a day later than it should have.

Two more lessons from the watch:

  • What you see on the display may not arrive in the data. The watch shows cardio load. It wasn't in a single file received, because the maker doesn't pass derived values on. I calculated them myself from the raw heart-rate stream that does arrive.
  • A field's name is a claim. The "sleep" field measured all the time in bed. For a month I read a number that meant something other than its name said.

The car: a month without drives looks like a month at home

My car's drives are logged by an open-source program, TeslaMate. When I was connecting it to the wiki, the first query to its database showed a hole: for almost two months it had not logged a single drive. Tesla had changed its API and TeslaMate stopped logging, while the machine it runs on looked perfectly healthy.

Nobody noticed, because nothing looked wrong. A month without drives renders exactly like a month spent at home.

What helped was a single number: the odometer reading minus the end of the last logged drive. The odometer keeps counting whether anything is being logged or not, so the difference is exactly the distance that was lost. It is the only signal that tells "I didn't drive" from "I didn't log". And after every fix it serves as the test: I take a drive, and if the difference doesn't go back to zero, the fix didn't take.

The bank: a source that is my own attention

With the bank it was the other way round. Direct API access is closed to individuals in the Czech Republic; I would need a licence. The free intermediary that offered a way around it stopped taking new users. What was left was a manual export from online banking.

That has one honest consequence, which I wrote down: this source cannot have an alarm. No script can tell "I spent nothing" from "I forgot to upload the statement". So instead of an alarm, the page itself shows how old its newest payment is, so that an old figure can't be quoted as today's.

Parsing was easy. Every statement is checked against the bank's own totals, and the program refuses to write if they don't match. I tried it: I changed one amount in a copy by one crown, and the error was found on the right line. The real work was classification. One payment was counted as a transfer between my own accounts only because my name was in its description.

Photos: the source turns around

I wanted the advisors to be able to read my photo library. It can't be done, and not by anyone: in March 2025 Google withdrew the permission that allowed a library to be read unattended. So the whole approach had to turn around. The source is the phone, from which photos are copied to my machine, and a local model sorts each one: receipt, meal, document.

Before I trusted the sorter, I measured it. It filed an identity document as a medical record. Worse, it reported high confidence on all hundred and seven photos. Confidence that never drops is not confidence. I deliberately didn't fix the prompt in the middle of the measurement, so the instrument wasn't recalibrated during the test.

Mail: filter on reading, not on writing

The morning briefing read my mail, the advisors acted on it, and then it was thrown away. Today only the messages' metadata is stored, never their content. The filter that separates real mail from bulk runs only when the page is generated. When I corrected it, the whole history was corrected by one regeneration, without a single call to Gmail.

It paid off on the very first day: the system had recorded one reply as silence for twelve days. The reply had in fact arrived.

Five copies of the same code

The five sources were built one after another, and each got its own copy of the same code: writing to the wiki, the lock, saving to git, the alerts. The copies drifted apart. Some saved files to git that weren't theirs. Five programs were writing with no shared lock. At the end of August I replaced them with one library and added the first tests. Before saving anything, I checked that the new code generated every page byte for byte the same as the old.

What holds for every source

A missing page means "not loaded", never "didn't happen".

That rule now applies to every advisor. Before they say something isn't in the data, they have to check that the data actually reaches the date in question.

How to start

  1. One shape for every source: raw data never changes, and pages are regenerated from it in full.
  2. For each source, write down how it fails silently. Then watch exactly that.
  3. Measure what is missing, not what arrived. Odometer minus logged drives; the age of the newest data.
  4. Check the alarm against history, not against how you imagine it should behave.
  5. Where an alarm is impossible, let the page admit how old it is.
  6. Make a second copy of the code, never a third.

Next time: how to put a single day together from all that data, when each source sees only its own piece of it.

← Back to the blog Get in touch