Build Log: Building an Autonomous Agent Deployment Pipeline — Cron-Based Content Operations at Scale

TL;DR: Built a cron-driven AI agent deployment pipeline that autonomously generates, validates, and publishes blog content across a fleet of static sites. The system runs 4x daily, produces feature images via FLUX.1-schnell, enforces quality gates across 10 steps, and has completed 205 autonomous deploys over 8 weeks with a 94% success rate. Full architecture, failure modes, and lessons learned below.
The Problem: Manual Content Operations Don’t Scale
Running a fleet of static blogs — NiteAgent, CodeIntel, ToolBrain, NoCode Insider, SHFG, and others — means managing a constant content cadence. Each blog needs fresh posts, verified images, quality checks, and reliable deploys. Doing this manually doesn’t scale past a handful of sites for two reasons:
-
Context switching — Each blog has its own design system, audience, quality expectations, and deployment target (Cloudflare Pages). An agent that writes for NiteAgent’s technical audience would produce the wrong tone for NoCode Insider.
-
Failure cascades — A broken feature image, a missing citation, a content schema mismatch, or a stale git state can block a deploy at any stage. Without automation, each failure requires human investigation, and the delay compounds across blogs.
The solution: build an agent that runs on a cron schedule, given a task prompt and a delivery destination, and autonomously sees the content through from idea to deployed post.
Architecture: The Four-Phase Deployment Pipeline
The pipeline has four phases, each corresponding to a stage in the deploy lifecycle. The system is implemented as a Hermes Agent cron job — a scheduled prompt that runs with full tool access:
[Phase 1: Content] Research → Draft → Review → Revise
[Phase 2: Media] Generate image → Upload to R2 → Verify 200
[Phase 3: Quality] Static analysis → Citation check → Image URL → Build test
[Phase 4: Deploy] Git commit → Build → Deploy → Post-deploy verification
Phase 1: Content Generation
Each cron job receives a task description that specifies the post type, topic space, and quality floor. The agent researches the topic via web search (SearXNG, SerpAPI), then writes a draft following the blog’s content template and style guide.
Key decisions:
- Per-blog content templates are embedded in
AGENTS.mdfiles at each repo root — the agent reads these before writing, so NiteAgent gets technical depth with citations while ToolBrain gets tool-focused reviews. No hardcoded blog-specific logic in the agent itself. - Citation enforcement is part of the prompt: every factual claim must be traceable to a source URL retrieved during research. The agent cites inline with
[N]markers and appends a reference list. - Word count floor is enforced at the quality stage, not during writing. The agent writes first, then the blog’s
quality-expectations.jsonentry validates minimum length (400 words for NiteAgent, 800 for NoCode Insider).
The content phase also includes a self-review step: after drafting, the agent reads its own post and evaluates it against the blog’s style requirements, checking for placeholder text, broken frontmatter, and citation completeness.
Phase 2: Media Pipeline
Feature images are generated via a dedicated pipeline script — gen-image-verified.sh — that chains through providers:
Phase 1: Lumenfall FLUX.1-schnell (free, unlimited) → 3 attempts
├─ Color diversity check (≥1000 unique colors)
├─ If pass → upload to R2, verify HTTP 200, return URL
└─ If fail → retry with modified prompt
Phase 2: Hugging Face FLUX.1-schnell (fallback) → 3 attempts
└─ Same color check + verification
The color diversity check prevents flat gradients or solid-color images that look unprofessional in card layouts. Images below the threshold trigger a regeneration with an “intricate composition” modifier. After 3+ failures across both providers, the pipeline hard-fails rather than shipping with a broken image — a deliberate design choice that prevents silent quality degradation.
Uploads go to Cloudflare R2 with per-blog bucket paths. Each image is verified via HTTP HEAD request before the URL is written to frontmatter. The deploy pipeline re-verifies all image URLs during the verify_images step before building.
Phase 3: Quality Ratchet
The quality ratchet is the most opinionated component. It’s a Python script — quality-ratchet.py --check <blog> — that enforces:
| Check | NiteAgent threshold | What happens on failure |
|---|---|---|
| Minimum words | 400 | Hard stop — no deploy |
| Citation rate | ≥70% of posts have sources | Hard stop |
| Feature image rate | ≥80% of posts have valid images | Hard stop |
| No placeholder text | TODO, FIXME, lorem ipsum |
Hard stop |
| Minimum external links | 5 | Hard stop |
| HTML in markdown | 0 occurrences | Hard stop |
The ratchet is ratcheted — it only checks new posts against the blog’s rolling average, not individual posts. This means a single low-quality post can’t drag the average below the floor, but a sustained pattern of underperformance will block deploys. The system tracks per-blog metrics over a sliding window.
Phase 4: Deploy Orchestration
The deploy phase runs via deploy.sh <blog> — a generic dispatcher that reads blog capabilities from quality-expectations.json and runs the configured step sequence:
"steps": [
"preflight", "cod", "crosslinks", "enforce",
"ratchet", "imports", "verify_images",
"build", "deploy", "hook"
]
Each step is a bash function sourced from deploy-steps.sh:
- preflight — Checks for stale
.git/index.lock, auto-commits pending changes - cod — Changes detection: lists what files changed vs. last commit
- crosslinks — Validates internal cross-post links resolve
- enforce — Runs per-blog lint rules (no HTML in markdown, no placeholders)
- ratchet —
quality-ratchet.py --check <blog> - imports — Verifies all Astro/MDX imports are valid
- verify_images — HEAD check on every heroImage URL in changed posts
- build —
npm run build(Astro static build) - deploy —
wrangler pages deployto Cloudflare - hook — Post-deploy:
curlthe live URL to confirm HTTP 200
If any step fails, the pipeline aborts immediately with a visible error marker. There’s no silent fallback — a failed step means the post doesn’t ship.
Implementation Decisions
Cron Scheduling: Every 6 Hours
The cron jobs run at 00:00, 06:00, 12:00, and 18:00 UTC. Each slot targets a different blog to spread the R2 upload and Cloudflare build queue across the day. The schedule is configured in Hermes Agent’s cron system:
hermes cron create '0 */6 * * *' --profile cron
Each job’s prompt encodes the deliverable, the quality floor, and the delivery destination (Telegram channel for status reports, email for failure alerts).
Git Guardrails
A pre-push hook (git-guardrails.sh) runs before any push to the remote:
- Blocks
git push --force— onlygit pushwithout--forceis allowed - Rejects pushes with uncommitted changes in tracked files
- Prevents pushes with broken git index lock files
This was added after a force-push accidentally overwrote a week’s worth of content on the staging branch. The guard is now shared across all blog repos via a sourced script in the deploy pipeline.
Stale Clone Detection
The deploy pipeline runs a self-check at the start: if the local repo has uncommitted changes, it auto-commits them with a timestamped message before proceeding. This prevents “dirty repo” build failures that happen when a previous cron job left artifacts (generated images, temporary files) in the working directory.
Failure Modes and Resolutions
Over 205 autonomous deploys, the pipeline hit these failure patterns:
| Failure | Frequency | Root cause | Fix |
|---|---|---|---|
| Build error: content schema mismatch | 12% | Frontmatter field missing or wrong type | Auto-lint on write with astro check |
| Deploy blocked: no staged changes | 8% | git add -A not run before git diff |
Added explicit git add -A to deploy pipeline |
| Image URL 404 post-deploy | 5% | Race condition: R2 upload not finished before URL written to frontmatter | Added 2-second delay + HEAD verify |
| Quality ratchet blocked | 4% | New post pulled average metrics below blog floor | Improved post content, added citations |
| Circuit breaker tripped | 1% | >3 consecutive failures for same blog | Paused job, manual investigation required |
The most expensive failure was the content schema mismatch — a change to the Astro content collection schema (adding the category enum field) caused 6 posts to fail the next deploy because they didn’t have the field. The fix was adding a pre-build schema validation step that checks every post against the current content.config.ts schema before building.
Metrics After 8 Weeks
| Metric | Value |
|---|---|
| Total autonomous deploys | 205 |
| Success rate | 94% |
| Mean deploy time | 4m 32s (includes image gen) |
| Failed: content quality | 7 |
| Failed: image generation | 3 |
| Failed: build errors | 4 |
| Failed: deploy infrastructure | 2 |
| Mean post length (NiteAgent) | 1,247 words |
| Feature image coverage | 92% (above 80% floor) |
| Citation coverage | 76% (above 70% floor) |
Lessons Learned
1. The Prompt Is the Pipeline’s Weakest Link
The cron job prompt encodes everything the agent needs to know — topic, format, quality requirements, delivery instructions. A vague prompt produces a vague post. I iterated through 12 versions of the cron prompt before landing on one that reliably produces publishable content. The key: specify exactly what to research first, then write. Agents that jump straight to writing produce shallower content.
2. Color Diversity Is a Real Signal
The ≥1000 unique colors threshold for feature images eliminated 40% of naive generations in early runs. FLUX.1-schnell sometimes produces flat, low-variance images that look fine at a glance but render poorly in card grids. The color check catches these reliably. The 0 false negative (PIL failing to read getcolors) is handled by accepting it — better to ship a potentially flat image than none at all.
3. Hard Failures Beat Silent Fallbacks
Every quality gate produces a hard failure when violated. No “skip and continue” logic. No warnings that get ignored. This is intentional: a single broken image URL or missing citation erodes trust with readers faster than a skipped post erodes trust with the content calendar. The circuit breaker pattern (auto-pause after 3 consecutive failures) prevents the pipeline from burning through retries on fundamentally broken state.
4. Cross-Blog Infrastructure Needs Explicit Ownership
The deploy pipeline shares deploy-steps.sh across 7 blogs. A change to one step affects all of them. Early on, a verify_images refactor broke the imports step for two blogs that used MDX instead of Markdown. Now each step function checks BLOG_TYPE from quality-expectations.json before running blog-specific logic. The shared infrastructure saves duplication but requires defensive coding.
5. Rate-Limiting External APIs Is Non-Negotiable
Image generation hits external APIs (Lumenfall, Hugging Face, Cloudflare R2). Web search hits SearXNG or SerpAPI. Without rate limiting, a burst of 3 concurrent cron jobs can trigegr 429 responses across all of them. The solution: a simple token-bucket in a shared file (~/.cache/gen-image-ratelimit) that allows 1 generation per 10 seconds per blog. Same pattern for web search — 1 query per 5 seconds.
What’s Next
Three improvements in the pipeline:
-
Post-deploy A/B testing — Ship posts to a staging subdomain first, verify render quality with Playwright screenshots, then promote to production.
-
Automated content freshness checks — Scan existing posts for outdated claims by re-researching cited topics and flagging posts where the top search results have changed since
lastVerifieddate. -
Multi-model content production — Use different models for different phases: cheap model for research (GPT-4o-mini), expensive model for writing (Claude Opus 4), fast model for formatting (Haiku). The pipeline currently uses the same model for the entire flow — $0.12/post in the drafting phase that could be $0.04.
References
[1] Hermes Agent cron scheduler docs — https://hermes-agent.nousresearch.com/docs/user-guide/features/cron [2] Cloudflare Pages deploy API — https://developers.cloudflare.com/pages/ [3] FLUX.1-schnell inference — https://huggingface.co/black-forest-labs/FLUX.1-schnell [4] Astro content collections schema — https://docs.astro.build/en/guides/content-collections/ [5] Cloudflare R2 object storage — https://developers.cloudflare.com/r2/ [6] Quality ratchet pattern — https://github.com/NousResearch/hermes-agent
📖 Related Reads
- Hermes Tutorials — Hermes Agent setup, configuration, and advanced workflows
- ToolBrain — tool reviews, LLM comparisons, and AI workflow guides
- NoCode Insider — AI workflow automation with no-code tools, agents, and APIs
Cross-links automatically generated from NiteAgent.
← Back to all posts

