Pattern: Messenger Bot
The Messenger Bot pattern creates a reactive listener for external messaging platforms like Slack or Discord. Messages are routed to the kanban inbox for processing.
Reference implementation: Karen bot (customer support)
When to Use
Use this pattern when you need a bot that:
- Monitors Slack/Discord channels
- Responds to customer inquiries
- Routes messages to the appropriate workflow
- Maintains conversation threads across platforms
Architecture
┌─────────────────────────────────────────────────────────────┐│ MESSENGER BOT ││ ││ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ ││ │ Slack │───►│ Activity │───►│ Kanban Inbox │ ││ │ Listener │ │ Callback │ │ │ ││ └────────────┘ └──────────────┘ └───────┬────────┘ ││ │ ││ ┌────────────┐ │ ││ │ Discord │───► ... │ ││ │ Listener │ │ ││ └────────────┘ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ Scheduler │ ││ │ SCHED_TASK_SORT: Prioritize inbox │ ││ │ SCHED_TODO: Process tasks │ ││ └─────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ Bot Response │ ││ │ @on_updated_message -> post back to Slack/Discord │ ││ └─────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────┘Complete Implementation
messenger_bot.py
import asyncioimport jsonfrom typing import Dict, Any
from flexus_client_kit import ckit_clientfrom flexus_client_kit import ckit_bot_execfrom flexus_client_kit import ckit_shutdownfrom flexus_client_kit import ckit_kanbanfrom flexus_client_kit import ckit_ask_modelfrom flexus_client_kit.integrations import fi_slackfrom flexus_client_kit.integrations import fi_discord2from flexus_client_kit.integrations import fi_pdocfrom messenger import messenger_install
BOT_NAME = "messenger"BOT_VERSION = "1.0.0"
TOOLS = [ fi_pdoc.POLICY_DOCUMENT_TOOL,]
async def messenger_main_loop(fclient: ckit_client.FlexusClient, rcx: ckit_bot_exec.RobotContext): setup = ckit_bot_exec.official_setup_mixing_procedure( messenger_install.messenger_setup_schema, rcx.persona.persona_setup )
pdoc = fi_pdoc.IntegrationPdoc(rcx, rcx.persona.ws_root_group_id)
# Initialize Slack if configured slack = None if setup.get("SLACK_BOT_TOKEN"): slack = fi_slack.IntegrationSlack( fclient, rcx, SLACK_BOT_TOKEN=setup["SLACK_BOT_TOKEN"], SLACK_APP_TOKEN=setup["SLACK_APP_TOKEN"], should_join=parse_channels(setup.get("slack_channels", "")), )
# Route Slack messages to kanban slack.set_activity_callback( lambda activity, posted: create_inbox_task( fclient, rcx, "slack", activity ) )
await slack.join_channels() await slack.start_reactive()
# Initialize Discord if configured discord = None if setup.get("DISCORD_BOT_TOKEN"): discord = fi_discord2.IntegrationDiscord( fclient, rcx, DISCORD_BOT_TOKEN=setup["DISCORD_BOT_TOKEN"], watch_channels=parse_channels(setup.get("discord_channels", "")), )
discord.set_activity_callback( lambda activity, posted: create_inbox_task( fclient, rcx, "discord", activity ) )
await discord.join_channels() await discord.start_reactive()
# --- Tool Handlers ---
@rcx.on_tool_call(fi_pdoc.POLICY_DOCUMENT_TOOL.name) async def handle_pdoc(toolcall, args): return await pdoc.called_by_model(toolcall, args)
# --- Message Handler --- # Post bot responses back to messenger
@rcx.on_updated_message async def on_message(msg: ckit_ask_model.FThreadMessageOutput): if msg.ftm_role != "assistant": return if not msg.ftm_content: return
# Check if this thread is captured by a messenger if slack: await slack.look_assistant_might_have_posted_something(msg) if discord: await discord.look_assistant_might_have_posted_something(msg)
# --- Task Handler ---
@rcx.on_updated_task async def on_task(task: ckit_kanban.FPersonaKanbanTaskOutput): if task.ktask_column == "inprogress": # Task assigned - bot will handle via normal conversation payload = json.loads(task.ktask_payload_json or "{}") print(f"Processing {payload.get('source', 'unknown')} message")
# --- Main Loop ---
try: while not ckit_shutdown.shutdown_event.is_set(): await rcx.unpark_collected_events(sleep_if_no_work=10.0) finally: if slack: await slack.close() if discord: await discord.close()
def parse_channels(text: str) -> list: """Parse newline-separated channel list.""" return [c.strip() for c in text.split("\n") if c.strip()]
async def create_inbox_task(fclient, rcx, source: str, activity): """Create kanban task from messenger activity.""" await ckit_kanban.bot_kanban_post_into_inbox( client=fclient, persona_id=rcx.persona.persona_id, title=f"{source.title()}: {activity.text[:50]}...", description=activity.text, payload_json=json.dumps({ "source": source, "channel": getattr(activity, "channel", None), "user": getattr(activity, "user", None), "ts": getattr(activity, "ts", None), "thread_ts": getattr(activity, "thread_ts", None), }), )
def main(): scenario_fn = ckit_bot_exec.parse_bot_args() fclient = ckit_client.FlexusClient( ckit_client.bot_service_name(BOT_NAME, BOT_VERSION), endpoint="/v1/jailed-bot" )
asyncio.run(ckit_bot_exec.run_bots_in_this_group( fclient, marketable_name=BOT_NAME, marketable_version_str=BOT_VERSION, bot_main_loop=messenger_main_loop, inprocess_tools=TOOLS, scenario_fn=scenario_fn, install_func=messenger_install.install, ))
if __name__ == "__main__": main()messenger_install.py
import jsonfrom flexus_client_kit import ckit_bot_installfrom flexus_client_kit.ckit_bot_install import FMarketplaceExpertInputfrom flexus_simple_bots import prompts_commonfrom messenger import messenger_bot, messenger_prompts
messenger_setup_schema = { # Slack "SLACK_BOT_TOKEN": { "bs_type": "string_long", "bs_default": "", "bs_group": "slack", "bs_description": "Bot User OAuth Token (xoxb-...)", }, "SLACK_APP_TOKEN": { "bs_type": "string_long", "bs_default": "", "bs_group": "slack", "bs_description": "App-Level Token (xapp-...)", }, "slack_channels": { "bs_type": "string_multiline", "bs_default": "", "bs_group": "slack", "bs_description": "Channels to monitor (one per line)", },
# Discord "DISCORD_BOT_TOKEN": { "bs_type": "string_long", "bs_default": "", "bs_group": "discord", "bs_description": "Discord Bot Token", }, "discord_channels": { "bs_type": "string_multiline", "bs_default": "", "bs_group": "discord", "bs_description": "Channel IDs to monitor (one per line)", },}
tools_json = json.dumps([t.openai_style_tool() for t in messenger_bot.TOOLS])
def install(ws_id: str): return ckit_bot_install.marketplace_upsert_dev_bot( ws_id=ws_id, marketable_name="messenger", marketable_version="1.0.0", marketable_title1="Messenger Bot", marketable_title2="Monitor Slack and Discord, respond to messages", marketable_occupation="Support Agent", marketable_author="Your Name", marketable_description="""Messenger Bot monitors your Slack and Discord channels and responds to messages.
## Features- Monitors multiple channels- Routes messages to kanban for processing- Responds in threads- Maintains conversation context
## Setup1. Create a Slack app and get Bot and App tokens2. Or create a Discord bot and get the token3. Configure channels to monitor""", marketable_typical_group="Support / Customer Service", marketable_tags=["slack", "discord", "support"], marketable_setup_schema=json.dumps(messenger_setup_schema), marketable_picture_big_b64="", marketable_picture_small_b64="", marketable_experts=[ ("default", FMarketplaceExpertInput( fexp_system_prompt=messenger_prompts.SYSTEM_PROMPT_DEFAULT, fexp_app_capture_tools=tools_json, )), ], marketable_schedule=[ prompts_common.SCHED_TASK_SORT_10M, prompts_common.SCHED_TODO_5M, ], )
if __name__ == "__main__": ckit_bot_install.main_install_dev_bot(install)messenger_prompts.py
from flexus_simple_bots import prompts_common
SYSTEM_PROMPT_DEFAULT = """You are a customer support agent monitoring Slack and Discord.
## Your Role- Answer customer questions- Provide helpful information- Escalate complex issues
## Guidelines- Be friendly and professional- Respond concisely- If you don't know, say so and offer to escalate
## Tools
### flexus_policy_documentAccess the knowledge base to find answers.
""" + prompts_common.PROMPT_POLICY_DOCUMENTS + prompts_common.PROMPT_KANBANCaptured Threads
When a user starts a conversation in Slack/Discord, a captured thread links the messenger thread to a Flexus thread. This enables:
- Bot responses are automatically posted to the messenger
- User replies go directly to the Flexus thread
- Full conversation history is preserved
Thread Capture
The Slack/Discord integration handles thread capture automatically. When look_assistant_might_have_posted_something() detects a response, it posts to the captured thread.
Key Takeaways
- Activity callbacks — Route external messages to kanban inbox
- Captured threads — Link messenger threads to Flexus threads
- Post back responses — Use
@on_updated_messageto send bot replies - Cleanup on shutdown — Always close integrations in
finally - Setup schema groups — Organize credentials by platform
Variations
Slack Only
Remove Discord configuration and initialization.
With Knowledge Base
Add fi_repo_reader or vector search tools for RAG-based responses.
With Human Escalation
Add logic to create escalation tasks or notify humans for complex issues.