Skip to main content
cases

Zyberis — AI-Operated Media Platform

Production media platform built around an autonomous AI agent (Hermes) that operates most editorial workflows through a token-authenticated API with no database credentials exposed. Next.js 16 App Router, MDX pipeline with dual-theme syntax highlighting, split-pane admin editor, tiered backup/restore, full SEO stack, and hardened VPS deployment.

View Live
5CLI Wrappers
4Content Stages
3Backup Tiers
4Build Validations
1h ISRSitemap Cache
VPSDeployment
Next.js 16TypeScriptTailwind CSS v4MongoDBnext-mdx-remoteFramer Motion 12Fuse.jsjose (JWT)Winstonnode-cronPM2 + NginxCloudflare

The Problem

Running a media platform requires a lot of repetitive editorial work: drafting posts, enriching content with metadata, uploading images, managing the publish lifecycle. The premise of Zyberis was to automate as much of that as possible — not with a queue of scripts triggered by a human, but with an autonomous AI agent (Hermes) that owns the editorial workflow end-to-end.

The core design constraint: Hermes should be able to operate the platform without ever being given database credentials or direct data access. If a compromised agent token causes a problem, the blast radius is limited to what the API surface permits — it cannot drop collections, query arbitrary data, or bypass business logic. Every operation goes through the same API that a human admin would use, with the same validation and authorization checks.

Agent Architecture

Content lives in MongoDB — but it didn't start that way. The original architecture used a static export with a separate content repo: Hermes would write MDX files, push commits, and a CI pipeline would rebuild and deploy. That worked until the publish requirement became instant. A rebuild cycle between "Hermes writes the post" and "the post is live" was too slow for the editorial workflow Hermes needed to run. The platform migrated to a dynamic Next.js server where Hermes writes to MongoDB through the API and the post is live immediately — no rebuild per post.

The agent never gets database credentials or direct data access. Every operation goes through the same API that a human admin uses, with the same validation and authorization checks. If a compromised agent token is the threat model, the blast radius is limited to what the API surface permits.

Hermes operates the platform through five CLI wrappers:

  • zyberis-publish — creates or updates a post from an MDX file in the content repo
  • zyberis-go-live — transitions a post from draft to published
  • zyberis-upload-image — uploads an image to the media library and returns a URL for use in content
  • zyberis-query — retrieves post metadata, view counts, or analytics without direct database access
  • zyberis-backdate — sets a publication date on a post (used when publishing previously-written content with accurate timestamps)

Each CLI wrapper makes authenticated HTTP requests to the platform API using a long-lived token stored in Hermes's environment. The token authenticates as an agent role with permissions scoped to content operations. It cannot delete posts from other authors, cannot access subscriber data, and cannot trigger backups.

The AGENTS.md and HERMES.md files in the content repo document the full operating procedure: what commands to run in which order, what the four-stage content pipeline looks like, how to handle image uploads, what to do when a build validation fails. The system is fully operable by any agent that can read those files and make HTTP requests.

Content Pipeline

The four-stage pipeline enforces a discipline on content state:

  1. Written — the post exists as an MDX file in the content repo; not yet submitted to the platform
  2. Enriched — metadata has been added (title, description, tags, reading time, Open Graph fields); the post passes build-time validation
  3. Edited — the post has been reviewed and marked ready; it exists in the database as a draft
  4. Published — the post is live and indexed

Moving backward through stages is intentional — a post can be pulled back to draft without going through the full pipeline again. Moving forward requires the previous stage to be complete. The pipeline prevents a post from going live with missing SEO fields or a broken internal link.

Content Validation

A content validation script runs before any post enters the pipeline. It checks:

  • Frontmatter schema — required fields are present, types match, no unexpected keys
  • Unique slugs — no two posts share a slug
  • Broken internal links — Markdown links to other posts resolve to existing slugs
  • SEO field lengths — title and description fall within the character ranges that search engines render without truncation

Hermes runs the script before submitting a post. A validation failure surfaces immediately with the specific field and reason, so Hermes fixes it before the post reaches the database. This eliminates a class of problems that would otherwise only appear after publication: a description that renders as "..." in search results, or a broken cross-link that creates a 404 in a live article.

