Entity
An entity is one named thing of an application’s ontology — and it is the class. Modelling a solution here is naming its things, exactly as object-oriented design always has.
An entity is a thing, never a who. It holds no agency: it is written to, read, versioned, carried, and it never acts. The actor is the who, and an actor has an entity, which is not the same as being one. Nothing about the primitive is confined to software state — a folder of work passing between hands, a company’s process, a document under revision are entities as legitimately as a database of records.
One repository, and the branches are the instances
The mapping is concrete down to the command:
| ontology | Git |
|---|---|
| entity | one repository, addressed by name — bare |
| create | init --bare, then the genesis ref |
| genesis | the ref instances are born from, and where the manifest lives |
| instance | a ref under refs/heads/ |
| its state | the files of a worktree at that ref |
| action | write in a private materialization, then commit with compare-and-swap |
| what an action changed | derived, never recorded — a commit stores whole state |
| federation | push / fetch |
$ entity create acme.note
80019cd59830ee70291f3d10a8036ff6960a3649
$ entity new acme.note n1
$ entity act acme.note:n1 write -- /usr/local/bin/append-outline
f0ac74c619036f66acdcc34224f3d7dcca321942
The coordinate is <entity>:<instance>, and <entity>:<instance>:<path> when you mean a file inside — never with a slash. entity new takes the class and the instance name as two arguments.
The RAM is the disk. Your object model survives intact while the object becomes durable, addressable, versioned and federated. And since nothing executes it, an entity has no methods — only actions done to it, and the events those acts publish.
The repository is bare because an instance is a branch and its state is a worktree: a default checkout at the root would lie about which of the two it is. Materialization is therefore a condition an instance is put into and released from, rather than a property it has — a face keeps a persistent worktree because someone is looking, an acting body takes a private one and discards it, and everything else is read at the ref with no worktree at all.
An instance is born from a commit, always
Which is why the operation that makes one is new, the class’s constructor, and its origin is an argument. From genesis — the ref holding the class’s uninitialized structure — comes a fresh object, empty the way a constructor leaves one. From a live commit of another instance comes a fork, an object that inherits a lived past.
One operation, two origins, and which it was stays legible forever in the history rather than in a second verb. Neither origin is privileged and no line is the true one; whether a fork is a variant, a retry or an alternative reading is the application’s word.
Instances do not interfere. Each is its own ref, and a write to one is invisible to the others until something joins them. Git shares one object store across every worktree, so the history is stored once however many instances are checked out.
Identity is minted, not derived
The genesis commit carries the entity’s name and a unique marker as trailers. Under content addressing nothing else would distinguish two entities: create of the same name on two machines yields byte-identical commits, which would federate as one line, silently. A clock only moves the collision to the same second — so the identity of a participant never hangs on a clock.
The name itself is DNS notation, borrowed from the internet whole: dotted, hierarchical, read right to left, resolving to one repository. No scopes, no owner-multiplexing.
The name identifies and describes; it never binds. The precedent to hold is the macOS bundle — .app is semantics, and the system knows what to do because it read the Info.plist within. The extension announces; the descriptor binds. So bentos.chat is our reading of the conversation ontology, sitting beside slack.chat under a word neither owns, and two entities are of one type because their manifests say so — sharing a subdomain suggests kinship and proves nothing. Nothing checks the spelling; a name that lies simply misleads its readers, exactly as on the internet.
A name is not a location. The same entity is installed in as many places as it does work in, and saying which copy is meant takes the full coordinate. One identity may stand at many coordinates at once, because the substrate is Git and copies are its native condition rather than an anomaly to reconcile.
The manifest is the only contract
Since the name binds nothing, what a thing is it says itself — in band, at the root of the genesis tree, in entity.yaml, so that it is readable without a worktree and travels with the thing.
$ entity info acme.note
declares no manifest
That answer is the ordinary condition of a freshly authored entity, and not a fault. When there is one it declares who it is, what it is, what it is made of, and what it ships: its executables, and the table binding landings to them. That second table is the substrate a diagram of actors was missing — which is why the installer is the only motor anyone writes designed.
Nothing here is invented. It is the universal install-time registration — XDG’s .desktop, the .app bundle — plus what the great precedents made first-class in band: Matter’s Descriptor cluster, D-Bus’s Introspectable.
What it does not declare is where to get it. Installing takes a URL — any URL Git accepts — and that is the whole of distribution. Resolving a name to a canonical address is discovery, one floor up; a manifest holding an origin would be a package repository smuggled into plumbing.
The mechanism landed before the schema, deliberately. One required field paves it — name — with type behind it, and every other field arrives when a living consumer needs it. Which means the reader must tolerate what it does not know: a manifest that grows by demand is only possible if unknown keys are ordinary rather than an error. The schema does not exist yet, and how deep the declaration goes is the open shape question open — stop at named parts and a catalogue is an index; type to the leaf and a stranger can render an entity it never saw. The cost is schema governance, and it is not decided.
The primitive reads the descriptor and declines to read the worktree beneath it. Content-blindness was always the floor refusing to interpret an application’s data; a thing’s declaration of what it is was never data.
The commit is closed by plumbing, and that is load-bearing
git commit takes no expected parent — it reads the tip when it runs. An act needs the opposite:
git update-ref <ref> <new-sha> <expected-sha>
A compare-and-swap, the third argument being the value the ref must still hold. Two actors that read the tip at the same commit cannot both land; the loser is refused and re-reads. An empty expected value means the ref must not exist, which is how a first action refuses to happen twice.
That single absence explains three things at once: why the last step is plumbing, why reaction mounts on reference-transaction rather than post-commit (the latter fires only for porcelain), and why refusal cannot rewrite — by the time the transaction is preparing, the payload is already hashed and only the ref is still in question.
The swap protects the ref and nothing inside a directory. Two actors materialized at one worktree overwrite each other before either reaches the swap, and then both land honestly — the concurrency the CAS existed to catch, happening one floor below it. Which is why an act materializes its own private area, and why a worktree that persists beside an instance belongs to whoever looks at it, necessarily lags, and must refresh before anything writes through it.
What an entity is not
Not an actor. No agency, no mailbox, no behaviour of its own. What looks like behaviour is a function of the entity armed at an occurrence, and the acting is somebody else’s.
Not a database, and not a document store. It is the class; a query language over it would require reading inside it, which the primitive will not do.
Not a package. It carries no origin and knows nothing of where it came from.
Not a Git repository dressed up. It is a Git repository used at a different altitude — and the difference is that clone, branch and commit are performed once, for everyone above, while the application supplies what they mean: a message appended, a page re-spoken, a version pinned.
What is still open is settled nowhere else either: how a transaction lands, how composition works, and the manifest’s concrete schema — all of them growing from use rather than from anticipation open.