beadle
A programmable agent daemon controlled by email, secured by GPG.

Beadle is an autonomous agent that receives instructions via email and
executes them as multi-stage pipelines. Commands are programs, the
daemon is the shell, pipelines are pipes, and GPG signatures are sudo.
By design, the owner controls what beadle can do by signing command
definitions, and the trust gate controls who can trigger it. The trust
gate is enforced today; command-signature enforcement is defined but not
yet wired (see below).
How it works. You send an email. Beadle verifies your identity
(PGP signature or Proton E2E encryption), checks your permissions,
decomposes your instruction into a pipeline of commands, and executes
them — mixing AI reasoning with fast CLI tools. You get the result
back as an email reply. The daemon runs on your machine. No cloud
service, no API keys shared with third parties.
Example. You email "summarize this" from your PGP-verified account:
- Beadle verifies your identity and
x (execute) permission
- The planner maps "summarize" to a pipeline:
[summarize, notify, reply]
- Stage 0: Claude reads your email and produces a structured summary (45s)
- Stage 1:
biff wall broadcasts "New summary: Deploy plan" to the team (10ms)
- Stage 2: Claude formats the summary and emails it back to you (45s)
The summary flows through the pipeline as JSON. Stage 1 is a
side-effect (passthrough) — it reads the data but doesn't modify it.
Stage 2 receives the full summary, not stage 1's "ok" output.
Two types of commands:
- Claude commands spawn an AI session for reasoning tasks:
summarization, analysis, code generation. 45-60 seconds per stage.
- CLI commands exec binaries directly for deterministic operations:
notifications, status checks, data transforms. Milliseconds per stage.
Both types are defined as YAML files with a signature field for owner
GPG-signing. Command-signature verification is currently a stub and is
not yet enforced; today the daemon gates execution on transport trust —
a PGP-verified sender matched to a permitted contact — not on the command
definitions themselves. Signature enforcement is the planned next step.
beadle-email
The shipping component is beadle-email — an MCP server that gives
Claude Code a real email address with cryptographic trust at every
layer.
- Claude emails you. Session summaries, build reports, deploy
notifications — anything Claude produces can be mailed to you or
your team. No webhook plumbing, no Slack integration. Just email.
- You email Claude. Send instructions, ask questions, or forward
context from your phone. Beadle's four-level PGP trust model and
per-contact permissions (
rwx) control exactly what Claude can
read, reply to, and act on — nothing is implicit.
Platforms: macOS, Linux
Quick Start
curl -fsSL https://raw.githubusercontent.com/punt-labs/beadle/fc23865/install.sh | sh
Downloads the beadle-email binary, verifies its SHA256 checksum, and attempts to install the Claude Code plugin (MCP tools + slash commands + hooks). If plugin installation fails, the script falls back to registering the standalone MCP server (no slash commands or hooks). Runs doctor to check your setup. Restart Claude Code after install. If you previously registered beadle-email as a standalone MCP server via claude mcp add, remove it first with claude mcp remove beadle-email to avoid duplicate registrations.
Plugin install provides MCP tools, slash commands (/inbox, /mail, /send, /contacts, /beadle), and hooks (two-channel display, automatic session setup). Standalone MCP provides only MCP tools --- no slash commands or hooks.
Inspect before running
curl -fsSL https://raw.githubusercontent.com/punt-labs/beadle/fc23865/install.sh -o install.sh
cat install.sh
sh install.sh
Manual install (other MCP clients)
mkdir -p ~/.local/bin
curl -fsSL https://github.com/punt-labs/beadle/releases/latest/download/beadle-email-darwin-arm64 -o ~/.local/bin/beadle-email
chmod +x ~/.local/bin/beadle-email
Replace darwin-arm64 with your platform: darwin-amd64, linux-arm64, linux-amd64.
Ensure ~/.local/bin is on your PATH. Configure your MCP client to run beadle-email serve.
Prerequisites
- An IMAP server for reading (Proton Bridge on localhost, Fastmail, Gmail IMAP, etc.)
- An SMTP server for sending (Proton Bridge, or any SMTP with STARTTLS support; implicit TLS/SMTPS on port 465 is not yet supported)
- GPG for signature verification
- (Optional) Resend API key for fallback sending
Features
- 23 MCP tools (21 always-on + 2 poll tools gated on config) --- list, search, read, reply, send, mark read/unread (single + batch), move/archive (single + batch), download attachments, verify signatures, inspect MIME, classify trust, list folders, address book (list/find/add/remove contacts), whoami,
switch_identity, enable/disable repo guidance, inbox polling (set interval, get status)
- Multi-identity via ethos --- identity resolved per-request from ethos sidecar. Repo-local config pins identity. Mid-session switching via
switch_identity tool. Fallback to default-identity file
- Two-dimensional trust --- transport trust (trusted/verified/untrusted/unverified) + identity permissions (rwx per contact per identity). Both must pass before autonomous action
- Four-level transport trust --- trusted (Proton-to-Proton E2E), verified (valid PGP), untrusted (bad PGP), unverified (no signature)
- Inline PGP verification ---
list_messages runs gpg --verify on signed messages automatically
- Slash commands (plugin only) ---
/inbox (process your inbox), /mail (email someone), /send (multi-channel outbound), /beadle (enable/disable repo guidance)
- Two-channel display (plugin only) --- compact panel summaries with full data in context, no raw JSON in conversation
- Proton Bridge native --- IMAP STARTTLS for reading, SMTP for sending, Resend API fallback
- Credential isolation --- secrets resolved at runtime from OS keychain, never stored in config files
- Health checks ---
doctor validates all dependencies; status shows active identity and configuration
| Tool |
Purpose |
list_messages |
List messages with trust levels. PGP signatures verified inline. Repo-scoped by default; all_repos widens. Paginate with offset. |
search_messages |
Search the mailbox by from / subject / since / text (server-side IMAP SEARCH). Repo-scoped by default; all_repos widens. |
read_message |
Read full message body, headers, attachments, and trust classification. Does not mark the message read. |
reply_message |
Reply to a message: threads via In-Reply-To/References, preserves the Re: + repo tag, quotes the original. Write-gated like send_email. |
send_email |
Send via Proton Bridge SMTP (primary) or Resend API (fallback). Resolves contact names inline. |
mark_message / batch_mark_messages |
Mark one or many messages read (or \Seen cleared with unread). Reports the count actually changed. |
move_message |
Move a message to another folder. Defaults to Archive. |
batch_move_messages |
Move multiple messages to another folder in one call. Returns the count actually moved. |
list_folders |
List all IMAP mailbox folders. |
show_mime |
Inspect multipart MIME structure, PGP parts, and attachments. |
verify_signature |
Verify PGP signature on a message. Returns signer info and key ID. |
check_trust |
Detailed trust classification with encryption type, origin analysis, and identity permission for sender. |
download_attachment |
Extract an attachment by MIME part index (from show_mime). Saves to identity-scoped directory. |
list_contacts |
List all contacts with effective permissions for the active identity. |
find_contact |
Look up a contact by name, email, or alias. Shows effective permissions. |
add_contact |
Add a contact (name, email, aliases, GPG key ID, permissions). |
remove_contact |
Remove a contact by name. |
whoami |
Return the active identity (email, display name, ethos handle). |
switch_identity |
Switch the active identity for this session. Pass an ethos handle or empty to reset. |
enable |
Turn beadle's repo guidance on or off in this repo (action: enable or disable). Deposits the guide + enabled marker and adds the @.punt-labs/beadle/CLAUDE.md import, or removes them. Same marker as the CLI enable/disable. |
set_poll_interval |
Set automatic inbox polling interval (1m, 5m, 10m, 15m, 30m, 1h, 2h) or disable (n). |
get_poll_status |
Return current polling configuration: interval, active state, last check time, unseen count. |
Commands
Available when installed as a Claude Code plugin.
| Command |
What it does |
/inbox |
Check beadle's email inbox. Optional natural language filter. |
/inbox 5m |
Set inbox polling interval (1m, 5m, 10m, 15m, 30m, 1h, 2h). |
/inbox n |
Disable automatic inbox polling. |
/inbox status |
Show current polling configuration. |
/mail |
Mail something to the owner or a specific recipient. |
/send |
Send via any channel (email today, Signal later). |
/contacts |
Manage address book (list, add with permissions, remove, find). |
/beadle enable / /beadle disable |
Turn beadle's repo guidance on or off in this repo (writes the enabled marker + CLAUDE.md import). Commit the change via a PR. |
Setup
Credential setup
Beadle resolves credentials at runtime through a priority chain: OS keychain → secret file → environment variable. On Linux, the keychain layer tries pass first, then secret-tool (libsecret / GNOME Keyring). On macOS, it uses the system Keychain.
# macOS Keychain (macOS)
security add-generic-password -s beadle -a imap-password -w 'your-bridge-password'
security add-generic-password -s beadle -a resend-api-key -w 'your-resend-key'
security add-generic-password -s beadle -a gpg-passphrase -w 'your-gpg-passphrase'
# pass (Linux, recommended — GPG-encrypted at rest with your own key)
pass insert beadle/imap-password # prompts for the value, hides input
pass insert beadle/resend-api-key
pass insert beadle/gpg-passphrase
# secret-tool (Linux, fallback — GNOME Keyring via libsecret)
secret-tool store --label='beadle imap-password' service beadle account imap-password
secret-tool store --label='beadle resend-api-key' service beadle account resend-api-key
secret-tool store --label='beadle gpg-passphrase' service beadle account gpg-passphrase
# Or secret files (~/.punt-labs/beadle/secrets/<name>, mode 600)
mkdir -p ~/.punt-labs/beadle/secrets
printf '%s' 'your-bridge-password' > ~/.punt-labs/beadle/secrets/imap-password
chmod 600 ~/.punt-labs/beadle/secrets/imap-password
# Or environment variables
export BEADLE_IMAP_PASSWORD='your-bridge-password'
export BEADLE_RESEND_API_KEY='your-resend-key'
Create the configuration file (~/.punt-labs/beadle/email.json) with your connection parameters.
Proton Bridge (localhost):
mkdir -p ~/.punt-labs/beadle
cat > ~/.punt-labs/beadle/email.json << 'EOF'
{
"imap_host": "127.0.0.1",
"imap_port": 1143,
"imap_user": "you@example.com",
"smtp_host": "127.0.0.1",
"smtp_port": 1025,
"smtp_user": "you@example.com",
"from_address": "you@example.com",
"poll_interval": "30m"
}
EOF
External IMAP + SMTP (Fastmail, Gmail, etc.):
{
"imap_host": "imap.fastmail.com",
"imap_port": 143,
"imap_user": "you@fastmail.com",
"smtp_host": "smtp.fastmail.com",
"smtp_port": 587,
"smtp_user": "you@fastmail.com",
"from_address": "you@fastmail.com",
"poll_interval": "10m"
}
Trust Model
| Level |
Sender |
Detection |
What It Means |
trusted |
Proton → Proton |
X-Pm-Content-Encryption: end-to-end + X-Pm-Origin: internal |
End-to-end encrypted by Proton infrastructure |
verified |
External |
gpg --verify returns exit 0 |
Valid PGP signature from a known key |
untrusted |
External |
gpg --verify returns non-zero |
PGP signature present but invalid or from unknown key |
unverified |
External |
No multipart/signed |
No PGP signature present |
PGP verification uses an isolated GNUPGHOME per operation. When no key is attached to the message, keys are bridged from the system keyring (~/.gnupg/) into the isolated environment.
Identity
Beadle reads identity from ethos (sidecar pattern — file reads, no import dependency). Resolution chain:
- Repo-local config —
.punt-labs/ethos.yaml with agent: <handle>
- Global ethos active —
~/.punt-labs/ethos/active
- Handle → ethos identity YAML —
~/.punt-labs/ethos/identities/<handle>.yaml supplies name and email; ~/.punt-labs/ethos/identities/<handle>.ext/beadle.yaml supplies the GPG key id. Always global, never a repo-local vendored copy.
- Default identity —
~/.punt-labs/beadle/default-identity (plain email string)
Each identity gets its own directory under ~/.punt-labs/beadle/identities/<email>/ with separate email.json, contacts.json, and attachments/. Root files are auto-migrated on first use.
Each contact has an optional permissions map keyed by identity email. Permissions use the Unix rwx model:
| Permission |
Meaning |
r (read) |
Beadle reads and surfaces the message. No autonomous action. |
w (write) |
Beadle may compose and send replies to this contact. |
x (execute) |
Beadle may execute instructions from this contact. |
All permissions are stored explicitly. There are no implicit overrides. Contacts without explicit permissions default to --- (no permissions). The address book is a whitelist: messages from unknown senders appear redacted in listings and cannot be read. r and w are enforced; x enforcement is planned. This is orthogonal to transport trust — both must be sufficient for autonomous action.
CLI
beadle-email list [--folder F] [--count N] [--offset N] [--unread] [--all-repos] # List (current repo by default)
beadle-email read <uid> [--folder F] # Read a message (leaves it unread)
beadle-email search [--from A] [--subject S] [--since D] [--text T] [--count N] [--offset N] [--unread] [--all-repos] # Search the mailbox
beadle-email mark <uid> [--ids a,b,c] [--unread] [--folder F] # Mark message(s) read/unread
beadle-email send --to ADDR --subject S --body B # Send an email
beadle-email reply <uid> --body B [--folder F] # Reply to a message (threaded, quoted)
beadle-email move <uid> [--folder F] [--to DEST] # Move a message
beadle-email folders # List IMAP folders
beadle-email contact list|add|remove|find # Manage contacts
beadle-email install # Set up beadle-email (once per machine)
beadle-email uninstall # Remove beadle-email
beadle-email enable # Turn on agent guidance in this repo
beadle-email disable [--purge] # Turn it off (--purge deletes the dir)
beadle-email serve [--config PATH] # Start MCP server
beadle-email doctor [--config PATH] # Check installation health
beadle-email status [--config PATH] # Current state summary
beadle-email identity # Show active identity
beadle-email identity set <handle> # Set per-repo identity
beadle-email version # Print version
# Global flags (supported by most subcommands; not: install, uninstall, version)
beadle-email --json list # JSON output
beadle-email --verbose doctor # Debug logging
beadle-email --quiet send --to ... # Errors only
install is a once-per-machine step (the binary and the MCP server, which is
provided by the beadle marketplace plugin). enable and disable are
per-repo: enable, run inside a repo, deposits beadle's agent user-guide at
.punt-labs/beadle/CLAUDE.md, writes an enabled marker, and adds the single
@.punt-labs/beadle/CLAUDE.md import line to the repo's CLAUDE.md so an agent
working there learns how to drive beadle. disable removes that line and the
marker, leaving the guide dormant; disable --purge deletes the
.punt-labs/beadle/ directory outright. This follows the org
Tool Enable/Disable standard.
When a single mailbox is shared by an agent across repos, list shows only the
current repo's mail by default (matched by the X-Beadle-Repo header or the
[owner/repo] subject tag, with the repo taken from the git origin remote).
Pass --all-repos to see the whole mailbox. If the current directory has no
repo, list shows everything. The background new-mail poller's count is scoped
the same way; the beadle-daemon mission runner is not (it processes owner
commands from every repo).
Documentation
Design Log |
Pipeline v2 Design |
Email Channel Plan |
Changelog
Development
make build # Build beadle-email binary
make check # All quality gates: vet + staticcheck + markdownlint + tests
make lint # Lint only (vet + staticcheck)
make test # Tests only (go test -race)
make dist # Cross-compile for darwin/linux arm64/amd64
make help # List all targets
License
MIT