Helm Inbox — Build Log

A second Telegram bot for the life OS that captures weekly-review signals into an append-only JSONL queue, drained at review time instead of enriched on the way in.

The life OS already had a Telegram bot. It’s the Lugar Común diary: I send a photo with a caption from my phone, an n8n workflow asks Claude to clean the prose, pick the right family member and date, and the result lands as a markdown file in the life repo. About eight seconds from thought to published moment.

That bot is a bad fit for weekly-review captures. When I notice mid-week that I finished a book, or Diego stood up for the first time, or I picked up groceries that should count toward a quest, I don’t want any of that to become a publishable diary entry. I want it to sit in a queue until Sunday’s review, where I cluster the week’s signals and fold them into the right buckets in the quarter tracker.

So today I built a second bot, Helm, with the inverse processing model.

The pipeline

Helm sends nothing to Anthropic. The Telegram message gets a chat-id allowlist check, a text-only filter, and a normalize step that computes the current ISO week (in Atlantic/Canary, since rollover is Monday 00:00 wall-clock for me) and stamps a UUID. The lambda receives { week_key, line } and appends one JSON line to inbox/mc/2026-Www.jsonl in the life repo. There’s no LLM in the pipeline at all.

It runs in the same Lambda as the diary bot. A sibling Lambda would have been cleaner to deprecate independently, but it would have meant duplicating the GitHub PAT wiring, the API Gateway, the usage plan, and the build for fifty lines of new code. So the existing service picked up a second route, POST /inbox/append, with its own Zod schema and a read-modify-write append. GitHub’s Contents API doesn’t truly append; you fetch the file, decode, concat, PUT with the previous sha. It dedupes on (chat_id, message_id) so an n8n retry doesn’t append twice. The stack name stays commonplace-commit-service even though it isn’t only commonplace anymore. Renaming a CloudFormation stack is a tax I’m not paying.

Sunday is where the design lives

The capture pipeline is the small half. balance-quarter (in claude-config) drains the queue: for every week between the quarter MDX’s updatedAt and today, read inbox/mc/<week>.jsonl if it exists and cluster by day. Pair the lines with git log from the same range. Git tells you what shipped; the inbox is what I noticed when I wasn’t at a keyboard. When the skill proposes a quest or habit tick from an inbox line, it quotes verbatim, because the in-the-moment phrasing is what makes the line worth keeping. After I sign off on the week, the skill git mvs the JSONL into inbox/mc/processed/. That’s the only mutation the system ever makes to the inbox.

The crypto wrinkle

n8n’s Code-node sandbox blocks require('crypto'). I had crypto.randomUUID() in the Normalize node and it errored on the first real Telegram message. The fix was an inline Math.random UUIDv4. The IDs don’t need crypto strength, just uniqueness within a couple hundred lines a week. Switched it, redeployed, sent Helm smoke 1 from my phone, got back ok 2026-W20 #1. The line is sitting in the repo waiting for next Sunday.

Voice, later

I was going to add a Whisper branch to transcribe voice messages at capture time so the JSONL stays grep-able mid-week. I cut it to ship today. If I find myself thumbing long messages instead of dictating, I’ll add it back.