Automated X Posting Strategy
Context
@joelclaw has an active X account with OAuth 1.0a API access. The system produces public content (blog posts, ADRs, discoveries) and internal operational data (deploys, health, friction fixes). We need guidance for what triggers a tweet, what the voice sounds like, and what the approval flow is.
Decision
Tweet Triggers
Three categories, each with a different automation level:
Auto-post (notify Joel after):
- New blog post published → tweet with title + link
- ADR status changes to
shipped→ tweet with ADR number + one-line summary + link - New discovery captured (if
public: truetag) → tweet with title + source link - Daily digest — “what shipped in joelclaw today” if ≥2 meaningful changes
- Weekly recap — aggregate of the week’s public content
All posts are autonomous. Gateway notifies Joel via Telegram after each tweet (silent notification with tweet text + link). No approval flow needed.
Never auto-post:
- Replies to other accounts (Joel says when to engage)
- Opinions, hot takes, anything that could be misattributed to Joel
- Anything touching other people’s work without Joel’s explicit ask
Voice
Panda is the author — @joelclaw is Panda’s account, not Joel’s. Tweets are Panda reporting on the system, not Joel announcing things. Use Joel’s writing style (from joel-writing-style skill) but speak as Panda — the agent.
- First person as Panda: “shipped X”, “wired up Y”, “the system now does Z”
- NOT: “I built” (implies Joel), “we’re excited” (marketing), “Joel released” (third person about owner)
- Terse, technical, slightly dry
- No emoji spam (one max, if any)
- No hashtags
- No “excited to announce” energy
- State what changed and link to it
Tweet Generation
All tweet text is LLM-generated via pi -p --no-session --no-extensions. No string templates, ever. Each tweet is generated fresh by piping a prompt to pi via stdin. Pi handles model selection and auth — no direct Anthropic/OpenRouter API calls in the pipeline.
The prompt includes:
- The
joel-writing-styleskill voice rules (terse, direct, dry) - Panda’s identity (agent voice, not impersonating Joel)
- The content context (what happened, relevant URL)
- Max 260 chars constraint (leaves room for t.co URL expansion)
- The swarm-tools tweet examples as few-shot guidance
- No emoji spam, no hashtags, no “excited to announce” energy
Implementation
Inngest Functions
-
x/post.requested— generic tweet event. Payload:{ text }. Posts immediately, then notifies gateway. -
Content publish hook — listens for
content/publishedevents (blog post deploy, ADR status change). Generates tweet text from template, firesx/post.requested. -
Discovery hook — listens for
discovery/notedcompletion. If discovery haspublictag, firesx/post.requested. -
Daily digest cron — runs at 6pm PST. Aggregates day’s public changes from slog + git. If ≥2 items, fires
x/post.requested.
X Posting Function
Single Inngest function handles all posting:
- Leases OAuth 1.0a secrets
- Signs with
requests-oauthlib(or port to Nodeoauth-1.0apackage) - Posts via Twitter API v2
- Emits
x/post.completedevent with tweet ID - Rate limit: max 5 tweets/day hard cap
Guardrails
- Daily cap: 5 tweets max (hard limit in function)
- Dedup: Don’t tweet the same URL twice (check Redis set)
- Cooldown: Minimum 30 minutes between tweets
- Kill switch:
joelclaw x pause/joelclaw x resumeCLI commands - Dry run: All functions respect
dryRunflag in event payload
Consequences
- Positive: joelclaw.com content gets automatic distribution
- Positive: Joel doesn’t have to remember to tweet about new posts
- Positive: Consistent voice and formatting
- Negative: Risk of posting something Joel wouldn’t want public
- Mitigation: Never auto-post opinions/replies. Daily cap + dedup + kill switch.
- Mitigation: All tweets are template-driven content announcements, not generated opinions. Joel notified after each post.