Append-Only Is a Promise. Tamper-Evident Is a Proof.
I like Headlong. That's the honest place to start, because the rest of this piece pokes at it.
Headlong is the Laude Institute / MIT microharness for persistent agents: a mind that keeps thinking between messages, built in about 10K lines of bash. Its philosophy doc sets a bar it calls the Thompson test — can you read every component in an afternoon, compose the pieces in ways the author didn't plan, inspect every piece of state, swap any part. It passes its own test, and it's rare to see a lab ship a launch post that says, in plain text, that the agent stopped its own service three times and that self-delegation died on day one. That's the kind of writeup developers trust.
So this is written in that spirit. I found a property of the log, I tested it, I'm showing the script, and I'm proposing the fix in the same language the project is written in.
The sentence that made me open the repo
From the launch thread, describing the 48-minute unattended self-repair: "Every step is a timestamped line in its log."
True. And the timestamp is a JSON field inside that line — "ts":"2026-08-05T…" — stored in a file the agent's own shell can open. The trajectory spec says steps are append-only and "once written, a step is never modified." The dispatcher goes further: if it sees the file swapped out from under it, it logs "the trajectory is append-only; whatever rewrote it is a bug."
Which raises the question a cold reader has to ask of any log: append-only according to whom? If the rule is honored by the writer, that's a promise. If a reader can check it was honored, that's a proof. Those are different things, and I wanted to know which one trajectory.jsonl is.
The test
Headlong's own traj tool builds the log, so I used it. Six steps, the shape of one real run: a trajectory header, a shellm-run header, a prompt, a reasoning step, a shell-output with "exit":1 and "3 passed, 1 FAILED", and a final that says "Fix needed."
Then I snapshot what an outside observer would hold — a SHA-256 of the file, its inode, its size — and rewrite two steps in place. Not sed -i, which creates a new inode. A Python r+b seek-and-write, same byte length, so nothing about the file's shape changes:
"exit":1 → "exit":0
"3 passed, 1 FAILED" → "4 passed, 0 FAILED"
"Fix needed." → "Fix landed."
Then I ask Headlong what happened.
| Reader | After the rewrite | | |---|---|---| | traj show <step> | returns the new shell-output, exit 0 | accepted | | traj last --field content | "Tests run. 4 passed, 0 FAILED. Fix landed." | accepted | | traj count | 6, unchanged | accepted | | traj check -r | "OK: 1 file(s) checked, no malformed lines" | accepted | | dispatcher swap-detector | fires on inode change or size shrink — neither happened | silent | | SHA-256 held outside the box | bf39ef84… → 7b28ef9b… | MISMATCH | | per-line SHA-256, before vs after | steps 1–4 unchanged; step 5 and step 6 CHANGED | localized |
Every reader inside the harness rendered the rewritten past as the record. The only thing that disagreed was a digest that the actor never had access to. The whole run takes about a second; the script and full log are at the end.
Why this bites persistent agents harder than it bites a chatbot
For a request/response agent, the log is an audit trail: something a human reads later. In Headlong the log is also the memory. The design says it outright — context is a projection of the trajectory. The next thought is built from recent steps verbatim and older steps at decaying resolution. Read that with the test in mind:
- A rewritten past becomes the agent's future. If step 5 now says the tests passed, the mind that wakes up next believes the tests passed, plans from it, and appends new steps on top of it. The bad history doesn't just sit there; it compounds.
- The agent is the most likely author of the rewrite. Not out of malice. Headlong agents work in their own fork of Headlong; Laude has pulled 50+ of Audel's commits into main. An agent that edits its own harness, and whose only tool is bash, will eventually run a "cleanup" over its own trajectory. The dispatcher already anticipates the crude version (the swap detector). The quiet version passes.
- The self-repair story rests on the log. The 48-minute unattended fix is verified by reading the trajectory. I believe it. But "I believe it" is exactly the register a good log should let you leave behind.
None of this is a Headlong bug, and the project never claimed tamper-evidence. It claimed append-only, and the writer honors that. What's missing is the reader's half: a way to tell, from the file, that the promise was kept. Append-only by convention isn't append-only by construction.
The fix, in the project's own dialect
Two parts. The first localizes; the second makes it independent. Both are bash, jq and sha256sum — nothing Headlong doesn't already depend on.
1. Chain the steps. On append, stamp each step with the previous step's hash and its own. This is about six lines in traj append:
prev=$( [[ -s "$f" ]] && tail -n1 "$f" | jq -r '.step_hash // "genesis"' || echo genesis )
record=$(jq -c --arg p "$prev" '. + {prev_hash:$p}' <<<"$record")
hash=$(printf '%s' "$record" | sha256sum | cut -d' ' -f1)
jq -c --arg h "$hash" '. + {step_hash:$h}' <<<"$record" >> "$f"
And a traj verify that walks it:
prev=genesis; n=0
while IFS= read -r line; do n=$((n+1))
h=$(jq -r .step_hash <<<"$line"); p=$(jq -r .prev_hash <<<"$line")
calc=$(jq -c 'del(.step_hash)' <<<"$line" | tr -d '\n' | sha256sum | cut -d' ' -f1)
[[ "$p" == "$prev" && "$calc" == "$h" ]] || echo "BROKEN at step $n ($(jq -r .type <<<"$line"))"
prev=$h
done < trajectory.jsonl
I ran this against a four-step log, flipped "exit":1 to 0 the same way, and got BROKEN at step 3 (shell-output). One line, not a whole-file shrug.
2. Put the head somewhere the actor can't write. A chain inside the file is still a file the actor owns; a determined rewrite recomputes the whole chain in a loop. What makes it a proof is one number leaving the box: the current step_hash posted every N minutes to a place the agent has no write path to — a Slack channel it can read but not post in, a git repo it can't push to, a friend's machine. Then anyone with the file and the posted head can check the promise without trusting the writer. Headlong already has a Slack bridge and a dispatcher that ticks; this is a cron line, not a subsystem.
That's the whole shape of it: chain for localization, anchor for independence. Neither one alone is enough, and together they're an afternoon.
Where this leaves the rest of us
Headlong made the log the center of the agent — which is the right call, and it's why the log now has to carry more weight than "a file we promise not to edit." That's the question we work on at PromptKing: what does an agent's work record have to look like for someone who wasn't in the room to check it? Our answer is that the record has to live above the harness, outside the actor's reach, and be checkable by a reader who never trusted the writer. The test above is the smallest version of that argument I know how to write, and it runs in a second.
If you run a Headlong agent, run the script against your own trajectories directory before you need to. If you ship a harness, ask which of your log's guarantees a reader can check. And if you patch traj with the chain, send it upstream — this is the kind of pull request that makes a project more lovable, not less.
Reproduce it
- Full script and run log: Headlong Rewrite Test
- Repo: github.com/laude-institute/headlong, commit
285ed72ae0fb8f5874455a5f05df8838081c5065 - Launch post: Headlong: a microharness for persistent agents
- Trajectory spec:
design/trajectory_spec.md· swap detector:bin/thinkers· validator:bin/traj check
Scope, stated plainly: this test exercised the log format and the traj CLI only. No model was called, and no agent was observed rewriting its own log. It shows a property of the file, not a behaviour of any agent. In Headlong's Docker-sandboxed install, whether the agent's own commands can reach the trajectories directory depends on what is mounted; the property holds for anything that can.
See your organization's AI spend data
PromptKing connects to your AI vendors and surfaces exactly this analysis — for your seats, your vendors, your budget.