LifeOSAI — Channels Architecture · sequential view

Two provider subsystems (Baileys WhatsApp, grammy Telegram) sit above a shared channels/ layer. Inbound paths converge on runAgentDispatch(); outbound goes through mcp__channels__message → action-runner → plugin → provider.
CHANNEL NETWORKS WhatsApp Network multi-device protocol · reached via Baileys WS QR / pairing-code login as a real account Telegram Bot API official Bot API · reached via grammy long-poll updates · bot token auth · /commands WHATSAPP SUBSYSTEM · apps/api/src/whatsapp/ socket.tsBaileys WS · reconnect30-min watchdog pairing.tspairing code · isPairedmulti-file auth state pipeline.tsparse · group metadebounce 500 ms dispatch.tsper-session queue→ runAgentDispatch delivery.tsStreamSink · bufferedtyping indicator lid + contactsJID/LID ↔ phone+ directory lookup also: contacts · group-history · reactions · receipt-tracker · media · permissions · channel-router TELEGRAM SUBSYSTEM · apps/api/src/telegram/ bot.tsgrammy Bot (singleton)bot-token auth polling.tslong-poll runnerno webhook needed pipeline.ts/commands + msgdebounce 500 ms bot-message-dispatch.tsper-session queue→ runAgentDispatch streaming/StreamSink · edit-in-placethrottled editMessageText agent-selector/agents pickeragent-router routes also: bot-message · group-history · tools · permissions · lifecycle · routes SHARED CHANNELS LAYER · apps/api/src/channels/ message-debounce.ts500 ms coalesce per chatused by both pipelines session-store.tsSessionEntry · DmScopemain · per-peer · per-ch. identity-links + chat-keymerges WA + TG by namebuildAgentChatKey() registry.tsplugin + alias map"wa"→whatsapp, "tg"→tg system-prompt.tsbuildChannelSystemPrompt+ user context prefix dispatch.runAgentDispatch()the harness invocation · used by BOTHcreates streaming session · token-pool rotation plugins/whatsapp.tsChannelPlugin impl (WA)delegates to whatsapp/ plugins/telegram.tsChannelPlugin impl (TG)delegates to telegram/ mcp__channels__message + action-runnerunified MCP tool · send/reply/react/poll/…30 msgs/min/target · strips <think> RUNTIME + OUTBOUND PATH Claude Code SDK harness createStreamingSession · SDKMessage • content_block_delta → sink.append • result → capture session_id • rate-limit → rotate token (5 retries) StreamSink (transport-specific) two implementations of one interface WhatsApp delivery: buffered send-once + typing indicator Telegram StreamEngine: edit-in-place via throttled editMessageText Outbound (agent reply) when agent calls mcp__channels__message 1. message-tool: rate-limit + clean 2. action-runner: resolve channel + target 3. ChannelPlugin.send / react / … 4. provider delivers · messageId returned PERSISTENCE channel sessions~/.lifeosai/sessions/ identity-links.jsoncross-channel merging Baileys authmulti-file creds + keys media uploadsGCS / filestore LID cacheJID ↔ phone voice transcriptsElevenLabs (audio in) group historyrecent N per chat runs + eventsheartbeatRun · logs STATE THE CHANNELS LAYER WRITES Session JSON + identity links are channel-layer state. Baileys auth + LID cache are WhatsApp-specific. Media + transcripts + run-events shared with orchestration. inbound inbound → runAgentDispatch → runAgentDispatch createStreamingSession() agent → MCP tool