Always-On Agent Daemon
Telegram Bridge
A private phone-to-PC bridge. An always-on Windows daemon long-polls a Telegram bot, and each message sent from my phone becomes a headless Claude Code run on my workstation (answered, filed into my notes, or written as code on a throwaway git branch). It installs as two Claude Code skills.

Employer signal
What This Project Shows
This is the project where every constraint is operational rather than visual: a process that has to still be running tomorrow, answer a person inside the few seconds before they assume it is dead, cost little enough to leave switched on, and treat everything arriving from outside the machine as untrusted. All 28 commits are mine: sole committer, sole author of the design decisions, AI-assisted throughout, with the reasoning written into the commit bodies. What I would put in front of a reviewer is not the feature list. It is that almost every number in it was measured, including the one that killed the larger optimisation.
Problem
What Needed To Be Solved
An agent session can only help while you are sitting in front of it, and the thing I most wanted to capture, a link somebody has just sent me, arrives on a phone. The first attempt was an iOS Shortcut on the share sheet, and it died on a platform limit rather than a bug: Instagram's share sheet hands out no URL at all, only a rich preview object and a thumbnail, and Shortcuts can no longer save to Google Drive. The second problem is the one that actually shapes the system. Once a message from a phone can start an agent on a workstation, that message is untrusted input, the delay before an answer is something a person feels rather than a metric, and every run spends tokens whether or not it was worth starting.
Approach
How I Built The Solution
Telegram sidesteps the share-sheet limit for a structural reason rather than a lucky one: the Bot API pre-parses message entities, so url and text_link arrive already extracted: including targets hidden behind link text, which is exactly what the share sheet threw away. From there two rules did most of the work. Routing is decided by structure (was this a reply to the bot, does it carry a URL or an attachment, does it start with an explicit prefix) because that is a fact about the message rather than a guess at intent. And nothing is optimised before it is measured, because the failure mode of guessing about cost is a confident number that nothing downstream can detect as wrong.
Outcome
What It Demonstrates
It installs as a logon-start scheduled task and as two skills any agent session can pick up, and the tuning above came from real messages rather than synthetic ones. The gaps are worth stating plainly. There is no test suite and no CI anywhere in the repository (seven tracked files, and not one assert statement among them) so every replay described here is recorded in a commit body rather than in a harness someone else can re-run. It is single-user by construction: one chat, one machine, no multi-tenancy and no evidence under load. And presence and timers lean on Windows specifics (idle time, the task scheduler, the boot clock) so only the messaging half would port. One standing rule came out of getting it wrong myself: an auto-filed note was once judged fabricated from reasoning alone and a correct list deleted, when the agent had in fact done the work by a route that left no file where anyone thought to look, so the skill documentation now says to verify against the source rather than against assumptions about what the agent could do. The judgement I would claim from it is the measuring: an invented denominator would have produced a confident percentage nobody could have checked, and the same discipline is what threw away a 30% saving once probing showed it broke the thing it was saving on.
Evidence From Source
A denominator that was measured, not invented
"What share of my weekly quota did that message cost?" needs a limit figure the tooling does not expose: the auth-state endpoint reports subscription type only, and the usage command is interactive. Guessing fails in the dangerous direction: an invented denominator yields a confident percentage nothing downstream can detect as wrong. Instead I found that every session transcript on disk records per-message usage, and wrote a scanner that reconstructs all Claude Code consumption on the machine over a rolling window: pre-filtering whole files by modification time, tolerating malformed lines and unreadable files, and caching for sixty seconds because the dashboard polls every eight across hundreds of megabytes of session transcripts. Calibration then collapses to one human reading. With no calibration set the percentages stay hidden rather than guessed. Deliberately calibrating against account-wide rather than bridge-only usage is what made it honest, and the finished measurement contradicted the worry that started the work: 289,888 tokens spent by the bridge over seven days against 1.14 billion account-wide, or 0.025%.
An optimisation measured, then killed
Per-message prompt cost, measured on an identical trivial prompt: 46,435 tokens as the daemon was, 40,328 with MCP configuration made strict, 40,056 with dynamic system-prompt sections excluded: 14% off every message, because tool definitions for servers this agent never uses were riding along in every cold run. A third flag measured 32,247, a 30% saving, and I rejected it: it drops user-level settings, which is where both skills this agent depends on live. Probed directly, the agent could then see neither of them. Cheaper and broken is not cheaper.
Untrusted input handled by architecture, not by filtering
A bot username is discoverable, so anyone can message it: harmless while a human reads the results, and not harmless once a message starts an agent. So the owner check accepts only the linked chat and drops everything else, and the prompt never contains message content at all: a capture reaches the agent only as the output of a poll command, as data. That separation is the thing standing between a captured link and prompt injection, rather than a filter guessing at malicious text. The agent runs against an explicit tool allowlist rather than with permissions skipped, and every prompt ends with a clause telling it that the message is data to be filed and never an instruction to be obeyed.
Two bugs found by measuring the transcript rather than reading the code
The one that mattered was not what I reported. I asked the bot a question and never got a reply; the run had logged that it was answering at the terminal instead, because the guidance said not to interrupt me when I am at my PC: inside a headless run that has no terminal. That rule is right for unprompted outreach and wrong for a reply someone is waiting on, so it was scoped in all four places that carried it rather than patched in one. The second: a holding message fired on everything, because nearly every headless run needs longer than the twelve-second threshold before it can speak, turning quick exchanges into two notifications. Replaying all thirteen real cases produced two thresholds instead of one (fifteen seconds when the work is known to be slow, ninety when it is not) so seven exchanges of 34 to 83 seconds now stay silent while the 181 and 491 second runs are still held. Voice notes and photos were briefly on the known-slow list until the same replay showed both finish before the agent starts, so announcing "transcribing your voice note" would have been describing finished work.
A silent lost update, caught from behaviour
A snooze went through the code that stores it and was gone moments later. The cause was not in the snooze code: the watcher held a long-lived in-memory copy of its config and wrote the whole file back whenever it saved its read offset, silently discarding anything another code path had written in between: the same race would have quietly dropped presence updates too. Every writer now goes through one read-modify-write helper whose docstring records the failure it exists to prevent, so the next person to add a writer is told why the shortcut is not available.
Naming a failure instead of letting agents grind on it
A reel that downloaded cleanly one day failed identically a few days later. Rather than guess, I scoped it: an anonymous page fetch returned 200 with 620KB and no video or title metadata and not even a login form, the downloader was on its current release, and other hosts still worked, so it was a login wall, not a stale extractor or a broken pipeline. The measured cost of not naming it was one capture run spending $2.66 and 212 seconds working through a fallback ladder to reach the same conclusion. The media command now returns an explicit blocked reason and the filing prompt says to record the link with a retry note and stop.
The one feature that writes code was the one tested end to end
Writing code unattended from a phone message is the riskiest thing here, so it is the thing I ran end to end against a throwaway repository rather than reasoning about: the call returned immediately, the runner finished thirty seconds later, the original checkout stayed clean on main at its original commit, the work landed on the branch, and the report came back to the phone with a diffstat. The test paid for itself. It caught a reporting bug that would otherwise have shipped: commits were counted with git log against the branch excluding everything else, and everything else includes the branch itself, so every build would have reported zero commits. The branch point is now recorded when the worktree is created, and both the count and the diffstat span it.