Autonomous X account. Researches AI/dev topics daily, writes and schedules posts, and replies to mentions in real time — all driven by xAI Grok via the Vercel AI SDK.
Three independent loops:
Posting pipeline — triggered once daily by cron:
GET /cron/daily → researcher → writer → scheduler → Neon → cron-job.org → POST /cron/execute-post → X API
- Researcher (
grok-4-latest, Responses API) —webSearch+xSearch, up to 10 steps. Produces a brief with 5–8 AI/infra content angles from the last 24 hours. - Writer (
grok-4-latest, Chat Completions) — turns the brief into 4–6 posts. Structured output via Zod. - Scheduler (
grok-4-1-fast-non-reasoning) — assigns times to named slots (morning/lunch/afternoon/evening/night), minimum 90 minutes apart. Structured output.
Posts land in Neon as pending. cron-job.org polls /cron/execute-post every 30 minutes to publish what's due.
Outbound engagement loop — triggered on a schedule:
POST /cron/outbound-engagement → getHomeFeed(100) → signal filter → outbound agent → like/retweet/follow → X API
- Fetches up to 100 tweets from the home timeline (accounts the user follows).
- Deduplicates by tweet ID; filters by signal threshold (≥10 likes, 100–500K author followers).
- Queries
outbound_engagement_logto skip already-acted tweets and authors on a 6-hour follow cooldown. - Outbound agent (
grok-4-latest, Chat Completions) — scores each candidate and returns like/retweet/follow decisions. Per-run caps: 10 likes, 3 retweets, 3 follows. - Executes actions via X API; logs all outcomes for dedup and cooldown enforcement.
Inbound engagement loop — real-time via X Account Activity API:
X webhook → POST /webhooks/x → inbound engagement agent → replyToTweet → X API
On every mention or reply, the inbound engagement agent (grok-4-latest) reads full thread context and returns one of:
- skip — spam, low-effort, or nothing to add
- reply/close — self-contained reply that ends the exchange
- reply/probe — reply ending with one pointed, technical question
Claims are written to engagement_log before the LLM call. Duplicate webhook deliveries are no-ops.
- Vercel AI SDK —
generateText(researcher) /generateObject(writer, scheduler, engagement) - xAI Grok — Responses API and Chat Completions (not interchangeable)
- Bun — runtime, package manager, test runner
- Hono — HTTP server on port 3010
- Neon + Drizzle ORM — Postgres (scheduled posts + engagement log)
- X API v2 (XDK) — OAuth1 for posting; Bearer token for thread hydration
- Docker —
oven/bun:1-alpine - Amazon ECR + EC2 — t3.micro, us-east-2, behind Nginx
- GitHub Actions — build → ECR push → SSH deploy on every
mainpush
bun install
cp .env.example .env
bunx drizzle-kit push
bun run devbun run test:researcher
bun run test:writer
bun run test:scheduler
bun run test:agents # full pipeline
bun run test:cron:daily
bun run test:cron:execute-post| Variable | Description |
|---|---|
XAI_API_KEY |
xAI API key |
X_API_KEY |
OAuth1 API key |
X_API_SECRET |
OAuth1 API secret (also used for webhook HMAC) |
X_ACCESS_TOKEN |
OAuth1 access token |
X_ACCESS_TOKEN_SECRET |
OAuth1 access token secret |
X_USER_ID |
Numeric user ID — required for like, retweet, follow, and webhook self-event filtering |
X_BEARER_TOKEN |
App-only bearer token (thread context hydration) |
X_HANDLE |
Agent's X handle without @ — used to enforce the 1:1 thread depth cap |
DATABASE_URL |
Neon Postgres connection string |
CRON_SECRET |
Required. Shared secret for /cron/* routes |
| Method | Path | Description |
|---|---|---|
| GET | / |
Health check |
| GET | /cron/daily |
Trigger posting pipeline (async, returns 202) |
| POST | /cron/execute-post |
Publish due posts; no body = scan all, { postId } = single |
| POST | /cron/outbound-engagement |
Trigger outbound engagement run (async, returns 202) |
| GET | /webhooks/x |
X CRC handshake |
| POST | /webhooks/x |
Incoming mention/reply events (HMAC-verified) |
Cron routes require the x-cron-secret header. Query-param auth is not supported (avoids leaking secrets to access logs).
Every push to main builds a linux/amd64 image, pushes to ECR, and SSH-deploys to EC2 via docker compose pull && up -d.
aws ecr get-login-password --region us-east-2 | \
docker login --username AWS --password-stdin <ACCOUNT_ID>.dkr.ecr.us-east-2.amazonaws.com
docker buildx build --platform linux/amd64 \
-t <ACCOUNT_ID>.dkr.ecr.us-east-2.amazonaws.com/twitter/agent:latest --push .
# On EC2
cd ~/twitter-agent && docker compose pull && docker compose up -dRegister the webhook (once after deploy):
bun scripts/subscribe-webhook.ts| Job | Endpoint | Schedule |
|---|---|---|
| Daily pipeline | GET /cron/daily |
Once daily |
| Publish due posts | POST /cron/execute-post |
Every 30 minutes |
| Outbound engagement | POST /cron/outbound-engagement |
Configurable (e.g. every 2h) |