Login
OCX protocol version 2
Login

This defines the messages this tree reads and writes. Fossil, senders and acting clones are described in Fossil; the tools, verdicts, failures and examples complete the definition. The glossary defines the words.

Version 2's completion and republication are built. The watcher writes ended before publishing; an explicit publish retries unfinished publications without launching the process.

2.1 Messages, and how they are found

An OCX message is a JSON object in a fenced block of a forum post of any mimetype, or in the text a ticket change adds to its ticket's comment (its icomment field). The fence is paired as CommonMark pairs it. Its opening is three backticks alone, or three backticks then json, as OCX writes it; up to three leading spaces, and spaces around json, are accepted; any other opening holds no message. Other text may come before or after it and is ignored; keep it short. A block holds an OCX message when its JSON repeats no key, has "ocx": 2, and has exactly one key besides the envelope's own (ocx, to, re), and that key's value is an object: the key names the message's kind, and its value is the content. A block with no key besides the envelope's, more than one, or one whose value is not an object holds no message. Keys inside the content, whatever their names, are content: a to or ocx there is not the envelope's. A post's or change's message is its first such block. Actors read no version 1 block: stop every open version 1 run before switching. Historical verdict and question readers retain their evidence only; see verdicts.

The envelope, layer 2:

Key Holds In
ocx 2 every message
to the login of the user asked to act where its kind asks for it (2.3)
re the full hash of the message it answers: the forum post or ticket change holding it where its kind asks for it (2.3)
one other key the kind, its value an object: the content, which layer 2 does not read every message

Layer 2 reads every message the same way, of whatever kind. OCX's own kinds are those of 2.3; a message of another kind is carried, addressed and answered by the same envelope, for whichever reader it is meant for (examples).

An artifact never changes. An edit of a post holding a message is a new message, named by the edit's own hash; the one edited stands until it is stopped. Several runs may share a thread or a ticket; each is answered on its own, by re.

Notation: {"a", "b"} is a JSON object with the keys a and b. "The server's order" is the order the server received artifacts in: by its receipt's number, then by the artifact's row number.

2.2 Who sent it

Authority comes from the server's receipts, not an artifact's user card. Each daemon and watcher asks the server's receipt tool. Until it answers, they decide nothing from that artifact. See senders.

2.3 The kinds: content, layer 3

Kind Envelope Sent by Content Answered by
run to, required a user holding admin or setup; or a user holding R, with the isolation refusal 1 asks (2.14) clone, objective, of, workspace, sandbox, limits, process started, ended, then publications; or ended alone
stop re: the run, required a user holding admin or setup; or the user holding R that sent the run {} the run's ended
publish to: the clone login, and re: the run, required the run's sender or a user holding admin or setup {} unfinished publications, or a reply saying why not
started re: the run, required OCX on the run's clone, as the run's to values, limits none
ended re: the run, required OCX on the run's clone, as the run's to outcome, exit, note, workspace, outputs none
Key In Holds Required
clone run the server-code of the repository that is to run it, all 40 lower-case hex digits (2.4) yes
objective run text: what the run is for, and what counts as passing yes
of run a check-in name resolved once; sets checkin without a workspace step no
workspace run a list of steps (2.7); absent, the run gets no workspace no
sandbox run every required key of 2.8; absent, refused unless the host has ROOT/settings/unconfined (refusal 5) no
limits run the keys of 2.9 it sets; absent, unlimited no
process run the keys of 2.10 yes
values started every value set (2.6), as set; passwords are absent yes
limits started the limits applied, as written yes
outcome, exit, note ended 2.12 as 2.12 says

ended may also hold workspace: removed for an unlaunched run, or kept: and why, for a run whose directory step asks removal once saved (publications pending after launch). Unknown report keys are ignored; an unfamiliar text outcome still ends the run. A stop's content and a publish must be empty: an unknown key makes either unreadable.

