Oct 29, 202611 min read/2026/10/29/who-is-working-on-what/

Who Is Working on What (Creatura, Part 5)

Agent identity bound to hardware, a signed work ledger, and a hub that can carry everything and forge nothing.

The mess

I run agents on more than one computer. Different chips, different operating systems, different tools. Claude Code keeps its history in one folder per project. Codex keeps its own, filed by date. Neither file says which machine it was on, which agent wrote it, what task it was for, or whether the work is still going.

So the simple question "who is working on what?" had a terrible answer: visit every computer and read every tool's files.

The design note in Creatura puts it in my own words, and the request was blunt: give the agents a strong identity tied to the hardware and the network, and a way to talk to each other as cells.

In Creatura, a cell is one creatura (one agent) on one vessel (one computer). No cell is in charge, and every cell keeps its own copy of the record.

The theory behind judging agents by what they did, not by what they say they did, is in Agents That Defend Goals. Here I want to show the plumbing.

Who: an identity bound to the hardware

A cell has three things.

  1. A creatura id. A stable id for the agent itself. It survives a move to another computer, a change of model, or a change of host tool. It answers "is this the same agent?"
  2. A cell key. A signing key pair generated on the vessel and never copied off it. The algorithm is ECDSA P-256, which .NET ships without any package, so the cells library stays free of dependencies.
  3. A vessel id. Derived from the platform's hardware identifier, but never equal to it.

That last point matters. The raw hardware identifier is read, hashed with a per-owner salt, and goes no further. The vessel id is "v-" plus the first 24 hex digits of an HMAC-SHA256 of that identifier. The raw value is never written to a file, never printed, never logged. There is a pre-registered test for exactly that: the identifier must not appear in any entry, log line or file the cell writes.

Then there is the owner key. It lives on the owner's vessel, and it signs one thing: a VesselEndorsement, which binds a cell's public key to a vessel id and a creatura. That endorsement is the root of trust for the ledger. A cell nobody endorsed is a stranger, however well it signs.

What: the work ledger

The ledger is an append-only log of signed entries, one numbered sequence per cell. There are eight kinds of entry:

public enum EntryKind
{
    Hello,      // this cell exists: creatura id, vessel, position, public key
    Claim,      // I am working on this: goal, repository and branch or folder, host tool and session
    Progress,   // a trace row, a stress level, a blocked or escalated step
    HandOff,    // I am stopping; this is where the work stands and why
    Release,    // done, with the evidence the goal cites
    Heartbeat,  // still alive, with the vessel's load
    Artifact,   // a file shared with other cells (the bytes are elsewhere)
    Warning,    // something this cell saw that the others may act on
}

The first design had six; Artifact came with shared files, and Warning from the convoy simulation, where a truck that catches a road lying warns the others.

The path of a piece of work is the chain of entries under one claim, readable afterwards like a route. A cell can write entries but never take them back.

Merging without a leader

Two ledgers merge by taking the union of their entries, keyed by cell and sequence, and dropping anything that fails verification: a bad signature, a missing or wrong endorsement, an owner the reader does not trust. If two different entries ever claim the same cell and sequence, the one with the smaller hash wins, everywhere.

That is all the agreement the ledger needs. No leader, no consensus protocol, and it converges whatever order the entries arrive in. Delivery can be "at least once", because a duplicate is just an entry the ledger already has.

Where Sync Framework becomes concrete

In Part 1 I told the story of how Sync Framework began: a road trip from Arizona into Mexico that went wrong, a flight back, and hours at an airport with a laptop. That's where its first version was born. I said then that it is now one of the frameworks inside Creatura. This is where.

Sync Framework moves deltas: small records of change that a client pushes to a server and other clients pull. Creatura's ledger maps onto that almost too neatly. One signed entry is one delta. The delta's operation is the entry's canonical JSON, and its identity is the cell that wrote it:

