Appearance
Hooka: An Overview
What hooka can do and how its pieces compose. Companion reading: ARCHITECTURE.md for internals and INTEGRATION.md for wiring anything new.
1. What hooka is
Hooka is a notification bus with memory. One Go server (hookad) accepts messages over HTTP, stores every one of them in PostgreSQL, and fans them out to Telegram chats, webhooks, and SMS numbers. Everything else (CLI, web dashboard, MCP server, voice loop, agent bridge) is a client of that one server.
Five primitives carry the whole system:
- Message: the unit of communication: title, text, priority (1–5), tags, optional attachments and raw Telegram payload fields.
- Topic: a named channel (
ci,alerts-critical,memos). Every message belongs to one. - Route: where a topic fans out: a Telegram chat (optionally a specific forum thread), a webhook URL, or an SMS number.
- Inbound capture: the reverse direction: messages typed into a configured Telegram chat become stored topic messages, so the bus hears as well as speaks.
- App: a namespace another program registers so it can add Telegram (or SMS) commands without Hooka implementing them. It is built from the primitives above: commands are published on the app's in topic, the app publishes replies on its out topic, and installing it into a chat is a route from the out topic to that chat. The configured admin can install one from a chat with
/app install <namespace> <name>.
The design bet: everything you see should be persisted somewhere. Because every notification, memo, alert, and agent status report lands in one database, the bus doubles as a data lake that agents can query, digest, and learn from later.
2. What hooka can do
In plain text, capability by capability.
Publish anything, from anywhere. JSON publishes to a topic; direct sends to a chat; batches of up to 100 messages with bounded fan-out; documents (PDFs up to 50MB, uncompressed via Telegram sendDocument); photos; photo albums (up to 10 as one media-group message); voice notes. Callers that cannot set headers can use a scoped token in the URL path — the master key is refused there on purpose, and the attempt is logged.
Deliver reliably. Idempotency keys make retries safe — replaying a publish returns the original message_id instead of double-sending. Failed deliveries retry with exponential backoff; terminal failures land in a dead-letter table you can list and replay (one, or all). Outbound Telegram is rate-limited, and direct /send-batch calls serialize messages targeting the same chat so Telegram's per-chat limit doesn't trip. A ?dry_run=true publish validates, renders the exact Telegram HTML, and reports which routes would fire — with zero side effects.
Route flexibly. A topic can fan out to any number of Telegram chats, webhooks, and SMS numbers at once. Telegram routes can target a specific forum thread (message_thread_id), which is what makes one supergroup act as dozens of logical channels. Webhook routes carry custom headers and are SSRF-guarded by default. Raw Telegram Bot API fields pass through publishes: inline keyboards, reply parameters, link-preview control, silent sends, protected content, message effects. Agents can even trigger the "typing…" indicator.
Listen, not just speak. Any Telegram chat or forum thread can be turned into an inbox with /capture <topic> — ordinary messages (and photo metadata) typed there are stored as topic messages, tagged telegram, inbound, with sender and thread metadata in the payload. Replies to hooka-delivered notifications flow to registered reply handlers, so a notification can be a conversation, not a dead end.
Accept messages from strangers, safely. Anonymous ingest hooks (/in/{token}) are publish-only capability URLs: mint one bound to a topic, hand the URL to an agent or service you don't control, and the URL itself is the credential — per-counterparty, revocable, rate-limited, optionally TTL'd and use-capped, HTML-escaped, and every delivered message opens with an unremovable 📥 via ingest "<name>" line. Unknown and revoked tokens all answer a uniform 404, and /in/ paths are redacted from request logs. The same URL is a GitHub webhook receiver: register it on a repository or organization and pushes, PRs, issues, reviews, releases, and CI failures arrive rendered as repository activity with a curated noise policy. Put a topic digest in front of a chatty source and hooka rolls it up into one grouped message per interval instead of a message per event.
Schedule the future. Reminders are one-shot (in: 30m, fire_at) or recurring (every: 1h, 5-field cron), delivered through the normal publish pipeline so routing, retries, and dead-letters all apply. A precise in-memory timer fires on time; a 30-second poller is the durability net after restarts; multi-instance claims use FOR UPDATE SKIP LOCKED. Creation is idempotent, and every create/list/get response includes a delivery receipt an agent can quote back to you.
Control access precisely. A master API_KEY for admin; scoped tokens with publish/read flags and topic patterns (deployments*) for everything else — fail-closed, no silent defaults. Per-topic ACLs can open anonymous publish or read where that's wanted. "Log in with Hooka" is a device-code flow: a small site requests a scoped token, the operator approves it from the dashboard, and the site exchanges a secret code for the token without ever touching the master key.
Search and mine the lake. Everything stored is queryable: full-text search across all topics (/search, Postgres FTS with relevance ranking), cross-topic message reads, per-topic duplicate-cluster detection by content fingerprint, and enrichment — a writer can attach tags back onto an existing message (POST /messages/{id}), which is how the digest worker labels history in place. Topics can be archived and unarchived as projects come and go. This is the layer that turns a notification log into a data lake — with one honest caveat: retention is a rolling window (30 days by default, configurable up to ten years), so the lake is only as deep as you configure it, and the database is worth backing up.
Stream and observe. Subscribers consume Server-Sent Events per topic (/topics/{topic}/stream) with since/priority/tag filters — publishes wake streams immediately, a DB fallback catches cross-instance writes. /topics/{topic}/status reports who is connected right now, by client name and role. Prometheus metrics, /health, /ready, and an Alertmanager webhook endpoint close the ops loop. Message history supports the same filters as the stream, plus include_payload=true for raw metadata.
Audit itself. The reserved audit topic receives the control plane's own events — dashboard logins (success, failure, rate-limit), token and ingest hook lifecycle, route and reminder changes, master-key-in-URL refusals, suspected ingest scanning. Dashboard-originated events carry a truncated IP and user agent; server-emitted events carry the acting token and address. Route the topic to an admin chat and the system tells on itself in real time.
Push to iOS. Optional APNs support (including VoIP pushes) lets an iOS client you build and sign register devices, so an agent can interrupt its user when a run needs input. See private-ios-push.md.
Talk agent-to-agent. The Telepathy/A2A layer adds actors, conversations, tasks, and artifacts on top of the same store, with a JSON-RPC facade (SendMessage, GetTask, SubscribeToTask over SSE) and an agent card at /.well-known/agent-card.json (per-actor cards live under /actors/{id}/). It's honest to call this a transport and task ledger, not an agent runtime: hooka records and relays; the actors themselves run elsewhere and post their own status and artifacts. See TELEPATHY_A2A.md.
3. How it composes
The composition rule is strict and simple: hookad is the only process that owns state; every other module is a client speaking HTTP/SSE. Modules never talk to each other directly — they compose by publishing into topics and subscribing to topics. That's why surfaces can be added (or abandoned) without touching the core.
Rendering diagram…
The clients in words:
| Module | What it is | How it composes with the core |
|---|---|---|
cmd/hooka | CLI | Thin HTTP client: publish (text/media), routes, tokens, ACLs, reminders, ingest hooks, app namespaces, dead letters, telepathy; self-updates from GitHub Releases |
web/ | Next.js dashboard | Server-side HTTP client; passphrase login; Flow/Dashboard/Topics/Reminders/Tokens/Audit views |
cmd/hooka-mcp | MCP server (stdio) | Tools for publish, search, recent messages, get message, summarize window, duplicates, reminders, forms, apps, and ingest-hook minting, so any MCP-capable agent gets hooka without custom code |
cmd/hooka-agent-bridge | Long-running worker | Subscribes to an input topic via SSE, drives an agent harness (OpenCode or Pi), publishes results to an output topic |
cmd/hooka-digest | Companion worker | Searches history, classifies untagged messages with internal/classify (TypeSafe System One: per-label probabilities, a 0–3 relevance level, actionable), writes tag_labels plus the full classification blob back onto the stored messages, publishes periodic digests (Anthropic API) into a topic. Optional; both services need your own API keys |
cmd/hooka-projector | Companion worker | Projects topics into a greppable per-day markdown tree — the data lake made grep-able |
cmd/hooka-hub | Linux voice loop | Records speech, transcribes, publishes to the agent input topic, waits for the run result, speaks it |
skills/ | Claude Code / Codex skills | In-repo skills teach agents the conventions: hooka (publish, media, routes, tokens, ACLs, dead letters), hooka-form (native forms), and hooka-reminder (schedule/inspect/cancel, with the server's receipt printed in-conversation) |
The four loops
Most workflows on hooka reduce to one of four loops over the same bus:
Rendering diagram…
- Notify. Anything with an API key (or a scoped token) publishes; the topic's routes decide where it surfaces. This is most traffic.
- Capture. Telegram chats marked with
/capture <topic>feed the bus in reverse. Notes to self become queryable data the moment they're sent. - Ingest. Capability URLs let agents and services you don't control push reports into your inbox with no credentials to leak and one command to revoke.
- Remote control. Topics as full-duplex pipes: a command topic in, a result topic out, an agent bridge between them. A chat, the voice hub, or an app on a phone are just different faces of this loop.
Deployment shape
hookad runs as one container next to PostgreSQL (the root compose.yaml runs both); the dashboard is an optional separate process that keeps every hooka call server-side; companion workers run wherever convenient. Full diagram in ARCHITECTURE.md.
4. Where to go deeper
- ARCHITECTURE.md: internals: publish path, inbound path, schema groups, deployment
- INTEGRATION.md: wiring a new service, SSE contract, Alertmanager, ingest hooks
- API.md: endpoint reference; openapi.yaml: the full API contract
- TELEGRAM_BOT_QUICKSTART.md: from a bot token to a working bot
- AGENT_BRIDGE.md: the remote-control loop's worker
- TELEPATHY_A2A.md: actors, conversations, tasks, artifacts
skills/: teach your agents the conventions