ended.outputs records each unversioned file's expanded as and SHA-256 hash, or null for a missing or forbidden file. The counted started records the check-in in values.checkin. Ended describes the process alone. Logs, files and verdict follow it; publication failure is a separate reply and changes nothing in ended. A durable plan retains unfinished publications. A publish addressed to the clone's login, naming the run in re, retries that plan from the kept workspace. It never launches a process. Files must still match the recorded hashes and stay within the workspace; files and verdicts are refused for stopped, lost or refused outcomes. A publish while the watcher holds the run lock does nothing and says so. Workspace removal waits until every publication has reached the server.

2.4 Who acts and whose answers count

Both to and the full clone must match the acting repository. A missing, malformed or different address is passed by, with no refusal posted. Reports answer the run by re and count only on its thread or ticket, received from its to. A counted started also requires a run sent by a different user holding admin, setup or R; a refusal's ended needs no such sender. See acting clones for the whole rule.

2.5 The two users

The clone's user writes OCX's reports. The process's user acts in the process's separate clone. Neither holds admin or setup on the server; the process's user must be no clone's user. create approves its password, then the password step collects it; a run never resets one. See users and passwords.

2.6 Values

A string names something OCX knows as $$name$$: $$, a name of letters, digits or _ (or secret: and a secret's name), then $$. Everything else is itself, $ and { included, so a shell's ${HOME} reaches the process as written. Values are read in every string of workspace, sandbox and process, at any depth, except sandbox.workspace_at, process.user, process.deliveries, process.hears and process.checkins, and never in clone or in any object's keys. OCX checks every value a run names when the run arrives, before any step: a run naming a value that nothing sets before it is refused then (refusal 3), and nothing is made. The default cwd is the workspace when set, otherwise /.

Value Set by Holds
root OCX the directory under which OCX makes runs' workspaces
repository_name OCX the server's repository's name: the last part of the server's URL
server_url OCX the server's URL, from which the process's clone is made
run OCX the run's hash, in full
clone_user OCX the run's to, the clone's user
process_user OCX the run's process.user
releases OCX the release root; its staged/ directory may be bound read-write
release OCX the directory of the OCX release that acts, which a process may read and run
secret:NAME OCX the server's repository's secret NAME; filled in process.env alone when available, and redacted in kept logs
workspace step directory the workspace's directory
process_clone step clone the path of the process's clone
tree step checkout or files the directory holding the check-in's files
checkin run's of, step checkout or files the check-in's hash, in full, resolved once

The password is held privately between steps: it is never a value, never in started.values, and $$password$$ is refused as unset. Secrets are available only to an actor that can read them; the daemon's remote reader supplies none.

2.7 workspace: a list of steps

Each step is {"step": KIND, ...}. Steps run in order. A step that fails ends the run refused, its note naming the step. The process runs as OCX's host account, inside its sandbox, in the workspace the steps make.

Step Keys Needs Does Sets
directory at, within $$root$$ none makes the directory, removing what was there, readable by OCX's host account alone workspace
password none none collects the process's user's approved password; changes no password; holds it privately for clone no value
clone into, within $$workspace$$ password makes the process’s clone from $$server_url$$, signed in as the process’s user with the privately held password, remembering it at into process_clone
checkout of, into within $$workspace$$ clone opens a checkout, from the process's clone, of the check-in of names (any name Fossil resolves), resolved once tree, checkin
files of, into within $$workspace$$ none writes the files of the check-in of names, resolved once in the run's clone, each executable as the check-in marks it, and nothing else: no repository, no password, so the process signs in as no one tree, checkin

The clone step has no password key. The password step collects through collect, signed in as the approved clone's user, and hands the password privately to clone. It sets no value. Current server approvals take precedence over the older local runs map; see passwords. A failed collection fails the step (refusal 9). A run from R alone takes no password step (refusal 1).

"Within" means below that directory, not the directory itself, once links and .. are resolved. A directory whose at is not within $$root$$, or is or holds another run's workspace in OCX's care, and a clone, checkout or files whose into is not within $$workspace$$, are refused when the run arrives, before any step (refusal 7).

Removal once saved requires a sandbox with ends_with_parent: true. The directory step also takes remove_on: "saved" (remove is read as its older name). Once the process and its children have ended, OCX removes the workspace only if its clone has sent every artifact and its checkout has no change, extra file or stash. Otherwise it keeps it and says why in ended.workspace. Without removal requested, it keeps the workspace.

2.8 sandbox

Every key except workspace_at and devices is required, so the run says all of it. On Linux, OCX passes the keys to bubblewrap (bwrap), applied in this table's order, so a later one is seen over an earlier one at the same place.

Key Type Meaning
uid, gid whole numbers the Linux account and group IDs the process runs as inside
linux_capabilities list of names the Linux capabilities it keeps; empty keeps none
network true or false whether it shares the host's network; it never sees the host's processes, accounts, host name or message queues
isolate_terminal true or false whether it is kept from every terminal outside the sandbox, in a Linux session of its own (bwrap --new-session)
ends_with_parent true or false whether it ends when its watcher ends (bwrap --die-with-parent)
proc, dev, tmp paths where it sees its own /proc, its own /dev, and an empty directory for temporary files
read_only list of paths host paths it sees read-only, each where it is on the host
read_write list of paths host paths it sees read-write; a path in both lists is read-write
devices list of paths, optional host devices it may open, each where it is on the host (bwrap --dev-bind-try), which a read_write path, mounted nodev, forbids; each lies within /dev, with no . or .. part and no / repeated or at its end; absent, none
homes list of paths host paths it sees read-write inside the workspace, by base name: /root/.claude as $$workspace$$/.claude
workspace_at absolute normalized path other than /, optional where the process sees its workspace; absent, the host path; names no value
created_files list of {"at", "text"} files made for it alone: at a path, text what the file holds

Nothing else of the host is visible to it. A path in read_only, read_write or homes is refused (refusal 7) when, once links are followed, it is, holds or lies within one of OCX's own files: the run's clone, ROOT/state/, ROOT/settings/, the release root (which holds OCX's programs and every workspace), or the passwords file. But $$workspace$$ may be named in any of the three, $$release$$ in read_only, and $$releases$$/staged read-write.

2.9 limits

Key Type Counted from Enforced on Linux by Past it
wall a whole number and s, m, h or d the launch; with deliveries start, the first timeout, then OCX ending its children ended, budget
cpu whole seconds the launch, for each process the kernel, SIGXCPU ended, budget
processes a whole number any moment the kernel, for the sandbox's own account no new process starts
file_size a size each file a process writes the kernel, SIGXFSZ ended, budget
memory a size any moment not yet provided, so refused (refusal 6) ended, budget

A size is a whole number of bytes, or a whole number and K, M or G for 1024, 1024² or 1024³ bytes. A size above 9223372036854775807 bytes is refused as the run's fault.

A processes limit without a sandbox is refused.

2.10 process

Key Type Default Meaning
user a login required the process's user (2.5)
command list of text required the program, then its arguments; a program without / is found on env's PATH
env object of text empty the process's whole environment; OCX adds nothing
cwd text $$workspace$$, or / with no workspace where it starts
manual true or false false true, a manual run: it waits for a person with local setup to start it with OCX's run
session true or false false false: it runs to completion; true: it is given what it subscribes to (2.11)
hears list of logins, or ["*"] for every user empty whose posts and ticket changes a session is given
checkins list of branch names empty the branches whose check-ins a session is given
deliveries feed or start feed feed: each delivery goes to the running process; start: each starts a process of its own, the same command, one at a time
terminal true or false false it runs on a terminal OCX holds, which a person may join with OCX's attach
ends true or false false a session also receives counted ended reports where it is
keep {"stderr", "stdout", "stdout_when"} 64K, 64K, failure, each on its own how much of the end of STDERR and STDOUT OCX keeps; STDERR always, STDOUT on failure (an exit but 0, or a signal) or always
verdict true or false false when its process has run and was not stopped, OCX writes a ci verdict (2.16) on $$checkin$$, as the clone's user, against the run: pass for exit 0, else fail and how it ended. Refused when neither of nor a step sets checkin (refusal 3), and in a session (refusal 2)
unversioned_files list of {"at", "as"} none when its process has run and was not stopped, OCX sends each file at, within $$workspace$$, as the unversioned file as, as the clone's user: only a regular file, never a link nor one leading out of the workspace; on a clone's host, only if the clone's user holds y on the server. Then a reply where the run is names each, with its hash, or why not. Never for a session whose deliveries is start

Streams:

Stream Without a terminal With a terminal
STDIN the line {"run", "objective"}; then, for a session that feeds, each delivery, a line each; otherwise closed the terminal: the first line and each delivery typed in, and what a person joined types
STDOUT a pipe OCX reads the terminal, which a person joined sees
STDERR a pipe OCX reads a pipe of its own, which OCX copies to the terminal

With deliveries start, each process reads the first line, then its delivery's line, then its input closes; with a terminal, each has a terminal of its own.

Kept logs and where things go:

When OCX writes As
the process is over ended, then logs, files and verdict (2.3) a reply in the run's thread, or a change to its ticket
a process exits, and a stream it keeps printed a byte $$workspace$$/stderr.log, $$workspace$$/stdout.log, when the run has a workspace files, rewritten for each process
the same a reply where the run is: each stream's end in a fenced block headed stderr or stdout, and the exit a plain post or ticket change, holding no OCX message, as the clone's user
its process has run, was not stopped, and the run names unversioned_files the unversioned files, then a reply naming each as the clone's user
its process has run, was not stopped, and the run asks a verdict a ci tag on $$checkin$$ a raw tag, as the clone's user

Reports count only where the run was written. Actor prose quotes any embedded OCX message, including one in hears, a shared as name or a failure note.

2.11 Subscriptions

What a session is given. An artifact that arrives is given only if it passes every filter, at most once, in the server's order.

# Filter Passes when
1 known the server has said who sent it
2 after the server received it after the run
3 no message it holds no OCX message, or is a question or decision; or, with ends, a counted ended where the session is
4 not its own its sender is not the process's user
5 where a post in the run's thread, a change to its ticket, or a check-in on a branch in checkins
6 heard a check-in or counted ended; or a post or change whose sender is in hears, or hears is ["*"]
Arrives Line
a post {"post": HASH, "from": LOGIN, "conversation": NAME, "text": TEXT}, NAME the raw tag console-name on the thread's first post as it is then, the field absent when it has none
a ticket change {"change": HASH, "ticket": TICKET-ID, "from": LOGIN, "text": TEXT}
a check-in {"checkin": HASH, "branch": BRANCH, "from": LOGIN, "comment": TEXT}, the branch as Fossil's timeline shows it

A watcher reads the thread, ticket and named branches through each completed pull, in server receipt order. It remembers deliveries, so an arrival is given at most once. A failed receipt request gives nothing from that read; it tries again at the next completion. An artifact the server has not received is skipped while later received ones may be given; if received later it may be given then. It does not block those after it. What waits for a process is bounded by its pipe; a line that does not fit may be dropped.

2.12 stop, and the outcomes of ended

A stop names no clone: OCX on the run's clone acts on it, from the senders of 2.3 alone; others are ignored.

outcome When exit note
refused a refusal of 2.14 holds absent required: which, in plain text
stopped a stop ended it, or came first absent absent
exited its process ended, by itself or by a signal, with no stop or limit its code, or 128 plus N when signal N ended it, as a shell writes it: an exit above 128 does not say which absent
budget past a limit absent required: which limit
lost OCX on the run's clone found its counted started but not the run in its care absent required: why

A reader that does not know an outcome takes the run as ended.

2.13 A run's life

The daemon pulls each clone whole, publishes its completion number, and hands unsettled runs to detached watchers. On restart it reads all runs; a waiting run is reconsidered at each completed pull. Chat wakes the daemon but is never a record of work. Nothing acts on the server's repository. At start, clone-listing sync is bounded to ten seconds; failure is logged as listing_failure in state/daemon.log and serving continues. Each round's passed lists only newly read runs. Misplaced stops get a reply naming where to post them.

A watcher takes the run lock and decides again under round.lock. A run for another address is ignored. Unknown receipts wait. A counted ended settles it; a launched run no longer in care is lost. Before launch, refusals 1 and 2 come before a stop; a stop comes before later refusals. A manual run waits for run. Runs from R alone wait while another such run is in care. There is no per-user lock: runs of one process's user may run together, on one or several hosts.

Before posting started, the watcher writes durable taken state. It launches only after the post is confirmed. It ends the process and what it left for a stop, a limit or the process's own exit. It posts kept logs, shares files and writes a verdict when allowed, handles removal once saved, then holds the exact ended in ending state until confirmed and writes done. A session with deliveries: start stays open between processes.

A free run lock with taken state means recovery ends the old processes and posts lost, never launches again. ending retries the held report; done does nothing. A missing post redirect leaves its outcome unknown: at another completion, sender, text and reply target are checked before a retry. A stop confirmed while started is unknown prevents launch. A watcher failure after started ends the process, keeps a requested removal unread, logs the failure and writes lost.

2.14 Refusals

Checked in order when a run arrives, and again when a person starts a manual run; the first that holds ends the run refused, written as the clone's user, so the refusal counts.

# Holds when Note names
1 its sender holds none of admin, setup and R on the server; or the run's to sent it; or a user holding R alone sent it and it has no sandbox, ends_with_parent false, devices, a path with .., a directory whose path does not end in $$run$$, or an unversioned name without $$run$$ (as Values.named reads whole names) which
2 a required key is missing, a key has the wrong type or form, or a key version 2 does not define, including retired version 1 keys the key
3 a step kind 2.7 does not have, a step whose needs are not met before it, or a value not set by then which
4 process.user is the run's to, any login ever named as a run's to, any login holding all node letters goC4w, or holds admin or setup on the server which
5 no sandbox, and the host has no ROOT/settings/unconfined that
6 a sandbox key or limit OCX cannot provide which
7 a path outside where 2.7 and 2.8 allow the path
8 the clone's user lacks i for a requested verdict that
9 a workspace step fails, collect refusing included the step, and why

A run written straight into a clone reaches the server as received from that clone's user, so refusal 1 holds: send runs to the server.

2.15 What OCX keeps

The record is in Fossil. Host state, settings and published clone addresses are listed in Fossil and clones. Workspaces remain unless removal once saved was requested. Per-run durable state prevents a second launch; a completion number bounds a whole pull. Completion and republication are built (2.3). Publication state retains the workspace until all publications have reached the server.

2.16 Verdicts

Verdicts defines writing and counting them.

2.17 Stability

Change Within version 2
a new key, in any kind, whose absence means what version 2 says without it allowed
a new message kind, step kind, value, outcome or refusal row allowed
renaming a key, changing its meaning, type or default, or removing it never: that is version 3
OCX meeting a key it does not know in a run refuses the run, naming the key (refusal 2)
a reader meeting a key it does not know in a started or ended ignores the key
a reader meeting a kind it does not read passes it by: it is for another reader (2.0)

A stop with any content key is ignored. Actors read only version 2. Stop open version 1 runs before switching. Server tool answers tolerate added fields, including fields nested in their results, while checking the required fields and answers actually requested.

2.18 Words and examples

A node is a host where OCX's daemon acts on runs, named by its hostname, such as x86-1 or aarch64-1. It is never a login, key, value or directory. The glossary gives the other words; the examples show whole messages.