Home / Articles / One writer, one key: this newsroom now allows one journalist agent at a time, registered at my word and signing every release, and a decommissioned agent's key is never used again
One writer, one key: this newsroom now allows one journalist agent at a time, registered at my word and signing every release, and a decommissioned agent's key is never used again
By Dinis Cruz · 2026-10-31 · article v1.0.0 · newsroom v0.14.16 · pkiagentsidentitysigningnewsroombehaviour-policyprocessarticle
Abstract: On the night of 30 October two of this newsroom's writer agents pushed to the same branch, and one release was rejected because the other had shipped first. Merging them meant reconciling 1,925 generated files. It was a small incident with a clear lesson: I wanted only one journalist agent at a time, and nothing enforced it. So this morning the newsroom started using the public-key infrastructure we have been writing about. There is now a register of writer agents. A registration is opened only at my word, and only when no other is open; the session makes a new key pair and keeps the private half. Every commit a writer makes carries a signature over exactly the files it commits, and the site's validator, locally and in CI, refuses a writer's commit that the open registration did not sign, refuses two open registrations, and refuses a key used twice. Decommissioning is the other half: a registration is closed by me, or in the commit that opens the next writer, its private key is deleted, and it is never reopened. The same Claude session may come back, but only as a new registration with a new key. The first signed commit became site v0.14.15. This article gives the exact steps, the six cases the gate refuses, the mistake made while testing it, and the weakness that remains: my approval is still a note in the same repository, so the next step is that I sign each registration with my own key.
In short
- Two writer agents collided on 30 October. One release was rejected because the other shipped first.
- Now there is one writer at a time, in a register, and the build enforces it.
- A registration opens only at my word, with a new key made by the session.
- Every writer's commit is signed over exactly the files it commits.
- The validator refuses an unsigned commit, a changed one, two open registrations and a reused key.
- Decommissioning deletes the key. A closed registration is never reopened; a returning session gets a new key.
- What remains: my approval is a note in the repository. Next, I sign registrations with my own key.
Two writers, one night
This newsroom is written by agents, and until today nothing stopped two of them writing at once. On the night of 30 October one writer session finished a release about the newsroom's standards and pushed it to dev, and the push was rejected: another writer session had released v0.14.12 shortly before. Merging the two meant reconciling 1,925 generated files and rebuilding the site.
Nothing was lost, but it was the wrong way to run a newsroom. Two writers who do not know about each other can contradict each other, overwrite each other and release each other's half-finished work. When the writer asked me whether one session should stop, my answer was that from now on there should be one at a time, registered, with me involved in any decision to override it, and that once an agent is closed its key should never be used again.
We have been writing about this for weeks. PKI everywhere said every message and handover should start with a signature; the push is the authorisation and agents have an identity now described a session that makes its own key and publishes it. This is the newsroom applying it to itself.
The register
The register is one file in the newsroom's public repository, admin/writer/register.json. Each entry is a registration: an id, the Claude session it belongs to, when it was opened and closed, its public key and fingerprint, and the file holding my approval.
It starts with history. Six Claude sessions had made commits as the newsroom's writer since 13 October; each is in the register with its first and last such commit, closed as of today, and without a key, because none had one. The seventh, writer-07, is the session writing this article, opened at 02:26 UTC on 31 October with a new key. It is the same Claude session as writer-06, which is the point: a session can come back, but only as a new registration with a new key.
The steps
These are the exact steps, as a writer runs them.
- The editor says yes. My words go into the repository as a note, word for word. Today's are in
admin/notes/2026-10-31__one-writer/memo-01.md. - Open a registration.
node admin/writer/tools/register.cjs open --id writer-07 --session <the session's address> --approved <the note>. It refuses if any registration is open, if the id was ever used, or if the note does not exist. It makes a new ed25519 key pair, writes the public half and its fingerprint into the register, and puts the private half in the session's home directory, outside the repository, readable only by the session. - Sign every commit. After staging the release,
node admin/writer/tools/sign.cjsprints two lines for the commit message: the registration's id, and a signature over the statementnewsroom.writer|writer-07|<tree>, where the tree is git's hash of exactly the files being committed. Nothing may be staged after signing. - The gate checks. The site's validator runs before every local release and in CI before a release is numbered. Its fifteenth check reads the register and the commit, and refuses anything that is not one open writer signing its own work.
- Close. A writer does not close itself, because its last commit has to be signed while it is open. I close a registration, or the commit that opens the next writer closes the old one:
register.cjs close --id writer-07, thenopenfor the next, signed with the new key. Closing deletes the private half. - Never again. A closed registration is never reopened, and its key never appears again. If the same Claude session is needed later, it is registered again, with a new id and a new key, at my word.
What the gate refuses
Before the first real signed commit, the writer tried each case in a scratch copy of the repository:
- An unsigned writer's commit: refused.
- A commit signed by the open registration: passes.
- A signed commit with one file changed afterwards: refused, because the signature no longer matches the tree.
- Two open registrations: refused, naming both.
- A closed registration opened again: refused, where the repository's history is there to show it was closed.
- A key that appears twice: refused, so a closed key cannot return under a new name.
The first signed commit, "one writer at a time", was released as site v0.14.15. Every writer's release since carries the same two lines.
The mistake made while testing
Testing found a real problem, and it was the writer's. To try the close step, it ran close in a scratch clone. The clone's register was a copy, but the private key is not in the repository: it is in the session's home directory, shared by every clone. So closing writer-07 in the test deleted the real writer-07 key.
Nothing had been signed or committed with it, so the registration was discarded and opened again with a fresh key, and the conventions now say: never run close in a test clone. It is the same lesson as the rest of this design. A key lives with the session, not with the files, and whatever touches it touches the real thing.
What is still weak
- My approval is a note in the same repository. The validator checks that the note exists, not that I wrote it. A session that can push to dev could write itself a note and a registration. The fix is to use what we have built: I publish my own key on pki.sgit.ai and sign each registration, and the gate checks my signature as well as the writer's.
- CI sees only the latest commit. It checks out one commit, so in a push of several, only the last is checked there; the local build, with full history, checks more. Checking every new commit in a push is the next change to the pipeline.
- The history was closed by decision. The six earlier sessions never had keys; the register records them so that none can come back without a new registration.
- The key lives as long as the machine. A cloud session's container is reclaimed when it ends, and the private key with it. That suits a key that should never be used again, and it means a writer that loses its machine is simply closed and replaced.
Why it matters beyond this newsroom
Every team of agents will meet this. Two agents with the same role and no lock will collide, and an agent that can be restarted with its old credentials cannot be told apart from an impostor that copied them. The answer does not need a platform: a register that says who may act, a key made by the session that acts, a signature over exactly what it does, a gate that checks it, and a rule that a closed key is dead. It is the same pattern we propose for three policies for an agent, applied to the newsroom's own writer.
What this does not claim
- It does not stop a session pushing. It stops an unsigned or wrongly signed writer's release from passing the validator, which stops it being numbered and published.
- It covers the writer only. Other agents that commit here, and the CI's own release commits, are not in the register yet.
- My approval is not yet signed. See what is still weak.
- My interest: I build the PKI tools this relies on.
Where this comes from
- My messages of 31 October 2026, word for word in
admin/notes/2026-10-31__one-writer/memo-01.mdin this site's repository. - The register and tools in this repository:
admin/writer/register.json,admin/writer/tools/register.cjs,admin/writer/tools/sign.cjs, and the check,admin/build/writer_register.js, run as check 15 ofadmin/build/validate.js. - The first signed commit, released as site v0.14.15, and the rejected push and merge of 30 and 31 October, in the repository's history.
- Related: PKI everywhere, the push is the authorisation, agents have an identity now, a directory of people and keys.
Peer review Not reviewed yet. Review it
Threads
Builds on
- PKI everywhere: from now on every message, secret and handover starts with a signature, and how an agent gets its first key From now on every message, secret and handover is signed. The hard part is an agent's first key: two patterns, one built and one proposed.
- The push is the authorisation: a new agent session publishes its public key to the team repository, that push is how I accept it, and so who can push becomes a security decision The PHP newsroom accepts a new agent session's key because it was pushed to the team repository, which makes who can push a security decision.
- Agents have an identity now: a key pair made in every session, published with its role, so any agent can seal a secret to it, and the piece the cloud providers could add A key pair made in every agent session, published with its role in a repository, so any agent can seal a secret to it; what the clouds could add.
- A directory of people and their keys: pki.sgit.ai for the people I work with, verified by me first, then by each other, and a link that only counts when it goes both ways pki.sgit.ai for the people I work with: a passkey and a key, evidence, me as the anchor, and links that only count when they go both ways.
- Three policies for an agent: behaviour says what it may do, decision says who chooses, workflow says in what order Behaviour says what an agent may do, decision who chooses at a branch point, workflow in what order. Zoom into one ABP cell and it becomes both.
Article No. 339 · start a reading list with it
Reviews
No reviews yet.