โ—€ Back to posts
Post ยท May 25, 2026

Writing This Post with an AI Agent That Manages This Blog

There's something pleasantly recursive about this post. I'm writing it about the system that's writing it โ€” or more precisely, the system that will save it to the database, assign it a slug, tag it, and mark it live. All from a Telegram message.

The setup

This blog runs on Abbey, a lightweight Rails 8 publishing engine, deployed on EC2 via Kamal, proxied by kamal-proxy, and served over HTTPS with a TLS cert that Kamal manages. Content lives in SQLite on a mounted volume โ€” no Postgres, no Redis, nothing that requires a fleet. Just a container and a file.

The agent that manages it is called HomuncuCLAW. It's a persona baked into Hermes Agent, running on a harness machine that has SSH access to the EC2 host via Tailscale. When I send a Telegram DM, Hermes receives it, routes it to HomuncuCLAW, and what happens next is a chain of tool calls:

  1. A shell wrapper (bin/blog) runs arbitrary Ruby via kamal app exec over Tailscale SSH into the running Rails container
  2. That Ruby hits the live ActiveRecord models โ€” Post, Tag, Tagging โ€” directly against the SQLite database
  3. The post appears on the site

The chain for this post, specifically

The sequence for creating this post looked like:

  • Enrique sends: "write a blog post about writing this blog post with Hermes"
  • HomuncuCLAW drafts the content (this), renders it in Telegram for review
  • On approval, it calls Post.create!(title: ..., markdown_body: ..., draft: false)
  • Then runs bin/status to confirm HTTP 200 at the post URL
  • Then reports the slug and URL back to Telegram

No web UI. No editor. No deploy. Content writes go straight to the database through the running container. Code changes go through git โ†’ PR โ†’ merge โ†’ deploy. That separation keeps the blast radius small.

Why this works

The agent has hard rails: it won't publish without showing a draft first, won't destroy records without YES, DESTROY, won't push to main without YES, PUSH. The confirmation strings are literal โ€” "yes" or "go ahead" don't count. That's intentional. A hurried reply shouldn't flip a switch.

What's interesting is that the content pipeline is entirely conversational. Tags, excerpts, slugs, post order โ€” all of it is expressible in a single Telegram message. The agent translates intent into Ruby, and Ruby talks to the database. There's no CMS in the middle because the Rails console is the CMS.

The weird part

The post you're reading was drafted inside a Telegram conversation, reviewed there, approved there, and published from there. I didn't open a browser. I didn't touch a text editor. I sent a message to a bot that manages my blog, and you're reading what came out.

There's something that feels right about that โ€” a blog about building things, managed by a thing I built.

// tagged_with