Configuration
Everything is configured in config.toml. Copy config.example.toml as a starting point. Secrets should use environment variables, not this file.
Persona
The [persona] section defines your agent's identity. It drives system prompts, web presence, and email branding.
[persona]
name = "My Agent" # Agent's display name
tagline = "Your AI-powered assistant" # Shown in emails + web
owner_name = "Your Name" # The human behind the agent
domain = "example.com" # Your website domain
booking_url = "" # Google Calendar link (optional)
linkedin_url = "" # LinkedIn profile (optional)
location = "San Francisco"
timezone = "America/Los_Angeles"
# Core personality (injected into system prompt)
personality = """\
You combine sharp analytical thinking with genuine warmth. \
Casual but competent."""
# What the agent helps with
capabilities = [
"Scheduling, prioritization, and time management",
"Business decisions and tradeoff analysis",
"Research and recommendations",
]
# What services you offer (for the web chat agent)
services = [
"Full-stack web apps (Next.js, React, Python, Node)",
"AI/ML integrations and automation",
"Technical consulting and architecture reviews",
]
Brand colors
CSS variables used in email templates and the web interface. Match these to your website's design.
[persona.brand]
bg = "#0a0a0a"
surface = "#141414"
border = "#2a2a2a"
text = "#e8e8e8"
text_muted = "#888888"
accent = "#f0c040"
accent_dim = "#c49a20"
Booking links
Optional booking links shown in outreach emails. Add as many as you want using TOML's array-of-tables syntax:
[[persona.booking]]
name = "Free Intro Call"
url = "https://calendar.app.google/your-link"
desc = "Quick chat to discuss your project"
[[persona.booking]]
name = "Paid Discovery Session"
duration = "60 min"
url = "https://calendar.app.google/another-link"
desc = "Deep-dive into requirements"
Inference
Local model configuration for on-device inference via llama.cpp. This is the recommended local backend for Termux/phone deployments — compiled with Vulkan GPU acceleration, no extra daemon needed, minimal memory overhead.
[inference]
model_path = "models/phi-3.5-mini-instruct-q4_k_m.gguf"
n_ctx = 4096 # Context window
n_gpu_layers = -1 # -1 = all layers on GPU (Metal/Vulkan)
n_threads = 4
No local model? That's fine. If no model file is present, the agent routes everything to cloud backends. The local model saves money on simple messages but isn't required.
Cloud tiers
The agent routes messages to cloud LLMs based on complexity. Configure two tiers:
[cloud.light]
provider = "groq" # fast + cheap
model = "llama-3.3-70b-versatile"
[cloud.heavy]
provider = "anthropic" # capable
# model = "claude-sonnet-4-5-20250514" # default
Supported providers
| Provider | Env var | Default model | Notes |
|---|---|---|---|
anthropic | ANTHROPIC_API_KEY | claude-haiku-4-5 | Claude models |
google | GOOGLE_API_KEY | gemini-2.5-flash | Gemini models |
openai | OPENAI_API_KEY | gpt-4o-mini | GPT-4o, o3 |
groq | GROQ_API_KEY | llama-3.3-70b-versatile | Ultra-fast inference |
together | TOGETHER_API_KEY | Meta-Llama-3.1-70B | Open-source hosting |
fireworks | FIREWORKS_API_KEY | llama-v3p1-70b | Fast open-source |
deepseek | DEEPSEEK_API_KEY | deepseek-chat | DeepSeek-V3, R1 |
mistral | MISTRAL_API_KEY | mistral-large-latest | Mistral Large, Codestral |
openrouter | OPENROUTER_API_KEY | anthropic/claude-sonnet-4 | 100+ models, one key |
ollama | none | llama3.2 | Local HTTP, no key needed |
cohere | COHERE_API_KEY | command-r-plus | Command R+ |
Ollama (desktop/laptop alternative): On macOS or Linux desktops, you can skip llama.cpp compilation by using Ollama instead. Install Ollama, pull a model (ollama pull llama3.2), then set provider = "ollama" in a cloud tier. No API key needed. On Termux/phone, stick with the built-in llama.cpp — it's lighter (no separate daemon) and has direct Vulkan GPU access.
Set the env var for your chosen provider. The agent auto-detects the key and creates the backend. You can mix providers across tiers (e.g., Groq for light, Anthropic for heavy).
Channels
Palmtop supports 10 messaging channels. Run one or many simultaneously.
Multi-channel mode
Run multiple channels at once — all share the same AgentLoop (memory, tools, inference):
# Single channel (default):
channel = "telegram"
# Multi-channel mode:
channels = ["telegram", "slack", "irc", "email"]
Supported values: telegram, sms, email, discord, slack, matrix, irc, whatsapp, xmpp, scuttlebot.
Telegram
[telegram]
# Set TELEGRAM_BOT_TOKEN env var
# allowed_users = [123456789] # restrict to specific user IDs
SMS (Termux:API)
[sms]
enabled = false
allowed_numbers = ["+15551234567"]
allowed_sender_names = []
poll_interval = 5
Email (as a channel)
Polls your AgentMail inbox and routes inbound emails through the agent. Add "email" to channels.
[email]
# api_key = "am_..." # or set AGENTMAIL_API_KEY
# inbox_id = "inbox_..." # or set AGENTMAIL_INBOX_ID
# poll_interval = 30 # seconds between inbox checks
# allowed_senders = [] # empty = accept all
Discord
Requires uv sync --extra discord (installs discord.py).
[discord]
# Set DISCORD_BOT_TOKEN env var
# allowed_users = [] # Discord user IDs (integers)
# guild_id = 0 # restrict to one server (0 = any)
# channel_id = 0 # restrict to one text channel (0 = any)
Slack
Socket Mode — no public URL needed. Requires uv sync --extra slack.
[slack]
# Set SLACK_BOT_TOKEN and SLACK_APP_TOKEN env vars
# allowed_users = [] # Slack user IDs (e.g. "U12345ABC")
Matrix
Works with matrix.org and self-hosted homeservers. Requires uv sync --extra matrix.
[matrix]
# Set MATRIX_HOMESERVER, MATRIX_USER_ID, MATRIX_ACCESS_TOKEN env vars
# allowed_users = [] # e.g. "@alice:matrix.org"
# allowed_rooms = [] # e.g. "!abc123:matrix.org"
IRC
Zero extra dependencies — raw asyncio sockets with the IRC protocol.
[irc]
# server = "irc.libera.chat"
# port = 6697
# nick = "palmtop"
# channels = ["#your-channel"]
# use_ssl = true
# allowed_users = [] # IRC nicks (case-insensitive)
Official WhatsApp Business Cloud API. Free tier: 1,000 conversations/month.
[whatsapp]
# Set WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_ACCESS_TOKEN, WHATSAPP_VERIFY_TOKEN env vars
# app_secret = "" # optional webhook signature verification
# allowed_numbers = ["+1234567890"]
# webhook_port = 8080
# webhook_path = "/webhook/whatsapp"
XMPP / Jabber
Works with any XMPP server (Prosody, ejabberd). Requires uv sync --extra xmpp.
[xmpp]
# Set XMPP_JID and XMPP_PASSWORD env vars
# allowed_jids = ["you@your-server.org"]
# mucs = ["room@conference.server.org"]
# muc_nick = "palmtop"
ScuttleBot
Multi-agent coordination via ScuttleBot IRC backplane. Zero extra dependencies.
[scuttlebot]
# server = "localhost"
# port = 6667
# nick = "palmtop"
# channels = ["#ops", "#engineering"]
# broadcast_tools = true # stream tool calls to coordination channels
Tools
All tools are optional. Configure only what you need.
| Tool | Config key | What it does |
|---|---|---|
| Web search | [search] | Brave, Serper, DuckDuckGo fallback chain |
| Google Calendar | automatic | Read/write events (OAuth) |
[email] | Send/receive via AgentMail | |
| Jira + Confluence | [atlassian] | Search, create, update issues and wiki |
| Vercel | [vercel] | Deploy projects |
| Railway | [railway] | Redeploy services |
| Cursor | [cursor] | Delegate coding tasks to Cursor Cloud Agents |
Web search
[search]
# Fallback chain: serper -> brave -> duckduckgo
# Set via env vars (multiple keys rotate automatically):
# BRAVE_API_KEYS="key1,key2,key3"
# SERPER_API_KEYS="key1,key2,key3"
# preferred_order = ["serper", "brave"]
Email (AgentMail)
[email]
# api_key = "am_..." # or set AGENTMAIL_API_KEY
# inbox_id = "inbox_..." # or set AGENTMAIL_INBOX_ID
Atlassian (Jira + Confluence)
[atlassian]
# domain = "yourcompany.atlassian.net"
# email = "you@example.com"
# Set ATLASSIAN_API_TOKEN env var
Deploy tools
[vercel]
# enabled = true
# default_project_id = "prj_xxxxxxxx"
# require_blessing = true # Telegram approval before deploy
[railway]
# enabled = true
# default_project_id = "uuid"
# default_service_id = "uuid"
# require_blessing = true
MCP servers
Connect to any Model Context Protocol server. The agent acts as an MCP client, launching servers on-demand:
[[mcp.servers]]
name = "atlassian"
description = "Jira and Confluence"
command = ["python", "-m", "palmtop.mcp.atlassian_server"]
[[mcp.servers]]
name = "custom-kb"
description = "Custom knowledge base"
command = ["python", "-m", "my_mcp_server"]
Sovereign engine
The autonomous engine handles multi-step tasks triggered via chat:
[engine]
enabled = true # requires cloud API keys
Trigger from Telegram: /engine draft a blog post or engine: research competitors
Proactive monitor
The agent checks for changes and reaches out when something needs attention:
[monitor]
enabled = true
calendar_interval_minutes = 10
email_interval_minutes = 15
jira_interval_minutes = 30
quiet_hours_start = 22 # 10 PM
quiet_hours_end = 7 # 7 AM
Voice
[voice]
enabled = false
stt_provider = "gemini" # "gemini", "whisper_cpp", "openai"
tts_enabled = false
tts_provider = "gemini"
tts_voice = "Kore" # Gemini voices: Kore, Puck, Charon, etc.
Environment variables
Secrets should be set as environment variables, not in config.toml:
| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY | One of these† | Anthropic API key |
GOOGLE_API_KEY | One of these† | Google AI API key |
OPENAI_API_KEY | No | OpenAI API key |
GROQ_API_KEY | No | Groq API key |
TOGETHER_API_KEY | No | Together AI API key |
FIREWORKS_API_KEY | No | Fireworks AI API key |
DEEPSEEK_API_KEY | No | DeepSeek API key |
MISTRAL_API_KEY | No | Mistral API key |
OPENROUTER_API_KEY | No | OpenRouter API key |
COHERE_API_KEY | No | Cohere API key |
TELEGRAM_BOT_TOKEN | Yes* | From @BotFather |
DISCORD_BOT_TOKEN | No | Discord bot token |
SLACK_BOT_TOKEN | No | Slack Bot User OAuth Token |
SLACK_APP_TOKEN | No | Slack App-Level Token (Socket Mode) |
MATRIX_HOMESERVER | No | Matrix homeserver URL |
MATRIX_USER_ID | No | Matrix bot user ID |
MATRIX_ACCESS_TOKEN | No | Matrix access token |
WHATSAPP_PHONE_NUMBER_ID | No | WhatsApp Business phone number ID |
WHATSAPP_ACCESS_TOKEN | No | Meta Graph API access token |
WHATSAPP_VERIFY_TOKEN | No | Webhook verification token |
XMPP_JID | No | XMPP bot JID (e.g. bot@server.org) |
XMPP_PASSWORD | No | XMPP account password |
AGENTMAIL_API_KEY | No | AgentMail inbox access |
ATLASSIAN_API_TOKEN | No | Jira/Confluence access |
BRAVE_API_KEYS | No | Comma-separated Brave search keys |
SERPER_API_KEYS | No | Comma-separated Serper keys |
VERCEL_TOKEN | No | Vercel deploy access |
RAILWAY_TOKEN | No | Railway deploy access |
CURSOR_API_KEY | No | Cursor Cloud Agents |
* Required only if Telegram is your channel. † At minimum you need one LLM provider key (any of the listed providers) + one channel configured.
Security
Palmtop includes a supply chain audit tool that checks dependencies for known attacks:
# Run locally
python scripts/audit_supply_chain.py --project .
# Strict mode (CI — fails on any finding)
python scripts/audit_supply_chain.py --strict --project .
# With PyPI metadata checks (slower, requires network)
python scripts/audit_supply_chain.py --pypi --project .
The audit runs automatically in CI on every push and pull request. It checks for:
- Known malicious packages (blocklist)
- Typosquatting (edit distance from popular packages)
- Suspicious install scripts (setup.py hooks)
- Yanked versions on PyPI