Harish Kumar
war-storyarchitect-mindsettooling

I refactor JWT middleware from my phone now.

Sarathi is a Telegram bot on top of the Claude Code CLI that lets me dispatch, monitor, and review coding agents from my phone. The control plane is shell scripts, SQLite, and tmux; the brain runs orchestration on free models. The point of the whole thing: the model isn't the moat — the orchestration is.

May 28, 20269 min

I was on my phone, reading, not at a desk. I typed this into a Telegram chat:

/task api refactor the JWT middleware and add refresh token rotation

Then I put the phone down.

A few minutes later it buzzed: the agent had a question about which token store to use. I answered inline, from the same chat. It kept going. When it finished, I got a session replay with the diff and the token cost attached.

I wasn't tethered to a laptop. I wasn't SSH'd into anything. I was running a coding agent the way you'd message a colleague — and that workflow is the whole reason the tool exists. I call it Sarathi: the charioteer. The one who steers while someone else does the fighting.

SarathiBot on the phone: the /task dispatch queues, runs the session with Peek / End / Respond / Hand Off controls, then reports the completed diff and a 64-tests-0-failures result — all inline in the chat.


What it actually is

Sarathi is a Telegram bot on top of the Claude Code CLI.

From necessity to luxury — the phone never stopped being my IDE: from typing HTML in ES File Explorer on a Micromax Canvas 2, to orchestrating a parallel fleet of agents via @SarathiBot on an iPhone 15. Same energy, different era.

That's the entire user-facing surface: a chat. From it I dispatch headless or interactive coding tasks, run several agents in parallel in isolated git worktrees, get pinged when one needs input, answer inline, and replay any session afterward with its cost attached.

A headless run, archived as a self-contained HTML session replay — the task, the final response, and the run's cost ($0.32), duration (181.6s), and outcome captured at the top.

The decision that matters is what's underneath:

The control plane is just shell scripts, SQLite, and tmux. No cloud. No containers. No LLM tokens wasted on orchestration. The model only runs where it matters: writing and reviewing code.

That last sentence is the thesis of the whole project. I'll come back to it. First the boring part — because the boring part is deliberate.


The control plane is boring on purpose

There is no framework here. A Telegram bot (Python, long-polling) takes a message and hands it to shell scripts. The shell layer owns session lifecycle, git worktrees, and health checks. Claude Code does the actual coding — headless via claude -p, or interactively inside a tmux session you can attach to. State lives in SQLite, WAL mode: agent_sessions, worker_runs, brain_memory.

A monitor daemon polls every session every 15 seconds. When an agent hits a permission prompt, the daemon clears the safe ones and escalates the real questions to Telegram. When you reply, the answer flows back into the session. Parallel agents launch staggered to stay under rate limits, each in its own git worktree so they can't step on each other's files. You define the roles — backend, QA, docs — and they run side by side; results flow back cleanly when they finish. The loop for any one task is implement → review → fix, and it keeps looping until the reviewer approves or escalates to a human.

None of this needs a cloud. None of it needs a container. And none of it spends model tokens.

Here's the number that convinced me the architecture was right: a five-task parallel run spends about 6,500 tokens on orchestration. If I'd let an LLM coordinate the same run — deciding what to dispatch, tracking what finished, routing results — it would have been 50,000–100,000. The metadata you need to route a task — its role, its description, its project context — is already sitting in the session record. You don't need a language model to read a database row.

Sarathi architecture — iPhone · Telegram (@SarathiBot) → a shell orchestration layer (session lifecycle, project dispatch, task routing) → parallel agents in isolated git worktrees (backend / QA / docs), a headless agent (claude -p), and the implement → review → fix chain; everything lands in SQLite (WAL mode), and a monitor daemon handles health and prompts every 15s. Bash · SQLite · tmux · Telegram — no cloud, no containers, one MacBook.


The brain: memory, planning, routing

A dispatcher that forgets everything between tasks is a toy. The next thing Sarathi needed was a brain — three parts.

Memory. Per-project, stored in SQLite with FTS5 full-text search: architecture decisions, naming conventions, constraints. Before a task dispatches, the brain pulls the relevant context and injects it into the prompt. The effect is mundane and enormous: by the third session on a codebase, you stop explaining yourself. The agent already knows the conventions because the last three agents wrote them down.

