Skip to content

Instantly share code, notes, and snippets.

@jacoblyles
Last active February 28, 2026 23:07
Show Gist options
  • Select an option

  • Save jacoblyles/161bfe8cb8f08331c6e255e1fb175ab6 to your computer and use it in GitHub Desktop.

Select an option

Save jacoblyles/161bfe8cb8f08331c6e255e1fb175ab6 to your computer and use it in GitHub Desktop.
MemoryWhole X Bot — Design Document #pagedrop
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MemoryWhole X Bot — Design Document</title>
<style>
:root { --bg: #0d1117; --fg: #c9d1d9; --accent: #58a6ff; --accent2: #f78166; --card: #161b22; --border: #30363d; --green: #3fb950; --yellow: #d29922; --red: #f85149; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.6; padding: 2rem; max-width: 900px; margin: 0 auto; }
h1 { color: #fff; font-size: 2rem; margin-bottom: 0.5rem; }
h1 span { color: var(--accent); }
.subtitle { color: #8b949e; font-size: 1.1rem; margin-bottom: 2rem; }
h2 { color: var(--accent); font-size: 1.4rem; margin: 2rem 0 1rem; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
h3 { color: #fff; font-size: 1.1rem; margin: 1.2rem 0 0.5rem; }
p, li { color: var(--fg); margin-bottom: 0.5rem; }
ul, ol { padding-left: 1.5rem; margin-bottom: 1rem; }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 1.2rem; margin: 1rem 0; }
.flow { display: grid; grid-template-columns: 1fr; gap: 1rem; margin: 1rem 0; }
.flow-step { background: var(--card); border-left: 3px solid var(--accent); padding: 1rem; border-radius: 0 6px 6px 0; }
.flow-step.approve { border-left-color: var(--green); }
.flow-step.queue { border-left-color: var(--yellow); }
.flow-step.reject { border-left-color: var(--red); }
.flow-step .step-num { display: inline-block; background: var(--accent); color: var(--bg); border-radius: 50%; width: 24px; height: 24px; text-align: center; font-size: 0.85rem; font-weight: 700; line-height: 24px; margin-right: 0.5rem; }
.flow-step.approve .step-num { background: var(--green); }
.flow-step.queue .step-num { background: var(--yellow); }
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
th { background: var(--card); color: var(--accent); text-align: left; padding: 0.6rem 0.8rem; border-bottom: 2px solid var(--border); }
td { padding: 0.6rem 0.8rem; border-bottom: 1px solid var(--border); }
code { background: #1c2128; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.9em; color: var(--accent2); }
.tag { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 12px; font-size: 0.8rem; font-weight: 600; margin-right: 0.3rem; }
.tag-open { background: rgba(248,129,73,0.15); color: var(--accent2); border: 1px solid rgba(248,129,73,0.3); }
.tag-decided { background: rgba(63,185,80,0.15); color: var(--green); border: 1px solid rgba(63,185,80,0.3); }
.tag-question { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid rgba(210,153,34,0.3); }
.question-list { list-style: none; padding: 0; }
.question-list li { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; margin: 0.8rem 0; }
.question-list li strong { color: #fff; }
.priority { display: inline-block; font-size: 0.75rem; font-weight: 700; padding: 0.1rem 0.5rem; border-radius: 4px; text-transform: uppercase; margin-left: 0.5rem; }
.p-blocker { background: var(--red); color: #fff; }
.p-important { background: var(--yellow); color: var(--bg); }
.p-nice { background: #388bfd26; color: var(--accent); border: 1px solid var(--accent); }
.diagram { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin: 1rem 0; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 0.85rem; white-space: pre; overflow-x: auto; line-height: 1.5; color: #8b949e; }
.diagram .hl { color: var(--accent); }
.diagram .gr { color: var(--green); }
.diagram .yl { color: var(--yellow); }
.diagram .rd { color: var(--red); }
</style>
</head>
<body>
<h1>📰 MemoryWhole <span>X Bot</span></h1>
<p class="subtitle">Design document for an X/Twitter bot that feeds memorywhole.tv</p>
<h2>Overview</h2>
<p>A bot account on X that anyone can @ to suggest stories for the MemoryWhole wiki. Whitelisted users get auto-published articles; everyone else goes into a review queue.</p>
<h2>How It Works</h2>
<div class="diagram">
User tweets: <span class="hl">@MemoryWholeBot</span> check this out [link/quote-tweet]
┌──────────────────┐
│ <span class="hl">Mention Detected</span> │ (polling every 5 min)
└────────┬─────────┘
┌──────────────────┐ ┌──────────────────────────────┐
│ <span class="yl">Whitelisted?</span> │──▶ │ <span class="gr">YES</span>: Research → Draft → Publish │
└────────┬─────────┘ └──────────────────────────────┘
│ NO
┌────────────────────────────────────────┐
│ <span class="yl">Research → Draft → Category:Proposed</span> │
│ Notify Jacob via Matrix │
└────────────────────────────────────────┘
Jacob reviews → <span class="gr">Approve</span> (publish) or <span class="rd">Reject</span> (delete)
</div>
<h2>Bot Commands</h2>
<table>
<tr><th>Command</th><th>Who</th><th>Action</th></tr>
<tr><td><code>@bot</code> (reply to tweet)</td><td>Anyone</td><td>Submit story for wiki</td></tr>
<tr><td><code>@bot judicial</code></td><td>Anyone</td><td>Tag as Judicial Watch category</td></tr>
<tr><td><code>@bot virginia</code></td><td>Anyone</td><td>Tag as Virginia Politics</td></tr>
<tr><td><code>@bot approve [title]</code></td><td>Whitelist only</td><td>Approve proposed article</td></tr>
<tr><td><code>@bot reject [title]</code></td><td>Whitelist only</td><td>Remove from proposed queue</td></tr>
<tr><td><code>@bot status</code></td><td>Whitelist only</td><td>Reply with queue stats</td></tr>
</table>
<h2>Article Pipeline</h2>
<div class="flow">
<div class="flow-step">
<span class="step-num">1</span>
<strong>Detect mention</strong> — Bot polls X API for new mentions. Extracts the parent tweet, any linked URLs, quote-tweet content, and thread context.
</div>
<div class="flow-step">
<span class="step-num">2</span>
<strong>Research</strong> — Trooper agent searches for additional sources (Brave Search), grabs primary documents, checks for existing wiki coverage.
</div>
<div class="flow-step">
<span class="step-num">3</span>
<strong>Draft article</strong> — Generates wiki markup with proper citations, categories, and neutral tone per MemoryWhole editorial standards.
</div>
<div class="flow-step approve">
<span class="step-num">4a</span>
<strong>Whitelisted → Auto-publish</strong> — Article goes live. Bot replies on X with the wiki link. Matrix notification sent to Jacob.
</div>
<div class="flow-step queue">
<span class="step-num">4b</span>
<strong>Public → Proposed queue</strong> — Article saved to <code>Category:Proposed Articles</code> with "Review Pending" tag. Bot replies "Thanks! Submitted for review." Matrix notification sent to Jacob.
</div>
</div>
<h2>Architecture</h2>
<div class="card">
<h3>Option A: OpenClaw Cron (Recommended for v1)</h3>
<ul>
<li>OpenClaw heartbeat/cron triggers Trooper every 5 minutes</li>
<li>Trooper checks X API for new mentions since last check</li>
<li>Processes each mention through the research pipeline</li>
<li>Publishes to wiki via MediaWiki API (already working)</li>
<li>Replies on X via API</li>
<li>Notifies Jacob via Matrix</li>
</ul>
<p><strong>Pros:</strong> No new infrastructure. Uses existing Trooper agent, wiki auth, and research capabilities.</p>
<p><strong>Cons:</strong> 5-min latency. Trooper agent must be available (not mid-task).</p>
</div>
<div class="card">
<h3>Option B: Dedicated Bot Service</h3>
<ul>
<li>Standalone Node.js/Python service on the server</li>
<li>Listens for mentions via X API streaming or polling</li>
<li>Calls Trooper agent via OpenClaw ACP for research/drafting</li>
<li>Handles X replies and wiki publishing independently</li>
</ul>
<p><strong>Pros:</strong> Always available, faster response, cleaner separation.</p>
<p><strong>Cons:</strong> More infrastructure to maintain. Needs deployment on the server.</p>
</div>
<h2>Whitelist Management</h2>
<div class="card">
<p>Stored in a config file at <code>/Users/argos/dev/trooper/config/whitelist.json</code></p>
<pre style="margin-top:0.5rem;color:var(--fg);">{
"auto_publish": [
"@cryptochamomile"
],
"admin": [
"@cryptochamomile"
]
}</pre>
<p style="margin-top:0.5rem;">Editable without redeploying. <code>auto_publish</code> = articles go live immediately. <code>admin</code> = can approve/reject proposed articles.</p>
</div>
<h2>Review Queue</h2>
<div class="card">
<h3>Wiki Side</h3>
<ul>
<li>Proposed articles live in <code>Category:Proposed Articles</code></li>
<li>Each has a banner: "This article was submitted by @username and is pending editorial review"</li>
<li>Category page serves as the dashboard</li>
</ul>
<h3>Approval Methods</h3>
<ul>
<li><strong>Via X:</strong> Reply to the bot's confirmation tweet with <code>@bot approve</code></li>
<li><strong>Via Matrix:</strong> Tell Trooper "approve [article name]"</li>
<li><strong>Via Wiki:</strong> Remove the Proposed category and add the real category manually</li>
</ul>
</div>
<h2>X API Requirements</h2>
<div class="card">
<h3>🆕 Pay-Per-Use Tier (Launched Feb 2026)</h3>
<p>X officially launched <strong>pay-per-use API pricing</strong> in February 2026. No more $200/mo minimum. You buy credits upfront and pay per API call, with endpoint-specific pricing visible in the Developer Console.</p>
<ul>
<li><strong>How it works:</strong> Purchase credits → balance decreases per API call → set spending limits per billing cycle</li>
<li><strong>Duplicate protection:</strong> Retrieving the same post/user multiple times in one day generally doesn't double-charge (with exceptions)</li>
<li><strong>Safety:</strong> Auto-top-up when balance is low, or hard spending cap that stops requests at limit</li>
<li><strong>Legacy free tier users:</strong> Get a one-time $10 voucher on switch</li>
<li><strong>Old plans still available:</strong> Basic ($200/mo) and Pro ($5k/mo) remain as options</li>
</ul>
</div>
<div class="card">
<h3>Estimated Cost for MemoryWhole Bot</h3>
<table>
<tr><th>Operation</th><th>Frequency</th><th>Notes</th></tr>
<tr><td>Poll mentions</td><td>~288/day (every 5 min)</td><td>Single endpoint call each</td></tr>
<tr><td>Read tweet content</td><td>~5-20/day</td><td>When processing submissions</td></tr>
<tr><td>Post replies</td><td>~5-20/day</td><td>Confirmation tweets</td></tr>
<tr><td>User lookup</td><td>~5-20/day</td><td>Whitelist checking</td></tr>
</table>
<p style="margin-top:0.8rem;"><strong>Estimated monthly cost: $5–15/mo</strong> at typical volumes. Orders of magnitude cheaper than the old $200/mo Basic tier.</p>
</div>
<p><strong>Setup:</strong> Register at <a href="https://developer.x.com/#pricing" style="color:var(--accent)">developer.x.com</a>, create a project + app, get API keys, buy initial credits ($10–25 to start).</p>
<h2>🔴 Open Questions</h2>
<ul class="question-list">
<li>
<strong>Q1: Bot X Account</strong> <span class="priority p-blocker">Blocker</span><br>
Do you have an X account for the bot, or do we need to create one? Name ideas: @MemoryWholeBot, @MemoryWholeTV, @memorywhole_bot?
</li>
<li>
<strong>Q2: X API Setup</strong> <span class="priority p-blocker">Blocker</span><br>
Need to register at developer.x.com with the bot account and buy initial credits (~$10–25). Pay-per-use pricing means this should run ~$5–15/mo instead of the old $200/mo. Do you want to set this up, or should I walk you through it?
</li>
<li>
<strong>Q3: Architecture Choice</strong> <span class="priority p-important">Important</span><br>
Option A (OpenClaw cron, simpler, 5-min delay) or Option B (dedicated service, faster, more work)? I recommend A to start.
</li>
<li>
<strong>Q4: Who else on the whitelist?</strong> <span class="priority p-nice">Nice to decide</span><br>
Just you for now, or are there other accounts that should auto-publish?
</li>
<li>
<strong>Q5: Bot personality on X</strong> <span class="priority p-nice">Nice to decide</span><br>
Should bot replies be dry and informational ("Article published: [link]") or have some personality ("📰 Archived. This one's going in the vault. [link]")?
</li>
<li>
<strong>Q6: Rate limiting for public submissions</strong> <span class="priority p-important">Important</span><br>
If the bot gets popular, should we cap submissions per user? e.g., max 5 proposed articles per day per account to prevent spam.
</li>
<li>
<strong>Q7: Duplicate detection</strong> <span class="priority p-nice">Nice to decide</span><br>
Should the bot check if a story is already on the wiki before creating a new article? (I'd say yes — match by URL and key entities.)
</li>
<li>
<strong>Q8: What triggers an article vs. what gets ignored?</strong> <span class="priority p-important">Important</span><br>
If someone @'s the bot with just "hey" or a meme, it shouldn't create an article. Rules: require a URL or quote-tweet? Require minimum text? Your call.
</li>
</ul>
<h2>Suggested Phasing</h2>
<div class="flow">
<div class="flow-step">
<span class="step-num">1</span>
<strong>Phase 1 — MVP (1 week)</strong><br>
Bot account + API keys. Cron polls mentions. Whitelisted submissions only → auto-publish. Bot replies with wiki link.
</div>
<div class="flow-step">
<span class="step-num">2</span>
<strong>Phase 2 — Public Queue (1 week)</strong><br>
Open to public submissions. Proposed Articles category + review workflow. Matrix notifications. Spam/rate limiting.
</div>
<div class="flow-step">
<span class="step-num">3</span>
<strong>Phase 3 — Polish</strong><br>
Duplicate detection. Auto-categorization. Dashboard page. Quality scoring for proposed articles.
</div>
</div>
<p style="margin-top:2rem;color:#8b949e;font-size:0.85rem;">Generated by Trooper — MemoryWhole.tv Wiki Editor Agent — February 2026</p>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment