* fix: switch group batching from fixed timer to 5-second debounce The old 10-minute fixed timer caused groups to feel unresponsive after inactivity. Now uses debounce: timer resets on every new message, flushes after 5 seconds of quiet. @mentions still flush immediately. New config: groupDebounceSec (default 5). Old groupPollIntervalMin still works (converted to ms) for backward compatibility. Fixes #229 Written by Cameron ◯ Letta Code "The user is always right. If there is a problem with the use of the system, it's the system that's wrong, not the user." -- Don Norman * docs: add group debounce configuration reference Document groupDebounceSec, instantGroups, and debounce behavior in the channel configuration section. Written by Cameron ◯ Letta Code "Good documentation is like a good joke: if you have to explain it, it's not that good." -- Kelsey Hightower
486 lines
14 KiB
Markdown
486 lines
14 KiB
Markdown
# Configuration Reference
|
|
|
|
Complete reference for LettaBot configuration options.
|
|
|
|
## Config File Locations
|
|
|
|
LettaBot checks these locations in order:
|
|
|
|
1. `LETTABOT_CONFIG` env var - Explicit path override
|
|
2. `./lettabot.yaml` - Project-local (recommended)
|
|
3. `./lettabot.yml` - Project-local alternate
|
|
4. `~/.lettabot/config.yaml` - User global
|
|
5. `~/.lettabot/config.yml` - User global alternate
|
|
|
|
For global installs (`npm install -g`), either:
|
|
- Create `~/.lettabot/config.yaml`, or
|
|
- Set `export LETTABOT_CONFIG=/path/to/your/config.yaml`
|
|
|
|
## Example Configuration
|
|
|
|
```yaml
|
|
# Server connection
|
|
server:
|
|
mode: cloud # 'cloud' or 'selfhosted'
|
|
apiKey: letta_... # Required for cloud mode
|
|
|
|
# Agent settings (single agent mode)
|
|
# For multiple agents, use `agents:` array instead -- see Multi-Agent section
|
|
agent:
|
|
name: LettaBot
|
|
# id: agent-... # Optional: use existing agent
|
|
# Note: model is configured on the Letta agent server-side.
|
|
# Use `lettabot model set <handle>` to change it.
|
|
|
|
# Channel configurations
|
|
channels:
|
|
telegram:
|
|
enabled: true
|
|
token: "123456:ABC-DEF..."
|
|
dmPolicy: pairing
|
|
|
|
slack:
|
|
enabled: true
|
|
botToken: xoxb-...
|
|
appToken: xapp-...
|
|
dmPolicy: pairing
|
|
|
|
discord:
|
|
enabled: true
|
|
token: "..."
|
|
dmPolicy: pairing
|
|
|
|
whatsapp:
|
|
enabled: true
|
|
selfChat: true # IMPORTANT: true for personal numbers
|
|
dmPolicy: pairing
|
|
|
|
signal:
|
|
enabled: true
|
|
phone: "+1234567890"
|
|
selfChat: true
|
|
dmPolicy: pairing
|
|
|
|
# Features
|
|
features:
|
|
cron: true
|
|
heartbeat:
|
|
enabled: true
|
|
intervalMin: 60
|
|
|
|
# Polling (background checks for Gmail, etc.)
|
|
polling:
|
|
enabled: true
|
|
intervalMs: 60000 # Check every 60 seconds
|
|
gmail:
|
|
enabled: true
|
|
account: user@example.com
|
|
|
|
# Voice transcription
|
|
transcription:
|
|
provider: openai
|
|
apiKey: sk-... # Optional: falls back to OPENAI_API_KEY
|
|
model: whisper-1
|
|
|
|
# Attachment handling
|
|
attachments:
|
|
maxMB: 20
|
|
maxAgeDays: 14
|
|
|
|
# API server (health checks, CLI messaging)
|
|
api:
|
|
port: 8080 # Default: 8080 (or PORT env var)
|
|
# host: 0.0.0.0 # Uncomment for Docker/Railway
|
|
# corsOrigin: https://my.app # Uncomment for cross-origin access
|
|
```
|
|
|
|
## Server Configuration
|
|
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `server.mode` | `'cloud'` \| `'selfhosted'` | Connection mode |
|
|
| `server.apiKey` | string | API key for Letta Cloud |
|
|
| `server.baseUrl` | string | URL for self-hosted server (e.g., `http://localhost:8283`) |
|
|
|
|
### Self-Hosted Mode
|
|
|
|
```yaml
|
|
server:
|
|
mode: selfhosted
|
|
baseUrl: http://localhost:8283
|
|
```
|
|
|
|
Run Letta server with Docker:
|
|
```bash
|
|
docker run -v ~/.letta/.persist/pgdata:/var/lib/postgresql/data \
|
|
-p 8283:8283 \
|
|
-e OPENAI_API_KEY="..." \
|
|
letta/letta:latest
|
|
```
|
|
|
|
## Agent Configuration (Single Agent)
|
|
|
|
The default config uses `agent:` and `channels:` at the top level for a single agent:
|
|
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `agent.id` | string | Use existing agent (skips creation) |
|
|
| `agent.name` | string | Name for new agent |
|
|
|
|
> **Note:** The model is configured on the Letta agent server-side, not in the config file.
|
|
> Use `lettabot model show` to see the current model and `lettabot model set <handle>` to change it.
|
|
> During initial setup (`lettabot onboard`), you'll be prompted to select a model for new agents.
|
|
|
|
For multiple agents, see [Multi-Agent Configuration](#multi-agent-configuration) below.
|
|
|
|
## Multi-Agent Configuration
|
|
|
|
Run multiple independent agents from a single LettaBot instance. Each agent gets its own channels, state, cron, heartbeat, and polling services.
|
|
|
|
Use the `agents:` array instead of the top-level `agent:` and `channels:` keys:
|
|
|
|
```yaml
|
|
server:
|
|
mode: cloud
|
|
apiKey: letta_...
|
|
|
|
agents:
|
|
- name: work-assistant
|
|
model: claude-sonnet-4
|
|
# id: agent-abc123 # Optional: use existing agent
|
|
channels:
|
|
telegram:
|
|
token: ${WORK_TELEGRAM_TOKEN}
|
|
dmPolicy: pairing
|
|
slack:
|
|
botToken: ${SLACK_BOT_TOKEN}
|
|
appToken: ${SLACK_APP_TOKEN}
|
|
features:
|
|
cron: true
|
|
heartbeat:
|
|
enabled: true
|
|
intervalMin: 30
|
|
|
|
- name: personal-assistant
|
|
model: claude-sonnet-4
|
|
channels:
|
|
signal:
|
|
phone: "+1234567890"
|
|
selfChat: true
|
|
whatsapp:
|
|
enabled: true
|
|
selfChat: true
|
|
features:
|
|
heartbeat:
|
|
enabled: true
|
|
intervalMin: 60
|
|
```
|
|
|
|
### Per-Agent Options
|
|
|
|
Each entry in `agents:` accepts:
|
|
|
|
| Option | Type | Required | Description |
|
|
|--------|------|----------|-------------|
|
|
| `name` | string | Yes | Agent name (used for display, creation, and state isolation) |
|
|
| `id` | string | No | Use existing agent ID (skips creation) |
|
|
| `model` | string | No | Model for agent creation |
|
|
| `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.) |
|
|
| `integrations` | object | No | Per-agent integrations (Google, etc.) |
|
|
|
|
### How it works
|
|
|
|
- Each agent is a separate Letta agent with its own conversation history and memory
|
|
- Agents have isolated state, channels, and services (see [known limitations](#known-limitations) for exceptions)
|
|
- The `LettaGateway` orchestrates startup, shutdown, and message delivery across agents
|
|
- Legacy single-agent configs (`agent:` + `channels:`) continue to work unchanged
|
|
|
|
### Migrating from single to multi-agent
|
|
|
|
Your existing config:
|
|
|
|
```yaml
|
|
agent:
|
|
name: MyBot
|
|
channels:
|
|
telegram:
|
|
token: "..."
|
|
features:
|
|
cron: true
|
|
```
|
|
|
|
Becomes:
|
|
|
|
```yaml
|
|
agents:
|
|
- name: MyBot
|
|
channels:
|
|
telegram:
|
|
token: "..."
|
|
features:
|
|
cron: true
|
|
```
|
|
|
|
The `server:`, `transcription:`, `attachments:`, and `api:` sections remain at the top level (shared across all agents).
|
|
|
|
### Known limitations
|
|
|
|
- Two agents cannot share the same channel type without ambiguous API routing ([#219](https://github.com/letta-ai/lettabot/issues/219))
|
|
- WhatsApp/Signal session paths are not yet agent-scoped ([#220](https://github.com/letta-ai/lettabot/issues/220))
|
|
- Heartbeat prompt and target are not yet configurable per-agent ([#221](https://github.com/letta-ai/lettabot/issues/221))
|
|
|
|
## Channel Configuration
|
|
|
|
All channels share these common options:
|
|
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `enabled` | boolean | Enable this channel |
|
|
| `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 |
|
|
|
|
### Group Message Debouncing
|
|
|
|
In group chats, the bot debounces incoming messages to batch rapid-fire messages into a single response. The timer resets on each new message, so the bot waits for a quiet period before responding.
|
|
|
|
```yaml
|
|
channels:
|
|
discord:
|
|
groupDebounceSec: 10 # Wait 10s of quiet before responding
|
|
instantGroups: # These groups get instant responses
|
|
- "123456789"
|
|
```
|
|
|
|
- **Default: 5 seconds** -- waits for 5s of quiet, then processes all buffered messages at once
|
|
- **`groupDebounceSec: 0`** -- disables batching (every message processed immediately, like DMs)
|
|
- **`@mention`** -- always triggers an immediate response regardless of debounce
|
|
- **`instantGroups`** -- listed groups bypass debounce entirely
|
|
|
|
The deprecated `groupPollIntervalMin` (minutes) still works for backward compatibility but `groupDebounceSec` takes priority.
|
|
|
|
### DM Policies
|
|
|
|
**Note:** For WhatsApp/Signal with `selfChat: true` (personal number), dmPolicy is ignored - only you can message via "Message Yourself" / "Note to Self".
|
|
|
|
For dedicated bot numbers (`selfChat: false`), onboarding defaults to **allowlist**:
|
|
|
|
- **`allowlist`** (default for dedicated numbers): Only specified phone numbers can message
|
|
- **`pairing`**: New users get a code, approve with `lettabot pairing approve`
|
|
- **`open`**: Anyone can message (not recommended)
|
|
|
|
### Channel-Specific Options
|
|
|
|
#### Telegram
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `token` | string | Bot token from @BotFather |
|
|
|
|
#### Slack
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `botToken` | string | Bot User OAuth Token (xoxb-...) |
|
|
| `appToken` | string | App-Level Token (xapp-...) for Socket Mode |
|
|
|
|
#### Discord
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `token` | string | Bot token from Discord Developer Portal |
|
|
|
|
#### WhatsApp
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `selfChat` | boolean | **Critical:** `true` = only "Message Yourself" works |
|
|
|
|
#### Signal
|
|
| Option | Type | Description |
|
|
|--------|------|-------------|
|
|
| `phone` | string | Phone number with + prefix |
|
|
| `selfChat` | boolean | `true` = only "Note to Self" works |
|
|
|
|
## Features Configuration
|
|
|
|
### Heartbeat
|
|
|
|
```yaml
|
|
features:
|
|
heartbeat:
|
|
enabled: true
|
|
intervalMin: 60 # Check every 60 minutes
|
|
```
|
|
|
|
Heartbeats are background tasks where the agent can review pending work.
|
|
|
|
#### Custom Heartbeat Prompt
|
|
|
|
You can customize what the agent is told during heartbeats. The custom text replaces the default body while keeping the silent mode envelope (time, trigger metadata, and messaging instructions).
|
|
|
|
Inline in YAML:
|
|
|
|
```yaml
|
|
features:
|
|
heartbeat:
|
|
enabled: true
|
|
intervalMin: 60
|
|
prompt: "Check your todo list and work on the highest priority item."
|
|
```
|
|
|
|
From a file (re-read each tick, so edits take effect without restart):
|
|
|
|
```yaml
|
|
features:
|
|
heartbeat:
|
|
enabled: true
|
|
intervalMin: 60
|
|
promptFile: ./prompts/heartbeat.md
|
|
```
|
|
|
|
Via environment variable:
|
|
|
|
```bash
|
|
HEARTBEAT_PROMPT="Review recent conversations" npm start
|
|
```
|
|
|
|
Precedence: `prompt` (inline YAML) > `HEARTBEAT_PROMPT` (env var) > `promptFile` (file) > built-in default.
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `features.heartbeat.prompt` | string | _(none)_ | Custom heartbeat prompt text |
|
|
| `features.heartbeat.promptFile` | string | _(none)_ | Path to prompt file (relative to working dir) |
|
|
|
|
### Cron Jobs
|
|
|
|
```yaml
|
|
features:
|
|
cron: true
|
|
```
|
|
|
|
Enable scheduled tasks. See [Cron Setup](./cron-setup.md).
|
|
|
|
### No-Reply (Opt-Out)
|
|
|
|
The agent can choose not to respond to a message by sending exactly:
|
|
|
|
```
|
|
<no-reply/>
|
|
```
|
|
|
|
When the bot receives this marker, it suppresses the response and nothing is sent to the channel. This is useful in group chats where the agent shouldn't reply to every message.
|
|
|
|
The agent is taught about this behavior in two places:
|
|
|
|
- **System prompt**: A "Choosing Not to Reply" section explains when to use it (messages not directed at the agent, simple acknowledgments, conversations between other users, etc.)
|
|
- **Message envelope**: Group messages include a hint reminding the agent of the `<no-reply/>` option. DMs do not include this hint.
|
|
|
|
The bot also handles this gracefully during streaming -- it holds back partial output while the response could still become `<no-reply/>`, so users never see a partial match leak through.
|
|
|
|
## Polling Configuration
|
|
|
|
Background polling for integrations like Gmail. Runs independently of agent cron jobs.
|
|
|
|
```yaml
|
|
polling:
|
|
enabled: true # Master switch (default: auto-detected from sub-configs)
|
|
intervalMs: 60000 # Check every 60 seconds (default: 60000)
|
|
gmail:
|
|
enabled: true
|
|
account: user@example.com # Gmail account to poll
|
|
```
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `polling.enabled` | boolean | auto | Master switch. Defaults to `true` if any sub-config is enabled |
|
|
| `polling.intervalMs` | number | `60000` | Polling interval in milliseconds |
|
|
| `polling.gmail.enabled` | boolean | auto | Enable Gmail polling. Auto-detected from `account` |
|
|
| `polling.gmail.account` | string | - | Gmail account to poll for unread messages |
|
|
|
|
### Legacy config path
|
|
|
|
For backward compatibility, Gmail polling can also be configured under `integrations.google`:
|
|
|
|
```yaml
|
|
integrations:
|
|
google:
|
|
enabled: true
|
|
account: user@example.com
|
|
pollIntervalSec: 60
|
|
```
|
|
|
|
The top-level `polling` section takes priority if both are present.
|
|
|
|
### Environment variable fallback
|
|
|
|
| Env Variable | Polling Config Equivalent |
|
|
|--------------|--------------------------|
|
|
| `GMAIL_ACCOUNT` | `polling.gmail.account` |
|
|
| `POLLING_INTERVAL_MS` | `polling.intervalMs` |
|
|
| `PORT` | `api.port` |
|
|
| `API_HOST` | `api.host` |
|
|
| `API_CORS_ORIGIN` | `api.corsOrigin` |
|
|
|
|
## Transcription Configuration
|
|
|
|
Voice message transcription via OpenAI Whisper:
|
|
|
|
```yaml
|
|
transcription:
|
|
provider: openai
|
|
apiKey: sk-... # Optional: uses OPENAI_API_KEY env var
|
|
model: whisper-1 # Default
|
|
```
|
|
|
|
## Attachments Configuration
|
|
|
|
```yaml
|
|
attachments:
|
|
maxMB: 20 # Max file size to download (default: 20)
|
|
maxAgeDays: 14 # Auto-delete after N days (default: 14)
|
|
```
|
|
|
|
Attachments are stored in `/tmp/lettabot/attachments/`.
|
|
|
|
## API Server Configuration
|
|
|
|
The built-in API server provides health checks and CLI messaging endpoints.
|
|
|
|
```yaml
|
|
api:
|
|
port: 9090 # Default: 8080
|
|
host: 0.0.0.0 # Default: 127.0.0.1 (localhost only)
|
|
corsOrigin: "*" # Default: same-origin only
|
|
```
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `api.port` | number | `8080` | Port for the API/health server |
|
|
| `api.host` | string | `127.0.0.1` | Bind address. Use `0.0.0.0` for Docker/Railway |
|
|
| `api.corsOrigin` | string | _(none)_ | CORS origin header for cross-origin access |
|
|
|
|
## Environment Variables
|
|
|
|
Environment variables override config file values:
|
|
|
|
| Env Variable | Config Equivalent |
|
|
|--------------|-------------------|
|
|
| `LETTABOT_CONFIG` | Path to config file (overrides search order) |
|
|
| `LETTA_API_KEY` | `server.apiKey` |
|
|
| `LETTA_BASE_URL` | `server.baseUrl` |
|
|
| `LETTA_AGENT_ID` | `agent.id` |
|
|
| `LETTA_AGENT_NAME` | `agent.name` |
|
|
| `TELEGRAM_BOT_TOKEN` | `channels.telegram.token` |
|
|
| `TELEGRAM_DM_POLICY` | `channels.telegram.dmPolicy` |
|
|
| `SLACK_BOT_TOKEN` | `channels.slack.botToken` |
|
|
| `SLACK_APP_TOKEN` | `channels.slack.appToken` |
|
|
| `DISCORD_BOT_TOKEN` | `channels.discord.token` |
|
|
| `WHATSAPP_ENABLED` | `channels.whatsapp.enabled` |
|
|
| `WHATSAPP_SELF_CHAT_MODE` | `channels.whatsapp.selfChat` |
|
|
| `SIGNAL_PHONE_NUMBER` | `channels.signal.phone` |
|
|
| `OPENAI_API_KEY` | `transcription.apiKey` |
|
|
| `GMAIL_ACCOUNT` | `polling.gmail.account` |
|
|
| `POLLING_INTERVAL_MS` | `polling.intervalMs` |
|
|
|
|
See [SKILL.md](../SKILL.md) for complete environment variable reference.
|