I have an agent loop that writes short posts for one social channel. It runs at night. It calls a script, the script produces a checked post, the agent reads the text out of the script’s output and puts it into its result, and a runner turns that result into a card I can approve in the morning. That is the whole chain. Five parts, one job.
In 30 days that loop ran 147 times and cost 100.13 USD. Posts delivered in those 30 days: 0. It is the most expensive loop I run and it has never put anything out. On the 7th and the 8th of September 2026 the script behind it produced a valid post on each night — on 2026-09-08 it was 264 characters with an internal score of 8.5, past every gate the pipeline has. Neither one arrived. Not rejected, not too weak. Lost between two parts.
I thought this was a findability problem. The agent could not reliably locate the post inside the script’s output, so it needed clearer markers. That was exactly the fix I shipped early on 2026-09-08: markers around the text so the model would find it. Underneath that sat an assumption I had never said out loud — that if you show a language model a piece of text clearly enough, it will carry it forward intact. The failure was a lack of clarity in the instruction, and clarity is something you can retrofit. One level up I was making the same mistake at larger scale. I had written a rebuild plan to pull rules out of prompt prose and into enforced code: thresholds, exclusion lists, format rules. Nine moves, each one argued, each one estimated. Harden the prose, I thought. Good plan.
Markers fix where the text is found. They do not fix why it goes missing. The cause is that a finished, already-checked piece of text was routed through the one part of the chain that can drop something on the way. This is not misbehaviour I could train out. A model that copies 264 characters is not breaking a rule when it copies 258 instead, or none at all. No prompt catches it, because there is no violation to forbid. It is the basic property of the part. The question that broke my plan came from the person I build this for, and it was two questions in one: should code drive the process rather than the models, and why is the code working with markdown files at all? Both halves landed. The text does not have to go through the model. The runner can pick it up where it is produced — it had been doing exactly that for the cover image since 2026-08-24, and nobody had drawn the analogy. The model should judge, not transport. Judging is the only thing it can do that nothing else can; transporting is something any assignment does without error.
The second half was worse for my ego, because it took a plan I liked and made most of it pointless. My plan wanted to harden prose that should not have existed. In two places the code read markdown back with regular expressions to reconstruct state — bold text, numbering, a “(~15 min)” suffix — while the same agent was already delivering the same information as validated JSON, and a database function was already fetching it from there. The code called the file path a “fallback for the transition phase” in its own comment. So I measured instead of believing the label: the most recent impulse file was from 2026-08-13, the most recent dispatch file from 2026-07-15. The transition phase was over. What remained was a second route to the same answer. One of the two functions was exported, commented, and called by not a single line.
So the finding is not “markdown is bad”. It is that of two versions of the same answer, the less accurate one is the one nobody maintains. A third function still reads markdown and is allowed to: its file is kept by hand and has no counterpart in the database. There is one route there, not two.
What I built instead
The runner now stamps the body into the payload itself. The script drops its result as a file; the runner reads that file and sets the body before the identity check, the test bench, the critic and the cover image ever look at it. The position is half the build. If it stamped afterwards, every one of those checks would be inspecting a text that never goes out, and the text that does go out would be unchecked.
Der Runner stanzt den geprüften Post-Body ein, statt ihn abschreiben zu lassen
The runner stamps in the checked post body instead of having it copied out
The prompt still asks the agent to mark the text, and that is deliberate. The marking is now the control sample. Where it is missing or differs, somebody knows in the morning that something is off. The model went from carrier to witness. Five findings go into the log instead of one yes-or-no. The fourth of them was not in the plan and is the original case itself: if the model marked nothing at all, the section is created anyway. The fifth reports instead of guessing — which of several marked sections is the post is something the code cannot know, and taking the first one would be a decision made out of ignorance.
Die Übergabe des X-Durchstichs an MarketingOps — die Datei zum Aufruf
The handover from the pilot script to the ops app — the file the call points at
That handover file is where I nearly built the next failure in. I wanted to call the score field “note”. It carries the pipeline’s own critique score, which is a best-of-N prefilter. The decision to publish is made in the other application by a different number, the score of a second critic. Measured on artifact 372, the same text: 8.5 here, NULL there. A field called “note” would have been an open invitation to hold the wrong number against the release threshold on the far side. It is called pipelineNote now, after what it actually is.
- 2026-09-08Gave the pilot script a place to put its result on disk, so the receiving app has something to read that is not a chat transcript. Named the score field after what it is rather than what it looks like, because the far side has its own number with the same shape.
- 2026-09-08Moved the body into the payload in the runner, ahead of every check that reads the payload. Wrote three ordering tests to nail the position down; two of them could not be made to fail and had to be rebuilt.
- 2026-09-08Wrote down, in the place where such things are declared, that an automated system can now put a draft on the card and out the door after the veto window without a human click.
- 2026-09-08Deleted the two code paths that reconstructed state by parsing markdown, after measuring how stale those files actually were. Kept the one reader whose file has no database equivalent, and made it return nothing rather than an empty row.
- 2026-09-08Checked the two machines against each other with checksums after shipping, found one half of the chain already in production and the other half missing entirely, and recorded why the drift report could not have told me that.
The tests that were green for nothing
Two of the three ordering tests could not be made to fail. They asserted that the stamping position was smaller than the test bench position, with both positions coming from an index lookup. When that lookup finds nothing it returns -1, and -1 is smaller than every real position — so the test passed just as happily when no stamping happened at all. The mutation check found that, not the writing of it. A neighbouring test had a different disease: it searched for the literal spelling of the environment variable in the runner, while the runner correctly uses the imported constant. A test that pins the spelling turns red the moment somebody improves the code.
Then the deploy. The drift check before shipping did not list my files from the ops side, and I almost drew the wrong conclusion — that they fall outside the transfer scope. Checksums on the target machine said the opposite: they had been sitting there since 13:57, carried along in a deploy from a session running in parallel. The other half was the one missing. Half shipped was worse here than not shipped at all: the runner would have set the environment variable, called the script, and the script would have written nothing. The log line would have read that there was no result file — and that is exactly the sentence my own check protocol reads as “the agent did not run the script”. A false diagnosis from a sentence I wrote myself.
=== WERKSTATT (Soll) ===
c5ab8645d09e09e0a0176d860cde8982 durchstich-x.mjs
45164cd1cc98ff7e98323d1718bbee83 server/lib/x-ergebnis.mjs
8d02d441b0ec77a9c0a03c24dbb85f43 server/lib/x-ergebnis.test.mjs
=== PRODUKTION VORHER ===
04c626695414a8b7ac45d71f2fd3e6a9 durchstich-x.mjs
ls: cannot access 'server/lib/x-ergebnis.mjs': No such file or directory
=== PRODUKTION NACHHER ===
c5ab8645d09e09e0a0176d860cde8982 durchstich-x.mjs
45164cd1cc98ff7e98323d1718bbee83 server/lib/x-ergebnis.mjs
8d02d441b0ec77a9c0a03c24dbb85f43 server/lib/x-ergebnis.test.mjs
--- laesst sich der Import aufloesen? ---
OK, exportiert: X_ERGEBNIS_ENV, X_ERGEBNIS_FELDER, schreibeXErgebnisDer Deploy-Befund zum Body-Stanzen: die Kette war halb ausgeliefert
The deploy finding on body stamping: the chain was half shipped
The generalisable part is small and I keep relearning it: a drift check measures what is missing. It does not tell you why something is not in the drift. A file is absent from that list for two opposite reasons — it is already over there, or it never falls into the transfer scope at all. Only checksums on the real machine separate the two.
The record does not name the tools I used. What it does show is that test files changed, so a test runner was involved — that is inference from the changed files, not something I wrote down at the time.
Not resolved yet. The suites are green — 3441 in the ops app, 807 in the content app, 0 red in each, against a baseline of 3370 green and 4 red before. But green suites are what got me here in the first place: one of the commits exists only because a fresh checkout would have had an import pointing at nothing, and the suite ran green purely because the file happened to sit in the working tree. The real measurement runs on the morning of 2026-09-09: does the log carry the line saying the post body was stamped, and does the night run’s card carry the script’s text? Until then I do not know whether I hit the right cause. What I have ruled out is the one I believed first — this is not about the model failing to find the text, because the text no longer passes through the model at all.
Measured 2026-09-08. Five commits that day, all in one afternoon; the fact sheet spans 0 days between the first and the last. The follow-up measurement was scheduled for the morning of 2026-09-09 and is not part of this record.
6ef8e78e · f1ab63ff · f54088b8 · 1eb7e69b · a31bbff2
