back to blog

A Walkie-Talkie to My Vault

I haven't opened Obsidian to journal in weeks. I still open it every day for everything else: blog drafts, Permanent notes, VulWall, Roamler, refining my thinking. What stopped happening there is capture. And my daily log has never been in better shape.

The journaling problem isn't discipline. It's friction. Open the app. Find today's note. Remember the template. Fight the formatting. By the time the tool is ready, the thought is gone, and you're three days behind on a ritual you swore you'd keep.

I solved it the dumb way. I put a Telegram bot in front of my vault and let AI do the filing. Now I journal by texting. The vault writes itself.

Here's the whole setup.


The Core Loop

I text any thought (a line, a paragraph, a dump) to a private Telegram bot. Or I hold the record button and speak. The bot replies with two buttons: Today or Yesterday. I tap one. Under five seconds later it reacts ๐Ÿ‘Œ to my original message, and my daily note in the Obsidian vault has the new content, cleanly refined, filed into the right section.

Voice is the half that makes the mobile moments actually work. Typing on a phone while watering flowers isn't possible. Holding a button and speaking is. Telegram voice notes land as Opus-in-Ogg audio, get piped to whisper-large-v3 on Groq, and come back as text in about a second. The bot shows me the transcript next to the Today/Yesterday buttons so I can catch any word Whisper got wrong before I commit. I almost never need to.

That's the entire user-facing experience. No app. No template. No friction. The hardest decision I make is today or yesterday.

Under the hood it's a Go service called DailyLog, running in Docker on my home lab, bind-mounted directly into my Obsidian vault. When I tap a day, it:

  1. Loads the existing daily note at vault/Daily notes/YYYY/MM/YYYY-MM-DD.md
  2. Appends my raw blurb to the end
  3. Sends the whole merged document to GPT-OSS-120B on Groq with a very strict system prompt
  4. Writes the refined output back to disk

The model choice matters here. Note-taking isn't a reasoning task. It's routing, grammar cleanup, and bullet grouping. A 120B open-weights model running on Groq's inference is more than enough for that, fast enough to round-trip under five seconds, and cheap enough that I can fire it a dozen times a day without thinking about the bill. Frontier models would be overkill. A smaller model would lose the grouping nuance. 120B-class is the sweet spot for this specific job.

The actual magic isn't the API though. It's the prompt.


The Prompt Is the Product

Every daily note in my vault follows a fixed shape:

## Personal
- โ€ฆ

## VulWall
- โ€ฆ

## Roamler
- โ€ฆ

Three sections. Personal for life, VulWall for my startup, Roamler for day-job work. That's it. The system prompt I hand Groq has eight rules, but three of them carry the weight.

1. Never drop information. Every piece of input must appear in the output. Not "summarized away," not "skipped because it sounds unimportant." If I mention a family member's package pickup tomorrow, that bullet exists in the final note. Even if it's about a future day. Even if it's someone else's plan. If in doubt, include it.

2. Route to the right section. Personal is home, family, errands, appointments, non-work messages. VulWall and Roamler are the two work buckets. Items go there only if they're clearly about those projects. Everything else defaults to Personal. No new sections ever. No ## Miscellaneous, no ## Notes.

3. Group related content. If I dump five sentences about fixing a kitchen wall (the research, the store run, the tools, the install, the leftover TODOs), the output is one rich bullet telling that story, not nine fragmented ones. Only split when topics are genuinely unrelated.

The group-don't-fragment rule is what turned this from "AI notes toy" into something I actually trust. Without it, a paragraph of connected thought becomes a scatter of disconnected bullets and the note reads like a grocery list. With it, the note reads like someone wrote it on purpose.

One more rule works in the background without making a speech about it: fix what's broken. Typos, wrong tense, run-on sentences, missing punctuation. The capture pass is also the polish pass. There's no editor downstream, because there shouldn't need to be.


Merge, Don't Replace

The other design choice that made this work: the bot never overwrites. It merges.

When I send my sixth note of the day, the bot doesn't regenerate the whole note from scratch. It loads the existing refined markdown, appends my new raw blurb after the last section, and hands the combined document to the model. The prompt tells Groq explicitly: preserve every existing bullet exactly; only integrate the new paragraph at the end.

This matters for two reasons.

  • Stability. A bullet I wrote this morning doesn't get subtly rephrased every time I add a new thought in the afternoon. What was refined stays refined.
  • Trust. I can look at a note mid-day and know that nothing I've logged has quietly mutated. The model only touches new content.

It's a small architectural decision that most "AI-powered notes" tools get wrong. They regenerate. Things drift. You stop trusting the output. You stop using the tool.


The Second Half Runs on Cron

The bot captures. Cron curates. Every script writes directly into the vault and pings me on Telegram when it's done.

When Script What it does
03:30 daily daily-plan.sh Reads goals and recent notes, generates a time-blocked plan for the day, delivers it via Telegram. This is my morning briefing.
03:45 Sunday weekly-review.sh Synthesizes the week's daily notes into themes, wins, and follow-ups. Writes to Self Reflection/Weekly/YYYY-W##.md.
03:00 Q1/Q2/Q3/Q4 quarterly-review.sh Distills thirteen weekly reviews into narrative arcs. No PR numbers, no item lists. Just what actually happened at a quarterly zoom level.
03:00 Sunday clippings-process.sh Scans the clippings inbox, drafts article summaries for keepers, drops the junk.
02:00 daily backup.sh Tars the whole home lab (vault, docker configs, automations) to SSD. Sundays additionally encrypt and ship off-site to Cloudflare R2.

Each of these is a small shell script. The script itself is mostly plumbing: source an env file, export ANTHROPIC_API_KEY, call the claude CLI with a prompt and an agent flag, pipe the result to notify.sh. Under 50 lines apiece. The actual work happens inside Claude.

