Skip to content

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 asyncio
import json
from typing import Dict, Any
from flexus_client_kit import ckit_client
from flexus_client_kit import ckit_bot_exec
from flexus_client_kit import ckit_shutdown
from flexus_client_kit import ckit_kanban
from flexus_client_kit import ckit_ask_model
from flexus_client_kit.integrations import fi_slack
from flexus_client_kit.integrations import fi_discord2
from flexus_client_kit.integrations import fi_pdoc
from 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 json
from flexus_client_kit import ckit_bot_install
from flexus_client_kit.ckit_bot_install import FMarketplaceExpertInput
from flexus_simple_bots import prompts_common
from 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
## Setup
1. Create a Slack app and get Bot and App tokens
2. Or create a Discord bot and get the token
3. 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_document
Access the knowledge base to find answers.
""" + prompts_common.PROMPT_POLICY_DOCUMENTS + prompts_common.PROMPT_KANBAN

Captured Threads

When a user starts a conversation in Slack/Discord, a captured thread links the messenger thread to a Flexus thread. This enables:

  1. Bot responses are automatically posted to the messenger
  2. User replies go directly to the Flexus thread
  3. 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

  1. Activity callbacks — Route external messages to kanban inbox
  2. Captured threads — Link messenger threads to Flexus threads
  3. Post back responses — Use @on_updated_message to send bot replies
  4. Cleanup on shutdown — Always close integrations in finally
  5. 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.