ADR-0129shipped

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: true tag) → 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-style skill 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

  1. x/post.requested — generic tweet event. Payload: { text }. Posts immediately, then notifies gateway.

  2. Content publish hook — listens for content/published events (blog post deploy, ADR status change). Generates tweet text from template, fires x/post.requested.

  3. Discovery hook — listens for discovery/noted completion. If discovery has public tag, fires x/post.requested.

  4. 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 Node oauth-1.0a package)
  • Posts via Twitter API v2
  • Emits x/post.completed event 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 resume CLI commands
  • Dry run: All functions respect dryRun flag 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.