The --agent flag is the hinge. It tells Claude Code which specialist to open the session as. Each agent is a markdown file under ~/.claude/agents/ with a system prompt, tool permissions, and a tighter scope. The mapping:

Script Agent
daily-plan.sh daily-planner
clippings-process.sh clippings-curator
weekly-review.sh life-coach
quarterly-review.sh life-coach

Each script leans on one specialist. Planning a day against recent notes and goals is an opinionated routing job, so daily-planner does it. Triaging a stack of clippings into keep-or-delete plus a draft note is a classification job, so clippings-curator does it. Synthesizing a week (or a quarter) of notes into themes and narrative arcs is a reflection job, so life-coach does it. Different agents, different system prompts, different jobs.

I ran the weekly and quarterly pipelines as two stages for a while: one agent to draft, a separate grammar-and-frontmatter agent to polish. Then I noticed I was paying to reload the whole life system twice just to run a checklist at the end. I moved the checklist into the drafting prompt and dropped the second stage. The output is the same. The bill is lower. The log is simpler. When a pipeline needs an editor, the right editor is usually the writer, one paragraph later in the same prompt.

But none of this would be useful if the agents didn't know my life. That's what CLAUDE.md is for.

The CLAUDE.md at the root of my vault documents everything an agent needs to act well: folder structure, templates, my life system (Life OS.md, Goals.md, My rules in life.md, Lessons.md, Wins.md), the detail-hierarchy rule, and when to invoke specialized advisory agents like startup-coach or startup-legal-advisor when the topic calls for them.

When the weekly-review cron runs at 03:45 on Sunday, it isn't firing a fixed prompt at an LLM. It's opening a Claude Code session scoped to my vault, with full filesystem access, with CLAUDE.md loaded, with my 2026 focus visible in Goals.md, with last quarter's narrative sitting in Self Reflection/Quarterly/. It knows the shape of a weekly review because it has read the template. It knows what not to include because it has read the detail-hierarchy rule. Then it writes the file.

The payoff is iteration cost. If a weekly review feels off, I don't deploy code. I edit the life-coach agent's markdown, or tweak a line in my vault's CLAUDE.md, or adjust the template. The next Sunday's cron run uses the new version automatically. The system has no release cycle. Every night is a hot reload.

The detail hierarchy is the organizing idea:

Daily     -> raw log, specific items, one-off errands
Weekly    -> grouped themes, 3-5 highlights, pattern-spotting
Quarterly -> narrative arcs, zero line items, direction and drift

Each level summarizes the one below. Detail never flows upward. A PR number belongs in the daily log and nowhere else. A quarterly review reads like a letter from your future self about the shape of the last three months.


Reliability Is Not Optional

This is all worthless the day I lose the vault.

Local SSD backups every night at 02:00, 14-day retention. Every Sunday, the same tarball gets GPG-encrypted with AES-256 and shipped to Cloudflare R2. The R2 bucket has a lifecycle rule: transition to Infrequent Access after 7 days, expire after 60.

The encryption passphrase lives in two places: the .env on the host, and a 1Password entry. If both disappear, the R2 backups are permanently unreadable. That's the design. Off-site backups that require no trust in Cloudflare.

Every backup run posts to Telegram:

  • โœ… Local backup complete with size and host
  • โœ… Local + R2 backup complete with R2 object key
  • โŒ Host backup FAILED with the specific reason

No dashboard. No log aggregator. Just Telegram. If my phone doesn't buzz at 02:05 with a green checkmark, something broke.


Why This Works When App-Based Journaling Didn't

Every journaling tool I've tried failed in week three. Obsidian with a beautiful template. Notion with elaborate databases. Apple Notes. Day One. Roam. All of them died the same way: the tool became more effort than the thought was worth.

Telegram isn't optional. It's open on my phone and laptop all day. The bot is one thread in a stack of conversations I'm already scrolling. There's no decision to journal. There's a thought, there's a chat window already right there, and the friction between them is zero.

The vault is still the system of record. Real writing, linking notes, building out Permanent notes, running DataView queries. All of that happens in Obsidian when I'm at my desk.

Telegram is for the daily log. The thought at a train station. The reminder while I'm watering flowers in the yard. The errand that popped into my head walking back from the store. My day doesn't happen behind a laptop, and those are exactly the moments where a note would have died on the walk home.

The bot catches them because capture is one gesture: press the button, talk. It's a walkie-talkie with an AI on the other end. No app switch, no typing, no decision about where the thought belongs. Press, talk, tap Today, back to whatever I was doing.


Steal This

The pieces are individually boring. The combination works.

  • Put a Telegram bot in front of your vault. Any stack works: Go, Python, Node. The hard part isn't the bot. It's deciding you deserve zero-friction capture.
  • Write a strict system prompt. Fixed section schema. Never-drop rule. Group-related rule. Spend more time on the prompt than on the code.
  • Merge, don't regenerate. Load existing output. Append raw input. Only transform the new part. Stability is a feature.
  • Let cron do the second draft. Weekly synthesis, quarterly narrative, morning briefing. You don't need to review. You just need the pipeline.
  • Let Telegram be the whole interface. Capture goes in, text or voice. Briefings, reviews, and alerts come out. One chat, both directions. The capture channel and the feedback channel being the same channel is what keeps the whole system in one context.

None of this is clever. It's a couple hundred lines of shell, a small Go service, and a prompt I've iterated on more than the code. The bar for "it works" is low. The bar for "I still use it in month six" is high. This one cleared it.

I still open Obsidian every day. Just not to journal. The vault is the healthiest it's been.

find me on