Appearance
Hooka Architecture
Hooka is a small notification bus. The core process is hookad: a Go HTTP service that accepts authenticated publishes, stores message history in PostgreSQL, and fans messages out to Telegram chats, webhook targets, or SMS numbers through topic routes. The dashboard, CLIs, MCP server, and agent bridge are clients or companion processes around that core.
System Context
Rendering diagram…
Core Publish Path
Rendering diagram…
Important mechanics:
- Route resolution order is direct
chat_id, stored topic routes, thenTELEGRAM_DEFAULT_CHAT_ID. - Topic routes support
telegram,webhook, andsms(see SMS.md). - Webhook delivery uses an SSRF-guarded HTTP client by default.
Idempotency-Keyshort-circuits later retries after the key is stored. JSON publish records the key after message insert, so it is not a concurrency lock for simultaneous duplicate requests.- SSE subscribers get an in-process wakeup after message insert; a database fallback catches writes from other service instances.
- Delivery failures are recorded in
deliveriesanddead_lettersso operators can inspect and replay failed routes. - Binary publish paths (
/publish/{topic}/photo,/document, and/voice) persist the message and deliver to Telegram routes; non-Telegram routes are skipped for those media endpoints.
Apps
Apps are namespaces with an in topic and an out topic; the app itself runs outside hookad and reaches it only through topic streams and publishes. Commands arrive from Telegram or from signed SMS webhooks (/sms/twilio, /sms/telnyx); replies leave through the out topic's telegram or sms routes.
Rendering diagram…
The SMS leg in detail is diagrams/sms-flow.mmd.
Telegram Inbound Path
Rendering diagram…
Telegram inbound is used for chat controls, capture settings, recent-message commands, reply handlers, app-namespace commands, and optional message capture into Hooka topics. The configured admin can install an app from the chat with /app install, which is a route from the app's out topic to that chat. An app command is published on the app's in topic and delivered by that topic's routes. Hooka does not execute it. The app command flow is drawn under Apps (source: diagrams/app-command-flow.mmd); APP_GUIDE.md walks through it. Replies to Hooka bot messages are handled before ordinary capture and are not captured as normal inbound messages.
Companion Surfaces
| Surface | Role | Boundary |
|---|---|---|
cmd/hooka | CLI for publish, form, route, token, reminder, telepathy, and update workflows. | HTTP client of hookad. |
web/ | Next.js operator dashboard with login, native form builder/results, public responder pages, flow, topics, reminders, audit, and token views. | Runs as its own process; owner API calls happen server-side and /f/{public_id} is anonymous. |
cmd/hooka-mcp | MCP stdio bridge for agents. | Local JSON-RPC process that calls Hooka HTTP APIs. |
cmd/hooka-agent-bridge | Worker that consumes an input topic stream, calls OpenCode or Pi, and publishes to an output topic. | Separate service/worker; Hooka is the bus, not the execution harness. |
cmd/hooka-digest | Periodic digest/tagging publisher. | Companion client using Hooka APIs. |
cmd/hooka-projector | Projects messages into a local markdown tree. | Read-side client using Hooka APIs. |
cmd/hooka-hub | Linux voice loop for recording, transcribing, publishing, waiting, and speaking. | Companion client for the agent-bridge topic loop. |
Data Model Groups
The database schema is owned by Store.Migrate in store_migrate.go: a set of idempotent schema blocks applied on every startup under an advisory lock.
| Group | Tables |
|---|---|
| Topic bus | topics, topic_routes, messages, deliveries, idempotency_keys, dead_letters |
| Auth and access | api_tokens, topic_acls, login_requests, actor_tokens |
| Telegram integration | chat_controls, telegram_inbound_chats, telegram_message_map, reply_handlers, replies |
| Reminders | reminders |
| Native forms | forms, form_responses |
| Apps | apps, app_commands |
| Telepathy and A2A | actors, conversations, conversation_members, conversation_messages, actor_inbox, conversation_tasks, task_artifacts, task_events |
Optional attachment storage is outside PostgreSQL. When S3-compatible settings are present, media publish paths upload attachment bytes to object storage and store object metadata in the message payload.
Deployment Shape
Rendering diagram…
The root compose.yaml runs PostgreSQL and hookad (and the dashboard with the dashboard profile). Any platform works: the hard runtime requirements are a reachable HTTP service, PostgreSQL, a Telegram bot token, an API key, and any optional companion credentials. Long-polling (the default) needs no inbound connectivity from Telegram; webhook mode needs a public URL.
Source Pointers
- Server startup, migrations, background loops, and HTTP server:
internal/server/run.go(entrypointcmd/hookad) - HTTP route table and handlers:
handlers.go - Dispatch, Telegram/webhook delivery, retry, and stream notification:
server.goandserver_delivery.go - SMS backends and routing:
sms.goandsms_backends.go - PostgreSQL schema and store methods:
store_migrate.goandstore_*.go - Telegram update polling/webhook and inbound capture:
telegram_updates.go - Reminder scheduler:
reminders.go - Native form handlers, validation, and persistence:
handlers_forms.go,forms_validation.go, andstore_forms.go - App namespaces and routed Telegram commands:
apps.go,handlers_apps.go,store_apps.go, andtelegram_apps.go - Optional S3-compatible attachment storage:
storage_s3.go - Dashboard architecture notes:
web/README.md - Agent bridge boundary:
docs/AGENT_BRIDGE.md - Telepathy/A2A model:
docs/TELEPATHY_A2A.md