Login
OCX protocol v1, draft 4.5
Login

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. Users

Every actor acts as a Fossil user of its own: each person, each agent, and each host (its node user). create (E8) makes one new user 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 user 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 user's capabilities; Fossil keeps the user itself.

Unless its record restricts it (E10), the agent holds its user'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 user 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 user 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 users 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 user 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",
  "user": "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"}
}

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:

USER is always the user that sent the artifact (E2).

Nothing else from Fossil reaches it. Two ways in are outside Fossil:

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 user, 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:

A response counts only when the record's node user 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 user 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 user's latest verdict from there, and every user'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 user sent it; bring the repository's git export up to date, when it has one
a post in a record's thread, by a user 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: user (default: a new one, E2), runner (default claude), objective, checkins (default []), hears (default: every user 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 user, 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 user (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.

Restriction What it limits How the node enforces it When
"no_password": true the agent 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 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 users in hears, which create can set in advance by giving each its user, 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).