--- title: Telegram Orchestrator url: https://blog.guigpap.com/en/workflows/telegram-orchestrator/ url_md: https://blog.guigpap.com/en/workflows/telegram-orchestrator.md category: tooling date: '2026-01-20' maturite: production techno: - n8n - telegram - claude application: - automation - operations --- # Telegram Orchestrator > Central Telegram bot, 124-node routing hub orchestrating 13 callback sub-workflows and 4 service handlers ## 1. What? — Definition and context The **Telegram Orchestrator** is the N8N workflow that acts as the entry point for every human interaction with the VPS infrastructure. It receives every message and every Telegram callback, checks authentication, routes to the right handler, and delegates long-running tasks to 13 callback sub-workflows + 4 extracted service handlers. > **Note - Fire-and-forget vs wait-for-result** > > The callback sub-workflows (SW-1 through SW-13) are **fire-and-forget**: the orchestrator triggers them, does not read their return value, and stays free to handle other messages. The service handlers (Docker, Odoo, N8N, General) are **wait-for-result**: the orchestrator waits for the response to format it and send it back to the user. ### Top-level components | Component | Role | |-----------|------| | **Telegram Bot** | User-facing surface (messages, inline buttons, ForceReply) | | **AI Router** | Intent detection via Claude (5 services: docker, n8n, odoo, content, general) | | **Command Router** | Parsing of `/menu`, `/docker`, `/n8n`, `/odoo`, `/help`, `/notif`, `/todo`, `/triage`, `/projet`, `/note`, `/blog`, `/research`, `/new`, `/conv`, `/endconv`, `/model`, `/plan`, `/templates`, `/mcp` | | **13 callback sub-workflows** | Auth, menus, n8n actions, photos, conversations, file provider, etc. | | **4 service handlers** | Docker, Odoo, N8N, General (Claude chat) | | **Binary Content Handler** | Photos (via Vision OCR), voice notes, vCard, PDF, videos | | **Conversation Agent + Plan Engine** | Multi-turn conversations and action plans | ### Architecture diagram ```mermaid flowchart TD Message["Telegram · message or callback"] subgraph Orchestrator["Telegram Orchestrator · 124 nodes"] direction TB ExtractUser["Extract User ID"] CheckBan["Check User Banned?"] RouteUpdate["Route by Update Type"] Parse["Parse Callback / Message Type"] RouteService["Route by Service"] end subgraph CB["Callback sub-workflows · fire-and-forget"] direction TB SW1["SW-1 File Provider · 20n"] SW2["SW-2 Docker Response Sender · 7n"] SW3["SW-3 New User Notifier · 7n"] SW4["SW-4 Auth Callback · 26n"] SW5["SW-5 N8N Action Response · 21n"] SW7["SW-7a Menu Renderer · 12n"] SW8["SW-8 Free Text Handler · 10n"] SW9["SW-9 Notif Mode Handler · 10n"] SW10["SW-10 Content Action · 10n"] SW11["SW-11 Conv Callback · 87n"] SW12["SW-12 Odoo Project Picker · 10n"] SW13["SW-13 Photo Action · 19n"] end subgraph SH["Service handlers · wait-for-result"] direction TB SHDocker["Service Handler Docker"] SHOdoo["Service Handler Odoo · 33n"] SHN8N["Service Handler N8N · 6n"] SHN8NEx["SH-N8N Action Executor · 37n"] SHGeneral["Service Handler General · 10n"] end subgraph Conv["Conversation system"] direction TB ConvMgr["Conversation Manager"] ConvAgent["Conversation Agent"] Plan["Plan Engine"] end Binary["Binary Content Handler · 21n"] Vision["Vision OCR · 14n"] Message --> ExtractUser --> CheckBan --> RouteUpdate RouteUpdate --> Parse --> RouteService Parse --> SW1 Parse --> SW4 Parse --> SW5 Parse --> SW9 Parse --> SW10 Parse --> SW11 Parse --> SW13 RouteService --> SW7 RouteService --> SW8 RouteService --> SW12 RouteService --> SHDocker RouteService --> SHOdoo RouteService --> SHN8N --> SHN8NEx RouteService --> SHGeneral RouteService --> Binary --> Vision RouteService --> ConvAgent ConvAgent --> Plan ConvMgr --> ConvAgent SHDocker --> SW2 RouteUpdate --> SW3 ``` ### The 21 commands | Category | Commands | |----------|----------| | **Menu** | `/menu`, `/start`, `/docker`, `/n8n`, `/odoo`, `/help`, `/notif`, `/todo`, `/triage` | | **Content** | `/note`, `/blog`, `/research` | | **Conversation** | `/new`, `/conv`, `/endconv`, `/model`, `/plan`, `/templates`, `/mcp` | | **Project** | `/projet` (sub-commands `list`, `create`, `status` with paginated picker) | --- ## 2. Why? — Stakes and motivations ### Problems solved | Problem | Without orchestrator | With orchestrator | |---------|----------------------|-------------------| | **Remote SSH access** | SSH connection for every action | Telegram message from mobile | | **Complex commands** | Memorise Docker commands | Natural language + menus | | **Security** | Exposed SSH | Authenticated bot + whitelist | | **Reactivity** | No problem notifications | Alerts + immediate actions | ### Why a sub-workflow refactoring (#273-#294)? The initial orchestrator had grown to **224 nodes**, to the point of slowing the N8N UI and making editing risky (moving a node could break off-screen connections). Three refactoring waves brought the parent down to 124 nodes by extracting fire-and-forget paths: | Wave | Issue | Effect | |------|-------|--------| | **Phase 1** | #273 | 224 → 142 nodes (5 SWs) | | **Phase 2** | #273 | 142 → 98 nodes (5 additional SWs) | | **Conv Mgr** | #278 | 47→21 + 32 (Conversation Command Handlers) | | **SH-N8N** | #279 | Service Handler N8N 40→6 + SH-N8N Action Executor 37 | | **Photo routing** | #176 | +9 nodes for photo conv-routing + SW-13 | | **MCP menu** | #294 | Conv Agent 16→21 + SW-11 72→87 | Each SW has a strict contract: passthrough or typed input, return value ignored by the parent (for fire-and-forget), no side effect on the main runtime. ### Key features | Feature | Description | |---------|-------------| | **Authentication** | User whitelist, admin approval, ban list | | **Intent detection** | Claude analyses natural language (5 services) | | **Interactive menus** | Inline buttons for frequent actions | | **Multi-service** | Docker, N8N, Odoo, Content from a single interface | | **Voice + Photo** | Audio transcription + image extraction | | **Conversations** | Multi-turn with Redis memory and tool calls | | **Action plans** | Multi-step with hybrid escalation | | **MCP** | Native n8n tools enabled per conversation | --- ## 3. How? — Technical implementation ### Authentication | Step | Action | |------|--------| | 1. Extract user ID | Pull `userId`, `chatId`, `firstName` from the message or callback | | 2. Check banned | Lookup Data Table `Telegram Banned Users` | | 3. Check authorised | Lookup Data Table `Telegram Authorized users` | | 4. Pending approval | If not authorised: Data Table `Telegram Pending Approvals` + admin alert | ```mermaid flowchart TD Msg["Message received"] IsBanned["Check Banned"] IsAuth["Check Authorized"] Process["Process request"] Pending["Insert Pending Approval"] Alert["Alert Admin · SW-3"] Approve["Callback approve / deny"] AuthSW["SW-4 Auth Callback Handler · 26n"] Msg --> IsBanned IsBanned -->|Yes| Ignore["Ignore"] IsBanned -->|No| IsAuth IsAuth -->|Authorized| Process IsAuth -->|Unauthorized| Pending --> Alert Alert --> Approve --> AuthSW ``` ### AI Router (Claude) When a text message is not an exact command and no conversation is active, the AI Router detects intent via Claude (cli-ollama): ```javascript const prompt = `You are an intent detection system for a Telegram assistant. Return ONLY valid JSON (no markdown, no backticks). Format: { "intent": "", "service": "", "action": "", "params": {}, "confidence": <0.0-1.0> } Services: - odoo: invoices, quotes, contacts, customers, projects, CRM - n8n: workflows, automations, executions - docker: containers (restart, status, logs) - general: conversation, generic questions User message: ${text}`; ``` | Confidence | Behaviour | |------------|-----------| | ≥ 0.7 | Routes directly to the service | | 0.5 - 0.7 | Asks confirmation (buttons) | | < 0.5 | Routes to `general` | Fallback chain: `claude-sonnet-yolo` (priority) → `claude-haiku-yolo` (if quota exceeded) → keyword routing (if API down). ### Service Handlers Four specialised service handlers, each with a narrow responsibility: #### Service Handler Docker | Action | Command | Protection | |--------|---------|------------| | `status` | `docker compose ps --format json` | All | | `logs` | `docker compose logs --tail 100` | All | | `restart` | `docker compose restart` | Except security-stack | | `update` | `docker compose pull && up -d` | Except security-stack | > **Danger - security-stack protection** > > The security-stack (Caddy + CrowdSec) is protected against destructive actions. An accidental restart would cut access to every service. #### Service Handler N8N (refactoring #279) The SH-N8N parent only handles the admin check and delegation. All N8N execution logic lives in **SH-N8N Action Executor** (37 nodes). | Action | Description | |--------|-------------| | `list_workflows` | List workflows with pagination | | `toggle_workflow` | Enable / disable a workflow | | `list_executions` | Recent executions (filters) | | `execute_trigger` | Trigger a whitelisted workflow | Trigger whitelist: only workflows present in `n8n_trigger_whitelist` can be triggered manually (rate limit: 1/min/workflow). #### Service Handler Odoo (refactoring #187) 33 nodes after the `/projet` extension. Covers contacts, invoices, quotes, CRM, projects. The `Odoo Project Picker` sub-workflow (SW-12) handles project pagination when the list overflows the inline keyboard. #### Service Handler General 10 nodes for `/help` and the generic Claude chat (open-ended questions outside any specific service). ### Binary Content Handler The Binary Content Handler (21 nodes) handles media: | Type | Pipeline | |------|----------| | **Photo** | Vision OCR (sub-workflow 14n, classification + structured extraction) | | **Voice** | Voice Transcription (Groq ≤30s, ElevenLabs >30s) | | **vCard** | Ingress Contacts → Odoo `res.partner` | | **PDF / video** | Metadata extraction + summary | Vision OCR returns a `{status, docType, extracted, text}` contract. Depending on `docType`: | docType | Routing | |---------|---------| | `business_card` | Odoo contact creation via Ingress Contacts (fire-and-forget) | | `invoice` / `screenshot` / `handwritten` / `general_document` | Display + `[Discuss]` button that starts a conversation | | `not_document` | Switch to AI Router for conversational handling | | `error` | Error notification via Notification Hub | ### Callback convention ``` # Navigation menu_
→ Menu (main, docker, n8n, odoo, help, notif, todo) # Docker menu_docker_stacks_ → Stack list for action docker__ → Run action on stack # N8N (admin) menu_n8n_workflows → Workflow list n8n_wf_ → Toggle workflow n8n_run_ → Trigger workflow # Conversations conv_switch_ → Change active conversation conv_delete_ → Archive plan_exec_ → Execute a plan plan_modify_ → Edit a plan plan_cancel_ → Cancel a plan # User management approve_ → Approve user (admin) deny_ → Deny user (admin) # Photos (#176) photo_discuss_ → Start a conversation about the photo photo_dismiss_ → Ignore the photo # Notifications hub notif__ → approve / retry / details / ignore ``` > **Caution - Parse Callback condition order** > > Parse Callback conditions must go from most specific to most general. A `startsWith('menu_')` condition placed before `startsWith('menu_n8n')` would capture every N8N route. Always test long prefixes before short ones. ### Best practice: `__rl` resource locator Execute Workflow nodes MUST use the `__rl` format for `workflowId`, otherwise they fail silently at runtime: ```json { "workflowId": { "__rl": true, "value": "", "mode": "id" }, "options": {} } ``` The flat format `{"mode": "id", "workflowId": ""}` produces "No information about the workflow to execute found" without surfacing the error. --- ## 4. What if? — Outlook and limits ### Current limits | Limit | Impact | Mitigation | |-------|--------|------------| | **Claude latency** | 2-5s for intent detection | Cache for frequent intents to design | | **No multi-language** | Prompts in French only | Sufficient for personal use | | **Telegram rate limiting** | 30 messages/second max | Never reached in normal use | | **Non-document photos = 2 LLM calls** | Detection + generic analysis | Acceptable for the conversation context | ### Evolution scenarios **If the team moves to multi-user**: - Granular per-handler permissions (currently: admin / non-admin) - Per-user audit logs of actions - Group notifications for critical actions **If functional extension is needed**: - Add a service to the Service Router Switch (new case) - Or enable an MCP server via the `/mcp` command in a conversation - The system is extensible by design — every addition is isolated in its own sub-workflow **If latency becomes critical**: - Pre-fetch frequent Data Tables in N8N memory cache - Pipeline Odoo calls (already partially done) - Migrate fire-and-forget paths to a dedicated Bull queue --- ## Related pages ### Infrastructure - [N8N Queue Mode](/en/infrastructure/n8n-queue-mode/) — Automation backend - [AI Stack](/en/infrastructure/ai-stack/) — cli-ollama for intent detection ### Workflows - [Conversational system](/en/workflows/systeme-conversationnel/) — Multi-turn conversations, plans, MCP - [Content Pipeline](/en/workflows/content-pipeline/) — Note and article capture - [Voice Transcription](/en/workflows/voice-transcription/) — Voice transcription - [Vision OCR](/en/workflows/vision-ocr/) — Photo classification + extraction - [Docker Updates](/en/workflows/docker-updates/) — DIUN approvals via callbacks - [Notification Hub](/en/workflows/notification-hub/) — Notification routing + callbacks - [Error Handler](/en/workflows/error-handler/) — Orchestrator error capture ### Reference - [Glossary](/en/reference/glossary/) — Webhook, Callback, Intent, Sub-workflow ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0