Read GLOSSARY.md first: it defines the words used here.
Protocol v1 is the standard these tools write to.
OCX has three entry points to one set of tools:
- CGI: pages served beside the Fossil repository by the
ocx2-cgiprogram, for people in a browser, or JSON for a client such as Stone. Fossil authenticates the viewer. - CLI: one
ocx2command, for people at a terminal, scripts and the node's own services. It acts as the user its clone remembers, or on the served host as the user it names, and prints text, or with--jsonthe same JSON the CGI and MCP return. Its own options,--json,--repositoryand--user, go before the tool's name: after it, an argument of the tool's own wins, ascreate's--user, the run's user, does. - MCP: the tools offered to a program, such as a run's in its sandbox,
acting as its user, within its Fossil capabilities. The program serves MCP
as
ocx2-mcp, or asocx2 --mcp, so a program's own configuration can start it.
Each tool below is defined once: what it does, who may do it, and what it leaves in Fossil. The entry points are adapters over that definition. They authenticate the caller and encode the request and result, but they never decide what the tool does. A tool's input, output and context are types whose schemas are derived, and every answer has one shape: the log of operations the tool performed, or in a dry run would perform, its output when it ran to the end, and why it stopped. The CGI, the CLI and MCP each offer every tool (the maintainer, a001dc306e). Who may use a tool is decided once, by the caller's user, its Fossil capabilities, and by the sandbox it runs in; a call that is not allowed is refused the same way from every entry point.
ARCHITECTURE.md explains the parts these tools act on.
Tools
The Built column is checked when the code provides the tool; until then the
row's CLI and MCP names are proposals. PLAN.md tracks each gap.
| Tool | Built | CGI | CLI | MCP |
|---|---|---|---|---|
| List the tools, give help, or show who is calling | ☑ | OCX page (the list), each tool with its form | ocx2, ocx2 list, ocx2 help, ocx2 status |
tools/list, ocx.list, ocx.help, ocx.status |
| Echo the input | ☑ | OCX page: echo | ocx2 echo WORD [--option OPTION] [--flag] |
ocx.echo |
| Say what a tool would do | ☑ | the dry_run checkbox on every form |
--dry-run, on every tool |
dry_run, an argument of every tool |
| Write a run | ☑ | OCX page: create | ocx2 create [--pattern URL] (--title TITLE \| --to POST \| --ticket TICKET) [--objective TEXT] [--fields JSON] [...] |
ocx.create, on the served repository |
| Start a manual run by hand | ☑ | OCX page: run | ocx2 run RUN |
ocx.run, on the served repository |
| Stop a run | ☑ | OCX page: stop | ocx2 stop RUN |
ocx.stop, on the served repository |
| Write a plain post | ☑ | not offered: post in Fossil's own forum page | ocx2 post TO TEXT |
ocx.post |
| Record a verdict | ☑ | OCX page: audit | ocx2 audit CHECK-IN pass\|fail NOTE AGAINST |
ocx.audit |
| Say who sent an artifact | ☑ | OCX page: receipt | ocx2 receipt [--artifacts 'HASH ...'] [--capabilities-of USER] |
ocx.receipt |
| Stage, accept or roll back a release | ☑ | OCX page: accept, rollback (stage is not offered) | ocx2 stage CHECKOUT, ocx2 accept, ocx2 rollback |
ocx.stage, ocx.accept, ocx.rollback |
| Be a node on another host | ☑ | not offered: it runs on its own host | ocx2 node --repository CLONE SERVER-URL PASSWORDS-FILE [--check] |
not offered: it runs on its own host |
| Install or remove the hook | ☑ | OCX page: hook, unhook; status shows where it stands | ocx2 hook [--node USER], ocx2 unhook |
not offered: a sandbox has no served repository |
| Keep the git export current | ☑ | OCX page: export; the hook, when a check-in arrives | ocx2 export |
not offered: a sandbox has no served repository |
| Import a git repository | ☑ | OCX page: import | ocx2 import UPSTREAM NAME [--mirrors DIR] |
not offered: a sandbox has no served repository |
| Watch a run | ☐ | Run page: live stream | ocx2 watch RUN |
ocx.watch |
| Read the record or the code | ☐ | Record page: Read | ocx2 read RECORD\|PATH |
ocx.read |
| Edit scratch | ☐ | Unit page: Edit | ocx2 edit < PATCH |
ocx.edit |
| Run a check | ☐ | Check-in page: Run | ocx2 check NAME |
ocx.check |
| Hand off a subtask | ☐ | Unit page: Hand off | ocx2 delegate LOGIN < TASK |
ocx.delegate |
| Propose a change | ☐ | Branch page | ocx2 propose BRANCH |
ocx.propose |
Write a run
create writes a run as your user on the served
repository, from the web page or --repository on its host, for a user with
admin or setup: the first post of a new thread (--title), a reply in the
thread of a post (--to), or a change to a ticket (--ticket), a post or
ticket named by its hash or a prefix of it. Its fields come in three layers,
each over the one before: an object given over an object merges into it,
member by member, and any other value replaces what was there, whether OCX
knows the field or not:
- a pattern:
--pattern URL, the text at an http or https URL whose first fenced JSON block holds a run's fields, fetched without signing in; - the fields named:
--objective,--nodeand--user, the run'sprocess.user; - the rest:
--fields, a JSON object, such as'{"process": {"session": true, "hears": ["stone"]}}'.
A pattern holds a way of working once, as data, so the person writing a run
gives only what differs. By convention it is the wiki page run/NAME, written
in Markdown, since Fossil's own wiki markup turns brackets into links: a line
saying what it is for, then a fenced JSON block of the run's fields, such as
protocol v1's examples' session that answers each post, without
its ocx, kind, node and objective.
ocx2 create --pattern https://HOST/REPO/wiki?name=run/claude --title "talk"
--objective "Answer what is said here." then writes that run, every field of
the pattern in it, in a new thread. Of a wiki page of the served repository
itself, create reads only the newest version that a user holding admin or
setup wrote, in the repository: a pattern names the program a node runs and
the host files it binds, and Fossil lets a user with check-in alone push any
wiki page (docs/battery/rounds.md, round 6). Any other URL is fetched as it
is, and the caller vouches for it. The run is checked as a node checks it, and
refused, writing nothing, if a node would refuse it as written. A write on the
repository itself starts no hook, so when the run names the node of this
host's hook, create has that node act on it at once, and its answer says
what the node did (acted). Every program reads its run, and a session its
deliveries, as lines of JSON on its standard input
(protocol v1, section 8.1); how an agent is made
to read them, with which arguments, is its run's data.
Start a manual run by hand
run RUN starts a run whose manual is true, which its node leaves waiting
for a person, for a user with admin or setup on the served repository, from
the web page or its host's command line. This host's node decides it as
started by a person and hands it to a watcher, which prepares it, writes its
started, runs its program and writes its ended, all as the node user, as
for any run. A run that is not manual, a run of another node, one its node
leaves waiting, such as while its user has another run in the node's care,
one the node refuses, with the note it wrote, and one that is not waiting for
a person, being started, over or in the node's care, are refused; a dry run
says which.
Stop a run
stop RUN writes a stop for a run as your
user, where the run is: a reply to its post in its thread, or a change to its
ticket. A node acts on it only if a user holding admin or setup wrote it.
When the run names this host's node, stop has it act at once: a program
running is ended, and a run not started is answered stopped.
Write a plain post
post TO TEXT writes TEXT as your user, from your clone's checkout: a reply in
the thread of the post TO names, or a change to the ticket it names, by its
hash or a prefix of it. The reply is composed as Fossil's forum artifact, put
into the clone and pushed, and the served repository takes it as a post by the
user that pushed it. Pushing needs Fossil's Check-In (i); a push the server
refuses is reported, and a user without Check-In posts in Fossil's own forum
page, as everyone does from the web. post refuses text that holds an OCX
message, so a program's posts never carry one: create writes a run, and
stop a stop. A run's program answers in its run's thread or on its ticket
this way.
Watch a run
Planned after protocol v1, with the agent-specific work: an agent's output read turn by turn, its status as JSON, and server-sent events, from a port of its own, since Fossil holds a page until it is complete.
Tools planned elsewhere
☐ rows with no section are in slices-parity.md, "Tools planned past parity"; the first five, in PLAN.md's bound surface.
Record a verdict
Tags a check-in audited with pass or fail, a note on one line, and the
objective it was judged against, as the reviewer's own user: a raw tag whose
value is pass: NOTE (against AGAINST)
(protocol v1's verdicts). AGAINST is 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. Nobody audits their own check-in. A local model's
review is evidence a reviewer may cite, not a verdict.
audit CHECK-IN pass|fail NOTE AGAINST needs Fossil's Check-In (i). The
check-in is a hash, a prefix of it or a name such as a branch. From a
clone's checkout the tag is made there and pushed, as a post is; on the
served repository, from the web page or --repository on its host, it is
made as the viewer. Either way the verdict is read back as the served
repository holds it: there, from its tags; from a clone, by a second push
that must send nothing. A push the server refuses, as it refuses a tag from
a user without Check-In, is reported as a verdict not given. A later
verdict on the same check-in replaces the earlier.
Say who sent an artifact
receipt --artifacts 'HASH ...' says of each artifact, by its hash in full,
who the served repository received it from, what they may do there, its
receipt's number and its row number, which order what the server received, or
that it has not received it; --capabilities-of USER,
what USER may do there. It reads the served repository, from the web page or
--repository on its host: a clone's own receipts name only its pulls
(Fossil, senders and nodes, section 3). It never says
where an artifact came from, and tells a caller without Read (o), or x
for a private artifact, that the server has not received it.
Stage, accept or roll back a release
stage CHECKOUT checks, builds and stages: it runs the checkout's own
checks, logged to ROOT/staged/gate.log as they run, builds its programs,
copies them into ROOT/staged/NAME (NAME its check-in's first twelve hex
digits), smokes that copy and offers it as next, the pre-release
candidate, which Fossil's page ocx2-cgi-next runs. Accepting serves it as
the active release candidate (ROOT/current, the page ocx2-cgi) and keeps
the one it replaces as previous; rolling back serves the previous release
again. Each needs Fossil's setup capability (the maintainer,
dd06d0a5e5),
and each takes --root ROOT, by default the root the program was released
into; --dry-run says exactly what it would do (HISTORY.md decision 31).
Every entry point checks the capability. The web page does not offer stage,
which runs the checkout's own code, and it shows the release root as the
server's, taking none from the request. A person with setup accepts and rolls
back there; the CGI takes a change only from a POST whose Referer is its own
page. Once a release is served, only the active release accepts: the
pre-release candidate may accept only a node's first release, trusted on first
use, and either may roll back. From a terminal, a user with setup stages, and
accepts or rolls back to recover, as ocx2 accept --root ROOT. A clone lists
its own cloner as setup, so what limits a run's program is its sandbox: it
stages into the staged area bound into its sandbox, as ocx2 stage . --root ~
when that is ~/staged, and never reaches the root itself.
Install or remove the hook
hook installs ocx2-hook as the served repository's after-receive hook:
ROOT/ocx2-hook --log ROOT/node/hook.log %R. ROOT/ocx2-hook is a link in
the release root to current/ocx2, so the hook runs the served release and
follows every accept and rollback without being installed again; the hook
logs each run to ROOT/node/hook.log and, when a check-in is among the
artifacts received, brings the repository's git export up to date (below);
posts, ticket changes and tags alone export nothing. Given --node USER, a
user of the served repository
holding no capability of its own (a node user there needs none), the hook is
that node of protocol v1 too: of the artifacts Fossil
lists as arrived, and nothing else, it answers each run for USER that it
refuses, that a stop came before, or whose program is lost, as USER. Each
run it can start it hands to a watcher of its own, which writes the run's
started, runs its process inside its sandbox and limits, posts the kept ends
of what it printed, and writes its ended
(protocol v1, section 10). A run with no
sandbox runs only on a node whose host holds the file ROOT/node/unconfined.
The process reads its run, and a session its deliveries, as lines of JSON on
its standard input. Installing with --node USER is the node
starting: hook then checks every run for USER, answering lost one whose
program its going down left, ending a running program whose stop the hook
missed, and handing on one it never started, so run it again after the host
restarts, or when the hook's log shows a run that failed
(Fossil, senders and nodes, section 5).
Installing again changes nothing, and a hook that runs ocx2-hook with
another command, such as another root's, is replaced. A hook an earlier
release installed with --login LOGIN still runs, its value unused; hook
replaces it. unhook removes every hook that runs ocx2-hook and leaves the
log. Neither touches the repository's other hooks. Each needs setup, takes --root ROOT as the release tools do,
and acts on the served repository: the web page's, or --repository on its
host, run as the server's account, which writes the root. A root that serves
no release is refused, since the hook would run nothing.
status shows, to any viewer, where the hook stands on the served
repository: installed as this root installs it, run otherwise, or absent;
the programs the repository's other after-receive hooks run; when Fossil's
backoffice runs hooks; and the hook's last run, from its log, with each
artifact it was given. Fossil starts its backoffice only after a page that
answers 200, so a form's post reaches the hook with the page shown after
it; the backoffice runs at most once a minute, and a receipt within that
minute waits for its end, or, with backoffice-nodelay on, for a later
page (docs/fossil.md, "Hooks").
Add a node on another host
A build machine or a lab board is a node of its own, working from a clone of
the server (Fossil, senders and nodes, section 5). To add one,
say lab-2:
- On the server, as its admin: a user
lab-2holdingoandiand nothing more, never admin or setup; and the run users its runs will act as, each holding what its runs need, never admin or setup, with a password you choose. A run user works for one node, the node of the first run that names it, so give each node run users of its own. - On the host: an OCX release root, as on the server's host, and a clone
that remembers
lab-2's password:fossil clone --save-http-password https://lab-2@SERVER/REPO lab-2.fossil. - A file of passwords, readable by the host's OCX account alone
(
chmod 600), outside every path a run binds:{"user": "lab-2", "password": "...", "runs": {"builder": "..."}}. - Start it:
ocx2 node --repository lab-2.fossil https://SERVER/REPO FILE --checkacts on every run forlab-2, as a node does when it starts. Then run it without--check, as often as the node should learn of new runs, from cron or a timer: each run pulls, and acts on the runs and stops among what the pull brought.
node signs in to the server as lab-2 and asks its receipt tool who sent
each artifact, since the clone's own receipts name only its pulls. It runs a
run's program as the run's user, cloning the server with that user's password
from the file, and sends each report to the server as it writes it, started
before the program is launched. Runs for lab-2 are written on the server, as
any run is; one written straight into the clone reaches the server as received
from lab-2, which sends no runs, so the node refuses it.
Fetch the git export
The program served from Fossil's extension root as git (git-next from
the release offered as next) serves each repository's git export read-only
over git's smart HTTP, for mirrors: git clone <ext>/git/NAME.git fetches
the export of NAME, the repository the URL serves, never another's, from
where fossil git export last wrote it (its last-git-export-repo
setting). A push, a viewer who may not read code, and a request over 50 MB,
raw or expanded, are refused. The hook keeps the export current after each
receipt; export (setup) exports now, one run at a time, refusing with what
Fossil said when it fails, and its dry run says what awaits, the autopush
and the last failure.
Import a git repository
import UPSTREAM NAME [--mirrors DIR] makes a new repository of a git
repository, beside the served one, for a user with setup on the served host
or its web page. It is NAME with the served one's extension
(/var/lib/fossil/NAME.fossil on the host), served at the served one's URL
with NAME as its last part. UPSTREAM is a git URL, or a directory on the
served host. Its branches are imported in a sandbox, the upstream's own
branch as trunk. Its git export is made in DIR, by default where the
served repository's is, seeded with the upstream's own commits, so what
Fossil adds exports onto them as a fast-forward; the upstream's tags stay
git's.
Its admin is the caller: fossil import -A makes your user its one user
besides Fossil's own. The password that makes is random and kept nowhere.
Instead, the new repository joins the served one's login group, made of the
two and named for the served one where there is none (the maintainer,
2026-10-08). Signing in to the served repository then signs you in to the
new one, and a password changed on the web changes in both. Members name
each other by their paths on the host, so the server must not jail itself:
scripts/start-fossil.cr runs it with --nojail. On disk the file belongs
to the account that ran the import: the web server's from the page, and from
the command line yours, which the server must be able to write.
The hook is installed on it as on the served one, so its export follows each
check-in. Its public URL is set as its email-url, the served one's with
NAME, so it is ready to use: a run's clone is made from it with no step by
hand. Where the served one has none, its administrator sets it. A name not of
letters, digits, ., _ and -, or a repository or export already there,
is refused. An import that fails leaves nothing, the new repository leaving
the login group before it goes.
What you handle
The tools above act on these.
- OCX messages (protocol v1, section 3),
in forum posts and ticket changes: runs and stops, and the node's
startedandended. - Pages: patterns for
create, by conventionrun/NAME. - A run's workspace, on its node, where its steps make it, by convention
ROOT/agents/USER/projects/PROJECT/RUN:clone, its user's clone,tree, its checkout, andstderr.logandstdout.log, the kept ends of what its process printed. - Check-in tags.
auditedandciverdicts, each with a note, on the check-in it judges.
Host programs
The node and server also run programs that no person or agent calls directly:
the Fossil after-receive hook, the watchers it starts, and the CGI program
itself. status and hook show where the hook stands.
The host scripts in scripts/ are the maintainer's own and are not part of
this interface.