Oshun Platform · Guides & deep dives

Telegram Bot, Commands, and Conversation

At startup/deployment, command-surface registration makes capabilities visible:

11sections5 minread1table

On this page

The Lilith bot turns Telegram updates into policy-checked responses and real effects. The supported live inbound path is the Oshun BFF webhook; the shared Telegram library contains the pure handler, command registry, delivery translation, keyboards, limits, and provider seams.

Entry and discoverability#

At startup/deployment, command-surface registration makes capabilities visible:

  • setMyCommands publishes the full member list to private chats;
  • a reduced list is published to group chats;
  • setChatMenuButton opens Open Lilith as a Web App when a Mini App URL is configured, otherwise it opens the command list;
  • t.me/<bot>?start=<payload> links map a closed payload vocabulary to rooms or the greeting.

Registration failures are reported but do not stop message handling. Operations must still alert: a bot that answers only commands users cannot discover is a functional outage.

Registered private-chat commands#

The canonical menu in deep-links.ts registers:

Command Member job
/sit Start a paced Tara ritual in chat.
/checkin Open the member's Arete check-in.
/tonight Show the Nyx sky window for the member's location/time.
/passage Read the current Nisaba passage.
/ask Ask Sophia for a grounded answer or an honest abstention.
/remind Create a reminder from a supported time expression.
/continue Resume the member's latest supported activity.
/library List the member's open items.
/streak Show streak/recovery state.
/save Capture a note or link through the effects port.
/timezone Set the member's IANA time zone or supported offset.
/settings Show quiet hours, voice, delivery, and clock settings.
/upgrade Begin the configured upgrade/payment path.
/help Explain the available command surface.

The handler also recognizes supporting controls and compatibility commands: /start, /menu, legacy /today, /sources, /quiet, /stop, /resume, /voice, and /unlink. Some are contextual/help actions rather than menu entries. A command being recognized does not mean its effect adapter or product room is configured; unavailable paths say so.

Group-chat boundary#

Personal commands are not allowed to reveal or mutate one member's state in a group/supergroup. The runtime—not only the menu—enforces the private-only set, including sitting/check-in/tonight/library/continue/streak/settings/reminders/ save/preferences/timezone/today/upgrade controls.

/passage and /ask can be shared because their content belongs to the shared passage or question, not a member profile. Buttons that need a Telegram Web App are private-chat-only; group messages use a safe deep-link fallback when a known mapping exists. Channel posts are not bot conversations.

Commands addressed as /command@botusername are normalized for Telegram group behavior. Another bot's addressed command must not be claimed. Edited messages and non-message update kinds are classified deliberately so a write is not silently repeated.

Update kinds#

The handler classifies text messages, voice messages, callback queries, inline queries, pre-checkout queries, successful payments, membership changes, and other supported Telegram update shapes. Each kind has an explicit claim/dedupe and response/delivery path. Unsupported or irrelevant updates produce no effect rather than a misleading reply.

Chat/member updates detect blocked/stopped state without trying to message a user who just blocked the bot. The delivery layer also recognizes Telegram's blocked-user descriptions and transitions delivery state accordingly.

Command execution model#

flowchart LR U[Verified, claimed update] --> K[Classify chat/update/command] K --> G[Scope, rate, safety, delivery-state guards] G --> H[Pure handler + room/grounder/effect ports] H --> R[BotResponse list] R --> P[Bot API call plan] P --> D[Deliver and inspect Telegram result] D --> A[Receipt/state/audit]

The handler separates reads and writes through ports. A room read can return ready, unavailable, forbidden, or missing-location/context states. A write can return applied, duplicate/idempotent, denied, invalid, unavailable, or failed. Copy reflects the outcome; no port means “not connected,” never “saved.”

Text and rendering#

Responses preserve disclosure and provenance for grounded content, escape or choose parse behavior deliberately, build supported inline keyboards, and degrade rich content only where the simpler form retains meaning.

Telegram text is bounded. Long output is split on safe boundaries into multiple calls rather than sent as one rejected payload. Callback-answer text, callback data, inline-result ids/counts, and start payloads have their own byte/length limits. Tests pin these limits and multibyte behavior.

Keyboards and callbacks#

Inline buttons can encode namespaced callback data, URLs/deep links, and Web App buttons. Callback data is parsed as a closed structure and bounded by byte length. Unknown, expired, malformed, unauthorized, or duplicate callbacks are answered safely and do not invoke arbitrary actions.

Callbacks support contextual save, reminder delivery/snooze/done, ritual cues, room actions, and upgrade/payment affordances where configured. The handler answers the callback query so Telegram removes the loading indicator, then executes or reports the actual effect.

Presence and media delivery#

The runtime can emit typing, record_voice, or upload_voice chat actions for update/response work. Presence is best effort; failure never becomes proof the underlying command failed or succeeded.

Delivery translates a response into one or more Bot API calls for text and declared media/file/voice forms, resolves upload/download data where required, and records each outcome. A null/invalid response does not create a provider call. Partial multi-message delivery is reported as partial, not success.

Rate and abuse controls#

Rate limiting keys actor/chat/update scope and policy, returns retry information, and runs before expensive grounding/provider work. Inline mode has additional abuse detection. Limits cannot be bypassed by command casing, bot addressing, callbacks, duplicate update delivery, or alternating update shapes.

Rate-limit and abuse copy avoids exposing internal thresholds or cross-user state. Crisis handling remains available under its dedicated policy and does not turn rate bypass into general command access.

Failure behavior#

  • Missing bot token/webhook secret or invalid request fails closed at the edge.
  • Duplicate or already-claimed update does not repeat the write.
  • Personal command in a group redirects the member to private chat without revealing state.
  • Unwired room, reminder, grounder, effect, upgrade, STT/TTS, or provider path says unavailable or abstains.
  • Provider application-level {ok:false} is a failure even with HTTP 200.
  • Telegram timeouts/5xx/rate limits use bounded retry policy and idempotency; malformed/forbidden/blocked failures do not retry indefinitely.
  • Long replies split; invalid buttons/media are withheld or safely degraded.
  • /stop halts delivery and /resume provides an explicit recovery path where the durable state adapter is available.

Source and tests#

Primary implementation is under libs/oshun/messaging-channels/src/telegram/{bot,command-surface,room-commands,deep-links,keyboard,deliver,bot-api-limits,rate-limit}.ts. Live composition is under apps/oshun/bff/src/routes/telegram.ts and apps/oshun/bff/src/telegram.

Contract, handler, group-scope, Bot API limit/conformance, delivery, webhook, dedupe/claiming, effect wiring, blocked-user, and deployment-parity tests live next to those modules and in BFF tests. The user journey is documented in Telegram assistant delivery.