Login
OCX protocol v1, draft 4.6
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.

It has four parts, each resting on Fossil directly:

  1. Fossil, the medium: what OCX relies on.
  2. OCX, the control plane: messages that ask a node to run work, and the node's answers.
  3. Verdicts, a data-plane standard: how anyone records a judgement of a check-in, and how anyone reads one.
  4. 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:

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"}
}

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:

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

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