return new Delta(entry.Cell, Encoding.UTF8.GetBytes(entry.ToJson()))
{
    DeltaId = 
quot;{entry.Id}:{entry.Hash()[..16]}", ... };

On the way in, LedgerDeltaProcessor parses each delta back into an entry and hands it to the same WorkLedger.Merge everything else uses. Nothing in a delta is ever executed. A delta that isn't an entry is rejected. A delta's identity is only the envelope, not the proof: a valid entry relayed by another cell is accepted, and a forged one sent under its own cell's name is not.

The project is Creatura.Cells.Sync, on the BIT.Data.Sync 3.0.0 package. The hub, Creatura.Cells.Hub, is an ASP.NET Core host built on BIT.Data.Sync.AspNetCore 3.0.0: Sync Framework's own sync controller, over a delta store of Creatura's. I wrote about the 3.0 release separately in SyncFramework 3.0: .NET 10, an in-memory client and an official MySQL provider.

The two projects helped each other. One of Creatura's pre-registered tests, C12, says a cell that pulls before it ever pushed must still push all of its own entries. It failed. The cause was in Sync Framework's MemoryDeltaStore: a client that pulled first got the server's index as its push cursor, and never pushed its deltas. That became fix #27 in the 3.0 release, and C12 now passes against Sync Framework with no workaround in Creatura.

A framework started in an airport in Mexico found its first real bug of the 3.0 line because an agent framework started in China needed it to be honest.

The hub: a relay that cannot forge

The cells sync through a hub, not peer to peer. A hub needs only one listening computer, and every cell, the hub's own included, syncs with it.

The hub is a relay, and it is built not to be trusted:

  • It holds no cell key and needs no private key at all. It is given a file of owner public keys to trust. Anything else in that file and it refuses to start. It can move entries; it cannot write one.
  • It verifies every entry it is pushed against the owners it trusts, and stores each valid entry once.
  • It answers only signed requests. Every request carries the cell's endorsement, the time, and the cell's signature over the method, the path, the time and a hash of the body. The time must be within five minutes of the hub's clock. Anything else gets a 401 with no ledger content.
  • It refuses replays. It keeps every request signature it has accepted, for twice the window, in its database. A request is good once, even across a restart.
  • It refuses revoked cells. If a vessel is lost, the owner signs a revocation: this cell, trusted up to sequence N. After that every ledger drops and refuses its later entries, the hub refuses its requests, and the revocation travels with sync to every other cell. Two revocations of one cell keep the smaller N, so the result is the same in any order. A revocation cannot be withdrawn; a cell that comes back gets a new key.

And then every cell checks every entry again when it merges. The hub's checks keep strangers out. The cells' checks are what the ledger actually trusts.

Plate IV of the Creatura anatomy page, the mesh: on the left, the owner's laptop holding the owner key and the hub, and a second machine joined by request, endorse and accept, both syncing signed entries over a private network; on the right, three trucks in the convoy sharing a ledger over an in-memory mesh

Shared files the hub cannot read

Agents produce things worth passing on: a report, a trace, a patch. The ledger carries the record, never the bytes, because every cell keeps the whole ledger and a file in it would be copied everywhere forever.

So creatura cell share encrypts the file first, with a fresh AES-256-GCM key per file, and wraps that key for each recipient cell using that cell's own encryption key (a second key pair, ECDH on P-256, announced in its hello). By default the recipients are every endorsed, unrevoked cell. The ciphertext goes to the hub, filed under its own SHA-256. The ledger gets a signed Artifact entry with the hashes, the wrapped keys and a sealed label. Even the file's name and type are encrypted.

The hub keeps only ciphertext: up to 64 MB per file, for 90 days. One test searches everything the hub stores for the plain bytes or the file name and must find neither. A file changed by one byte at the hub is refused at fetch, and nothing is written.

Joining a vessel

The owner key never moves. A new vessel joins in three steps:

  1. On the new vessel, creatura cell request creates its cell and writes a request holding only public values.
  2. On the owner's vessel, creatura cell endorse signs it into an endorsement.
  3. Back on the new vessel, creatura cell accept checks that the endorsement covers its own key and vessel, keeps it, and writes its hello.

An endorsement made for another key or another vessel is refused. Entries endorsed by a different owner never reach the hub or my ledger.

Claude Code, turned into claims

The first agents on the network aren't Creatura's own. They are my Claude Code sessions, joined through their hooks. creatura cell hook reads one hook event and writes at most one entry:

  • the first prompt of a session writes a Claim (the goal comes from the folder and branch, or from the first line of the prompt, scrubbed of anything that looks like a secret);
  • each finished turn writes one Progress, with tool counts only;
  • the end of a session writes a HandOff.

A session ending is not the goal being done, so Release stays explicit. The hook prints nothing and always exits 0, because anything it printed would land in the model's context, and a non-zero exit could block a prompt. It never waits on the network: syncing runs as its own job, once a minute.

Did it work?

Each slice was written as pre-registered tests before the code: C1 to C22 for the cells, the hub and revocation, F1 to F8 for shared files. Each test was also run against a deliberate break of the thing it guards. In the SQLite slice, two breaks survived the first version of the tests, so the tests were strengthened until every break failed.

In a live run between two of my vessels, one joined by request, endorse and accept, claimed work and synced through the hub. Both ledgers ended with 52 entries from 2 cells and the same digest.

What it doesn't do yet

One creatura spread across two vessels, task assignment between cells, sharing with people outside my own fleet, and encrypting the ledger itself at the hub are all later work. The hub stores the ledger unencrypted, so it does know which cell shared a file for which claim, and when.

But the question I started with now has an answer, and I don't have to visit every computer to get it.

This is Part 5 of a series about Creatura. Part 1 is how it began, Part 2 the architecture, Part 3 the hypotheses that failed, and Part 4 the convoy. The code is on GitHub: github.com/egarim/creatura, and the full design is in design-cells.md.