Most “AI for social media” tutorials stop at “use ChatGPT to write your captions.” That’s not an agent. That’s a copywriter with extra steps.
A real AI agent decides what to post, when to post it, generates the content, optionally creates an image, and pushes it to LinkedIn, X, Instagram, or wherever else — without you babysitting it. It’s the difference between a glorified text generator and a system that actually runs your publishing pipeline.
In this tutorial we’ll build one. Python, Claude (or GPT — your call), and a publishing layer that doesn’t require you to fight OAuth flows on five different platforms. End result: a working agent you can deploy on a free-tier server and forget about.
Total build time: ~2 hours. Monthly cost: under $10 if you keep it lean.
Who this is for: Developers and technical founders who can read Python, want to ship something that runs in production, and would rather spend an afternoon building than a year paying for Hootsuite.
Table of Contents
- What Is an AI Agent for Social Media (and What It Isn’t)
- What We’re Building
- Step 1 — Define a Single Job for Your Agent
- Step 2 — Pick Your LLM and Framework
- Step 3 — Solve the Hard Part: Publishing
- Step 4 — Wire Up the Agent (with Code)
- Step 5 — Add a Human-in-the-Loop Approval Step
- Step 6 — Schedule and Deploy It
- Common Failure Modes (and Fixes)
- Going Further — Multi-Agent Workflows and MCP
- FAQ
What Is an AI Agent for Social Media
Before we touch any code, let’s pin down what we mean by “agent.” The word gets thrown around so loosely that it’s worth being precise.
A basic automation runs a fixed sequence: trigger → action. Every Monday, post this template. No decisions, no context.
A chatbot responds to a single user message and stops. It doesn’t remember, doesn’t plan, doesn’t act on the world.
An AI agent does three things a chatbot doesn’t:
- It plans — given a goal, it breaks the work into steps.
- It uses tools — it calls APIs, reads files, fetches URLs, posts content.
- It loops — it observes the result of its actions and adjusts.
For social media, that means an agent might: pull your latest blog post, decide which platforms it suits, draft three caption variants, generate a matching image, schedule the post for your audience’s peak hour, then notify you on Slack to approve. It does all of that without you wiring each step manually.
That’s the bar. If your “agent” is just a Make.com flow with an OpenAI node in the middle, that’s fine — it works — but call it a workflow, not an agent. Real agents reason. We’re building a real one.
What We’re Building
Here’s the architecture before we write any code:
┌──────────────────────────────┐
│ Trigger / Cron │
│ "every day at 9am" / event │
└──────────────┬───────────────┘
│
┌───────▼────────┐
│ Agent Core │
│ (Python + LLM) │
└─┬───┬───┬────┬─┘
│ │ │ │
┌──────────┘ │ │ └──────────┐
▼ ▼ ▼ ▼
┌────────────┐ ┌──────────┐ ┌────────────┐ ┌────────────┐
│Source Tool │ │Image Gen │ │ Approval │ │ Publishing │
│(blog, RSS, │ │(DALL·E, │ │(Slack DM │ │ Layer │
│ news API) │ │ etc.) │ │ or webhook)│ │ │
└────────────┘ └──────────┘ └────────────┘ └─────┬──────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ LinkedIn │ │ X │ │Instagram │
└──────────┘ └──────────┘ └──────────┘
The flow:
- A scheduler kicks the agent awake.
- The agent fetches a content source (your blog feed, a news API, a topic queue).
- It drafts post variants using an LLM.
- It generates or selects an image.
- It sends the draft to you for approval.
- On approval, it hands off to a publishing layer that posts everywhere at once.
The two layers most tutorials get wrong are the publishing layer and the approval loop. We’ll do both properly.
Step 1 — Define a Single Job
The biggest mistake when building your first agent is giving it too much rope. “Run my social media” is not a job. “Turn each new blog post into one LinkedIn post and one X post, scheduled for 9 AM the next weekday” is a job.
Pick one. Here are five concrete starter jobs that work well:
- Repurpose blog posts. New post in your RSS feed → agent drafts platform-specific variants and queues them.
- Daily digest poster. Agent reads top 5 stories from a niche RSS, picks the best one, posts a take.
- Quote-of-the-day bot. Generates a branded image from a rotating quote list and posts it.
- Build-in-public auto-poster. Reads your GitHub commits or changelog, drafts a “shipped this” post.
- Newsletter-to-thread converter. Takes your latest newsletter, breaks it into a 6-tweet thread.
For this tutorial, we’ll build the first one — blog post → LinkedIn + X post — because it’s the highest-leverage job and the architecture generalizes to everything else.
Step 2 — Pick Your LLM and Framework
Two decisions to make before writing code: which LLM provider, and which framework (if any).
Choosing the LLM
For social media drafting, the gap between top-tier models is small. You can pick based on cost, latency, and writing voice.
| Model | Strengths for social | Watch out for |
|---|---|---|
| Claude (Sonnet / Opus) | Strong at matching voice from few-shot examples; good at staying inside character limits; less hedging | Slightly higher latency than mini models |
| GPT-4 class | Massive ecosystem, great tool-use support | Can drift toward generic “AI voice” if not prompted carefully |
| Gemini (Pro / Flash) | Cheap, fast, decent quality | Voice consistency can wobble |
| Open-weights (Llama 3, Qwen) | Self-hosted, no per-token cost | You’re paying in GPU time and ops headaches |
For this tutorial we’ll use Claude because it tends to follow style instructions tightly, which matters when you’re trying to sound like you and not like a corporate LinkedIn account. Swap in any provider — the code structure doesn’t change.
Choosing the Framework
You have four real options:
- Raw Python with the LLM SDK. Maximum control, minimum magic. Best if you want to learn what’s happening.
- LangGraph. Great when your agent has branches, retries, and human-in-the-loop steps. Shines for multi-agent setups.
- CrewAI. Higher-level abstraction with “roles” (writer, editor, publisher). Faster to get started; less control.
- n8n / Make.com. No-code-ish. Good for non-developers. Less flexible once you outgrow them.
For a single-job agent, raw Python is the right choice. It’s the smallest footprint and you’ll understand every line. We’ll graduate to LangGraph in the multi-agent section at the end.
Step 3 — Solve the Hard Part: Publishing
This is the section every tutorial glosses over, and it’s where 80% of the work actually lives.
Here’s what nobody tells you: the LLM part is easy. The publishing part is brutal.
If you want your agent to publish to LinkedIn, X, and Instagram, here’s what each platform actually requires:
| Platform | API | Auth flow | Pain points |
|---|---|---|---|
| Marketing API + Share API | OAuth 2.0 + product enablement form | Org page posting requires manual review by LinkedIn; tokens expire every 60 days | |
| X (Twitter) | API v2 | OAuth 2.0 PKCE | Free tier is heavily rate-limited; media upload is a separate v1.1 endpoint |
| Graph API | Business account required + Facebook Page link | No personal accounts; 24-hour post window for some endpoints | |
| TikTok | Content Posting API | OAuth 2.0 + sandbox approval | Aggressive review process for production access |
| Threads | Threads API | OAuth via Meta | Newer API, fewer features |
Translation: building OAuth flows for five platforms, refreshing tokens, handling rate limits, and shipping media uploads is a multi-week project before your agent even drafts its first post.
You have two paths.
Path A: Build it yourself
You write OAuth handlers per platform, store tokens in a database, build refresh logic, handle media upload chunking, and wrap it all in a service. Realistic time investment: 2–3 weeks for one developer to get production-ready, plus ongoing maintenance every time a platform changes its API.
If you want to go this route, the LangChain team’s open-source social-media-agent is the best starting reference. They use Arcade for the auth layer.
Path B: Use an API-first publishing layer
This is what I recommend for almost everyone, and especially for agentic workflows where the agent itself needs to call the publishing API as a tool.
SchedPilot is built for this exact pattern. One API, multi-platform publishing, no per-platform OAuth headaches on your end. More importantly, its API is designed to be called by AI agents — schedule, reschedule, query post status, and pull engagement metrics are all simple tool calls your agent can make directly. That matters because in an agentic workflow the agent decides when to post based on context (audience timezone, engagement trends, content type), not just a fixed cron.
Here’s the build-vs-use comparison I keep coming back to:
| Decision factor | Build it yourself | Use SchedPilot |
|---|---|---|
| Time to first post | 2–3 weeks | Same afternoon |
| Platforms supported | Whatever you build | LinkedIn, X, Instagram, TikTok, Threads, more |
| OAuth & token refresh | Your problem | Handled |
| Media upload (images, video) | Custom per platform | One endpoint |
| Agent tool-call ergonomics | DIY | API designed for it |
| When platforms change APIs | You fix it | They fix it |
| Cost | Dev time + infra | Subscription |
| Best for | Learning project, niche needs | Shipping something real |
For this tutorial we’ll use SchedPilot for the publishing layer so we can focus on the agent logic, which is the actually interesting part. If you’d rather build the publishing yourself for the learning value, the rest of the tutorial still applies — just swap the SchedPilot API call in Step 4 for your own publishing function.
Step 4 — Wire Up the Agent
Time for code. We’ll build a minimal but production-shaped agent that:
- Reads your blog’s RSS feed
- Detects new posts since the last run
- Drafts a LinkedIn post and an X post using Claude
- Sends the drafts for approval
- On approval, schedules them via SchedPilot
Project setup
bash
mkdir social-agent && cd social-agent
python -m venv .venv && source .venv/bin/activate
pip install anthropic feedparser requests python-dotenv
Create a .env file:
env
ANTHROPIC_API_KEY=sk-ant-...
SCHEDPILOT_API_KEY=your-key-here
BLOG_RSS_URL=https://codeillusion.io/feed.xml
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...
The agent core
Create agent.py:
python
import os
import json
import feedparser
import requests
from datetime import datetime, timedelta
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv()
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
SCHEDPILOT_KEY = os.getenv("SCHEDPILOT_API_KEY")
RSS_URL = os.getenv("BLOG_RSS_URL")
# ----- 1. Source: pull the latest blog post -----
def get_latest_post(state_file="last_post.json"):
"""Return the newest post if it's newer than what we've seen."""
feed = feedparser.parse(RSS_URL)
if not feed.entries:
return None
latest = feed.entries[0]
# Check what we processed last time
try:
with open(state_file) as f:
last_seen = json.load(f).get("link")
except FileNotFoundError:
last_seen = None
if latest.link == last_seen:
return None # nothing new
# Update state
with open(state_file, "w") as f:
json.dump({"link": latest.link, "ts": datetime.now().isoformat()}, f)
return {
"title": latest.title,
"link": latest.link,
"summary": latest.summary,
}
The drafting step
This is where the LLM earns its keep. Two things matter:
- Few-shot examples beat instructions. If you want the agent to sound like you, paste 3–5 of your best posts into the prompt.
- Constraints in the prompt, not after. Telling the model “max 280 characters” works better than truncating output.
python
# ----- 2. Draft platform-specific posts -----
VOICE_EXAMPLES = """
Example LinkedIn post:
---
Spent the weekend rewriting our auth layer. Three lessons:
1. JWT in localStorage is fine for 99% of apps. Stop arguing about it.
2. Refresh tokens are where the bugs hide.
3. The best auth code is the auth code you didn't write.
If you're building from scratch in 2026, just use Clerk or Supabase Auth. Your time is worth more than the lock-in.
---
Example X post:
---
unpopular opinion: 90% of "AI agents" you see on twitter are just chatgpt with a for loop
real agents have memory, tools, and the ability to fail gracefully
stop calling your prompt template an agent
---
"""
DRAFT_PROMPT = """You are drafting social media posts for codeillusion.io, a blog about coding, AI, and developer tools.
Voice guidelines:
- Direct, opinionated, specific. No corporate hedging.
- Concrete over abstract. Numbers and examples beat adjectives.
- One idea per post. Don't try to summarize the whole article.
Here's the blog post to repurpose:
Title: {title}
URL: {link}
Summary: {summary}
Here are reference posts in our voice — match this tone:
{examples}
Output a JSON object with two keys:
- "linkedin": a LinkedIn post (max 1300 chars, can use line breaks for readability)
- "twitter": an X post (max 270 chars to leave room for the link)
Both posts should end with the link to the article. Output JSON only, no preamble."""
def draft_posts(post):
msg = client.messages.create(
model="claude-opus-4-7",
max_tokens=1024,
messages=[{
"role": "user",
"content": DRAFT_PROMPT.format(
title=post["title"],
link=post["link"],
summary=post["summary"][:1500],
examples=VOICE_EXAMPLES,
)
}]
)
raw = msg.content[0].text.strip()
# Strip markdown fences if the model adds them
if raw.startswith("```"):
raw = raw.split("```")[1]
if raw.startswith("json"):
raw = raw[4:]
return json.loads(raw.strip())
The publishing step (via SchedPilot)
Here’s where the agent calls SchedPilot to schedule the posts. The exact endpoint shape will match SchedPilot’s docs — treat the function below as a clean reference.
python
# ----- 3. Schedule via SchedPilot -----
SCHEDPILOT_API = "https://api.schedpilot.com/v1"
def schedule_post(content, platforms, scheduled_at):
"""Schedule a single post to one or more platforms."""
response = requests.post(
f"{SCHEDPILOT_API}/posts",
headers={"Authorization": f"Bearer {SCHEDPILOT_KEY}"},
json={
"content": content,
"platforms": platforms, # e.g. ["linkedin", "twitter"]
"scheduled_at": scheduled_at, # ISO 8601
},
timeout=15,
)
response.raise_for_status()
return response.json()
def next_weekday_at(hour=9):
"""Return ISO timestamp for the next weekday at given hour."""
now = datetime.now()
target = now.replace(hour=hour, minute=0, second=0, microsecond=0)
if target <= now:
target += timedelta(days=1)
while target.weekday() >= 5: # skip Sat/Sun
target += timedelta(days=1)
return target.isoformat()
Wiring it together
python
# ----- 4. The main loop -----
def run_agent():
post = get_latest_post()
if not post:
print("No new post. Exiting.")
return
print(f"New post detected: {post['title']}")
drafts = draft_posts(post)
# Send for approval (next section)
approved = request_approval(post, drafts)
if not approved:
print("Drafts rejected. Exiting.")
return
# Schedule
scheduled_at = next_weekday_at(hour=9)
schedule_post(drafts["linkedin"], ["linkedin"], scheduled_at)
schedule_post(drafts["twitter"], ["twitter"], scheduled_at)
print(f"Scheduled for {scheduled_at}")
if __name__ == "__main__":
run_agent()
That’s the skeleton. We’ll fill in request_approval next.
Step 5 — Human-in-the-Loop
Here’s a rule worth tattooing on your wrist: never let an agent post fully autonomously on day one.
The horror stories are easy to find. Agents that posted half-finished drafts. Agents that pulled outdated stats and cited them as fact. Agents that responded to a press inquiry with a generic FAQ answer. The fix is a human-in-the-loop step that costs you 30 seconds per post and saves you from one career-ending screenshot.
The simplest pattern: the agent posts the drafts to a Slack channel, you click ✅ or ❌, and the agent waits for the verdict.
python
# ----- 5. Approval via Slack webhook + emoji reaction -----
def request_approval(post, drafts):
"""Post drafts to Slack and wait for approval reaction."""
webhook = os.getenv("SLACK_WEBHOOK_URL")
message = f"""*New social drafts ready*
*Source:* <{post['link']}|{post['title']}>
*LinkedIn:*
{drafts[‘linkedin’]}
*X:*
{drafts[‘twitter’]}
React with ✅ to approve, ❌ to reject."""
requests.post(webhook, json={"text": message})
# In production: poll Slack API for the reaction, or use Slack Events API.
# For a quick version, use a CLI prompt locally:
answer = input("Approve these drafts? [y/N]: ").strip().lower()
return answer == "y"
For a more polished version, swap the input() call for Slack’s reactions API (poll every 30 seconds for a checkmark on the message). That gives you true async approval — the agent runs, posts to Slack, exits, and a separate worker finalizes the schedule when you react.
If you want fully autonomous after a trial period, gate it behind a confidence check: only auto-publish if the post is below a complexity threshold (no stats, no quotes, no proper nouns), and route everything else to manual review.
Step 6 — Schedule and Deploy
You have three deployment paths, ranked by laziness:
Option 1: Cron on a $5 VPS (most control)
Add to your crontab:
cron
0 8 * * * cd /home/you/social-agent && /home/you/social-agent/.venv/bin/python agent.py >> agent.log 2>&1
This runs the agent every day at 8 AM. Works great. Costs $5/month on Hetzner or DigitalOcean.
Option 2: GitHub Actions (free, zero infra)
Create .github/workflows/agent.yml:
yaml
name: Social Agent
on:
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install -r requirements.txt
- run: python agent.py
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
SCHEDPILOT_API_KEY: ${{ secrets.SCHEDPILOT_API_KEY }}
BLOG_RSS_URL: ${{ secrets.BLOG_RSS_URL }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Free, no server to maintain. The catch: state files (last_post.json) won’t persist between runs unless you commit them back to the repo or use a small KV store.
Option 3: SchedPilot’s native scheduler (laziest)
If you don’t need custom timing logic, just have the agent send the drafts to SchedPilot with a future timestamp and let SchedPilot handle the firing. Your agent script only runs once per new blog post — no cron needed if you use a webhook trigger instead.
Cost breakdown (real numbers)
For a one-post-per-day agent:
| Item | Monthly cost |
|---|---|
| Anthropic API (Claude, ~30 calls/month, ~5K tokens each) | ~$2 |
| SchedPilot subscription | starts at ~$15 |
| VPS (if using Option 1) | $5 |
| Total | ~$22/month |
Cheaper than two months of a single seat on most enterprise social tools. And it does what you tell it to do.
Common Failure Modes
I’ll save you the pain of discovering these the hard way.
1. The hallucinated stat
You ask the agent to summarize a post and it generates “studies show 73% of teams…” that doesn’t exist anywhere in the source. Fix: in your prompt, explicitly say “only use facts present in the source. If a stat would strengthen the post but isn’t in the source, omit it rather than invent it.”
2. The off-brand post
The model defaults to LinkedIn-influencer voice — “🚀 Excited to share…” Fix: aggressive few-shot examples and a banned-words list. Nathan Karns’ approach of explicitly listing every word the model isn’t allowed to use (“delve, harness, cutting-edge, unlock, revolutionize”) is overkill that works.
3. The token-expired silent failure
Your agent runs, “publishes” successfully according to its logs, but nothing actually appears on LinkedIn because the OAuth token expired three weeks ago. Fix: every publish call should verify the platform actually received the post (read it back). With SchedPilot, this is built in — the API returns the post status. If you’re rolling your own, add a verification step.
4. The duplicate post
You restart the agent during testing and it processes the same blog post twice. Fix: the last_post.json state file in our code, or a proper KV store like Redis or SQLite if you’re running multiple instances.
5. The rate-limit cascade
Your agent retries on a 429 and gets banned for an hour. Fix: exponential backoff on retries, and respect the Retry-After header. SchedPilot handles platform-side rate limits for you, but if you’re calling the LLM in a loop (e.g., generating multiple drafts and picking the best), add backoff there too.
Going Further
The agent we built is a single-purpose worker. Once it’s running smoothly, the natural next step is a multi-agent system.
The multi-agent pattern
Instead of one agent doing everything, you split the work:
- Researcher agent — pulls trending topics in your niche from news APIs and X.
- Writer agent — drafts post variants given a topic and voice guidelines.
- Editor agent — critiques the draft, checks for off-brand language, suggests edits.
- SEO agent — adds hashtags, mentions, and keyword optimization.
- Publisher agent — schedules via SchedPilot at the optimal time per platform.
Each agent is small, focused, and easy to debug. They communicate by passing structured messages (usually JSON). LangGraph is purpose-built for this — its graph model lets you define each agent as a node with explicit edges and conditional routing.
MCP and agentic publishing
Model Context Protocol (MCP) is the emerging standard for letting AI agents discover and call tools. Instead of hardcoding API calls in your Python script, you expose your tools as an MCP server, and any MCP-compatible client (Claude Desktop, Cursor, your own LangGraph agent) can use them.
For social media specifically, this is huge. An MCP-compatible publishing layer means your Claude assistant can directly schedule posts, query analytics, and reschedule based on performance — no code changes, no custom integration. SchedPilot’s API is built with this pattern in mind, which is why it slots cleanly into agentic workflows where the agent itself makes scheduling decisions rather than following a fixed cron.
The mental model shift: you stop writing “if blog post then post at 9am” and start telling the agent “my goal is consistent reach across platforms; you have these tools — figure it out.”
That’s the real promise of agentic AI for social media. The tutorial in this article is the foundation. Once you have it running, you can layer agency on top one piece at a time.
FAQ
How much does this cost to run?
Roughly $20–25/month for a one-post-per-day agent: a few dollars in LLM API calls, a SchedPilot subscription, and optionally a $5 VPS. Compare with a single seat on Hootsuite or Sprout Social ($99+/mo) and you’re paying a quarter of the price for something that does exactly what you tell it.
Do I need coding skills?
For this tutorial, yes — you should be comfortable reading Python and editing config files. If you’re not, the no-code path (Make.com or n8n + SchedPilot) gets you 70% of the same result with a visual builder, in exchange for less flexibility down the road.
Which platforms are hardest to post to programmatically?
Instagram and TikTok by a wide margin. Both require business accounts, manual API approval, and have aggressive review processes for production access. LinkedIn is moderate (token refresh is annoying). X is easy if you stay within free-tier limits, painful if you need volume. This is a big part of why a publishing API like SchedPilot is worth using — they’ve already done the platform-approval gauntlet.
Can the agent run fully autonomously?
Technically yes, practically no — at least not on day one. Run it with human-in-the-loop approval for the first month so you can catch tone issues, hallucinated facts, and off-brand outputs. Once you’ve reviewed 30+ posts and the model is consistently solid, you can graduate to “auto-publish low-risk content, route everything else to approval.” Don’t skip the trial period.
What if I want to use OpenAI / Gemini instead of Claude?
Swap the anthropic client for openai or google.generativeai. The prompt structure stays identical. The rest of the agent — RSS parsing, SchedPilot calls, approval loop, scheduling — is provider-agnostic.
How do I extend this to handle images or video?
Add an image generation step between drafting and publishing. For static images, use DALL·E 3, Flux, or a templated approach (RendrKit, Bannerbear, or Canva’s API). For video, use Runway or Pika. SchedPilot accepts media URLs in the same POST /posts call, so the integration is one extra field in your payload.
Can I have the agent reply to comments and DMs too?
Yes, but that’s a separate agent — engagement is a different problem from publishing, with much higher stakes if it goes wrong. Build the publishing agent first, get comfortable with the patterns, then layer engagement on top with a stricter approval flow.
Wrapping Up
The pattern in this tutorial scales. The same six-step structure — source, draft, approve, publish, deploy, monitor — works for blog repurposing, daily digests, build-in-public bots, and pretty much anything else you’d want an agent to do on social media.
The two pieces that matter most are the publishing layer (don’t underestimate it; use SchedPilot or be prepared to spend weeks on OAuth) and the human-in-the-loop step (don’t skip it until you’ve earned the right to).
Everything else is plumbing.