CRITICAL: TOKENS PAY RENT
Every output token must produce actionable value. Violations:
- •NO PASSIVE SUMMARIES - Regurgitating conversation content without action items, code, or artifacts is FORBIDDEN
- •NO AGREEMENT WITHOUT IMPLEMENTATION - "I agree with X" must be followed by code/file/commit implementing X
- •NO RHETORICAL QUESTIONS - Ask only when you cannot proceed without the answer
- •NO PRAISE/VALIDATION - Skip "great question" / "you're right" - proceed to work
When reviewing message history:
- •Extract ACTION ITEMS → create files, send messages, write code
- •Extract DECISIONS → update configs, create artifacts documenting the decision
- •Extract BLOCKERS → file issues, send follow-up messages
- •NEVER just summarize what was discussed
Enforcement: If output contains summary without artifact, STOP and create the artifact first.
Beeper MCP Skill
Access all messaging networks through Beeper's unified interface.
Quick Start
# Search for a chat mcp__beeper__search "contact name" # Send a message mcp__beeper__send_message chatID="..." text="Hello!" # List recent chats mcp__beeper__search_chats limit=10
Core Tools
| Tool | Purpose |
|---|---|
search | Find chats, groups, or people by name |
search_chats | List/filter chats by type, inbox, activity |
search_messages | Find messages by content (literal match) |
get_chat | Get chat details and participants |
list_messages | Get messages from a specific chat |
send_message | Send text message to a chat |
archive_chat | Archive/unarchive a chat |
set_chat_reminder | Set reminder for a chat |
focus_app | Open Beeper Desktop to specific chat |
Search Guidelines
CRITICAL: Queries are LITERAL WORD MATCHING, not semantic search.
- •RIGHT:
query="dinner"orquery="flight" - •WRONG:
query="dinner plans tonight"orquery="travel arrangements"
Multiple words = ALL must match. Use single keywords.
User Identity Clarification
IMPORTANT: Beeper/Matrix has TWO identifiers per user:
- •Matrix userID:
@username:beeper.com(permanent, searchable) - •Display name: User-chosen name (can differ from userID)
Example: User @jsmith:beeper.com may have display name "John Smith"
When search finds a match:
- •The search matched the userID OR display name
- •Chat participant lists show display names, not userIDs
- •To see userIDs, use
list_messagesand checksenderIDfield
When reporting search results:
- •Cross-reference
list_messagesto mapsenderID↔senderName - •Report as "username (displays as 'Display Name')" for clarity
Resource-Aware Message Processing
CRITICAL: Always work backwards from most recent messages to avoid:
- •Re-processing already-handled tasks
- •Repeating fixed issues
- •Heap exhaustion from loading too much history
Default Workflow (Backwards-First)
// 1. Start with most recent (no cursor = newest first)
const recent = await list_messages(chatID, { limit: 20 });
// 2. Check if already processed (use DuckDB tracking)
const unprocessed = recent.items.filter(msg => !isProcessed(msg.id));
// 3. Process only new messages
for (const msg of unprocessed) {
await processMessage(msg);
markProcessed(msg.id);
}
// 4. If need more history, paginate backwards
if (needsMoreContext) {
const older = await list_messages(chatID, {
cursor: recent.cursor,
direction: 'before',
limit: 20
});
}
DuckDB Tracking Schema
CREATE TABLE IF NOT EXISTS beeper_processed_messages ( message_id VARCHAR PRIMARY KEY, chat_id VARCHAR, processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, task_extracted BOOLEAN, issue_status VARCHAR -- 'open', 'fixed', 'duplicate' ); CREATE INDEX idx_msg_chat ON beeper_processed_messages(chat_id, processed_at DESC);
Check Before Processing
function isAlreadyFixed(messageText) {
// Query DuckDB for similar fixed issues
const similar = db.query(`
SELECT issue_id, description
FROM fixed_issues
WHERE description % ? -- Full-text similarity
LIMIT 1
`, [messageText]);
if (similar.length > 0) {
console.warn(`⚠️ Similar issue already fixed: ${similar[0].description}`);
return true;
}
return false;
}
Workflow Patterns
Find and Message Someone
- •
search "person name"- Get chatID - •Verify identity:
list_messages chatID="..." limit=5- Check recent messages ONLY - •
send_message chatID="..." text="..."
Identify Users in a Chat
- •
list_messages chatID="..." limit=10- Get RECENT messages only (not entire history) - •Map userID ↔ displayName from the subset
Search Message History (Resource-Aware)
- •
search_chatsto get chatIDs of relevant chats - •
search_messages chatIDs=[...] query="keyword" limit=20 dateAfter="2026-01-08T00:00:00Z" - •Never omit dateAfter - prevents loading entire chat history
Monitor Unread
search_chats unreadOnly=true inbox="primary" limit=10
Filter by Network
Use accountIDs parameter after getting accounts via get_accounts.
Message Formatting
Messages support Markdown. Use sparingly for clarity.
Chat Types
- •
single- Direct messages (1:1) - •
group- Group chats - •
any- All types
Inbox Filters
- •
primary- Non-archived, non-low-priority - •
low-priority- Low priority inbox - •
archive- Archived chats
Resource-Aware Random Walk Pattern
NEVER pull full message history into context. Instead:
1. Query in DuckDB First
-- Store messages incrementally, query locally CREATE TABLE IF NOT EXISTS beeper_messages ( id VARCHAR PRIMARY KEY, chat_id VARCHAR, sender_id VARCHAR, sender_name VARCHAR, text TEXT, timestamp TIMESTAMPTZ, processed BOOLEAN DEFAULT FALSE ); -- Sample recent messages via random walk SELECT * FROM beeper_messages WHERE chat_id = ? ORDER BY RANDOM() -- Ergodic sampling LIMIT 5;
2. TreeSitter for Structure Extraction
# Extract code blocks from messages without loading full text tree-sitter parse --scope source.markdown message.md \ | grep -E "(fenced_code_block|code_span)"
3. Triadic Walker Pattern
MINUS (-1): Validate message exists in DuckDB before fetching ERGODIC (0): Random walk sample from local cache PLUS (+1): Fetch ONLY if not in cache, with strict limit
4. Context Budget Enforcement
CONTEXT_BUDGET = 10000 # chars
current_context = 0
def safe_fetch(chat_id, limit=5):
# Check DuckDB cache first
cached = db.query("SELECT * FROM beeper_messages WHERE chat_id = ? LIMIT ?", chat_id, limit)
if len(cached) >= limit:
return cached # Zero network cost
# Fetch only missing, with limit
remaining = limit - len(cached)
fresh = mcp__beeper__list_messages(chatID=chat_id, limit=remaining)
# Enforce budget
for msg in fresh.items:
msg_size = len(msg.get('text', ''))
if current_context + msg_size > CONTEXT_BUDGET:
break
current_context += msg_size
db.insert("beeper_messages", msg)
return db.query("SELECT * FROM beeper_messages WHERE chat_id = ? LIMIT ?", chat_id, limit)
5. SICP Lazy Evaluation
;; Don't fetch until actually needed
(define (beeper-messages chat-id)
(delay
(mcp__beeper__list_messages chatID: chat-id limit: 5)))
;; Only force when required
(define (get-latest-sender chat-id)
(let ((msgs (force (beeper-messages chat-id))))
(cdar msgs))) ; Just sender from first message
GF(3) Integration
This skill operates as ERGODIC (0) in triadic compositions:
- •Coordinates message flow between networks
- •Synthesizes cross-platform conversations
- •Neutral hub for communication triads
Triadic Fetch Strategy
| Trit | Role | Beeper Action |
|---|---|---|
| MINUS (-1) | Validator | Check DuckDB cache, reject if stale |
| ERGODIC (0) | Coordinator | Random walk sample, enforce budget |
| PLUS (+1) | Generator | Fetch fresh data, strict limit param |
Conversation Branch Awareness (Higher-Order Wiring)
Track conversation threads as wiring diagrams - morphisms between topic states.
Branch Detection Schema
CREATE TABLE IF NOT EXISTS beeper_conversation_branches ( branch_id VARCHAR PRIMARY KEY, chat_id VARCHAR NOT NULL, parent_branch_id VARCHAR, -- NULL for root topic VARCHAR NOT NULL, first_message_id VARCHAR, last_message_id VARCHAR, status VARCHAR DEFAULT 'open', -- 'open', 'resolved', 'merged', 'stale' created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, resolved_at TIMESTAMP, FOREIGN KEY (parent_branch_id) REFERENCES beeper_conversation_branches(branch_id) ); CREATE TABLE IF NOT EXISTS beeper_branch_transitions ( from_branch VARCHAR, to_branch VARCHAR, transition_type VARCHAR, -- 'fork', 'merge', 'abandon', 'resolve' message_id VARCHAR, -- message that triggered transition timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (from_branch, to_branch, message_id) );
Wiring Diagram Structure
Conversation as Category:
- Objects: Topic states (branches)
- Morphisms: Message sequences that transform one topic to another
- Composition: Thread continuation
┌─────────────────┐
│ patents (open) │◄──── current focus
└────────┬────────┘
│ fork @ msg:838941332
┌───────────────┼───────────────┐
▼ ▼ ▼
┌────────┐ ┌───────────┐ ┌──────────┐
│ GF(3) │ │bisimulation│ │ toad OOM │
│resolved│ │ resolved │ │ open │
└────────┘ └───────────┘ └──────────┘
Branch Detection Heuristics
def detect_branch(messages: list) -> list[Branch]:
branches = []
current_topic = None
for msg in messages:
# Topic markers
if msg.text.startswith('**Re:') or msg.text.startswith('Re:'):
# Explicit reply = potential branch
topic = extract_topic(msg.text)
if topic != current_topic:
branches.append(Branch(
topic=topic,
fork_message=msg.id,
parent=current_topic
))
# Numbered lists often indicate parallel threads
if re.match(r'^\d+\.', msg.text):
items = extract_numbered_items(msg.text)
for item in items:
branches.append(Branch(topic=item, parent=current_topic))
# Questions create potential branches
if msg.text.strip().endswith('?'):
branches.append(Branch(
topic=f"Q: {msg.text[:50]}",
status='awaiting_response'
))
return branches
Zigger Chat Branch State
Track active branches in zigger conversation:
-- Query current branch state for a chat SELECT b.branch_id, b.topic, b.status, COUNT(t.to_branch) as child_count FROM beeper_conversation_branches b LEFT JOIN beeper_branch_transitions t ON b.branch_id = t.from_branch WHERE b.chat_id = '!NhltGRLZWLUeHEBiFT:beeper.com' -- zigger GROUP BY b.branch_id ORDER BY b.created_at DESC;
Before Responding: Check Branch Context
def get_branch_context(chat_id: str) -> dict:
"""Always call before responding to understand conversation topology."""
# Get open branches
open_branches = db.query("""
SELECT topic, status, first_message_id
FROM beeper_conversation_branches
WHERE chat_id = ? AND status = 'open'
""", chat_id)
# Get unresolved questions
questions = db.query("""
SELECT topic FROM beeper_conversation_branches
WHERE chat_id = ? AND topic LIKE 'Q:%' AND status = 'awaiting_response'
""", chat_id)
return {
'open_branches': open_branches,
'unanswered_questions': questions,
'should_address': questions[0] if questions else open_branches[0]
}
Wiring Composition Rules
- •Fork: One message spawns multiple topics → create child branches
- •Merge: Response addresses multiple branches → mark as merged
- •Resolve: Explicit closure ("done", "fixed", "shipped") → mark resolved
- •Abandon: No activity for 7 days → mark stale
Integration with Tokens Pay Rent
When reviewing messages, branch tracking prevents:
- •Re-answering resolved questions
- •Missing open threads
- •Losing context on forked discussions
Update branch state as side effect of every beeper interaction.
Sending Files via CLI
The MCP send_message tool only supports text. For file attachments, use matrix-commander CLI.
Prerequisites
# Install matrix-commander via nix (requires olm exception)
mkdir -p ~/.config/nixpkgs
echo '{ permittedInsecurePackages = [ "olm-3.2.16" ]; }' > ~/.config/nixpkgs/config.nix
nix-env -iA nixpkgs.matrix-commander
Setup Credentials (One-Time)
Extract Matrix credentials from Beeper's local database:
# Get credentials from Beeper's account.db
sqlite3 ~/Library/Application\ Support/BeeperTexts/account.db "SELECT * FROM account;"
# Output format: user_id|device_id|access_token|homeserver
# Create matrix-commander credentials
mkdir -p ~/.config/matrix-commander/store
cat > ~/.config/matrix-commander/credentials.json << 'EOF'
{
"homeserver": "https://matrix.beeper.com",
"user_id": "@YOUR_USER:beeper.com",
"device_id": "YOUR_DEVICE_ID",
"access_token": "syt_YOUR_TOKEN",
"room_id": ""
}
EOF
Find Chat Room ID
# Use Beeper MCP to search for chat
BEEPER_ACCESS_TOKEN=$(/Users/alice/.cargo/bin/fnox get BEEPER_ACCESS_TOKEN --age-key-file /Users/alice/.age/key.txt) \
curl -s -H "Authorization: Bearer $BEEPER_ACCESS_TOKEN" \
"http://[::1]:23373/v1/chats?limit=100" | \
python3 -c "import json,sys; [print(f\"{c['title']}: {c['id']}\") for c in json.load(sys.stdin)['items'] if 'TARGET' in c.get('title','').lower()]"
Send Files
# Send single file with message ~/.nix-profile/bin/matrix-commander \ -c ~/.config/matrix-commander/credentials.json \ -s ~/.config/matrix-commander/store \ --room '!ROOM_ID:beeper.com' \ -f /path/to/file.ext \ -m "Description of file" # Send multiple files ~/.nix-profile/bin/matrix-commander \ -c ~/.config/matrix-commander/credentials.json \ -s ~/.config/matrix-commander/store \ --room '!ROOM_ID:beeper.com' \ -f /path/to/file1.pdf /path/to/file2.db \ -m "Attached files"
Quick Send Function
Add to shell config (~/.bashrc or ~/.zshrc):
beeper-send-file() {
local room_id="$1"
local file="$2"
local message="${3:-Sent via CLI}"
~/.nix-profile/bin/matrix-commander \
-c ~/.config/matrix-commander/credentials.json \
-s ~/.config/matrix-commander/store \
--room "$room_id" \
-f "$file" \
-m "$message" 2>&1 | tail -3
}
# Usage: beeper-send-file '!NhltGRLZWLUeHEBiFT:beeper.com' /tmp/data.db "Database backup"
Common Room IDs
Query and cache frequently-used room IDs:
# Save to ~/.config/beeper-rooms.txt
cat > ~/.config/beeper-rooms.txt << 'EOF'
barton-matrix=!NhltGRLZWLUeHEBiFT:beeper.com
# Add more as needed
EOF
# Helper to lookup
beeper-room() {
grep "^$1=" ~/.config/beeper-rooms.txt | cut -d= -f2
}
# Usage: beeper-send-file "$(beeper-room barton-matrix)" /tmp/file.db
Troubleshooting
- •"Missing session" warnings: Normal for encrypted rooms with many devices, file still sends
- •E147 errors: Check file exists and is readable
- •M_UNKNOWN_TOKEN: Re-extract token from Beeper's account.db (it may have rotated)
MCP Server Config
{
"beeper": {
"command": "/bin/sh",
"args": [
"-c",
"BEEPER_ACCESS_TOKEN=$(/Users/alice/.cargo/bin/fnox get BEEPER_ACCESS_TOKEN --age-key-file /Users/alice/.age/key.txt) exec npx -y @beeper/desktop-mcp"
]
}
}
Requires:
- •
fnoxat/Users/alice/.cargo/bin/fnox - •Age key at
/Users/alice/.age/key.txt - •
npxin PATH - •Beeper Desktop running
- •For file sending:
matrix-commandervia nix