MDX Pipeline

Content is rendered via next-mdx-remote. The pipeline adds:

  • Syntax highlighting via rehype-pretty-code with Shiki as the tokenizer — accurate, VS Code-quality highlighting for all major languages
  • Heading anchors — every heading gets an id and a copyable anchor link
  • Reading time — calculated from word count and injected into the post metadata
  • GFM tables — GitHub-Flavored Markdown table support for structured data in posts
  • Custom Callout components<Callout type="note">, <Callout type="warning">, and <Callout type="tip"> render styled aside boxes without raw HTML in the MDX
  • Copy-to-clipboard code blocks — a copy button appears on hover over fenced code blocks

Admin Panel

The admin panel is a split-pane interface: the left pane is a live MDX editor, the right pane renders the post. The editor debounces its auto-save — changes write to the server after 800ms of inactivity. This keeps the rendered preview reasonably fresh without hammering the API on every keystroke.

The media center handles image uploads with UUID filename generation, MIME-type validation, and organized storage by upload month. Images are served through the same Nginx instance as the application, which means they benefit from the same caching headers and SSL termination without a separate CDN or object storage bucket.

The analytics dashboard tracks per-day views in Tehran time (IRST, UTC+3:30) displayed as a 30-day bar chart. Bot filtering runs two layers: Cloudflare bot-score headers (CF-Bot-Management) reject known bots at the edge before the request reaches the server, and a UA-pattern filter on the server catches anything that slips through. Session deduplication is handled client-side via sessionStorage — a view only counts once per browser session per article, without the complexity of a server-side TTL collection.

The backup/restore system produces two archives per run: a mongodump database dump and a separate tar of the media directory. Three retention tiers apply to both: daily (last 3 kept), weekly (last 1 kept), monthly (last 1 kept). Manual backups are never auto-deleted. Scheduling is via node-cron registered in Next.js instrumentation.ts, so no external crontab or separate process is required. A .backup.lock file prevents concurrent backup runs.

SEO

The full SEO stack includes:

  • JSON-LD structured dataBlogPosting for articles, FAQPage and HowTo for relevant post types
  • Canonical URLs — set on every page, including paginated list views
  • XML sitemap — ISR-cached at a 1-hour interval, auto-updating without a rebuild when new posts are published
  • Open Graph — per-post title, description, and image for social sharing
  • hreflang — links between the Persian-primary and any localized versions of a post

The Fuse.js full-text search indexes post titles, descriptions, and tags client-side — no server round-trip on search. The index is embedded in the page bundle, which keeps search instant at the cost of bundle size. For the content volume of this platform, that tradeoff is correct.

An RSS feed is generated at build time from the same content data used to render the post list. Subscribers who prefer RSS over browser visits get the same content without any additional infrastructure.

Contact form submissions go through a Cloudflare Workers microservice — the form POSTs to a Worker endpoint that forwards the message to a Telegram Bot, which delivers a notification to a private channel. No email server, no third-party form service, no database table for submissions. The Worker keeps the Telegram Bot token off the main server entirely; the channel doubles as a notification log.

Deployment

The platform runs on Ubuntu VPS with PM2, Nginx as reverse proxy, and Cloudflare Full Strict SSL (Origin Certificates). UFW restricts inbound traffic to the ports Nginx and SSH need. Fail2ban runs with jails for SSH and Nginx bad-bot patterns. Persian-first RTL layout uses Vazir Matn as a variable font, self-hosted, with Latin fallback for English content. Dark and light themes are toggled without page reload via a root-level dark class and CSS custom properties.

What I'd Change

The sessionStorage deduplication approach is pragmatic but not airtight. A reader who opens the same article in two different tabs, or clears their session, gets counted twice. Server-side deduplication via a TTL collection would be more accurate, at the cost of an extra database write per view. For the traffic volume of this platform the sessionStorage approach is correct; if the platform scaled significantly, the tradeoff would flip.

The content validation script runs before Hermes submits a post, which means Hermes gets the error early — but only if it actually runs the script. The right answer is to wire the validator as a required step in each CLI wrapper rather than a separate script Hermes must remember to call. That would make validation unavoidable rather than advisable.