The whole agency workspace answers from a phone, so a question no longer waits until somebody is back at a desk.
Three are waiting on you. Two need a reply before the sign off, one needs the brief approved.
Project | Waiting on | Days ----------+------------+----- Atlas | your reply | 2 Meridian | your reply | 4 Northwind | brief | 1
A voice note in, the answer back, and the thread that remembers it.
The situation
How this used to work
The system that runs the agency was only ever as useful as your ability to reach it, and reaching it meant opening a laptop. Everything it knew was on a machine you had to be sitting in front of.
That is a real constraint rather than an inconvenience. The questions worth asking a system that knows every client, every project and every file are the ones that occur to you between two other things, and none of those moments happen at a desk.
What we built
The system
A chat bot, in the messaging app already open on the phone, that is not a chat bot. What answers is the same full agent that runs on the machine, with the same access to the same workspace.
The single design decision everything else follows from: each thread in the chat is one persistent agent session. Starting a new thread spins up a fresh agent, and replying inside an old one continues that conversation with all of its context intact. The messaging app's own threading became the session browser, so no interface had to be built for it at all.
It takes voice notes and screenshots as well as text. A voice note is transcribed and treated as though it had been typed; a screenshot is saved and handed to the agent to open and look at.
One instance runs per workspace, and they are sealed off from one another. Each has its own credentials, its own working directory, its own automation backend and its own chat group. Nothing crosses between them.
How it works
The pipeline, step by step
One group, one person
Messages are accepted from one configured group and, within it, from the first human who speaks. Everyone else is ignored silently rather than refused. An agent with this much access needs its front door narrow, and the narrowest useful door is one person.
A pasted wall of text is one message
Everything that arrives goes into a buffer with a short timer that resets on each new message. Chat apps split a long paste into several messages, and without this the agent would answer the first fragment of a thought while the rest was still arriving.
Three fragments of one thought, answered once.
The context is loaded once
The first message in a thread loads the whole workspace context and the session identifier is written to disk. Every message after that resumes that session instead of starting over. The identifier is re-saved after every single run, because compaction can hand back a different one and missing that would quietly orphan the conversation.
Restarts do not cost the conversation
Sessions are written atomically and read under a lock, and a corrupted file degrades to an empty set rather than taking the process down. If the whole thing is restarted mid conversation, the thread carries on. And when an agent run fails outright, the broken session is deleted and the reply says so in plain words, so the next message starts clean instead of failing forever.
Everything degrades rather than breaks
Long answers are split at a sensible line break, not mid word. Very long ones are delivered as a file instead. Tables are rebuilt as aligned monospace blocks so they survive a phone. A picture that fails to send as a picture is resent as a document. Each of those is one fallback deep, and the reason is that the alternative on a phone is a wall of red text.
What it made comes with it
Files the agent produced during the run are detected and pushed into the chat behind the answer, images as images and documents as documents. Reports are rendered as proper paginated documents and charts on a fixed palette, so what arrives on the phone is finished rather than a path to something on a server.
The third one failed as a picture and went as a file instead.
What changed
The result
The workspace is reachable from wherever the question occurs, by typing, by talking, or by sending a picture of the thing you are looking at.
It runs one isolated instance per company, always on, restarting itself if it dies. Each bills its own account, so the interactive tooling is never competing with the always-on one for the same quota.
The most interesting bug in it is one nobody would guess at. Returning early from the loop that reads the agent's replies crashes the process, because closing that stream from a different task tears down its cancellation scope in the wrong place. The fix is to never break out of the loop: capture the result, let it finish, and the comment saying so is repeated at both places where somebody would be tempted.
The stack
Front end
- Telegram
- threads as sessions
- voice notes
- screenshots
Agent
- Claude Agent SDK
- per-workspace settings
- prime then resume
Transcription
- Whisper
Output
- paginated PDF reports
- matplotlib charts
- monospace tables
Runtime
- Python
- asyncio
- systemd
- restart always