A Claude agent that lives on Telegram
I run a Claude Code agent on a small VPS. I talk to it through Telegram, like a contact in my phone. It reads and writes a git repo of plain markdown files, and that repo is its whole memory. Nothing lives in the chat history. Nothing lives in a database.
Why
Chat apps forget. Close the window, and the model starts from zero next time. I wanted something that keeps state across restarts, crashes, and context limits, and that I could inspect and edit by hand if I ever disagreed with what it remembered.
The design borrows directly from Andrej Karpathy’s LLM Wiki gist. His point: don’t make the model re-read raw sources and re-derive the same facts on every query. Instead, have it read once, write what it learned into a small set of markdown pages, and treat those pages as the real memory. The context window is scratch space. The files are the truth.
The repo
Three folders do the work:
raw/: source material dropped in by hand: articles, transcripts, data exports. Read-only. The agent never edits it.assets/: images, including charts it generates.wiki/: the layer the agent owns. Flat, no subfolders. Anindex.mdcatalog, an append-onlylog.md, and one file per topic. Every page carries frontmatter: atype(source, concept, goal, list or overview), tags, dates, and arelatedlist of links to other pages. Links are bidirectional: if page A links to page B, B links back. A linter checks this and fails if it doesn’t hold.
A CLAUDE.md at the root is the constitution, read at the start of every
session: what each folder is for, how to file a new source, when to write
to the wiki versus just answering in chat, what counts as out of scope.
Running it unattended
None of this works if the process falls over the first time the VPS reboots or the network blips. The setup that keeps it alive is a handful of systemd user units plus one tmux session:
-
claude-telegram.servicestarts Claude Code inside a detached tmux session, withRestart=alwaysso a crash comes back a few seconds later. The launch command looks like this:claude --model claude-opus-5 --effort medium --permission-mode auto \ --channels plugin:telegram@claude-plugins-official--channels plugin:telegram@claude-plugins-officialis a channel plugin: it gives the session a Telegram bot as an input and output, separate from Claude’s own Remote Control feature, which I don’t use here. -
claude-watchdog.timerruns every minute, checks the service, the tmux session, and the Telegram connection, and restarts what’s broken. -
claude-telegram-refresh.timerruns every 15 minutes and, once a session has been alive for 6 hours and has sat idle for 15 minutes, restarts it so the next reply starts from a clean context. A 24-hour ceiling forces a refresh even if it never sits idle that long on its own. -
agent-wiki-git-sync.timerruns every 15 minutes: pulls with--ff-only, runs the wiki linter, and commits and pushes anything that passes. A per-turn lock, set by Claude Code hooks, stops it from committing a half-written file mid-reply. -
agent-wiki-lint.timerruns the same structural check daily, as a standing health signal separate from the one gating each sync.
Every service start is a fresh Claude Code context, with no --continue. The
tradeoff is that a fresh session doesn’t remember the last few exchanges
on its own, which matters for a phone conversation with natural pauses in
it. A pair of hooks bridges that gap: one rebuilds a bounded, mechanical
journal of recent turns as the session runs, and freezes it as a handover
when the session ends. The next fresh session gets that handover injected
as context at startup. It’s short-term memory only. The durable record
is still whatever got written into wiki/.
Scheduled work
Some things shouldn’t wait for me to ask. A small job runner turns a
recurring Claude task into a spec file plus a prompt file, and handles
locking, validation, delivery, logging, and retention so each job isn’t
another bespoke script. Each job becomes a claude-job@<name>.timer
systemd instance. Mine currently run a nightly wiki contradiction sweep
and a weekly note, delivered over Telegram or written straight into the
wiki.
A starting point
I’ve published the setup as a template repo:
agent-wiki-template.
It’s the same vault structure and infrastructure described above,
stripped of my own content: a CLAUDE.md with the frontmatter schema and
workflows already written, a few example wiki pages showing the pattern,
the infra/ scripts and systemd unit files, an installer, and a
SETUP.md written as an install guide for a coding agent to follow end
to end, stopping only at the points that need a human: a Telegram bot
token, a sudo password, a browser login.
To use it:
git clone https://github.com/alexander-miguel/agent-wiki-template ~/agent-wiki
cd ~/agent-wiki
claude
Then tell the agent: “Read SETUP.md and set this up on this host.” It
needs a Linux box with systemd user services, a cheap VPS is enough,
Node 22 or newer, git, curl, jq, tmux, and Python with PyYAML, plus
the Claude CLI and a Telegram bot token from @BotFather. The README covers
the rest, including a security tradeoff worth reading before turning it
on: the session runs in an unattended permission mode, so anyone who can
message the bot can direct it. The fix is to set Telegram’s pairing
policy and allowlist yourself before the first start.
Early use
So far it has:
- kept a running log of a training block, week by week
- built and tracked a training plan for someone else
- run a periodic wiki health check that trims bloated pages and flags contradictions before they pile up
- held a reading list and a small stack of recipes
- written this post