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

ProviderEnv varDefault modelNotes
anthropicANTHROPIC_API_KEYclaude-haiku-4-5Claude models
googleGOOGLE_API_KEYgemini-2.5-flashGemini models
openaiOPENAI_API_KEYgpt-4o-miniGPT-4o, o3
groqGROQ_API_KEYllama-3.3-70b-versatileUltra-fast inference
togetherTOGETHER_API_KEYMeta-Llama-3.1-70BOpen-source hosting
fireworksFIREWORKS_API_KEYllama-v3p1-70bFast open-source
deepseekDEEPSEEK_API_KEYdeepseek-chatDeepSeek-V3, R1
mistralMISTRAL_API_KEYmistral-large-latestMistral Large, Codestral
openrouterOPENROUTER_API_KEYanthropic/claude-sonnet-4100+ models, one key
ollamanonellama3.2Local HTTP, no key needed
cohereCOHERE_API_KEYcommand-r-plusCommand 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)

WhatsApp

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.

ToolConfig keyWhat it does
Web search[search]Brave, Serper, DuckDuckGo fallback chain
Google CalendarautomaticRead/write events (OAuth)
Email[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:

VariableRequiredDescription
ANTHROPIC_API_KEYOne of these†Anthropic API key
GOOGLE_API_KEYOne of these†Google AI API key
OPENAI_API_KEYNoOpenAI API key
GROQ_API_KEYNoGroq API key
TOGETHER_API_KEYNoTogether AI API key
FIREWORKS_API_KEYNoFireworks AI API key
DEEPSEEK_API_KEYNoDeepSeek API key
MISTRAL_API_KEYNoMistral API key
OPENROUTER_API_KEYNoOpenRouter API key
COHERE_API_KEYNoCohere API key
TELEGRAM_BOT_TOKENYes*From @BotFather
DISCORD_BOT_TOKENNoDiscord bot token
SLACK_BOT_TOKENNoSlack Bot User OAuth Token
SLACK_APP_TOKENNoSlack App-Level Token (Socket Mode)
MATRIX_HOMESERVERNoMatrix homeserver URL
MATRIX_USER_IDNoMatrix bot user ID
MATRIX_ACCESS_TOKENNoMatrix access token
WHATSAPP_PHONE_NUMBER_IDNoWhatsApp Business phone number ID
WHATSAPP_ACCESS_TOKENNoMeta Graph API access token
WHATSAPP_VERIFY_TOKENNoWebhook verification token
XMPP_JIDNoXMPP bot JID (e.g. bot@server.org)
XMPP_PASSWORDNoXMPP account password
AGENTMAIL_API_KEYNoAgentMail inbox access
ATLASSIAN_API_TOKENNoJira/Confluence access
BRAVE_API_KEYSNoComma-separated Brave search keys
SERPER_API_KEYSNoComma-separated Serper keys
VERCEL_TOKENNoVercel deploy access
RAILWAY_TOKENNoRailway deploy access
CURSOR_API_KEYNoCursor 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: