Login
Using OCX
Login

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:

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:

  1. 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;
  2. the fields named: --objective, --node and --user, the run's process.user;
  3. 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:

  1. On the server, as its admin: a user lab-2 holding o and i and 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.
  2. 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.
  3. 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": "..."}}.
  4. Start it: ocx2 node --repository lab-2.fossil https://SERVER/REPO FILE --check acts on every run for lab-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.

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.