DANIEL ARIAS // STARQUIXOTE

CLU — a personal coordinator that lives in Telegram

Hand-drawn blueprint of CLU: construction phases, message flow, and the three shared memory channels
// The question

Can I have a personal coordinator that lives in Telegram and keeps its memory in tools I already own — Google Calendar and Drive — instead of a database I'd have to provision, secure and back up forever?

// What I learned

The AI call was never the hard part. The hard part is durable memory without new storage, and the answer was to stop thinking of memory as a database and start thinking of it as a shared environment: CLU writes to Calendar and Drive, and my desktop tooling reads the same places. No file transfer, no sync layer. Also learned the unglamorous way that what you documented and what you deployed drift apart silently — and only a written audit catches it.

// Why it stopped here

Not stopped — running, phases 0 through 6, for about a dollar or three a month. What's deliberately deferred is the model swap: it runs on Gemini today, and moving it is a single-file job whenever there's a reason to.

CLU is a small agent you talk to inside Telegram. Behind the chat window sits a Cloudflare Worker — code that runs on demand at the network edge, with no server for me to keep alive — wired to Google Calendar and Google Drive.

The personality is deliberate: cold, terse, efficient. A Tron-flavoured operator, not a chirpy assistant. It executes, confirms, reports, in one to three lines. That tone is a design decision — a coordinator should feel like a control surface, not a friend.

The constraint that shaped everything

Most "build an assistant" tutorials reach for a database. I didn't want one. A database is a thing you provision, secure, and back up forever, and I'd be maintaining it long after the novelty wore off.

Use the tools I already live in as the memory.

Diagram of three memory channels: Google Calendar and Google Drive shared between CLU and desktop tooling, plus Cloudflare KV private to CLU
// Synchronisation by observation. CLU writes events to Calendar and notes to Drive. My desktop tooling reads the same two places. Neither one knows the other exists — the shared environment is the sync layer. A third channel, Cloudflare KV, stays private to CLU for short-term chat state.

That's the part I'd repeat on any future project. A thought dictated into my phone becomes context on my desktop with no file transfer, no export step, and no integration between the two agents — because they aren't integrated. They just observe the same environment.

Three principles it's built on

Three core principles: serverless 24/7, secrets outside code, one-time OAuth
// The serverless trinity. Nothing runs on my hardware. No key is ever committed to the repository — they're injected as edge secrets. And a single one-time authorisation produces a permanent refresh token, so the thing never asks me to log in again.

How a message actually travels

Seven-step message lifecycle from Telegram ingress to state update
// Seven steps, start to finish. Telegram forwards a webhook; the Worker checks the message came from an allowed chat and drops anything else on the spot; it pulls recent history from KV, assembles the payload, runs the model, replies, and appends the turn back to KV — capped at twelve turns with a seven-day expiry.

The validation step is worth pausing on. A public webhook endpoint is exactly that — public. Without an allow-list check as the very first thing the Worker does, anyone who found the URL would be talking to my calendar.

The loop that makes it an agent rather than a chatbot

Tool-use loop: model decides whether a tool is needed, worker executes it, result feeds back
// Tool use, with brakes. The model decides whether a request needs a tool. If yes, the Worker runs the actual Calendar or Drive call and feeds the result back — repeating up to five turns, a failsafe against infinite looping. Temperature sits at 0.3 for cold, deterministic behaviour, and the reply is capped at 800 tokens to enforce the terse reporting style.

Those three numbers are the personality, as much as the prompt is. A high temperature and an uncapped response would produce a chatty assistant no matter what the instructions said.

What the code is

Isometric map of the codebase: orchestrator, brain interface, tool dispatcher, persona, config and auth script
// Six files, one job each. An orchestrator handling routes and the tool loop; a brain interface that is the only place the model API is touched; a tool dispatcher owning OAuth and the Google APIs; a persona file holding the identity; the Cloudflare config; and a one-off local script to capture the initial OAuth consent.

The audit that caught me out

Here's the part I didn't plan to write, and the reason this entry got rewritten.

Documenting the system properly turned up a gap between what I'd written down and what was actually deployed — and this entry had been repeating the wrong version publicly.

Architecture matrix comparing documented intent against deployed reality, showing the brain is Gemini rather than the documented Claude
// Documented intent vs deployed reality. My design docs said the brain was one model. The running Worker calls a different one. Neither the docs nor this entry had noticed.

CLU runs on Gemini 2.5 Flash. My architecture notes — and the earlier version of this post — said Anthropic's Claude, because that was the plan when I wrote them. The code went one way, the documentation stayed where it was, and nothing forced the two to meet.

I only caught it because building this technical reference meant checking every claim against the source. The fix took one line; finding it took an audit.

Two things I take from that:

  • Documentation rots silently. It doesn't throw an error. It just quietly becomes fiction, and then you publish it.
  • The isolation saved me. Because every model call lives in exactly one file, this is a one-file swap rather than an archaeology project. That wasn't foresight about this problem, but keeping the external dependency behind a single boundary paid for itself anyway.

How it got built, and what it costs

Build progression across phases zero to six, marking which steps were manual configuration and which were generated code
// Phases 0–6. Each phase marks what I configured by hand — accounts, credentials, OAuth consent — against what was generated as code. The split matters: the manual steps are the ones nobody can do for you, and they're most of the friction.

Total running cost is roughly one to three dollars a month. That's the whole argument for this shape of project. There's no server sitting idle, no database on a monthly plan, no platform subscription — the Worker only costs anything in the instants it's actually running, which for a personal coordinator is a few seconds a day.

Operating it

Operating procedures: deploy, tail logs, local testing, token regeneration, and three diagnostic routes
// Standard procedures. Deploy, live log tailing, local testing, and token regeneration — plus three diagnostic routes: a health check, one that verifies every required key is present and that Telegram is reachable, and one that re-points the webhook if it ever drifts.

The diagnostic routes are there because of how this fails. When something breaks in a serverless agent, the symptom is almost always the same — nothing happens — and without a route that tells you which credential is missing, you're guessing.

Why it's in the log

This is exactly the kind of thing I build to "it works" and then keep using. It's been running quietly since, coordinating actual work, for the price of a coffee a quarter.

And it earned its place twice: once as a working tool, and once as the project that taught me documentation drifts from reality unless something makes them meet.

← Back to log