System ESM Reference Build 01

An unattended writing system that publishes under my own name

It drafts a post every other day, in my voice, anchored to things that actually happened. Then it stops and waits for me, because it is my real professional profile.

Reference build. I built this for myself, not for a paying client. It runs in production on my real LinkedIn profile, which is the part that makes it worth showing.

9posts published
14drafts produced
5documented failures
Livesince 2026-08-10

The problem

Cadence, not ideas

I ship work constantly and almost none of it was visible. Posting consistently is the kind of task that loses to whatever is on fire that day, so it just never happened.

The obvious fix is to have an LLM write the posts. The obvious fix is also how people end up with a feed full of content that sounds like nobody, says nothing, and occasionally claims something that isn't true under their own name.

So the interesting problem was never "can a model write a LinkedIn post." It was: can a system write on a schedule, in my voice, about things that actually happened, and be safe to leave running.

What I built

An agent that drafts without being asked

It picks the next content pillar from a fixed five-way rotation held in state. It pulls real source material, either from my decision log for posts about actual work, or from a commentary bank for the opinion pieces. It writes the post in my voice, following a style encode built from things I actually wrote. It runs structural checks, saves the draft, renders a preview I can read in a browser, and generates a card image.

Then it stops and waits for me. I approve or reject. Nothing publishes without that.

SCHEDULED RUNevery other day, 15:07
PICK PILLARdeterministic, from rotation state
PULL REAL SOURCEdecision log, or the commentary bank
WRITE THE POSTthe model, in my voice encode
STRUCTURAL CHECKSdeterministic. flags, never blocks
DRAFT + PREVIEW + CARD
I APPROVEnothing ships without this
PUBLISH · ADVANCE ROTATION · WRITE THE LEDGER ROW

deterministic code the model human gate

The hard part

Publishing under your own name, unattended, without the system embarrassing you

A content generator is a toy. A content generator wired to your real professional reputation is a liability, and it fails in four specific ways. Each one needed its own guard, and I found three of the four the hard way.

Failure 1

It sounds like a robot

The voice encode is not a description of my voice, it is samples of it. Real paragraphs I actually wrote, pasted in, plus explicit rules: short sentences, plain words, no jargon stacking, minimal emoji and never in the hook.

Then a deterministic checker enforces the mechanical parts. Length band, hook length, hashtag count, no URLs in the body, and a banned list so it never names a product I sell instead of describing the outcome. The checks flag, they never block, because a human reads every draft anyway.

Failure 2FOUND THE HARD WAY

It says something that isn't true, under your name

This is the one that matters, and it is the one that bit me.

The rule is that every post has to be anchored in something real. Posts about my own work pull from my decision log. Commentary pulls from a bank of takes I actually hold. If there is nothing specific and true to say, the instruction is to say less.

That was not enough. On 2026-08-26 a draft claimed the automation had published "for the first time." The agent had inferred that from a missing dependency, reasoning that a method which had ever run would have left its library installed. Plausible. Also a guess presented as a fact, and it had already reached a log entry too.

The fix was not a better prompt. It was a change to the record: every ledger row now states how each post was published, by hand or through the API, so the question is answerable from data next time instead of inferable. Plus a standing rule, don't claim a first unless the record shows it.

That is the pattern I trust. When generated text makes a claim, the fix is to make the claim checkable, not to ask the model more nicely.

Failure 3FOUND THE HARD WAY

It repeats itself

Rotation lives in a state file, not in the prompt, because a model asked to remember what it posted last week will confidently make something up. State is bookkeeping and bookkeeping is code.

It still went wrong. Two posts made the same argument three days apart. The cause was two layers down: rotation only advances when a post is actually published, which is correct, but the finalize step was crashing on a file copy before it got to the advance. A bookkeeping step that crashes looks exactly like a bookkeeping step nobody ran.

Fixed by making finalize idempotent, and by making "state agrees with the ledger" a checked precondition before drafting rather than an assumption.

Failure 4FOUND THE HARD WAY

It stops quietly

The worst failure for anything unattended. Mine did a version of this: nothing was published for a week, rotation only advances on publish, so every scheduled run redrew the same pillar and three near identical drafts stacked up from one source file. The approval gate was working exactly as designed and the queue cost was completely invisible.

The rule that came out of it: a draft sitting unreviewed for more than two days is not neutral, it costs the next slot. Post it, reject it, or archive it.

Evidence

What you can check

ClaimArtifact
It publishes real posts9 live LinkedIn URLs, one per row in the content ledger
It runs on a schedule, unattendedA launchd job, every other day at 15:07
The approval gate is real14 drafts logged, 9 published. One rejected outright, four archived unposted
The structural checks existmethods/validate_post.py, runnable
Rotation is deterministicrotation-state.json, advanced only on publish
It publishes through the APIThree posts, 2026-08-25 to 2026-08-27. Earlier rows do not record the method, so I do not claim them
The failures are documentedFive incidents, each with a dated ledger entry and a structural fix

That last row is the one that matters. Nine published posts is the least interesting number here. Anyone can show you output. The five documented failures are the actual evidence, because they show what happens when an unattended system goes wrong and nobody is watching, which is the only question worth asking before you leave one running.

Every one of the five was silent. A duplicate post. A stalled rotation. An unsupported claim. A cropped image. Not one of them threw an error a human would see.

One number I am not going to give you. Engagement. I have not wired view or reaction data into the ledger, so I have no performance claims to make and I am not going to invent any. What I can show you is that it runs, that it is safe, and that when it broke I found out and fixed it properly.

Stack

An agent workspace, not a workflow pipeline

The dividing line I use: deterministic where the input is structured and the decision is a rule you could write down, agentic where the input is messy and the output is a judgement or a piece of prose.

Writing a post in a specific voice from a decision log is squarely the second kind. Picking the next pillar, checking hook length, advancing state, writing the ledger row, all of that is the first kind. So the split is the architecture. Eight small Python methods do every deterministic thing. The model does exactly one job, writing, and it is not allowed to own state.

Where a workflow tool would have won. If this were pulling structured rows and posting them on a threshold, n8n would be the right answer and I would have used it. It is the tool I reach for when the work is plumbing. This isn't plumbing.

It is also not fully autonomous, on purpose. I approve every post. The workspace supports a mode that publishes without asking and I have not turned it on, because it is my actual professional profile and the cost of one bad post outweighs the convenience. The gate is the feature.

Cost

Near zero, and why that number is misleading

The drafting runs under my existing Claude Code subscription rather than the API, so each draft has no marginal cost. Publishing through LinkedIn is free at this volume. There is a standalone runner built and documented that would use the API and would cost real money per run, but that is not the path in use, so I have not measured it and I am not going to quote it.

A client build would pay API rates. Anyone showing you $0 for a system like this is either riding a subscription the way I am, or not counting properly.

For a real client

What I'd do differently

The limits

What this does not prove

Worth saying plainly, because most case studies you'll read won't.

  • This is my own system, not paid client work. Nobody hired me to build it.
  • It is not fully autonomous. A human approves every single post, and that is a design decision I would defend, not a gap I am hiding.
  • I have no engagement or performance data, so this proves the machine works, not that the content performs.
  • Nine posts over eighteen days is a real track record but a short one.
  • The near zero running cost comes from riding an existing subscription and would not transfer.

What it does prove: I can build a scheduled generation system that writes in a specific voice, stays anchored to real source material, keeps its own state honestly, fails safely, and gets fixed properly when it breaks. That is the machine. The output on top of it is interchangeable.