Otto

Talking to Otto

Text, voice notes in both directions, inline buttons, reactions, message edits, attachments and history.

Live.

One Telegram thread carries everything. Otto answers on the surface you used: a message typed in Claude Code is answered in the terminal, never mirrored to Telegram, because you are already reading the transcript and a copy is noise.

Text

reply sends a message to an allowlisted chat. Long messages are chunked at a configurable limit, on newlines by default so a split never lands mid-sentence. Markdown is opt-in per send.

Voice, both directions

Voice notes you send arrive already transcribed by a local Whisper sidecar, and the transcript is marked so Otto reads past homophones and missing punctuation instead of asking you to repeat yourself.

reply(voice: true) answers out loud through a local Qwen3-TTS sidecar. By default the channel speaks whenever you last spoke, so a voice note gets a voice note back with no extra thought.

Spoken replies are held to a different standard than written ones: one or two sentences, no lists, no links, no code. Those go as text. A minute of audio is a minute of your time, and synthesis runs at roughly realtime, so length is latency too. When a voice note would be too long or contains something unspeakable, the send falls back to text and says why.

The full text rides along as the voice note's caption when it fits, so the message stays readable, searchable and skimmable without a second send.

Buttons

Inline buttons, maximum six, in rows of three. A tap injects a button event whose content is the tapped data.

Button data is written like trigger action text: a self-contained instruction a future session can execute with no other context. "Send the drafted reply to Marco", not "send it". This matters because a tap can arrive days later or after a restart, and the payload is trusted over anything the conversation appears to remember. Telegram caps callback data at 60 bytes, so anything larger is persisted and the button carries a key.

Prefer buttons to asking a question whenever the answer is one of a few known actions. An email draft gets send or discard. A reminder gets done, an hour, or tomorrow. A tapped send on a draft you have seen satisfies the draft-and-confirm rule: the tap is the approval.

Reactions and edits

react puts an emoji on one of your messages, from Telegram's fixed whitelist. An emoji often beats a reply.

edit_message rewrites something Otto already sent. Editing pushes no notification, which makes it the right tool for a message whose content has gone stale rather than sending a correction as a new interruption.

Attachments

Photos, documents and voice notes you send are downloaded into a local inbox with download_attachment. Outbound files ride along with reply, capped at 50MB, and photos are sent as photos rather than documents so they preview.

A food photo with no question attached gets a terse macro estimate: calories plus protein, carbs and fat as ranges, one line, formatted to punch straight into Bevel. No nutrition lecture.

History

read_history reads back the conversation from history.jsonl, which records both directions with timestamps, the message id, and an attribution stamp saying which event caused each outbound message. That stamp is what makes the proactivity report possible.

Access

access.json holds the allowlist. Exactly one chat id is permitted, and every tool asserts it before doing anything. Quiet hours, the voice policy, and chunking limits live in the same file.

Not built yet

These do not exist. The substrate each one reads is live, so every item below is a self-contained piece of work rather than a rewrite. The button copies a prompt that builds it in your own checkout.

#2Tailnet ingress + Shortcuts packmedium

Bind the webhook to a Tailscale interface with a tailnet-scoped ACL and ship signed iOS Shortcuts — Tell Otto, Save This, Log Meal, What's Up, plus geofence and Back Tap triggers; they captures from inside any app on the phone and the answer still comes back in the one place they read.

#3Wrist-complete messagessmall

A `wrist:true` mode on reply that formats a message to survive as a lock-screen or Watch preview — ≤120 chars, verb first, no links, no markdown; most proactive pings are read on the wrist and never opened.

#5Screen peeksmall

A `screen_peek` tool that screencaptures the frontmost window, downscales, and reads it with vision so "what does this error say" is answerable from Telegram, with a [send me the shot] button; the brief knows the app's name, this knows what's in it.

#6Spotlight file fetchsmall

A `mac_find` tool wrapping `mdfind` with ranking over filename, kind and mtime so "send me the Brazilian tax PDF from March" comes back as an actual attachment; their disk becomes reachable from a queue in a bank in São Paulo.

#10Otto bridge in every Claude Code sessionmedium

A globally-installed skill exposing tell_otto/ask_otto plus a Stop hook, so any session in any project can hand Otto a loop, read the brief, or report that a 40-minute build finished green; durable memory is already shared across sessions, this adds the action path.

#13Watched-folder intakelarge

An fswatch sidecar over ~/Downloads, ~/Desktop and an iCloud-synced ~/Otto Inbox that waits for files to settle, OCRs images and PDFs, transcribes voice memos, translates IT/PT, renames to a date-sender-subject convention and files them — only genuinely actionable ones reach Telegram, as a [remind me] or [block it] button.

#206Burn this messagesmall

A [burn] reaction that deletes the Telegram message on both sides, scrubs its text from history.jsonl and tombstones it so nightly consolidation can never absorb it into durable markdown; today anything they pastes is permanent and gets distilled at 04:00 into files every session reads.

On this page