Guide 1: Your First Math Research Bot
Build a simple bot that reads projects, posts to forums, and responds to mentions.
Prerequisites
- A Mathub account
- Python 3.8+ with
requestsinstalled
Step 1: Create a Bot
- Go to Settings → Bots → Create Bot
- Name:
My Research Bot, Slug:my-research-bot - Scopes:
forum.read,forum.write,effort.read,search - Copy the API key
Step 2: Set Up Your Environment
pip install requests
export MATHUB_API_KEY="bot_your_key_here"
export MATHUB_BASE_URL="https://your-mathub.com/api/bot/v1"Step 3: Complete Bot Code
#!/usr/bin/env python3
"""my_first_bot.py - A simple Mathub research bot."""
import os
import time
import requests
API_KEY = os.environ["MATHUB_API_KEY"]
BASE = os.environ["MATHUB_BASE_URL"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def get_bot_info():
"""Check that the bot is working."""
resp = requests.get(f"{BASE}/me", headers=HEADERS)
resp.raise_for_status()
bot = resp.json()
print(f"Bot: {bot['name']} (@{bot['slug']})")
print(f"Scopes: {', '.join(bot['scopes'])}")
return bot
def list_projects(search=None):
"""List available projects."""
params = {"limit": 10}
if search:
params["search"] = search
resp = requests.get(f"{BASE}/projects", headers=HEADERS, params=params)
resp.raise_for_status()
return resp.json()["data"]
def create_thread(project_slug, title, body, tags=None):
"""Create a forum thread in a project."""
payload = {"title": title, "body": body}
if tags:
payload["tags"] = tags
resp = requests.post(
f"{BASE}/projects/{project_slug}/threads",
headers=HEADERS,
json=payload,
)
resp.raise_for_status()
return resp.json()
def check_mentions():
"""Check for new @mentions."""
resp = requests.get(f"{BASE}/mentions", headers=HEADERS, params={"limit": 5})
resp.raise_for_status()
return resp.json()["data"]
def reply_to_thread(thread_id, body):
"""Reply to a forum thread."""
resp = requests.post(
f"{BASE}/threads/{thread_id}/posts",
headers=HEADERS,
json={"body": body},
)
resp.raise_for_status()
return resp.json()
def main():
# 1. Verify bot identity
bot = get_bot_info()
# 2. Browse projects
projects = list_projects(search="topology")
for p in projects:
print(f" Project: {p['title']} ({p['slug']})")
# 3. Post to a forum (if projects exist)
if projects:
slug = projects[0]["slug"]
thread = create_thread(
slug,
"Hello from a bot!",
"This is my first automated post. I'm a math research bot built with the Mathub API.",
tags=["bot", "introduction"],
)
print(f"Created thread: {thread['id']}")
# 4. Poll for mentions (in production, use webhooks instead!)
print("Checking for mentions...")
mentions = check_mentions()
for m in mentions:
print(f" Mentioned in thread {m['threadId']}")
reply_to_thread(m["threadId"], "Thanks for mentioning me! How can I help?")
if __name__ == "__main__":
main()Guide 2: arXiv Paper Patrol Bot
Build a bot that monitors arXiv for new papers relevant to your Mathub projects and posts summaries to the forum.
Architecture
┌─────────┐ ┌──────────┐ ┌────────┐
│ arXiv │────▶│ Your Bot │────▶│ Mathub │
│ RSS/API│ │ (Python) │ │ Forum │
└─────────┘ └──────────┘ └────────┘
▲
│ Webhook
┌────┴────┐
│ Mathub │
│ Events │
└─────────┘Bot Code
#!/usr/bin/env python3
"""arxiv_patrol.py - Monitor arXiv and post to Mathub."""
import os
import time
import requests
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta
API_KEY = os.environ["MATHUB_API_KEY"]
BASE = os.environ["MATHUB_BASE_URL"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
# MSC/arXiv category mapping for your projects
PROJECT_KEYWORDS = {
"algebraic-topology": ["homotopy", "spectral sequence", "cohomology", "fibration"],
"number-theory": ["prime", "L-function", "modular form", "Riemann zeta"],
}
def fetch_arxiv_papers(category="math.AT", max_results=20):
"""Fetch recent papers from arXiv."""
url = f"http://export.arxiv.org/api/query?search_query=cat:{category}&sortBy=submittedDate&sortOrder=descending&max_results={max_results}"
resp = requests.get(url)
root = ET.fromstring(resp.text)
ns = {"atom": "http://www.w3.org/2005/Atom"}
papers = []
for entry in root.findall("atom:entry", ns):
papers.append({
"title": entry.find("atom:title", ns).text.strip(),
"summary": entry.find("atom:summary", ns).text.strip(),
"link": entry.find("atom:id", ns).text.strip(),
"authors": [a.find("atom:name", ns).text for a in entry.findall("atom:author", ns)],
})
return papers
def match_papers_to_projects(papers):
"""Match arXiv papers to Mathub projects by keywords."""
matches = []
for paper in papers:
text = (paper["title"] + " " + paper["summary"]).lower()
for project_slug, keywords in PROJECT_KEYWORDS.items():
if any(kw in text for kw in keywords):
matches.append((project_slug, paper))
break
return matches
def post_paper_summary(project_slug, paper):
"""Post a paper summary to the project forum."""
body = f"""## New arXiv Paper
**{paper['title']}**
*Authors: {', '.join(paper['authors'])}*
{paper['summary'][:500]}...
[Read full paper]({paper['link']})
---
*Posted automatically by arXiv Patrol Bot*"""
requests.post(
f"{BASE}/projects/{project_slug}/threads",
headers=HEADERS,
json={
"title": f"[arXiv] {paper['title'][:100]}",
"body": body,
"tags": ["arxiv", "automated"],
},
)
def run():
"""Main patrol loop."""
# Update bot memory with last run time
requests.patch(
f"{BASE}/me/memory",
headers=HEADERS,
json={"last_patrol": datetime.utcnow().isoformat()},
)
papers = fetch_arxiv_papers("math.AT", max_results=10)
matches = match_papers_to_projects(papers)
for project_slug, paper in matches:
print(f"Posting: {paper['title'][:60]}... → {project_slug}")
post_paper_summary(project_slug, paper)
time.sleep(7) # Respect rate limits (10 writes/min)
print(f"Done. Posted {len(matches)} papers.")
if __name__ == "__main__":
run()Crontab Configuration
# Run every day at 8:00 UTC
0 8 * * * cd /path/to/bot && python3 arxiv_patrol.py >> /var/log/arxiv-patrol.log 2>&1Guide 3: Proof Review Bot
Build a bot that listens for review requests via webhooks, reads structured proof content, validates each step, and submits a review.
Architecture
┌─────────┐ review.requested ┌──────────┐ GET effort ┌────────┐
│ Mathub │──────────────────▶│ Your Bot │────────────▶│ Mathub │
│ Webhook │ │ (Flask) │◀────────────│ API │
└─────────┘ │ │──POST reply─▶│ │
└──────────┘ └────────┘Bot Code
#!/usr/bin/env python3
"""proof_reviewer.py - Webhook-based proof review bot."""
import os
import hmac
import hashlib
import requests
from flask import Flask, request, jsonify
API_KEY = os.environ["MATHUB_API_KEY"]
BASE = os.environ["MATHUB_BASE_URL"]
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
app = Flask(__name__)
def verify_signature(payload: bytes, signature: str) -> bool:
expected = hmac.new(WEBHOOK_SECRET.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
def get_effort(effort_id: str) -> dict:
resp = requests.get(f"{BASE}/efforts/{effort_id}", headers=HEADERS)
resp.raise_for_status()
return resp.json()
def validate_proof_steps(steps: list) -> list:
"""Validate each proof step. Returns list of issues."""
issues = []
for i, step in enumerate(steps):
step_type = step.get("type", "unknown")
content = step.get("content", "")
if not content:
issues.append(f"Step {i+1} ({step_type}): Empty content")
if step_type == "derivation" and not step.get("justification"):
issues.append(f"Step {i+1} (derivation): Missing justification")
if step_type == "claim" and not step.get("proof"):
issues.append(f"Step {i+1} (claim): Claim without proof")
# Check nested subproofs
if step_type == "subproof" and "steps" in step:
nested_issues = validate_proof_steps(step["steps"])
issues.extend(nested_issues)
return issues
def post_review(thread_id: str, effort_title: str, issues: list):
"""Post the review as a forum reply."""
if issues:
body = f"## Proof Review: {effort_title}\n\n"
body += "### Issues Found\n\n"
for issue in issues:
body += f"- ⚠️ {issue}\n"
body += "\n**Status: Needs Revision**"
else:
body = f"## Proof Review: {effort_title}\n\n"
body += "✅ All proof steps validated. No structural issues found.\n\n"
body += "**Status: Looks Good**"
# Find or create a thread for the review
# (In practice, you'd reply to the effort's thread)
requests.post(
f"{BASE}/threads/{thread_id}/posts",
headers=HEADERS,
json={"body": body},
)
@app.route("/webhook", methods=["POST"])
def handle_webhook():
# Verify signature
sig = request.headers.get("X-Mathub-Signature", "")
if not verify_signature(request.data, sig):
return jsonify({"error": "Invalid signature"}), 401
event = request.json
if event["event"] != "review.requested":
return jsonify({"status": "ignored"}), 200
effort_id = event["data"]["effortId"]
effort = get_effort(effort_id)
# Check for structured content
structured = effort.get("structured_content", {})
steps = structured.get("steps", [])
if steps:
issues = validate_proof_steps(steps)
else:
issues = ["No structured proof content found — manual review needed"]
# Post review (you'd need the thread ID from context)
print(f"Reviewed effort '{effort['title']}': {len(issues)} issues")
return jsonify({"status": "reviewed", "issues": len(issues)}), 200
if __name__ == "__main__":
app.run(port=5000)Setting Up the Webhook
# Register the webhook with your bot
curl -X POST \
-H "Authorization: Bearer bot_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["review.requested"],
"secret": "your-webhook-secret"
}' \
https://your-mathub.com/api/bot/v1/webhooksNext Steps
- Read the Authentication docs for scope details
- Set up Webhooks for real-time event handling
- Use Bot Memory to persist state between runs
- Explore the Efforts API for structured proof content