From 4e697001c04cc7eae713c40d72e9cd44d40d92fa Mon Sep 17 00:00:00 2001 From: Jason Carreira Date: Sat, 21 Feb 2026 06:38:06 -0500 Subject: [PATCH] docs: update conversation routing and group config documentation (#318) Adds documentation for per-channel conversation routing and updated group configuration options. Fills the gap left by the implementation PRs. Closes #304 Written by Cameron and Letta Code "Documentation is a love letter that you write to your future self." -- Damian Conway --- README.md | 56 ++++++++++++++++++++++++++++---- SKILL.md | 52 ++++++++++++++++++++++++++---- docs/configuration.md | 41 ++++++++++++++++++++++- lettabot.example.yaml | 75 +++++++++++++++++++++++-------------------- 4 files changed, 174 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index e08c7b1..fb973c1 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ Then ask your bot things like: ## Channel Setup -LettaBot uses a **single agent with a single conversation** across all channels: +By default, LettaBot uses a **single agent with a single shared conversation** across all channels: ``` Telegram ──┐ @@ -208,6 +208,35 @@ Signal ────┘ - Pick it up on WhatsApp - The agent remembers everything! +You can also enable **per-channel conversations** (one conversation per channel adapter, not per chat/user): + +``` +Telegram ──→ CONVERSATION (telegram) +Slack ─────→ CONVERSATION (slack) +Discord ───→ CONVERSATION (discord) +WhatsApp ──→ CONVERSATION (whatsapp) +Signal ────→ CONVERSATION (signal) + (shared agent memory across channels) +``` + +Configure in `lettabot.yaml`: + +```yaml +conversations: + mode: shared # default: one conversation across all channels + heartbeat: last-active # "dedicated" | "last-active" | "" +``` + +For multi-agent configs, the same block lives under each agent: + +```yaml +agents: + - name: MyAgent + conversations: + mode: per-channel + heartbeat: dedicated +``` + | Channel | Guide | Requirements | |---------|-------|--------------| | Telegram | [Setup Guide](docs/getting-started.md) | Bot token from @BotFather | @@ -226,12 +255,23 @@ Configure group batching and per-group response modes in `lettabot.yaml`: channels: slack: groupDebounceSec: 5 - instantGroups: ["C0123456789"] + mentionPatterns: ["@mybot", "hey bot"] groups: - "*": { mode: open } - "C0987654321": { mode: listen } # observe only, reply on mention + "*": { mode: mention-only } # default for all groups + "C0987654321": { mode: listen } # observe, only reply on mention + "C0123456789": { mode: open, allowedUsers: ["U123"] } ``` +Group modes: +- `open`: process + respond to all group messages +- `listen`: process for memory, but auto-replies only on mention +- `mention-only`: only process when mentioned +- `disabled`: ignore all group messages + +Notes: +- `instantGroups` still bypasses batching (legacy). +- `listeningGroups` and `groups..requireMention` are deprecated; use `groups..mode`. + See `SKILL.md` for the full environment variable list and examples. ## Bot Commands @@ -244,7 +284,9 @@ See `SKILL.md` for the full environment variable list and examples. ## Background Tasks (Heartbeats & Cron) -Heartbeats and cron jobs run in **Silent Mode** - the agent's text responses are NOT automatically sent to users during these background tasks. This is intentional: the agent decides when something is worth interrupting you for. +Heartbeats always run in **Silent Mode**. Cron jobs are **silent by default**, but can auto-deliver responses when created with `--deliver channel:chatId`. + +Silent Mode means the agent's text responses are NOT automatically sent to users during background tasks. This is intentional: the agent decides when something is worth interrupting you for. To send messages during silent mode, the agent must explicitly use the CLI: @@ -254,9 +296,9 @@ lettabot-message send --text "Hey, I found something interesting!" The agent sees a clear `[SILENT MODE]` banner when triggered by heartbeats/cron, along with instructions on how to use the CLI. -**Requirements for background messaging:** +**Requirements for background messaging (silent mode):** - The **Bash tool must be enabled** for the agent to run the CLI -- A user must have messaged the bot at least once (to establish a delivery target) +- A user must have messaged the bot at least once (to establish a delivery target) unless you provide an explicit target If your agent isn't sending messages during heartbeats, check the [ADE](https://app.letta.com) to see what the agent is doing and whether it's attempting to use `lettabot-message`. diff --git a/SKILL.md b/SKILL.md index 04a740f..8dc1bb6 100644 --- a/SKILL.md +++ b/SKILL.md @@ -176,21 +176,52 @@ Each channel supports three DM policies: - **`allowlist`**: Only specified user IDs can message - **`open`**: Anyone can message (not recommended) +## Conversation Routing + +By default, all channels share **one conversation**. You can switch to per-channel conversation histories. + +**Single-agent config (top-level):** +```yaml +conversations: + mode: shared # "shared" (default) or "per-channel" + heartbeat: last-active # "dedicated" | "last-active" | "" +``` + +**Multi-agent config (per agent):** +```yaml +agents: + - name: MyAgent + conversations: + mode: per-channel + heartbeat: dedicated +``` + +Notes: +- `per-channel` means one conversation per **channel adapter** (telegram/slack/discord/etc), not per chat/user. +- Agent memory remains shared across channels; only the conversation history is separated. +- `heartbeat` controls which conversation background triggers use: a dedicated stream, the last active channel, or an explicit channel name. + ## Group Settings (Optional) Group settings apply to Telegram, Slack, Discord, WhatsApp, and Signal. **YAML fields (per channel under `channels.`):** - `groupDebounceSec`: Debounce seconds for group batching (default: 5) +- `groups`: Map of group IDs to config (use `*` as the default) + - `mode`: `open` | `listen` | `mention-only` | `disabled` + - `allowedUsers`: Restrict who can trigger the bot in that group + - `receiveBotMessages`: Allow bot/bot messages (default: false) +- `mentionPatterns`: Extra regex patterns for mention detection (Telegram/WhatsApp/Signal) +- `instantGroups`: Group IDs that bypass batching (**legacy**) - `groupPollIntervalMin`: Deprecated (minutes) -- `instantGroups`: Group IDs that bypass batching -- `listeningGroups`: Group IDs where the bot observes and only replies when mentioned +- `listeningGroups`: Deprecated (use `groups..mode: listen`) -**Environment variables (non-interactive onboarding):** +**Environment variables (non-interactive onboarding):** +Only legacy group options are supported here; prefer editing `lettabot.yaml` for `groups` config. - `_GROUP_DEBOUNCE_SEC` (seconds, e.g. `5`) - `_GROUP_POLL_INTERVAL_MIN` (deprecated, use `_GROUP_DEBOUNCE_SEC` instead) -- `_INSTANT_GROUPS` (comma-separated) -- `_LISTENING_GROUPS` (comma-separated) +- `_INSTANT_GROUPS` (comma-separated, legacy) +- `_LISTENING_GROUPS` (comma-separated, legacy) Example: @@ -201,8 +232,11 @@ channels: botToken: xoxb-... appToken: xapp-... groupDebounceSec: 5 - instantGroups: ["C0123456789"] - listeningGroups: ["C0987654321"] + mentionPatterns: ["@mybot", "hey bot"] + groups: + "*": { mode: mention-only } + "C0987654321": { mode: listen } + "C0123456789": { mode: open, allowedUsers: ["U123"] } ``` ## Configuration File @@ -215,6 +249,10 @@ server: apiKey: letta_... agentId: agent-... +conversations: + mode: shared + heartbeat: last-active + channels: telegram: enabled: true diff --git a/docs/configuration.md b/docs/configuration.md index c433233..adc3681 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -36,6 +36,11 @@ agent: # Note: model is configured on the Letta agent server-side. # Use `lettabot model set ` to change it. +# Conversation routing (optional) +conversations: + mode: shared # "shared" (default) or "per-channel" + heartbeat: last-active # "dedicated" | "last-active" | "" + # Channel configurations channels: telegram: @@ -149,6 +154,9 @@ agents: # displayName: "🔧 Work" # Optional: prefix outbound messages model: claude-sonnet-4 # id: agent-abc123 # Optional: use existing agent + conversations: + mode: shared + heartbeat: last-active channels: telegram: token: ${WORK_TELEGRAM_TOKEN} @@ -164,6 +172,9 @@ agents: - name: personal-assistant model: claude-sonnet-4 + conversations: + mode: per-channel + heartbeat: dedicated channels: signal: phone: "+1234567890" @@ -187,6 +198,7 @@ Each entry in `agents:` accepts: | `id` | string | No | Use existing agent ID (skips creation) | | `displayName` | string | No | Prefix outbound messages (e.g. `"💜 Signo"`) | | `model` | string | No | Model for agent creation | +| `conversations` | object | No | Conversation routing config (shared vs per-channel) | | `channels` | object | No | Channel configs (same schema as top-level `channels:`). At least one agent must have channels. | | `features` | object | No | Per-agent features (cron, heartbeat, maxToolCalls) | | `polling` | object | No | Per-agent polling config (Gmail, etc.) | @@ -243,7 +255,9 @@ All channels share these common options: | `dmPolicy` | `'pairing'` \| `'allowlist'` \| `'open'` | Access control mode | | `allowedUsers` | string[] | User IDs/numbers for allowlist mode | | `groupDebounceSec` | number | Debounce for group messages in seconds (default: 5, 0 = immediate) | -| `instantGroups` | string[] | Group/channel IDs that bypass debounce entirely | +| `instantGroups` | string[] | Group/channel IDs that bypass debounce entirely (legacy) | +| `groups` | object | Per-group configuration map (use `*` as default) | +| `mentionPatterns` | string[] | Extra regex patterns for mention detection (Telegram/WhatsApp/Signal) | ### Group Message Debouncing @@ -264,6 +278,31 @@ channels: The deprecated `groupPollIntervalMin` (minutes) still works for backward compatibility but `groupDebounceSec` takes priority. +### Conversation Routing + +By default, all channels share a single conversation. You can split conversations per channel adapter. + +**Single-agent config:** +```yaml +conversations: + mode: shared # "shared" (default) or "per-channel" + heartbeat: last-active # "dedicated" | "last-active" | "" +``` + +**Multi-agent config:** +```yaml +agents: + - name: work-assistant + conversations: + mode: per-channel + heartbeat: dedicated +``` + +Notes: +- `per-channel` means one conversation per **channel adapter** (telegram/slack/discord/etc), not per chat/user. +- Agent memory remains shared across channels; only the conversation history is separated. +- `heartbeat` controls which conversation background triggers use: a dedicated stream, the last active channel, or an explicit channel name. + ### Group Modes Use `groups..mode` to control how each group/channel behaves: diff --git a/lettabot.example.yaml b/lettabot.example.yaml index 7a6a54a..d540720 100644 --- a/lettabot.example.yaml +++ b/lettabot.example.yaml @@ -14,11 +14,46 @@ server: # mode: docker # baseUrl: http://localhost:8283 -agent: - name: LettaBot - # displayName: "💜 Signo" # Prefix outbound messages (useful in multi-agent group chats) - # Note: model is configured on the Letta agent server-side. - # Select a model during `lettabot onboard` or change it with `lettabot model set `. +agents: + - name: LettaBot + # displayName: "💜 Signo" # Prefix outbound messages (useful in multi-agent group chats) + # Note: model is configured on the Letta agent server-side. + # Select a model during `lettabot onboard` or change it with `lettabot model set `. + + # Conversation routing (optional) + conversations: + mode: shared # "shared" (default) or "per-channel" + heartbeat: last-active # "dedicated" | "last-active" | "" + + channels: + telegram: + enabled: true + token: YOUR-TELEGRAM-BOT-TOKEN + dmPolicy: pairing # 'pairing', 'allowlist', or 'open' + # groupDebounceSec: 5 # Debounce seconds (default: 5, 0 = immediate) + # instantGroups: ["-100123456"] # Groups that bypass batching (legacy) + # Group access + response mode: + # groups: + # "*": { mode: listen } # Observe all groups; only reply when @mentioned + # "-1001234567890": { mode: open } # This group gets all messages + # "-1009876543210": { mode: mention-only } # Drop unless @mentioned + # mentionPatterns: ["hey bot"] # Additional regex patterns for mention detection + # slack: + # enabled: true + # appToken: xapp-... + # botToken: xoxb-... + # groups: + # "*": { mode: listen } + # "C0123456789": { mode: open } + # discord: + # enabled: true + # token: YOUR-DISCORD-BOT-TOKEN + # groups: + # "*": { mode: listen } + # "1234567890123456789": { mode: open } # Server or channel ID + # whatsapp: + # enabled: true + # selfChat: false # BYOK Providers (optional, api mode only) # These will be synced to Letta API on startup @@ -32,36 +67,6 @@ agent: # type: openai # apiKey: sk-YOUR-OPENAI-KEY -channels: - telegram: - enabled: true - token: YOUR-TELEGRAM-BOT-TOKEN - dmPolicy: pairing # 'pairing', 'allowlist', or 'open' - # groupPollIntervalMin: 5 # Batch interval for group messages (default: 10) - # instantGroups: ["-100123456"] # Groups that bypass batching - # Group access + response mode: - # groups: - # "*": { mode: listen } # Observe all groups; only reply when @mentioned - # "-1001234567890": { mode: open } # This group gets all messages - # "-1009876543210": { mode: mention-only } # Drop unless @mentioned - # mentionPatterns: ["hey bot"] # Additional regex patterns for mention detection - # slack: - # enabled: true - # appToken: xapp-... - # botToken: xoxb-... - # groups: - # "*": { mode: listen } - # "C0123456789": { mode: open } - # discord: - # enabled: true - # token: YOUR-DISCORD-BOT-TOKEN - # groups: - # "*": { mode: listen } - # "1234567890123456789": { mode: open } # Server or channel ID - # whatsapp: - # enabled: true - # selfChat: false - features: cron: false heartbeat: