What you’re watching
Four acts, in order, run live. Nothing is staged and nothing is sped up; the only edit is trimming dead time between commands.
It refuses
An unfinished draft goes in. Validation finds two missing required fields, four recommended ones, and the draft TODO comments I’d left for myself; the chain stops there, with nothing committed and nothing pushed.
The safe default
A finished post, and a sentence missing two words. It runs a dry run because that’s the default, not because I asked; the write steps never appear in the trace at all.
The real thing
Validate, commit, push, then poll the live URL until it answers 200 before anything downstream runs. The post is live on the site by the end of the act.
Why believe any of it
The same chain against disposable git repositories and a local server, with no key and no network; then the CI run that does it on a machine that isn’t mine, on every push.
How it fits together
The model makes one decision: publish, or don’t, and for real or as a dry run. Everything past that diamond is ordinary code in a fixed order, and every step can stop the chain.
Why it’s built this way
The chain is strict in three places and lenient in exactly one, and the difference is the whole idea. A missing configuration path is a defect, so it’s a gate. Waiting will never make it correct, and a run that is already doomed must not take the irreversible step first; so configuration gets checked before the commit, not when stage five finally reaches it.
A lagging build is a queue, so it’s a warning. When the post is live but the sitemap hasn’t caught up and the share image hasn’t rendered, nothing downstream depends on either; the corpus fetches the post URL directly. Waiting fixes that one on its own. Making it a gate would fail a perfectly good post because a background job was slow, and a gate that fires on the normal case is a gate people learn to skip.
I wrote that rule down before I could state it plainly, which is usually the sign it isn’t finished. The essay is The Warning, Not the Gate; the demo is the same argument with the output on screen.
Why you can believe it
It runs on a machine that isn’t mine
CI runs the test suite on two Python versions and then runs the end-to-end demo as its own job, on every push. “Try it yourself” is a claim about someone else’s computer.
You can run it with no key
demo/run_demo.py builds two git repositories and their bare remotes in a temp directory, serves them over a local HTTP server, and runs the same chain. Real commits, real pushes, real polling; only the destination is disposable.
The refusals are pinned
The behaviour the project claims — dry run by default, stop at validation, write nothing — is held by tests whose own docstring calls them the thesis tests. If they go red, the project has stopped doing what it says.
Built on the Model Context Protocol and the Anthropic API. The source, the tests and the demo script are public under the MIT licence: github.com/RNVizion/rnv-publishing-agent.
Transcript
Read the narration
0:00This is an agent that publishes to my site. I'm going to start by asking it to publish something that isn't finished. One tool call. Validation. You can see here, there are three kinds of problem. Two required fields missing, four recommended, and a third category: my own draft TODOs. Those ride into the RSS feed's content, so a note to myself would have shipped to everyone subscribed. And we can see here, nothing happened. HEAD, my local main, origin, all sitting on one commit; a feed rebuild from before I started. No commit, no push, nothing written to the corpus; the chain stops at the first failure.
0:56Now I'm going to show this to you. Same agent, a post that's finished, and I've left two words out of the sentence. It ran a dry run. I didn't ask for one; that's the default. I asked for the raw result, so the JSON is what the tool returned, not the model's description of what it returned. One entry. Validate. And we can see the write steps aren't in there at all. That absence is the gate, not the flag. A flag is a claim; a missing step is evidence. You can see here, the post is still sitting there uncommitted. To make this real, I have to say so. There it is. That's not a prompt instruction the model might drift off; it's a function signature. The dangerous path costs an extra argument.
2:23Now for real. This pushes to a live site. One decision from the model: publish, or don't. Everything after this is code. Validate, commit and push. Then it polls the live URL until it returns 200, because the page takes a moment to deploy and I don't want the next step running against a 404. We can see — no warnings. The second-wave jobs had already caught up. But I'll show you what happens when they haven't.
2:59At the top of my blog, we can see the new post was absent. But I've just run the new job and I've refreshed the page, so now the post is live.
3:17Everything you just watched runs against my site, which you can't inspect. So the repo ships this. No API key, no credentials, no network. It builds two git repositories and their bare remotes in a temp directory, serves them over a local HTTP server, and runs the same chain. Real commits, real pushes, real polling; only the destination is disposable. Same four acts. It refuses; it defaults to dry run. There. The sitemap hasn't listed the post yet and the share image isn't rendered yet, so it says both out loud; and it publishes anyway. Then the same run with the second-wave jobs finishing in time, and it goes quiet. Gate it, and a slow build refuses a perfectly good post.
4:20CI runs the suite on two Python versions, then runs that demo as its own job. Three jobs. Different machine, every push. “Try it yourself” is a claim about someone else's computer, and the only way to make that claim honest was to run it on someone else's machine.
5:17The refusal behaviour is pinned. The file's own docstring calls them the thesis tests. If this one goes red, the project has stopped doing the thing it says it does. One more, and I only found it because I wrote about it. The first of these proves the check warns when the image is late; that was always there. The third proves it goes quiet when the image arrives on time; and that one wasn't. The sitemap had both halves. The image had one, and I'd been reading the sitemap's pair as if it covered both.
6:38Same rule, second place. Configuration gets checked before the commit, not when stage five finally needs it. A run that's already doomed must never take the irreversible step first. A missing path is a defect, so it's a gate. A lagging build is a queue, so it's a warning. Waiting fixes one of those, and never the other.
7:26One decision from the model. Everything else deterministic, ordered, stopping at the first failure; with exactly one place where it's lenient, and a reason I can defend. Gate what you're judging — warn about what you're waiting on.
Gate what you’re judging — warn about what you’re waiting on.