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 name
its caller gives, or else 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.
Unless its record restricts it (E10), the agent holds its login's password in
its clone and writes to the repository with Fossil, as a person does. Its
capabilities are then its only limit, and Fossil's i admits any write: any
branch, any tag, any wiki page.
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",
"agent": "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,
"restrict": {"no_password": true, "branch": "docs-sync"},
"sandbox": {"network": true, "read_only": [], "read_write": []},
"budget": {"wall": "2h"}
}
agent: 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.restrict: the restrictions its node must hold the agent to (E10).{}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;unenforced: its node could no longer enforce a restriction, sandbox field or budget limit its record asks for, whichnotenames (E10);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, from its
backoffice. A write made on the node itself does not start the backoffice, so
each of OCX's tools that writes on the node runs it afterwards. 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 node: agent (default: a new login, E2), 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), restrict (default {}), sandbox (default {"network": true, "read_only": [], "read_write": []}), budget (wall required) |
checks that this node can enforce every restriction, sandbox field and budget limit asked (E10), 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 | 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 |
push |
none | sends the agent's new check-ins from its clone to the node's repository, as its login (E10) | an agent whose record restricts no_password |
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, unless its record restricts
no_password; then only push sends its check-ins on. 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, and push only where asked; 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,
budget and restrict: 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).
E10. Restrictions
A record may ask its node to hold its agent to restrictions beyond its sandbox
and budget. Each is optional, and none is the default. A node may enforce any
of these restrictions, or none, and OCX's status lists the ones it enforces.
- Before the run:
createchecks that its node can enforce every restriction, sandbox field and budget limit the record asks for. If it cannot,createstops, naming the first one, and writes nothing. Acreatedry run makes the same check and writes nothing either way. - In the record:
createwritesrestrictout in full,{}included, so a reader sees the whole boundary. Withoutno_password, the agent's only limits are its Fossil capabilities, andiadmits any write;create's answer says so. - During the run: if its node can no longer enforce one of them, it ends
the run at once, and the response's outcome is
unenforced(E5), naming it.
| Restriction | What it limits | How the node enforces it | When |
|---|---|---|---|
"no_password": true |
the agent holds no password for its login | its clone gets none; its tools run on the node, outside its sandbox, writing as its login; its check-ins leave only by push, which copies them from its clone to the node's repository and then runs Fossil's backoffice, so the hook sees them at once |
from its start, at each tool call |
"branch": NAME |
its check-ins go only on branch NAME | push refuses any check-in on another branch |
at each push |
"own_thread": true |
it posts only in its own record's thread | post refuses a to outside that thread |
at each post |
branch and own_thread need no_password: an agent that holds its password
can write past any tool. Agents work together by naming each other's logins in
hears, which create can set in advance by giving each its agent, and by
leaving own_thread off.
Justified by: Ax1 (a record states its whole boundary before it runs, 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 before asking).