OCX message. A JSON block in a forum post or a ticket change, fenced in Markdown: write it with a line
```json, the JSON, and a line
```; a reader also takes a bare ``` fence. Other text may come
before or after it, and nodes ignore it; keep it short. An artifact holds one
OCX message: a reader takes the first fenced block whose JSON has an ocx key,
and ignores any other. Every message has
"ocx": 1, a kind, and node: the one node it is for, named by its
repository's server-code (40 hex digits, matched exactly). There are four kinds:
- Requests,
runandstop. A node acts on one only when it is for that node and the Fossil user it came from holds admin or setup. - Reports,
startedandended. Only the node a run is for writes them, as its own Fossil user.
Every message but a run names the run it is about in its field run: the
run's Fossil hash, in full, in hex. It is written where that run is, as a reply
in its thread or as a change to its ticket. Several runs may share a thread or
a ticket; each is answered on its own. A message never changes: an edit of one
is ignored, so to change a request, stop it and send another.
Run. The OCX message of kind run: a request that a node run one program.
Its fields, besides ocx, kind and node, are these, and those that
Runner, Subscription and Limits add:
{"ocx": 1, "kind": "run", "node": "6cbe285e95a3a124176569ee9243f2a4e9ecdc20",
"objective": "Build check-in abc123 for the board; it passes when the build succeeds.",
"runner": "command", "command": ["make", "board"], "checkout": "abc123",
"user": "builder", "session": false, "limits": {"wall": "30m"}}
objective(text): what the job is for, and what counts as passing. Only a run has one.runner: how the node runs the program (see Runner).user(text): the Fossil user the program acts as.session(true or false): how long the program runs.false, the default: to completion. It starts, does its work and exits, like a CI check, a build or an on-target test.true: as a session. It stays running, and the node gives it, one at a time, in the order they arrived, each post or check-in the run subscribes to (see Subscription), until it exits, is stopped, or reaches its limits.
limits: what the program may use (see Limits).manual(true or false, default false): whether the node waits for a person to start the run by hand, with OCX'sruntool (see Tools), instead of starting it when it arrives.
Started and ended. The node's reports on a run, written where the run is.
{"ocx": 1, "kind": "ended", "node": "6cbe…", "run": "FULL-HASH-OF-THE-RUN", "outcome": "exited", "exit": 0}
started: the run's program is running. Its program starts once, so a run gets at most onestarted, a session too, however many posts it is given.ended: the run's program is not running, and never will be again. A run gets exactly one, and itsoutcomesays why:refused: the node did not start it, because it cannot give it what it asks (its runner, its user or its limits); nostartedwas written;exited: the program finished by itself;exitholds its exit code, a whole number;stopped: astopended it, or came before it started;budget: it reached a limit, whichnotenames in words;lost: the node went down while it ran. The node writes this when it starts again, and never starts that run again.
A stop for a run that is already over does nothing. A run with a started
and no ended is running; a run with an ended is over. So a run's OCX
messages come in one of these orders, and no other:
run,ended(refused, or stopped before it started);run,started,ended;run,started,stop,ended.
Runner. How a node runs a run's program; the run's node says where. A
node offers some runners, and OCX's status lists them; a node answers a run
whose runner it does not offer with ended, refused. Every runner runs its
program in a fresh checkout of the run's checkout: a check-in's hash, or a
branch's name, meaning its latest check-in. The runners:
commandruns the program named in the run'scommand(a list: the program, then its arguments) once, to completion: a build, a test, a check of one check-in.ciruns it for every check-in. It is always a session: for each check-in on a branch in the run'scheckins, it runs the run'scommandonce, to completion, in a fresh checkout of that check-in, and records the result as aciverdict on it (see Verdict).- An agent runner, such as
claudeorcodex, runs that agent program, usually as a session. Its program, the home files its runs share and its environment come from the node's wiki pagesandbox/NAME, written by a user holding admin or setup. The run'sremote(true or false, default false) says whether people may also reach the session through the agent's own remote control, as Claude's offers; what is said that way leaves no artifact.
For both, command is what is run, and the run's objective says in words
what counts as passing. So to run something once, use command; to run it for
every check-in on some branches, use ci. Special hardware is a node, not a runner: to test each
check-in on dev on a board attached to a host, send a ci run whose node is
that host's, such as
{"runner": "ci", "command": ["make", "board-test"], "checkins": ["dev"], …}.
Subscription. What a session is given while it runs, set by two fields of its run, both optional and empty by default:
checkins: branch names. Each check-in that arrives on one of them, unless the run's own user sent it, is given asCheck-in HASH on BRANCH by USER.hears: Fossil users. Each post one of them makes in the run's thread, or each change one of them makes to the run's ticket, is given asPost HASH from USER:orChange HASH to TICKET from USER:, followed by its text as written, which may run over several lines.
A session is first given Run HASH: OBJECTIVE. Deliveries reach its program's
input whole, one at a time, in the order they arrived; one that arrives while
the program is busy waits its turn. USER is the user Fossil received the
artifact from. Nothing is given to, or kept for, a run that is not running, and
a run that is not a session is given nothing. A run's own posts, such as its
questions, answers and progress, are plain posts in its thread, with no OCX
message.
Limits. What a node holds a run's program to, in the run's limits. Each
is optional; a limit left out takes the node's own default, and the node's
started report lists, in a limits of the same shape, every limit it
applies. A node that cannot enforce a limit a run asks for answers it ended,
refused.
- Time and resources:
wall(a whole number followed bys,m,hord),cpu(seconds, in total),memory(bytes, or a number followed byK,MorG),processes(at once). Reaching one ends the run,budget. - What it sees:
network(true or false), andread_onlyandread_write(lists of absolute host paths). Besides these it sees only its checkout, its program and the host's system files, which it may only read. - What it may write in Fossil:
no_password(true or false): the program holds no password for its user. Its posts, verdicts and check-ins go through OCX's tools, which the node runs as its user.branch(a branch name): its check-ins go on that branch only.own_thread(true or false): it posts only in its run's thread or ticket.
branch and own_thread are enforced by OCX's tools, so asking for either
also means no_password. A tool refuses a write these forbid, and the run
goes on. Without no_password, the program's Fossil capabilities are its
only limit in Fossil.
Sender. The Fossil user an artifact was received from. Each repository records it for every artifact it receives, by an authenticated sync, through its web page, or by a write on the repository itself. For a write on the repository itself it records whatever user the writer names, so on a node only OCX's tools write, each naming the user it signed in. A repository does not copy senders to its clones: only the repository that received an artifact knows who sent it. The user written inside an artifact is whatever its writer's clone said, and proves nothing by itself. OCX decides by sender:
- A
runor astopmay be sent by any user holding admin or setup. The node acts on one only if its sender holds admin or setup. startedandendedare written only by the node, as its own user.- A post or ticket change is given to a session only if its sender is in the
run's
hears; a check-in, only if its sender is not the run's own user. - A verdict counts only if its sender holds check-in (
i) and is neither the check-in's sender nor the user named in it. No one's verdict on their own check-in counts.
Who may read threads, tickets and check-ins is Fossil's own business, by its capabilities; OCX adds nothing to it.
Verdict. A tag on a check-in that records a judgement of it: audited for
one made by a person or an agent, ci for a program's check. Write it as a raw
tag (Fossil's --raw), so it never becomes a name for the check-in. A reader
reads older sym-audited and sym-ci tags as verdicts too. Its value is pass: or
fail:, then the reason on one line, then (against X), where X names the
objective it was judged against: a run's full hash, or the path of a file that
states the objective, such as PLAN.md, read as it stands in the check-in
judged. Two examples:
- a reviewer's judgement: tag
audited, valuefail: the new command has no tests (against PLAN.md); - a
cirun's check: tagci, valuefail: 2 tests failed (against RUNHASH).
Anyone may write an audited verdict, with OCX's audit tool or with Fossil's
own fossil tag add; it needs no run. But a verdict on your own check-in never
counts: the author of a check-in cannot pass it. A ci run writes a ci verdict, as the
run's user, on each check-in it checks, against the run itself.
Reading verdicts. A verdict counts when its sender may add one (see
Sender). A run named in it must be about the check-in: its checkout is that
check-in, or its checkins include the check-in's branch. Fossil shows only a
tag's latest value on a check-in, but keeps every tag change, with its user,
in its event table (type g). So a reader takes each user's latest verdict
from there, and every user's latest verdict counts. How to combine them is the
reader's choice: any fail fails, a majority, one named reviewer. OCX's queue
and activity, CI checks and merge gates are all readers. Only the node knows
senders: a reader there counts by sender; a reader on a clone can count only by
the user named in each tag change, and says its count is unverified.