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. Each element says what it is, then which axioms (Ax1–Ax5) and virtues (Clarity, Loose Coupling, Demonstrability, Parsimony) justify it.
E1. One store
Everything that must last is a Fossil artifact: posts, check-ins, tags. 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).
E2. Logins
Every actor acts as a Fossil login of its own: each person, each agent, and each
host (its node login). create (E8) makes one new login per agent: the runner's
name, a hyphen and six random hex digits, such as claude-7f3a2b. An agent's
login may read, post, check in and add tags, and nothing more: create sets
exactly those capabilities, with none inherited (Fossil's u would add the
reader's), never admin or setup. When the agent's response is written (E5), its
node withdraws all of the login's capabilities; Fossil keeps the login itself.
An artifact's writer is the login that sent it to the node's repository, by an authenticated sync or through its web page. A write made on the node itself records whatever login the writer names, so on the node only OCX's tools write, each as the caller it authenticated. Only that repository records who sent each artifact: other clones cannot check attribution, and whoever can write the node's repository file can change the record. A name written inside an artifact proves nothing.
Justified by: Ax1 (what an agent may do is fixed before it runs), Ax4 (no leftover logins that can still write), Demonstrability (attribution, on the node).
E3. The record
A task is defined by a record: the first post of a new forum thread, holding one JSON block fenced in Markdown (a line
```json, the JSON, a line
```). Only create writes records, as its caller, and starts their
agents. A record counts only when a login with admin or setup wrote it. A record
is never edited; an edit of one is ignored. To change a task, stop it and
create another.
{
"ocx": 1,
"kind": "record",
"login": "claude-91c0de",
"node": "6cbe285e95a3a124176569ee9243f2a4e9ecdc20",
"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,
"sandbox": {"network": true, "read_only": [], "read_write": []},
"budget": {"wall": "2h"}
}
login: the agent's own login (E2).node: the host that runs it, by its repository's server-code. Every other host ignores the record, so no two hosts run the same agent.runner: the agent program:claudeorcodex. A node finds each runner's program, the home files its agents share and its environment in the repository's wiki pagesandbox/NAME.objective: the task in plain words, and what counts as passing. Verdicts are judged against it (E6).checkins: the branches whose check-ins it is given (E4).[]for none.hears: the logins whose posts in its thread it is given (E4).[]for none.remote: whether people may reach the agent through its runner's own remote control, as Claude's offers (E4). Only a runner that offers one may say true.sandbox: what its processes see of the host besides its own checkout, its runner's program and the host's system files, which it may only read:network(true or false),read_onlyandread_write(absolute host paths).budget: limits on all its processes together, from its start to its end.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).
Justified by: Ax1 (the boundary is fixed and readable before the run; one node per record means never two runs), Clarity.
E4. The agent's life
create writes the record and starts the agent, one process for its whole run.
Its first message is Record HASH: OBJECTIVE. It runs until its process exits,
it is stopped, it reaches its budget, or its node goes down. While it runs, its node gives it these
messages as input, one at a time, in the order they arrived; one that arrives
while it is busy waits its turn:
Post HASH from LOGIN: TEXT, for each post in its thread by a login inhears, edits included, never its ownCheck-in HASH on BRANCH by LOGIN, for each check-in that arrives on a branch incheckins(its name exactly), unless its own login sent it
LOGIN is always the login that sent the artifact (E2).
Nothing else from Fossil reaches it. Two ways in are outside Fossil:
- a person with admin or setup may attach to its terminal at the node. Each attachment leaves a post in the record's thread naming who attached, when, and for how long;
- its runner's remote control, when the record's
remoteallows it. What is said through it leaves no artifact.
Messages are not kept for an agent that is not running, and nothing records them, so an objective cannot promise every check-in. Two agents that check in on each other's branches can wake each other until their budgets end.
Justified by: Ax1 (it acts on the events its record names, bounded by its budget; it never polls), Parsimony (one agent per record; one field per kind of event).
E5. The response
When the agent's run ends, its node replies to the record, as the node's own login, with one fenced JSON block:
{"ocx": 1, "kind": "response", "answers": "FULL-HASH-OF-RECORD", "outcome": "exited", "note": "exit 0 after 52 seconds"}
outcome, the first that applies:
stopped: bystop;budget: it reached a limit, whichnotenames;lost: its node went down during the run. The node writes this when it starts again; once the run's start plus itswallhas passed, any login with admin or setup may write it instead, so a node that never returns leaves no run open;exited: its process ended by itself;notegives the exit code.
A response counts only when the record's node login wrote it. Each record gets exactly one, and the node then withdraws the agent's rights (E2).
Justified by: Demonstrability (every run's end is recorded where it was asked, even a lost one), Ax4 (no run is left open).
E6. Verdicts
A verdict is a tag named audited on a check-in. 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 verdict counts only when added by a login that neither sent the check-in nor
is the user Fossil records in it. A record named must count, and must have the
check-in's branch in its checkins. Fossil shows only the latest audited tag
on a check-in. Every tag change stays in the repository's history with its
user, so readers take each login's latest verdict from there, and every
login's verdict counts. The protocol records verdicts; it does not combine
them.
Justified by: Ax2 (always judged against a named objective, which is about that check-in), Demonstrability (attributed and kept), Loose Coupling (a verdict needs no agent: a person may judge against a file).
E7. The hook
Fossil runs OCX's hook on the node after artifacts arrive. Fossil's summary
of each artifact names the user written inside it, and may call a tag change
unknown, so the hook reads each artifact's kind, branch and sender from the
repository. It filters what arrived by fixed rules, does each action whatever
another's outcome, and returns without waiting for any agent:
| Arrived | Action |
|---|---|
| a check-in | give it to each running agent whose checkins has its branch, unless that agent's login sent it; bring the repository's git export up to date, when it has one |
a post in a record's thread, by a login the record hears, not by its agent |
give it to the record's running agent |
| anything else | nothing |
The hook starts and stops nothing; only tools do.
Justified by: Ax1, Loose Coupling (the hook never waits on an agent, and starts none), Parsimony, Clarity (one table says everything it does).
E8. Tools
Nobody writes these blocks by hand. People and agents act by calling tools:
people on the web page or the command line, agents through MCP (how AI agents
call tools), which their node offers them. 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. Blocks 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 it did not, naming the field at fault. log lists the
steps the tool took.
| Tool | Input | What it does | Who |
|---|---|---|---|
create |
the record's own fields (E3) except login and node: runner (default claude), objective, checkins (default []), hears (default: every login holding admin or setup when create runs, written out), remote (default: true when the runner offers it), sandbox (default {"network": true, "read_only": [], "read_write": []}), budget (wall required) |
makes the agent's login, writes the record as a new thread on this node, starts the agent | admin or setup |
post |
to (a post's hash), text (Markdown) |
replies to that post. An agent may post only in its own thread | anyone who may post |
audit |
checkin, verdict (pass or fail), reason, against |
records a verdict on a check-in: the audited tag of E6 |
anyone who may check in, never on their own check-in |
stop |
record |
ends the agent's run; its node writes the response (E5). Run on the record's node; refused when the agent is not running | admin or setup |
network defaults to true because both runners reach their own service. An
agent checks in with Fossil, as people do; OCX adds no tool for it. OCX's other
tools, for releases, the git round trip and attaching, write none of these
blocks.
Justified by: Clarity (typed inputs, one answer shape, the same tools from
three places, and create takes the record's own fields, so there is one
shape to learn), Loose Coupling (MCP is a boundary between the runner and the
node that either side can replace), Demonstrability (the dry run), Parsimony
(four tools, no notations to learn).
E9. Versions
Every block carries "ocx": 1. Version 1 may later gain keys, still as
"ocx": 1. A reader ignores keys it does not know, except inside sandbox and
budget: a key it does not know there may be a limit, so it refuses the
record. A reader 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 reader refuses: the hook ignores the block, and a tool
stops, naming it.
Justified by: Loose Coupling (other programs can rely on the formats), Ax4 (meanings cannot drift, and no limit is silently dropped).