Build a Self-Healing AI Content Agent with Claude Agent SDK
Write a post, format it for X, reformat for Bluesky, render a video, upload to YouTube, cross-post to Instagram. Repeat daily. That was 2-3 hours of my day, so I built a self-healing AI content agent that does all of it. It runs 24/7 on a Mac…

I got tired of the content grind. Write a post, format it for X, reformat for Bluesky, render a video, upload to YouTube, cross-post to Instagram. Repeat daily. It was eating 2-3 hours every day.
So I built a self-healing AI content agent that does all of it. It runs 24/7 on a Mac mini, publishes to five platforms, and fixes itself when things break. Claude Code runs on my existing Max subscription. The only variable cost is Gemini for image generation, which is pennies per image.
It is a persistent agent, not a chatbot. It runs on a schedule, does the work, sends me the results on Discord, then goes back to monitoring. I approve everything from my phone.
Here is the architecture, with real code from the production system.
What the Agent Actually Does
Every day, without me touching anything:
- Scans X for viral tweets in AI and automation, then drafts quote tweets with tactical breakdowns
- Generates YouTube Shorts. Writes the script, makes the voiceover with Edge TTS, renders with Remotion, generates a thumbnail, uploads.
- Publishes to X, Bluesky, Instagram, and YouTube, each formatted for the platform
- Tracks analytics: YouTube stats, Google Search Console data, X engagement metrics
- Sends everything to Discord. I tap ✅ or ❌ from my phone.
The approval step matters. Every piece of content gets human review before it goes live. The agent handles the execution, call it 95% of the work. The remaining 5% is judgment, and that stays with me.
The Stack
Recurring cost is near zero if you exclude the Claude Code Max subscription I already pay for. Gemini image generation is the only variable API cost, a few cents per image. A content manager costs $3-5K/month, a virtual assistant $500-1K/month.
Architecture: One Persistent Agent Session
The first version was a Python daemon that spawned separate claude -p processes for each task. It worked, but each task started with zero context. The agent couldn’t remember what it posted yesterday or what performed well last week.
The current version is one persistent TypeScript agent session that never dies. Discord messages, scheduled tasks and webhook events all feed into the same agent loop, so the context carries across everything it does.
// index.ts — the entry point
async function main() {
const agent = await startAgent() // persistent Agent SDK session
const bot = await startDiscord(agent) // Discord.js — feeds messages into agent
startScheduler(agent) // node-cron — feeds tasks into agent
startWebhooks(agent) // GitHub webhook listener
await bot.sendMessage('general', 'Koda online. Ready for tasks.')
}The Agent Core
Built on Anthropic’s Claude Agent SDK. Streaming input mode is what makes it work. The agent stays alive and takes new messages whenever they arrive, whether from Discord, the scheduler or a webhook.
// agent.ts — persistent session with message queue
const agent = new Agent({
model: 'claude-sonnet-4-20250514',
tools: [...mcpTools, ...agentTools],
systemPrompt: loadFile('SOUL.md'),
})
// Message queue — Discord, scheduler, webhooks all push here
const messageQueue: Message[] = []
export async function sendMessage(content: string, source: string) {
messageQueue.push({ content, source, timestamp: Date.now() })
await processQueue()
}Session persistence means the agent recovers from restarts. On crash, pm2 restarts the process. The agent loads its session ID from disk and resumes with full context history.
The Scheduler
17 scheduled tasks running on node-cron. Each task is a prompt that gets fed into the same agent session, so the agent has full context when executing.
// scheduler.ts
const tasks: ScheduledTask[] = [
{
name: 'youtube_analytics',
cron: '0 7 * * *', // 7 AM daily
prompt: 'Pull YouTube analytics for the last 7 days. Compare to previous period.',
type: 'silent' // runs without approval
},
{
name: 'viral_scan',
cron: '0 10 * * *', // 10 AM daily
prompt: 'Scan X for viral tweets in AI/automation. Draft quote tweets.',
type: 'approval' // sends to Discord for approval
},
{
name: 'social_post',
cron: '0 12 * * *', // Noon daily
prompt: 'Draft a post for X following brand-voice-skill.md.',
type: 'approval'
},
{
name: 'goal_check',
cron: '0 8 15 * * *', // 8:15 AM daily
prompt: 'Check GOALS.md. If any goal is behind, propose actions.',
type: 'silent'
}
]Tasks are deduplicated. If youtube_analytics already ran today, it gets skipped on re-runs. Results are tracked in .task-results/YYYY-MM-DD.json.
Self-Healing
When a task fails, the agent gets the full error output in context and tries to fix it. It is the same persistent session, so it already knows the codebase and the recent changes.
Up to 2 heal attempts per task. If it still fails, I get a Discord alert with the error details.
The old Python daemon spawned a fresh Claude instance to do the healing, and that instance knew nothing about what went wrong. Healing inside the same session means the agent still remembers what it was trying to do and which tools it called.
Risk Classification
Not every action needs approval. Checking analytics is safe, posting a tweet is not, so the agent classifies every tool call by risk level:
// YOLO risk classifier
const riskLevels = {
HIGH: ['post_tweet', 'publish_video', 'delete_tweet', 'gmail_send'],
MEDIUM: ['generate_image', 'skool_airtable_sync', 'create_record'],
LOW: ['youtube_analytics', 'gsc_search_analytics', 'gmail_search']
}HIGH-risk actions get sent to Discord for approval before executing. LOW-risk actions run silently. MEDIUM adapts based on whether I’m active in Discord or idle.
Process Management: pm2
The agent runs under pm2, a Node.js process manager that handles auto-restart, logging, and monitoring.
# Start the agent in daemon mode
npm run daemon # runs: pm2 start ecosystem.config.js
# Check status
pm2 status
# View logs
pm2 logs koda
# Restart
pm2 restart kodapm2 restarts the agent automatically on crash (max 10 restarts). Logs go to data/logs/koda-*.log. The agent sends a startup message to Discord every time it boots, so I know when restarts happen.
Discord as Control Plane
This was a better choice than building a web dashboard. Discord is already on my phone, it supports rich embeds, reactions and threads, and it costs nothing.
The Discord bot (discord.js) routes messages bidirectionally:
- Me → Agent: I type in the Discord channel, the bot feeds it to the agent session
- Agent → Me: The agent sends results, approvals, alerts back to Discord
- Reactions: ✅ to approve, ❌ to reject content before publishing
The agent sends structured messages for different events:
Content approval:
🎬 New YouTube Short ready for review
Title: A Teaspoon of Neutron Star Weighs 6 Billion Tons
Duration: 58 seconds
Platforms: YouTube, Instagram
React ✅ to approve or ❌ to rejectAnalytics digest:
📊 YouTube Analytics — Last 7 Days
Views: 1,247
Watch time: 42.3 hours
Subscribers: +8
Top video: Neutron Star (959 views)
Shorts feed: 93% of trafficSelf-healing alert:
🔧 Self-healed: viral_scan
Error: X API rate limit exceeded
Fix: Added exponential backoff (2s, 4s, 8s)
Status: Task completed on retry #1I can also send commands directly in the Discord channel: “post this to X”, “check YouTube stats”, “draft a blog post about X.” The agent picks it up and responds in the same thread.
The Video Pipeline
Going from a script to a published YouTube Short takes about 3 minutes of compute time, and nothing from me beyond the approval tap:
- Script → Claude writes the narration based on a topic
- Images → Gemini generates scene-specific images matching each narration segment
- Voiceover → Edge TTS (en-US-AndrewNeural) generates natural-sounding audio
- Captions → Whisper.cpp creates word-level timestamp sync on Apple Silicon Metal
- Render → Remotion composites everything into a vertical 1080×1920 video
- Thumbnail → Gemini generates a background, Python overlays title text with glow effects
- Preview → Compressed version sent to Discord for approval
- Publish → Uploads to YouTube and Instagram simultaneously
# The full pipeline in one command
python orchestrate_video.py tutorials/neutron-star.json
# Or step by step
python generate_voiceover.py tutorials/neutron-star.json --update-durations
npx remotion render TechTutorial out/neutron-star.mp4 --props=/tmp/props.json --gl=angle
python publish.py out/neutron-star.mp4 --title "Title" --platforms youtube,instagramTotal cost per video: a few cents in Gemini API fees for images. Edge TTS, Whisper, and Remotion are all free.
The Memory System
An agent that forgets everything between sessions is useless for content work. It needs to know which topics performed well and what voice to write in.
I built a 6-layer memory system:
Layer 1: Bootstrap files. Loaded every session. Identity (SOUL.md), skills (SKILL.md), user context (USER.md), operational rules (CLAUDE.md). These are the agent’s “personality.”
Layer 2: Observations. The agent records patterns as it works using the observe() tool. “Space Shorts get 10x more views than nature curiosities.” “Negation lists outperform feature lists on X.” Tagged by type: rule, preference, fact, habit, event.
Layer 3: Dream cycle. A nightly job that consolidates observations. Deduplicates similar entries, applies importance decay (rules last 365 days, events expire in 14 days), and promotes recurring patterns to the curated learnings file.
Layer 4: Daily logs. What happened today. Actions, outcomes, decisions, errors. Written continuously throughout the session, not batched at the end.
Layer 5: Curated learnings. The distilled “brain.” Under 100 lines of hard-won knowledge. “Shorts must be under 60 seconds.” “Images must exactly match narration.” These feed directly into content decisions.
Layer 6: Search. Before making any decision, the agent searches across all layers for relevant past context.
The dream cycle is what keeps the whole thing usable. Without it, observations pile up forever and the agent drowns in its own notes. Only patterns that show up 3+ times get promoted to long-term memory, and the rest decays.
MCP Servers: Connecting Everything
Model Context Protocol (MCP) is how the agent talks to external services. Each API gets its own MCP server that exposes typed tools:
- YouTube MCP: upload videos, get analytics, manage playlists, read comments
- X MCP: post tweets, get engagement metrics, delete posts
- Bluesky MCP: post, repost, like, get timeline
- Gmail MCP: search emails, send, create drafts, manage calendar
- Airtable MCP: read/write tables for CRM and content tracking
- Google Search Console MCP: search analytics, indexing status, submit sitemaps
- n8n MCP: workflow management and data tables
- Context7 MCP: documentation lookup for any library
The agent calls these tools naturally in conversation:
Agent: "Let me check yesterday's YouTube performance."
→ Calls youtube_analytics_overview(start_date="2026-04-04")
→ "Views were up 23% — the magnetar Short is picking up.
847 views in the first 24 hours."I wrote no integration code or webhook plumbing for any of this. The MCP server handles auth, rate limiting and response formatting. The agent calls the tool and gets structured data back.
vs. Hiring a Content Manager
The agent wins on execution speed and cost. A human wins on creative judgment and strategy. That split is why I kept the decisions and handed over the execution.
vs. n8n / Make / Zapier
I used n8n for a year before building this. Here’s why I switched:
- n8n handles data flow between APIs, and it is good at “when X happens, do Y.” Content creation runs on judgment, context and iteration, which never fits a straight line of nodes.
- An AI agent can look at analytics, decide what content to create, write it, generate assets, format it for each platform, and adapt based on what worked last time.
- The tradeoff: n8n is more reliable for simple automations. The agent is more capable but needs monitoring, hence pm2 and the self-healing system.
Use n8n when the workflow is “trigger → transform → send.” Use an agent when it involves creative decisions. I cover how to automate blog publishing as one example of agent-driven workflows that go beyond what node-based tools handle well.
Getting Started
If you want to build something similar:
- Start with the Agent SDK. Don’t wrap Claude Code CLI in a shell script like I did in v1. The Claude Agent SDK gives you typed tools, streaming input, and session persistence out of the box.
- Add Discord early. It becomes the control plane. Every action sends a message, and nothing publishes without approval. Discord.js makes that straightforward.
- Use pm2 for process management. Auto-restart on crash, log rotation and monitoring are already in there. Do not write your own watchdog.
- Build a risk classifier. Reads are safe, posts and deletes are not. Classify your tools and gate only the dangerous ones.
- Use the memory system. Even a simple LEARNINGS.md file that the agent reads at startup improves content quality over time.
FAQ
What does “self-healing” mean for an AI agent?
The agent detects its own failures (API timeouts, rate limits, broken scripts) and attempts to fix them automatically. A circuit breaker pauses failing tasks after 3 consecutive errors, and a heal loop retries with context about what went wrong. Up to 2 heal attempts per task before alerting a human.
How much does it cost to run this system?
Near zero beyond a Claude Max subscription. Edge TTS, Whisper.cpp, Remotion, and pm2 are all free. The only variable cost is Gemini image generation: a few cents per image. Total variable spend is under $1/day running 15+ tasks.
Can this replace a content manager entirely?
For execution, yes. The agent schedules, formats, renders and publishes faster than a person does. Strategy and judgment calls are another matter, which is why every publish still waits for my approval in Discord.
What happens when the agent crashes?
pm2 auto-restarts the process within 5 seconds. On startup, the scheduler detects any tasks that were missed during downtime and runs them. The agent sends a startup message to Discord so you know a restart happened.
I’m documenting the full build process (agent setup, MCP server configuration, video pipeline, memory system) in my Build & Automate community. The step-by-step modules with real production code live there.
Related Reading
- Run a Claude Code Agent in Production: How to run Claude Code as a real production agent. Observability, retries, secrets, drift handling.
- Write Claude Code Agent Skills That Actually Work: Custom skills that consistently fire and produce useful output.
Related
Automatiser bilagsføring med Fiken API og Claude Code
Jeg videresendte kvitteringer til Fiken-inboksen og lot dem ligge. Så satt jeg en kveld i måneden og førte dem manuelt: finn leverandøren, velg konto, velg MVA-kode, last opp PDF-en. Fire klikk, tretten ganger. Så skrev jeg en Claude-skill som gjør det via Fiken API-et.
Automate Your Blog Publishing in 2026: From Draft to Live in One Command
Blog automation saved me 15-20 minutes per post. The manual version went like this: write in your editor, open WordPress, paste and reformat, upload a featured image, configure SEO metadata, hit publish. Three or four posts a week adds up to hours of clicking. So I built a system that takes a markdown…
Docker Containers with Unraid NFS: Fix Stale File Handle Errors
If you’re running Docker containers with Unraid NFS shares, or any server serving NFS shares, you’ve probably encountered the “stale file handle” problem. I’m running Proxmox as my hypervisor with multiple VMs: one running Unraid as my NAS, and another running my media stack (Plex, Radarr, Sonarr, etc.) in Docker containers. The Docker VM pulls…