Context
A side project on unfamiliar ground: running a VPS, writing an agent harness by hand, plugging a model in through OpenRouter. The use case: an Obsidian vault shared with someone else, reachable anywhere, with no sync subscription. A single copy of the vault on the VPS, Telegram as the interface. Pepper reads and writes the Markdown notes, creates todos, assigns them and notifies the person concerned.
Scope
Handled alone, end-to-end: spec and technical plan written before any code, then development, tests and the move to production on the VPS. The harness is written by hand rather than taken from a framework, which keeps control over what the model is allowed to do.
Product decisions
- Tool surface kept to five tools (read, write, list notes, list assets, generate an image): no shell, no web access, no deletion
- Zone partitioning enforced in the harness, paths validated: the model never reaches the other user's zone
- No database: the Markdown files are the source of truth, written to Obsidian conventions (wikilinks, tags, frontmatter), so Obsidian Sync stays an option later
- LLM behind a one-method interface: OpenRouter in production, a pre-recorded client in tests, model picked through an environment variable
- The /status, /notes and /help commands answered without calling the model: immediate, no cost
Highlights
- Tool-calling loop with conversation memory, running on Gemini Flash via OpenRouter
- Assignment: the todo goes into the shared zone and the other user gets a direct message
- Image generation, returned on Telegram and archived in the vault
- Tests colocated per module (Vitest), CI on lint, typecheck, tests and build at every push
- Continuous execution on a VPS under pm2, automatic restart, hardened SSH (keys, UFW, fail2ban)
- Running costs of 5 to 8 € a month, spend cap set on the API from day one
Stack
TypeScript
Node.js 24
grammY
OpenRouter
Vitest
Biome
pm2
Scaleway