01 Problem
Capture every thought by voice, text or photo with no friction, and get it back later as notes that can be searched and read. Both halves have to be honest: nothing lost on the way in, nothing invented on the way out.
- self-hosted
- n8n, Postgres and Whisper on one rented server; the vault on the home PC.
- free tier
- Chat models through a router on free-tier limits, about 50 requests a day.
- one person
- Built between 2026-08-28 and 2026-09-12, alone.
- private data
- The vault leaves the machine through Obsidian Sync, so every write obeys a redaction contract.
Everything on this page that looks like data is synthetic. No real capture, note or message is shown.
02 Architecture
Grouped left to right: capture, process, store, deliver, read. Node labels follow the workflow node names. The dashed edge is the error path.
03 One note's journey
One synthetic voice note, step by step. The model outputs at steps 5 to 7 are real outputs from the re-enactment run below, on invented input. Everything else is built by the re-enactment script or written by hand, and each step says which.
04 Re-enactment: what the run measured
The production pipeline ran free-tier models inside n8n. This page does not show that system running. Instead tools/reenact.py replays the pipeline's two model steps on three synthetic days, with prompts written for this demo, against three Claude models through the Claude Code CLI. It repeats each call and applies the same kind of checks the Validate node applies.
| model | step | calls | usable | failed check | invented dates | fenced JSON | latency median (min to max), s |
|---|
Cost is not measured. The CLI calls here report no token counts, and none are estimated. The sample is small (3 repeats per day and model) and synthetic; read it as a smoke test of the guardrails, not a benchmark.
05 Second story: the read path
The pipeline wrote into a vault that nothing could read back. It was a good microphone with no memory. So the effort moved to the read side: on 2026-09-02 an agent workflow read about 46 project roots and wrote a structured vault that AI tools now read over MCP. It is agent orchestration with a human gate and verifiers, not a chatbot.
| project roots read | ~46 | Workflow A surveyed, harvested and reconciled; it wrote nothing. |
|---|---|---|
| files read | 391 | Each logged with its sha256, so the next pass re-reads only what changed. |
| plan review | 2 + 1 | Two adversarial critics and one revision, then a human approval gate. |
| coverage rows | 88 | Each source covered or explicitly dropped with a reason. |
| dropped | 44 | Planned notes cut by Workflow A, the critics and the owner at the gate. |
| notes written | 93 + 31 | 93 new, 31 updated, 124 in all. Updates append or patch; they never rewrite. |
| bundles | 75 | 74 went through a fix round; 73 finished with a high or medium verifier finding still open. |
| second pass | 202 | Findings through a fixer and a targeted verifier on 2026-09-06; 17 bundles needed a second round; 72 of 73 closed clean. |
The part I'd point to is the verifier result: after the first write pass, 73 of 75 bundles still had an open finding. That is what the verify and fix steps are for; without them those findings would sit in the vault unnoticed. Key, wallet, credential and .env files are never read, by rule. The contract forbids renaming or moving a note. Two false positives in the note checker's redaction heuristics were fixed only with the owner's approval.
06 Design decisions and trade-offs
| decision | why | cost |
|---|---|---|
| Error alerts go to a hardcoded chat | The alert must still fire when Postgres is what broke. | A config value lives in a workflow; changing it means editing the workflow. |
| Unknown senders get no reply at all | A Telegram bot is public; silence tells a stranger nothing. | A misconfigured id also gets silence, which is harder to debug. |
| captured_at = Telegram message time | A slow transcription once filed a note after shorter ones sent later. | Insert order and day order can differ, so every query has to sort by it. |
| Vision refuses tiny payloads; prompt forbids invention | Without the guard the model invents a plausible receipt. | A refused photo has to be sent again. |
| Pull-only sync over SSH from the home PC | No open sync port, no relay; each run fetches only what is new. | Notes arrive only when the PC is on. |
| Differing collision is written beside the original as .regenerated-<runId>.md | A regenerated day must not overwrite hand edits in Obsidian. | Duplicates to merge by hand. |
| Remote scripts end with an __OK__ sentinel | ssh failures show only in the exit code; a dropped connection must not look like a quiet day. | One more convention every remote script has to follow. |
| Stage quiet files, verify each by MD5, archive last | n8n writes non-atomically and keeps writing during a pull. | A note waits one extra run. |
| Invented dates dropped; only days that produced a file are marked processed | An invented date could file notes under a day that never happened and leave the real day looking done. | A dropped file means a day is regenerated. |
| Evening check-in is report-first, at most 3 questions, plain text | Three days of questions went unanswered; the prompt had been tuned for form. The Telegram node sends unparsed. | Fewer questions, some worth asking go unasked. |
The "cost" column is my reading of each trade-off, not a measured figure.
07 What broke, and how I found it
2026-08-30: manual runs of the evening workflow seemed to hang on the Ask LLM step. I ruled out model ids, response_format support, server egress latency, timezone settings and apparent duplicate workflows. The model was never called. Get Day had returned zero rows, Build Prompt returned an empty array, and n8n skipped every downstream node. A skipped node renders greyed out, the same as one still spinning. The 0.35 s execution time gave it away.
The fix, "Nothing captured" with no model call, landed in note generation on 2026-09-02. The evening workflow is still silent on an empty day. Silence that looks like failure is the bug I most want gone.
08 Status
In daily use from 2026-08-28 to 2026-09-03; paused by choice since.
- Daily capture files exist for 2026-08-28, 08-31, 09-02 and 09-03 and none after. There are 32 topic files.
- Why paused: the pipeline had no read path. Nothing it wrote could be read back by it, so more capture only added to a pile. The work moved to the read side: the vault, story two above.
- The last pipeline release was v1.0.0 on 2026-09-12 (hourly pull, backlog sweep, invented-date drop).
- The scheduled pull log has no line after 2026-09-14 00:05. That is unexplained; no notes are known to be lost.
- The post-redesign workflows were never tested against real captures. The database held 21 captures, all from 2026-08-28.
- Two regressions from the 2026-09-02 rewrite are open: the receipt arithmetic self-check and the receipt image save were dropped.
09 What I'd change
- Build the read path first. The chosen design is a push-only mirror of a named vault subset, next to the pull and over the same SSH, into a folder n8n can read. It was chosen over a Postgres index.
- Record the signal that cannot be backfilled. Add a questions table so each answer, skip or "useless" is recorded against the question it belongs to.
- Give the evening run the empty-day branch: one IF node, one Telegram line, no model call.
- Restore the two dropped guardrails and prove it with a receipt eval run before and after.
- Fire on accumulation, not the clock. The free-tier budget was named as the real design pressure, but no node counts requests.
- Sort in code, not in the prompt. From this page's re-enactment: models asked to list captures "in time order" put the after-midnight capture first. Rows already arrive in captured_at order, so code should emit the list and the model should only write prose. This fix has not been tested.
10 Demo vault excerpt
An invented vault for a fictional user, in the same folder shape. The files under capture/ are the sample model output from the re-enactment. The project notes are made up for illustration.
11 Sources
Each claim about the real pipeline and the ingest, with the note in my private vault it comes from. The vault is not published. Re-enactment numbers come from data/reenactment.json.