The scope of the first slices of the rebuild, as the maintainer set it out (a001dc306e). A slice is a check-in that implements one specific, justifiable function, passes every guard, and has coverage demonstrating that function.
Slice 0: the reset
The guards are made independent of src/, and then src/ is reset to
nothing (the maintainer,
ba2c79940b), on
a new ocx2 branch, leaving ocx-human-feedback as the working reference
(7fcdcca8bf).
What stays is the documentation, the guards, CI, the data files and the gap
lists.
Keep
- Documents:
README.md,ARCHITECTURE.md,USAGE.md,PLAN.md,HISTORY.md,ROADMAP.md,AGENTS.md, and everything underdocs/. - CI:
ci.cr,spec/spec_helper.cr,spec/support/, every guard inspec/guards/, the gates inspec/gate/and the host checks inspec/host/. shard.ymlandshard.lock, with no targets built fromtools/until a slice adds one.scripts/, untouched: it is the maintainer's (decision 22 inHISTORY.md).
Make the guards independent first
Only three parts of spec/ use the library today:
spec/spec_helper.crreads the branch's check-ins and their tags throughOCX::Checkout. A small reader of the clone moves intospec/support/, which every guard about check-ins and audits uses.spec/guards/question_spec.crtestsOCX::Question, the question shape. The shape's parser moves intospec/support/, so precept C4 (Question Shape) keeps a guard until a slice puts questions to people again.spec/guards/provenance_spec.crtestsOCX::Deployand the source oftools/deploy.cr, both of which the reset removes. It is listed indocs/precepts/exceptions.jsonas waiting for the deploy slice, and the listing fails the guard once that slice exists, so it cannot stay pending.
Data files that name source files
Their guards require an exact match, so they change in the same check-in as
the source they name, at the reset and whenever a file goes:
docs/precepts/exceptions.json, spec/coverage/ratchet.json,
docs/design/modules.json (then crystal run spec/support/generate.cr) and
facts.json, docs/survey/functionality.json and gaps.json, the rows of
docs/interfaces.md, and PLAN.md's parity contract.
Slice 0.5: four ways in, one tool
The first slice is the harness every later tool runs in: one executable,
ocx2, reached four ways, and one tool that answers what it was given. It
does nothing useful by itself. What it settles is the inputs each way in
really carries, so the first useful tool (slice 1) and the first useful
path (slice 3) are built on inputs observed, not assumed (the maintainer, in
the session reviewing slice 1).
The executable chooses how it was entered by its name (argv[0]), as the
console's CGI program already does to serve git
(tools/cgi.cr).
A name ending -next is the same entry, served from the release offered as
next:
| Entered as | Style | Input | Output |
|---|---|---|---|
ocx2 |
CLI | arguments | text, or JSON with --json |
ocx2-cgi in Fossil's ext root |
CGI | the request | Markdown in Fossil's skin, or JSON |
ocx2-mcp |
MCP | JSON-RPC on standard input (initialize, tools/list, tools/call; trunk's src/mcp_server.cr) |
JSON-RPC |
ocx2-hook |
hook | Fossil's %R and the received artifacts on standard input |
a log line; calls by record from slice 3 |
git in Fossil's ext root |
git | the request | git's smart HTTP, read-only (slice 5) |
The CLI, the CGI and MCP reach every tool; the hook reaches only the tools that change something, each called by a post on a record (USAGE.md, "Calling a tool by record"). Who may run a tool is decided by the caller's capabilities and by the sandbox it runs in, never by the way in.
The one tool: what was given
status answers in every style with what the entry resolved and what it
received: the release, the style, the login and its capabilities, the
repository and checkout, the request's fields by name, the names of the
environment variables present but not read, and how long the run took.
Nothing else. The run with no command is list, the usage as data, and
echo answers its arguments as it read them (HISTORY.md decision 33).
The harness
A spec starts the one executable each of the four ways from captured inputs
and checks what status answers:
- CGI: the environment and body Fossil gives an ext program, captured from the served host once (the request fields of Fossil's server extensions), replayed with any method, query, path and body, so the CGI takes arbitrary input without a browser.
- Hook:
%Rand the artifact lines Fossil writes to the hook's standard input, captured from an installed hook, replayed the same way. Fossil keeps nothing a hook writes (standard output is an unread pipe, standard error/dev/nullunder the server), so the hook appends one JSON line per run to its own log,/var/log/ocx/hook.logon the served host (the maintainer), and writes nothing to standard output. The directory is made once, like the release root; the outer sandbox (the CGI and the hook) writes it, and a session's sandbox can have it bound in to read. - MCP: a JSON-RPC session as a client sends it.
- CLI: arguments, from a checkout and from outside one.
Tools, defined once
A tool is one typed definition (HISTORY.md decisions 32 and 33): its name,
one summary, the capability it needs, a typed input (with dry_run) and a
typed output, each field described once, and one method. Input, output and
context are elements (src/ocx/elements.cr) whose JSON and schemas are
derived from their types, so the CLI, the forms, MCP's tools/list (the tool
list's answer) and help are renderings of them, and adding a tool writes
no code for any entry point. Every answer has one shape: a log of typed
operations, the result only when the tool ran to the end, and why it
stopped (16a5a0e43d).
The log is the one place the flag is honored: a check is performed in a
dry run too, a change only for real. Specs run each tool's example, and
its dry run, which must change nothing.
Each tool checks what it needs, explicitly. What it needs about the caller and the place comes resolved in the caller (below); the tool does not resolve it. The capability is checked for every caller in every entry point, as Fossil reads capabilities (setup implies every one, admin every one but setup), and a refusal names what the caller held. Everything else a tool needs is an argument or a check in its log, and a refusal names what is missing. The CGI shows every tool to every viewer as a card, with its form only when a POST of it would run (HISTORY.md decisions 34 and 35).
The context, resolved once at the wrapper
Each entry point resolves one caller before any tool runs, and resolves in it everything about the caller and where the call acts: the repository, the checkout, the login, its capabilities and the release root. A tool method reads the context and never works these out itself; it only checks that what it needs is there and refuses, naming what is missing, when it is not. So tools never read the environment (precept C6, Explicit Arguments: the served CGI request fields and the build settings are its stated exceptions). The CLI and MCP find the caller as Fossil does outside a server. The last column says what is built:
| Need | CGI | CLI and MCP | Hook | Built |
|---|---|---|---|---|
| Identity | FOSSIL_USER |
--user, else the default user of the repository: the enclosing checkout's, or the one named with --repository |
none: the server's service user | yes; binding.json is not read (slice 2) |
| Capabilities | FOSSIL_CAPABILITIES |
the login's capabilities in that repository; none where there is no repository; a clone lists its cloner as setup | none | yes |
| Repository | FOSSIL_REPOSITORY |
--repository, else the enclosing checkout's (in a work unit, its clone) |
%R |
yes |
| Request | REQUEST_METHOD, QUERY_STRING, PATH_INFO, CONTENT_LENGTH, HTTPS, HTTP_HOST, HTTP_REFERER, FOSSIL_URI, body (exactly CONTENT_LENGTH bytes: on a GET Fossil leaves standard input open) |
arguments, or the JSON-RPC call | artifacts on standard input | yes |
| Page nonce | FOSSIL_NONCE |
none | none | read; no page has a script to use it |
| Home and path | stated, never inherited | the account's home; fossil gets only that and its path |
stated | yes |
| Record | the query | an argument, or the bound unit | the artifacts received | no (slice 3) |
| Working and target branch | the record's definition | the binding | none | no (slice 3) |
| Secrets | none | none read by a command: the unit's clone remembers its own login's password, and Fossil uses it | none | yes |
| State and pipes | read only | the workspace's files | the wake pipe, written | no (slice 3) |
| Clock | start time, for the run's duration | start time | start time | yes |
| Build settings | read by value, for stage's gate note |
the same | the same | yes |
| The rest of the environment | what Fossil and the web server pass | the shell's, or the MCP client's | what the Fossil server passes | yes, by name only |
The environment is an input
Every entry style is started with an environment OCX does not choose, and
that environment is an input whether or not OCX reads it: it reaches every
program OCX starts unless a wrapper says otherwise, and some of it changes
what those programs do. This is a fact to account for, not a channel for
configuration, which stays explicit (precept C6, Explicit Arguments;
the maintainer,
7d1ff175d5).
Examples that matter here: FOSSIL_USER and FOSSIL_HOME change fossil's
login and settings; CRYSTAL_CACHE_DIR, CRYSTAL_PATH and SHARDS_*
change a build; HTTP_PROXY and HTTPS_PROXY change where a sync goes;
TMPDIR, LANG, LC_* and TZ change file places, messages and times.
- Read once, at the wrapper. The wrapper reads into the context only the
variables the table above names; it validates them, and no command reads
the environment after that. It read
TERMtoo, which a run at a terminal passed on (the maintainer, 7242762209), until protocol v1 left no run at a terminal. - Seen, not silent. The wrapper records the names, never the values, of the other variables present, since a value may be a secret, and the undirected run shows them, so an input that changes behavior can be seen.
- Stated for each program OCX starts.
fossilgets onlyHOMEandPATH(src/ocx/fossil.cralready does this); a sandboxed child gets its plan's.crystalandshardsare the exception today: they inherit the caller's environment, so a release's gate note is to record the build settings it was made with. Only an explicit list of settings that hold no secret is recorded by value (CRYSTAL_CACHE_DIR,CRYSTAL_PATH,CRYSTAL_LIBRARY_PATH,SHARDS_INSTALL_PATH,SHARDS_BIN_PATH), with any login and password in a URL masked; any otherCRYSTAL_*orSHARDS_*variable present is recorded by name only.ocx2 stagewrites them into the gate note (codex-cloud-1, 21b142edc7). - Shown by a test. Each entry style is started with
FOSSIL_USERandFOSSIL_HOMEset to strangers' values, and the test shows they change no identity, repository or setting, and are listed as present. Built for the CLI and MCP, and forFOSSIL_HOMEin the CGI, whereFOSSIL_USERis the viewer by design; the hook's waits for the harness.
References: Fossil's server extensions
(the FOSSIL_* variables and CGI fields), its
hooks (substitutions,
standard input, and the write transaction a hook runs inside), and
docs/fossil.md. The hook must not open the repository it was
called for; it hands the hashes on and returns (precept I5, Rollback Journal).
Where each style runs
The CGI and the hook run in the outer sandbox, the Fossil server's, which reaches the served repository, the release root and Fossil's extension root; a session's sandbox sits inside it and reaches only its workspace (the maintainer, dd06d0a5e5). So acts on what the server owns, such as setting a login's password, are the CGI's and the hook's, for a caller with the capability.
What the code holds now
Built: the frame above; list, help, status (with the node's
server-code) and echo; stage, accept and rollback, kept since
releases are needed and stage is how a release is previewed; audit
stripped (the accepted release's records verdicts meanwhile); the web page,
a card for each tool; the captured inputs in spec/fixtures/fossil-2.27 and
spec/ocx/harness_spec.cr, which replays them, and the hook's log, its only
output. What the capture showed: Fossil passes an ext program the Referer
but not Sec-Fetch-Site or Origin (its src/extcgi.c lists what it
passes), so a change is taken from a POST whose Referer is under Fossil's own
base URL, as Fossil checks its own forms; it states a setup login's
capabilities expanded; and on a GET it leaves standard input open, so a CGI
reads exactly CONTENT_LENGTH bytes.
Slice 1: workspace create
The first useful tool: make a work unit's workspace, reachable from all four
ways in. It replaces the old release's workspace create, which is how an
agent is brought in today.
- Input: the record the unit works on, its login, its branch, and the repository's URL.
- Who: a caller with admin or setup, since creating sets the login's password. The login itself is made by a person in Fossil's own user pages; a login with admin or setup is refused as a unit's.
- What it does: sets a fresh random password on the login, in the
served repository; clones over HTTPS with that password in the URL, in
the same process, so it is never written down elsewhere and never on the
record; opens the tree on the branch; writes
binding.json(the record, login, branch and remote, without the password). Setting the password makes every older clone of that login stop syncing, so a login has one live workspace (HISTORY.md decision 29). - Output: the workspace's directory, the record, login and branch.
- Not in it: running anything. Running a task in the workspace's sandbox is its own tool (slice 3).
- Where it lives:
ROOT/agents/LOGIN/projects/PROJECT/RECORD, theocxrelease's layout and binding, so itstaskworks there until slice 8;ROOT/agentsis a link the root's owner makes once, which the page shows as the server's. MCP does not offer it: a sandbox has no served repository. - Twice, or cut off:
binding.jsonis written last, so the directory says how far a create got. Bound to the same record and login with a clone that syncs: returned as it is, with no new password. A clone without a binding: completed if it syncs, else made again. Unable to sync, or bound otherwise: refused, unless--replacefirst moves it aside for inspection. The answer says created, existing, completed or replaced.
Slice 2: OCX Records and responses on a ticket
The second tool: add a ticket change as the caller's login, either an OCX
Record (GLOSSARY.md) or a response to one, so every later path has a place
to ask and to answer. OCX Records start on tickets: a ticket change names
its ticket-id, and Fossil's own command line can add one (fossil ticket),
while it cannot post to a forum. Forum posts as OCX Records and responses
follow after this slice, for milestone-sized work that needs a thread.
Reachable all four ways.
- Input: the ticket-id, or a title for a new ticket; the text; for an OCX Record, the tool, its arguments and the node, by its server-code; for a response, the hash of the OCX Record it answers.
- Who: a login allowed to change tickets (Fossil's
w); the change is the caller's, never another login's. - How: chosen with the harness:
fossil ticketin the caller's clone, then sync, or Fossil's own ticket form over HTTPS. Either way Fossil records the caller as the login that delivered it. - A response names both the ticket-id and the OCX Record's hash, never the ticket-id alone. Which artifact defines a task and the fields that carry it are designed here; a ticket field of its own is preferred over text for the record's hash, where the need is known and stable, and whether the ticket page shows it is the repository's setting.
- Output: the new ticket change's hash, and the ticket-id.
Releases
stage, accept and rollback are built; the maintainer accepted the
node's first release, a7a5e719eb90, from ollama.openbeagle.org's page on
2026-10-06 (HISTORY.md decision 31), and a rollback there is still to show.
Slice 3: an OCX Record creates a workspace and runs a task
The smallest useful path: a person with admin or setup adds an OCX Record
to a ticket; the hook runs it: the workspace is created (slice 1) and a
task runs in its sandbox; the result comes back as a response on the
ticket (slice 2). Running is a tool of its own (the old release's
workspace run), so the CLI, the CGI and MCP can run it too for testing.
- The hook is the process that runs the call. Fossil does not wait for
it (
docs/fossil.md, "Hooks"), so it waits out the write transaction it was started in, reads the OCX Record, sets the password, clones and runs; it is the daemon in its smallest form. It writes its own log, since Fossil keeps nothing it says. - The caller is the login that delivered the OCX Record (Fossil's
rcvfrom), and its capabilities decide. - The OCX Record names its node by the clone's server-code; every other node ignores it. Whether a record can name every node is open.
- Each new OCX Record runs once: it gets one final response, answered or failed; an edit is a new record, asking again.
Short tasks and long ones; what a crash leaves
A call says whether its task completes or stays (the session definition's
"completes" or "stays", USAGE.md "What you handle"). This slice takes
only tasks that complete: creating a workspace, a response, a bounded run.
A task that stays, a session answering turn after turn, needs the daemon
(slice 8). The hook runs a task to its end and exits, and a crash can come
at any step, after a password is set or a task has run but before the
response, so what a run leaves is written on the record, and what is
ephemeral stays on the node (decision 3):
- Running: the node's lock on the record, marked before any side effect and cleared once the run is answered: the node's word that it runs the record, in place of a claim (the maintainer, 0defbd1144).
- Answered or failed: the run's response, as the node's own login, which a person makes in Fossil's user pages, never the caller or a unit.
- Interrupted: a lock found marked that no run holds. Its task may have
run, so the next hook run (a hook is a hint, not a queue) answers the run
cut off as interrupted, which closes the record: it never runs it again
unasked. Resuming a task whose steps are recoverable went with the
workspacetool, the one that did (the maintainer, 2026-10-08). - One at a time: the same lock, one per record on the node.
Built: work, which the hook runs after each receipt given the node's own
login (spec/ocx/work_spec.cr); a lock is held while its run lives.
One record for workspace, session and listeners
A unit's whole definition is one post: a fenced
```json block
holding the workspace (its sandbox), the session (the process) and the
records and branches it listens to. Fencing keeps it readable, because Fossil
renders an unfenced JSON post as prose and loses its layout. Today's reader
ignores a fenced definition, which is how sol-ocx lost its login directories
(69bf7e7e9a
was skipped, 5ad49ff621
followed). The new reader takes the fenced block, and a guard requires a
fenced and an unfenced copy of a definition to read the same.
Each unit holds its own login
Every unit has a Fossil login of its own, made by a person in Fossil's user pages. Whoever creates the unit's workspace, with admin or setup, sets a fresh random password on that login and clones with it in the same process (HISTORY.md decision 29); the password is never on the record or in a file of its own, only in the clone's remembered remote. The child then reads, commits, syncs and reports through that clone directly; there are no host-mediated operations. Tests show:
- after a second create, the first clone of that login no longer syncs;
- a login with admin or setup is refused as a unit's;
- a login without the check-in capability is refused a verdict, not told it succeeded, since Fossil drops the tag without an error.
The sandbox holds
Tests show, from inside a running child:
- the served repository and other units' workspaces cannot be opened or listed;
- a write outside the unit's workspace is refused;
- a CPU, memory or process limit stops the child, and the stop is reported on the record;
- the child's end, by exit or by signal, is reported on the record.
The structural guard on the sandbox's single call site stays beside these
tests; it shows where confinement starts, and they show that it holds.
Built: run and its sandbox (spec/ocx/run_spec.cr), with CPU, memory,
file-size and wall-clock limits. A process-count limit is not set: Linux
exempts a task whose host user is root, as the server's is, so it comes
when tasks run as a host user of their own.
Milestone: one demonstrated path
After slices 1 to 3 and 10, one path is shown end to end on the served host:
a person adds an OCX Record to a ticket; the hook creates the unit's
workspace and runs one bounded task in its sandbox; the response lands on
the ticket. The same demonstration shows a sync that fails and a run killed
midway and recovered as slice 3 says, both among PLAN.md's
failure demonstrations. An agent's edit, its check and a person's
acceptance come with slice 8 and after.
The slices after it, to ocx parity and past it, are in
slices-parity.md.