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 locallyCREATE TABLE IF NOTEXISTS beeper_messages (
id VARCHARPRIMARY KEY,
chat_id VARCHAR,
sender_id VARCHAR,
sender_name VARCHAR,
text TEXT,
timestamp TIMESTAMPTZ,
processed BOOLEANDEFAULTFALSE
);
-- Sample recent messages via random walkSELECT*FROM beeper_messages
WHERE chat_id = ?
ORDERBY 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 = 0defsafe_fetch(chat_id, limit=5):
# Check DuckDB cache first
cached = db.query("SELECT * FROM beeper_messages WHERE chat_id = ? LIMIT ?", chat_id, limit)
iflen(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 budgetfor 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:
-- Query current branch state for a chatSELECT
b.branch_id,
b.topic,
b.status,
COUNT(t.to_branch) as child_count
FROM beeper_conversation_branches b
LEFTJOIN beeper_branch_transitions t ON b.branch_id = t.from_branch
WHERE b.chat_id ='!NhltGRLZWLUeHEBiFT:beeper.com'-- ziggerGROUPBY b.branch_id
ORDERBY b.created_at DESC;
Before Responding: Check Branch Context
defget_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.
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"]}}