Memory — the first component. A three-second voice memo ("add rate limiting to the auth endpoints") comes in; the bot already knows the stack, the conventions, and the constraints, because it pulled per-project context from SQLite + FTS5 before running. /remember and /forget manage what it keeps.

Planning. Vague intent goes in; an ordered list of subtasks with explicit dependencies comes out. But the plan surfaces to me before anything runs — /plan with an approval keyboard. Approve, edit, or cancel. The rule I held to: the system stays autonomous in operation but never autonomous in decision. It can run a ten-step plan unattended. It cannot decide on its own that the plan was a good idea.

Planning — the second component. /plan decomposes a vague goal into ordered, dependency-aware subtasks and surfaces them behind an Approve / Edit / Cancel keyboard before a single step launches as a tmux session. The operator sees the plan first, every time.

Routing. This is the part I'm proudest of. The brain itself — planning, classification, reflection — runs entirely on free models: Ollama locally (gemma3:12b) for the fast paths, OpenRouter's free tier (qwen3-coder:free) for the heavier reasoning. Paid models only run inside the actual code generation. That's not a cost hack. It's an architectural commitment: orchestration overhead should never scale with model spend. The cheap work stays cheap no matter how expensive the frontier gets.

Voice input lands here too — local faster-whisper, no API — so a dispatch can be something I say instead of type.

Routing — the third component. The brain (plan · memory · reflect · recover) runs on free models (Ollama · OpenRouter); a deterministic shell handles session lifecycle and health with no LLM tokens; paid models run only inside the CLI workers — Claude Code today, with Gemini, CodeRabbit, and Figma CLIs next — where they earn it. State lives in SQLite: sessions, worktrees, brain_memory (FTS5).


Two interfaces, one source of truth

The chat is good for command and notification. It is bad for watching five sessions at once. So the latest piece is a second interface: a terminal TUI, built with Textual.

The TUI is a fleet view. Every session across every project, live output streaming with scroll lock, split-pane deep dives into any one of them, and cost, token, and performance metrics tracked per project and per worker. Both interfaces read and write the same SQLite database. The bot is for command and notification. The TUI is for visibility and analysis. Neither tries to be both — and because the database is the contract between them, neither has to know the other exists.

The fleet at a glance in the terminal — every running session with its project and role (hermes-session.sh list), the live monitor tailing fleet health and pending alerts, and the completed-run log streaming alongside.

(In the screenshots, hermes is Sarathi's internal session engine — the shell layer the Telegram bot drives.)


CLI-agnostic by design

Claude Code is the default worker. It is not the only possible one.

Every task is tagged with a worker type and a priority. The architecture treats Claude Code as one implementation of a worker contract — and anything that implements that contract can be a worker: Gemini CLI, CodeRabbit CLI, Figma CLI, a custom shell tool. The brain decides which worker fits which step.

Two things are explicitly still ahead, not done: a public worker registry — the contract documented so others can plug in their own tools — and priority-based model routing inside the workers, P0 tasks to stronger models, P2 to lighter ones. The contract is in place; the routing logic is being tuned. I'm saying that plainly because the gap between "designed for" and "shipped" is exactly where most tooling write-ups quietly lie.


The model isn't the moat

Here's the thesis I've been building toward:

Agentic systems aren't going to be defined by their models. The frontier models will commoditize faster than people expect. The defensible layer is the orchestration — memory that compounds, planning that respects user agency, routing that respects compute economics.

Every architectural choice in Sarathi falls out of that one belief. The control plane is model-free because orchestration shouldn't pay model prices. The brain runs on free models because the coordination layer shouldn't get more expensive as the workers do. The worker interface is a contract, not a hard dependency on one vendor, because the worker is the commodity. Memory compounds because that's the part that actually improves with use — the model won't; it'll just get swapped for a better one next quarter.

If that's right, the interesting work over the next few years isn't picking the model. It's building the layer that makes a fleet of interchangeable models actually useful — and keeping the human in the decisions that matter.


What to expect from a personal tool

This is a tool I built for myself. It runs on my laptop. There's no install command, it isn't on npm, and there's no support promise — I'm not the right person to ask for an SLA on a side project.

I'm writing about it because the architecture is the part worth sharing, not because it's a product. If it becomes one, that's a different post.

It runs on a single machine, no cloud and no containers, which means the cost of it sitting idle for a month is zero. That's by design too.

And yes — the demo I recorded for this was orchestrated with Sarathi.