An autonomous, LLM-driven agent that runs a brand's social presence across all of Meta — Facebook, Instagram, and Threads — from a single command.
An LLM brain decides what to post (it picks the post type and writes fresh, on-brand copy, grounded in the real menu and steered away from recent posts) and writes the caption in the brand's voice. The agent then renders a branded post image and publishes it to every enabled Meta platform. Designed to run on a schedule (cron/heartbeat) with no human in the loop, and built so you can demo the entire pipeline with --dry-run — no tokens, nothing posted.
Graceful degradation: the LLM brain is optional. With no API key it falls back to a deterministic template engine, so the full pipeline still runs and demos with zero credentials and zero cost.
Deployed for a real client — Lord of the Wings, a wings restaurant in Greece (captions in Greek). The brand is just config; the agent is reusable.
Small businesses need a steady stream of on-brand posts across Facebook, Instagram and Threads. Doing that by hand is the chore that never gets done. The hard part isn't generating one post — it's the multi-platform publishing, where each Meta surface has different rules (Instagram and Threads won't accept a file upload; they need a public image URL). This agent handles that fan-out automatically.
plan post ──▶ write caption ──▶ render image (HTML→PNG) ──▶ publish to all Meta platforms
(LLM brain) (LLM brand voice) (Playwright) │
▼
┌──────────────┬───────────────┬──────────────┐
▼ ▼ ▼
Facebook Instagram Threads
(file upload) (URL bridge) (URL bridge)
│ ▲ ▲
└──── public image URL ────────┘
The bridging trick: Instagram and Threads can't take a local file — they need a publicly reachable image URL. Facebook can upload a local file and hands back a public CDN URL. So the agent publishes to Facebook first, then reuses that URL to post the same image to Instagram and Threads. (Without Facebook, set public_image_base_url to your own host.)
| Platform | How it publishes | Needs |
|---|---|---|
| Facebook Page | Local file upload → /{page}/photos |
META_ACCESS_TOKEN |
| Instagram Business | Create media container → publish (2-step) | public image URL (bridged from FB) + linked IG account |
| Threads | Create container → publish (2-step) | THREADS_ACCESS_TOKEN + public image URL |
Each platform is a small pluggable class in engine/platforms/ — add a new surface by writing one Platform subclass and registering it. One platform failing never blocks the others (partial-success by design).
# 1. credentials (secrets live only in .env, never in config)
cp .env.example .env # add META_ACCESS_TOKEN; add OPENAI_API_KEY for the LLM brain
# 2. dependencies (rendering engine + LLM SDK)
pip install -r requirements.txt && python3 -m playwright install chromium
pip install pytest && python3 -m pytest tests/ -q # 7 offline tests (no credentials needed)
# 3. see the whole pipeline without posting anything
# (with OPENAI_API_KEY the LLM plans + writes; without it, template fallback)
python3 autopilot.py --dry-run
# compare: force the deterministic engine instead of the LLM
python3 autopilot.py --dry-run --no-llm
# 4. generate one specific post to Facebook + Instagram
python3 create_post.py --type product_spotlight \
--headline "CRUNCH HOUR" --subline "Crispy wings, hot sauce, zero patience." \
--publish --platforms facebook,instagramGo fully autonomous with cron:
0 18 * * * cd /path/to/meta-social-agent && python3 autopilot.py >> memory/logs/cron.log 2>&1config.json (no secrets here) controls which platforms run:
{
"platforms": {
"facebook": { "enabled": true, "page_id": "" },
"instagram": { "enabled": true, "ig_user_id": "" },
"threads": { "enabled": false, "threads_user_id": "" }
}
}create_post.py / autopilot.py CLI + autonomous driver
│
▼
engine/
brain.py ◀── LLM brain: plan_post() decides what to post, write_caption() writes it
assets.py pick a brand photo by tag/mood
caption.py deterministic caption fallback (hook → sensory → CTA → signature → hashtags)
templates.py fill an HTML post template
renderer.py HTML → PNG via headless Chromium (Playwright)
publisher.py ◀── orchestrator: publish_everywhere() fans out across platforms
platforms/
base.py PublishResult + shared Graph API helpers
facebook.py local upload + produces the bridged public URL
instagram.py container → publish
threads.py container → publish
output/ contains real generated posts from production runs (product spotlights, promos, memes, seasonal cards) across several months. The brand voice and templates are tuned for Lord of the Wings.
Python · OpenAI API (LLM brain — planning + brand-voice copywriting, JSON-mode structured output) · Playwright (headless Chromium rendering) · Meta Graph API (Facebook, Instagram) · Threads API · HTML/CSS templating. No heavyweight framework — the agent loop, LLM brain, multi-platform publisher, and renderer are hand-built.
- LLM brain, optional by design. The brain plans the post and writes the caption; if the
openaiSDK orOPENAI_API_KEYis missing, orllm.enabledis false inconfig.json, every brain call returnsNoneand the agent falls back to the deterministic engine. The pipeline never breaks for lack of a key. - Grounded, non-repetitive copy. The planner is fed the real menu catalog (from
assets/brand/) and the last few posts (frommemory/logs/) so it references real items and avoids repeating recent angles. - Structured output. Planning uses JSON mode and the result is validated (post type must be a real template) before it touches the pipeline.
- Dry-run everywhere. Every platform supports
dry_run, so the full pipeline is demoable with zero credentials and zero live posts. - Secrets discipline. Tokens live only in
.env(gitignored).config.jsonnever holds a secret. - Reusable. Swap the brand by editing
config.json,post_config.json(the voice spec the LLM follows) andtemplates/— the engine is brand-agnostic.