OCX extends Fossil, a version-control system whose repository also holds forum threads, tickets, wiki pages and tags. Each item Fossil stores is an artifact, named by its hash, and never changes once written. OCX is written in Crystal. It lets people and AI agents work together on a repository.
It has four parts, each resting on Fossil directly:
- Fossil, the medium: what OCX relies on.
- OCX, the control plane: messages that ask a node to run work, and the node's answers.
- Verdicts, a data-plane standard: how anyone records a judgement of a check-in, and how anyone reads one.
- Tools: how people and programs write all of this.
What the work itself writes (check-ins, posts, tags) goes straight to Fossil, never through the control plane. The control plane never depends on a verdict, and a verdict needs no record. Each element says which axioms (Ax1–Ax5) and virtues (Clarity, Loose Coupling, Demonstrability, Parsimony) justify it.
Part 1. Fossil, the medium
F1. One store
Everything that must last is a Fossil artifact: posts, check-ins, tags, ticket changes. These are the evidence of what happened. A host's own log is for diagnosing problems; it is not evidence, and may be lost.
Justified by: Demonstrability (artifacts are immutable, synced to every clone, readable later), Parsimony (no second store).
F2. Users, and who sent what
Every actor acts as a Fossil user of its own: each person, each running
program, and each node (its node user). A user's capabilities are the outer
limit of what it may do, and Fossil's i admits any write: any branch, any tag,
any wiki page.
An artifact's sender is the user it reached a repository from, by an authenticated sync or through the web page; each repository records this for itself, and does not copy it to clones. A write made on a node itself records whatever user the writer names, so on a node only OCX's tools write, each as the caller it authenticated. The user named inside an artifact proves nothing.
Justified by: Demonstrability (attribution, on the repository that received it), Ax1 (what a user may do is fixed in Fossil before it acts).
F3. A network on a persistent store
OCX treats Fossil as a network whose every message lasts:
- Sending is writing an artifact. Sync carries it to every clone; nothing is lost on the way.
- The hook is an interrupt; the repository is the truth. Fossil runs the after-receive hook from its backoffice after artifacts arrive. A write made on the node itself starts no backoffice, so whatever writes there runs it afterwards. A node that missed a hook reads the repository again.
- The answer is the acknowledgement, and it lasts. A request that has its answer is never acted on again, and what is still open is read from the repository, never kept anywhere else.
- Nothing is retracted. Stopping, correcting and replacing are new artifacts that point at old ones.
- Order comes from references (a reply names the post it answers, a check-in its parents), not from clocks.
Justified by: Parsimony (no queue, no state store, no retries), Ax4 (every open request is found again after any failure), Demonstrability (every request and answer is kept).
Part 2. OCX, the control plane
C1. Messages
An OCX message is one JSON block fenced in Markdown (a line
```json,
the JSON, a line ```) inside a forum post, either a new thread's first
post or a reply in any thread, or inside a ticket change. Its thread, or its
ticket, is its place. Every message carries "ocx": 1, its kind (record,
stop or response) and the node it is for: a node, by its repository's
server-code. A stop names the record it stops in stops, and a response
the record it answers in answers, each by its full hash. Two repositories
that share a server-code (a copy, or a clone made from a file) would both act,
so a node's repository is made new or cloned over HTTP.
A node acts on a message only when it names this node, its version is known,
and its sender (F2) may send that kind: a user holding admin or setup for a
record or a stop, the node's own user for a response. Every other message
it ignores, writing nothing. An edit of a message is ignored; to change a
request, stop it and send another. Nobody writes messages by hand: tools do
(Part 4).
Justified by: Clarity (one envelope, three kinds), Loose Coupling (anything that can write a post or a ticket change can send one), Ax1 (only the senders it names can ask a node for anything).
C2. The record
A record asks a node to run a program as one user, on the artifacts it names, within limits:
{
"ocx": 1,
"kind": "record",
"node": "6cbe285e95a3a124176569ee9243f2a4e9ecdc20",
"user": "claude-91c0de",
"runner": "claude",
"objective": "Keep docs/ in step with each check-in on trunk; a check-in passes when every command it changes is documented.",
"checkins": ["trunk"],
"hears": ["pat"],
"remote": true,
"restrict": {"no_password": true, "branch": "docs-sync"},
"sandbox": {"network": true, "read_only": [], "read_write": []},
"budget": {"wall": "2h"}
}
user: the Fossil user the run acts as (C5).runner: how it runs:claudeorcodex: a session, one process for the whole run, given each artifact as a delivery. A node finds each runner's program, the home files its runs share and its environment in the wiki pagesandbox/NAME, counted only when a user with admin or setup wrote it.command: the programcommandnames (a list: the program, then its arguments), run to completion once for each check-in it is given, in a fresh checkout of that check-in. For CI.
objective: the task in plain words, and what counts as passing.checkins: the branches whose check-ins it is given.[]for none.hears: the users whose posts in its place, or changes to its ticket, it is given.[]for none, and always[]for acommand.remote: whether people may reach a session through its runner's own remote control, as Claude's offers. Only a runner that offers one may say true.restrict: the restrictions its node must hold it to (C6), written out in full:{}for none.sandbox: what its processes see of the host besides its checkout, its program and the host's system files, which they may only read:network(true or false),read_onlyandread_write(absolute host paths).budget: limits on all its processes together: for a session, its whole run; for acommand, each run.wallis required: a whole number followed bys,m,hord. Optional:cpu(seconds, in total),memory(bytes at once, or a number followed byK,MorG) andprocesses(at once).
A CI record differs only in its runner:
{"runner": "command", "command": ["crystal", "run", "ci.cr"], "user": "ci-dev", "checkins": ["dev"], "hears": [], …}.
Justified by: Ax1 (the whole boundary is fixed and readable before anything runs), Clarity (one record for a session and for CI), Parsimony.
C3. What a node does
| It receives | It does |
|---|---|
a record for it |
checks that it can enforce every restriction, sandbox field and budget limit (C6). If it cannot, it writes the response refused, naming the first. Otherwise it gives the record's user exactly its capabilities (C5) and opens the record. A session starts at once; its first delivery is Record HASH: OBJECTIVE. A command waits for its first check-in |
a check-in on a branch in an open record's checkins, not sent by that record's user |
gives it to the run: a session gets Check-in HASH on BRANCH by USER; a command runs once on it |
a post in an open record's place, or a change to its ticket, by a user in its hears, not by its own user |
gives it to the session: Post HASH from USER: TEXT, or Change HASH to TICKET from USER: TEXT |
a stop for one of its open records |
ends the run, and writes the response stopped |
| anything else | nothing |
When a session ends by itself or reaches its budget, the node writes the
response. A command record stays open until it is stopped. Starting, a node
reads its open records (those with no response) and answers lost for each
session whose process is gone; a command record simply goes on.
USER is always the artifact's sender (F2), read from the repository, never from Fossil's summary, which names the user written inside it. A session takes one delivery at a time, in the order they arrived; one that arrives while it is busy waits its turn. Nothing is kept for a record that is not open, so an objective cannot promise every check-in. Two runs that check in on each other's branches can wake each other until their budgets end. Each action happens whatever another's outcome, and the hook returns without waiting for any run.
Two ways into a session are outside Fossil: a person with admin or setup may
attach to its terminal at the node, and each attachment leaves a post in the
record's place naming who attached, when and for how long; and its runner's
remote control, when remote allows it, which leaves no artifact.
Besides OCX, a node's hook may do work of its own on a check-in, such as bringing the repository's git export up to date; that is not part of this protocol.
Justified by: Ax1 (a run acts only on what its record names; nothing polls), Ax4 (open records are found again from the repository), Parsimony (one table), Loose Coupling (the hook never waits on a run).
C4. The response
The node answers each record once, as its node user, at the record's place: a reply to the record's post, or a change to its ticket.
{"ocx": 1, "kind": "response", "node": "6cbe…", "answers": "FULL-HASH-OF-RECORD", "outcome": "exited", "note": "exit 0 after 52 seconds"}
outcome, the first that applies:
refused: the node could not enforce what the record asks;notenames it;stopped: by astop;budget: a limit was reached, whichnotenames;unenforced: the node could no longer enforce a restriction, sandbox field or budget limit, whichnotenames (C6);lost: the node went down during a session. It answers this when it starts again; once the run's start plus itswallhas passed, any user with admin or setup may answer it instead, so a node that never returns leaves no record open;exited: the session's process ended by itself;notegives its exit code.
After the response, the node withdraws the run user's capabilities (C5).
Justified by: Demonstrability (every request is answered where it was asked, even a lost one), Ax4 (no record is left open).
C5. The run's user
create (Part 4) names the run's user, or makes a new one: the runner's name, a
hyphen and six random hex digits, such as claude-7f3a2b. The node gives that
user exactly the capabilities to read, post, check in and add tags, with none
inherited (Fossil's u would add the reader's), never admin or setup, and
withdraws them all when the record is answered. Fossil keeps the user itself.
Unless the record restricts no_password (C6), the run holds its user's
password in its clone and writes with Fossil as a person does, its capabilities
its only limit.
Justified by: Ax1 (what a run may do is fixed before it runs), Ax4 (no leftover users that can still write), Demonstrability (all a run writes is attributed to it).
C6. Restrictions
A record may ask its node to hold its run to restrictions beyond its sandbox and
budget. Each is optional, and none is the default. A node may enforce any of
them, or none, and OCX's status lists those it enforces.
| Restriction | What it limits | How the node enforces it | When |
|---|---|---|---|
"no_password": true |
the run holds no password for its user | its clone gets none; its tools run on the node, outside its sandbox, writing as its user; its check-ins leave only by push, which copies them into the node's repository and runs the backoffice |
from its start, at each tool call |
"branch": NAME |
its check-ins go only on branch NAME | push refuses a check-in on another branch |
at each push |
"own_thread": true |
it posts only in its record's place | post refuses a to elsewhere |
at each post |
- Before the run: a node that cannot enforce what a record asks answers it
refused(C4). Acreatedry run on the node makes the same check. - During the run: a node that can no longer enforce one ends the run and
answers
unenforced. branchandown_threadneedno_password: a run that holds its password can write past any tool. Runs work together by naming each other's users inhears, whichcreatecan set in advance by giving each itsuser, and by leavingown_threadoff.
Justified by: Ax1 (a record states its whole boundary, runs only where that boundary holds, and stops when it no longer does), Parsimony (no restriction unless asked), Loose Coupling (a node that enforces none still runs every record that asks for none), Clarity (a caller sees what a node enforces).
C7. Versions
Every message carries "ocx": 1. Version 1 may later gain keys, still as
"ocx": 1. A reader ignores keys it does not know, except inside sandbox,
budget and restrict: a key it does not know there may be a limit, so the
node answers the record refused. A node also refuses a runner it does not
offer. A key is never renamed and never changes meaning. A change that breaks
these rules is version 2, which a version 1 node ignores.
Justified by: Loose Coupling (other programs can rely on the formats), Ax4 (meanings cannot drift, and no limit is silently dropped).
Part 3. Verdicts, a data-plane standard
V1. A verdict
A verdict is a tag on a check-in: audited for a judgement, by a person or an
agent, or ci for a program's check. Its value is pass: or fail:, then the
reason on one line, then (against X). X names the objective it was judged
against: a record's full hash, or the path of a file that states the objective,
such as PLAN.md, read as it stands in the audited check-in.
A command run's verdict is written for it: its node adds a ci verdict on the
check-in it ran on, pass when the program exited 0 and fail otherwise, its
reason the program's last line of output, against its record.
Justified by: Ax2 (always judged against a named objective), Loose Coupling (a verdict needs no record: a person may judge against a file).
V2. Reading verdicts
A verdict counts only when its sender (F2) neither sent the check-in nor is
the user written in it. A record it names must have the check-in's branch in its
checkins. Fossil shows only the latest tag of a name on a check-in, but every
tag change stays in the repository with its user, so a reader takes each user's
latest verdict from there. Every user's verdict counts; how to combine them is
the reader's choice. Readers include people, OCX's queue and activity, and
checks such as a merge gate or a CI check.
Justified by: Demonstrability (attributed and kept), Ax2, Clarity (one rule for every reader).
Part 4. Tools
Nobody writes messages or verdicts by hand. People act through tools on the web
page or the command line; a run calls them through MCP or OCX's command line,
which its node offers it. Each tool's input is typed and checked before anything
happens; a field it does not know is refused. A hash may be given as its first
10 or more characters; one that names more than one artifact is refused.
Messages and tags always hold full hashes. Every tool takes dry_run (default
false): it checks the input and the caller's rights, says in its log what it
would write, and writes nothing. Every tool answers in one shape: {"tool",
"dry_run", "log", "result", "stopped"}. result is the hash of what it wrote,
or null. stopped is null when the tool did its work; otherwise it says why
not, naming the field at fault. log lists the steps it took.
| Tool | Input | What it writes | Who |
|---|---|---|---|
create |
the record's fields (C2), node defaulting to this node's, user to a new one, runner to claude, checkins to [], hears to every user holding admin or setup when it runs, written out, remote to true where the runner offers it, restrict to {}, sandbox to {"network": true, "read_only": [], "read_write": []}; budget with its wall is required; and where: to (a post to reply to), ticket, or neither, for a new thread |
a record, at that place; on the node itself it runs the backoffice, so the record is acted on at once |
admin or setup |
stop |
record |
a stop for it, at its place |
admin or setup |
post |
to (a post's hash), text (Markdown) |
a reply to that post | anyone who may post |
audit |
checkin, verdict (pass or fail), reason, against, tag (audited, the default, or ci) |
a verdict (V1) | anyone who may check in, never on their own check-in |
push |
none | the run's new check-ins, copied from its clone into the node's repository as its user (C6) | a run whose record restricts no_password |
status |
none | nothing: says who and where the caller is, and which restrictions this node enforces | anyone |
network defaults to true because both session runners reach their own
service. OCX's other tools, for releases, the git round trip and attaching,
write no messages and no verdicts.
Justified by: Clarity (typed inputs, one answer shape, the same tools from
every place, and create takes the record's own fields), Loose Coupling (MCP is
a boundary either side can replace), Demonstrability (the dry run), Parsimony
(six tools, no notation to learn).