Login
History
Login

The events, decisions and failures that shaped OCX: where it came from, why earlier approaches were reset, what to keep from them, and the decisions the ocx branch was founded on. Where OCX is going is in PLAN.md.

Where it came from

What to keep

The maintainer's list of the design points worth saving, from the prompt given to agent-sol at the command line and recovered from its history (4617cd3fdf):

  1. JSON posts on tickets or forum threads carry the communication between humans and agents, so mechanical tools can use those hashes directly as a shared starting point for executing something.
  2. HTTPS sync for isolation: real file-system isolation, with access to the primary served repository through the login assigned to each agent. It is better isolation than trunk had.
  3. The hook drives behavior from data updates, without polling.
  4. Staged built binaries, with acceptance and roll-back.
  5. audited and ci tags with their own metadata, to give confidence in check-ins.
  6. Code coverage checks, and a mechanism, not yet fully verified, to keep them from taking too long on each new check-in.
  7. Useful guards, such as keeping each third-party executable's call site in a single place, though the wrappers do not yet describe well what each executable is used for.
  8. The 400-line limit on each file.

Well, we'll see how we merge all this, but I'm trying to go back to the foundation yet again and capture what really works. It seems we arrived at a few design points worth saving, like communicating between humans and agents using JSON posts on tickets or forum threads, which enables mechanical tools to directly use those hashes to get a shared starting point upon which to execute something. I think we also arrived at a better isolation mechanism than we had with the "trunk" design where https-based sync allows real file-system isolation while providing agent-login assigned access to the primary served repository. We identified the hook mechanism to drive behaviors based on data updates without polling. We identified a good process for staging built-binaries with acceptance and roll-back. We began adding "audited" and "ci" tags with additional metadata to provide some confidence in check-ins. We added code coverage checks and a mechanism (I haven't fully verified) to avoid coverage checks taking an excessive amount of time to run for each new check-in. I believe we developed some useful guards, such as keeping third-party executable call-sites at a single point, though I'm not confident in the design of the wrappers to encompass a functional description of the executable utilization. I think the 400-line-per-file limit was also helpful. What other elements of this new design/implementation seem to be really valuable? I want to try to get the most critical points into README.md and PLAN.md.

The maintainer has also said on the record:

Decisions of 2026-09-21

These decisions, made when the ocx branch began, record that branch's reasons; code and older documents cite them by number. The README governs the axioms and virtues, ARCHITECTURE.md the structure and PLAN.md the purpose; PLAN.md's gap table and phases govern the current work. A conflicting historical decision is evidence to resolve, not a second source of authority.

  1. The objective is a workbench coordinating any agents over Fossil; the native agent is one interchangeable runner beside claude and codex; third-party CLIs stay supported on Pro logins for cost.
  2. Working mode: the maintainer fixes form and target, the model produces the history, the maintainer audits by sample; the plan, not trunk, is the target.
  3. Fossil's design is followed: the server is the only writer of served repositories; agents hold clones under their own logins; ephemeral state never enters a repository; audit-worthy communication is tickets and forum in Fossil's own shape.
  4. The branch merges into trunk at parity with one honest merge check-in; history is never glossed.
  5. Tickets are not task records; a run writes back to the artifact that prompted it.
  6. Question cards are derived, not stored; the question is a body convention; the reply is a post.
  7. Build and deploy are separate steps with a smoke check between.
  8. Multi-repository from day one, with no repository-specific dependency in code; only the project's own repository holds this code; serving stays with the Fossil server; the Stone relationship is Fossil-contractual.
  9. Units are not named in advance; trust comes from saying what will be done and doing it.
  10. The name is OCX.
  11. The audit record is a tag on the audited check-in, not a file in the tree and not a wiki page.
  12. No environment variables: configuration is an explicit argument, and the host is discovered from the running server, not described to the code.
  13. A session's home is its workspace: the binding is ~/binding.json, the tree ~/tree, OCX's prompt ~/prompt; a CLI's own login directory is bound under the workspace by its basename, read-write, and nothing else of the host's home is visible. The prompt is passed where the command says ({prompt}), and task ~ inside the sandbox writes the answer as the workspace's identity.
  14. A command-line agent answers as a session in the sandbox: claude in its default permission mode with its tools named (bypassing permissions is refused to root, which the sandbox's user is), codex in its full-auto mode with its own sandbox inside OCX's; the agent's printed answer, or the file it leaves, is the turn, and the check-in is named whether the agent committed on the branch itself or OCX committed what it left.
  15. The task a ticket prompts is its title and its comment as last set whole; what was appended to the comment since are turns on the record, never the task (a prompt built from the accumulated comment made every later ask malformed).
  16. The served console shows one project per request, the one Fossil says the request is for, and shows its tickets and its threads only to a viewer whose Fossil capabilities read them (r, 2), as Fossil's own pages do. Trunk gated its console on a login (docs/fossil-auth-cgi.md) because that console could act and because a viewer must see only projects they may see; this console cannot act, so the first reason is retired, and the second is met by Fossil's capabilities rather than a gate of OCX's own.
  17. OCX is served beside trunk, not in its place: its own link under the ext root is what precept A4 (Staged Deployment) judges; trunk's links, which resolve into its checkout, stand until the maintainer takes them down, because the contract between the two is Fossil's record and nothing else. Served repositories found in WAL mode are migrated to rollback mode in place with Fossil's own tool (done 2026-09-22 for the three that were).
  18. The console lives inside the Fossil skin, as trunk's did: a logged-in person of sufficient capability visiting the repository keeps every feature of the Fossil web server and gets, in the same chrome, the questions addressed to them first, then the open questions, then what agents are doing and did (the order of trunk's dashboard: Needs you, In work, done). Fossil wraps a text/html ext response whose outermost element is <div class='fossil-doc'> in the skin; the same program serves a bare page under any link name but ocx, kept because it is useful on its own. The first cut of the console (F.3, G.2) threw this learning away; the maintainer pointed it out on 2026-09-22.
  19. Serving needs no shell after the first time: the deploy step gates on ci.cr, builds, releases a copy named for its check-in with the gate's result beside it, smokes it, and stages it as next; the console shows a setup or admin viewer what is current, what is staged (with a preview link, /ext/ocx-next) and where a rollback goes, and takes an accept or a rollback as a POST carrying the token from the release root, moving only the current, previous and next links. The served links point at those and never move again. Trunk's lessons kept: a source commit is not live (deploy-verify), and a copy without a name became a stale divergent binary (ticket 926b5abe); here every copy is named by its check-in and the served surface is what is verified.
  20. The served console reads the served repository Fossil names in the request, query-only and with Fossil's busy timeout, as Fossil's own pages do; it is the server's ext program, part of serving, not an agent, so precept I4 (Server-Only Writer) admits it and every served project has a console with nothing to configure and nothing to sync. The first cut read a clone named in a sources file (the maintainer found /stone/ext/ocx empty on 2026-09-22): a misapplied precept, since the nine corruptions came from writers and WAL, never from a reader.
  21. Whoever stages a release cannot choose what runs, nor alter what runs once chosen. The release root has two areas: staged/, the agent's, where releases are written and next is offered, the only path bound into a workspace that builds releases; and the root itself, the server's, holding current, previous, the token and every accepted release, each a copy taken from staged/ at acceptance and smoked before current moves. The maintainer's objection on 2026-09-22 to a workspace holding the whole root; the first install is the root owner's, once.
  22. scripts/ holds translations of trunk's start-fossil.sh and scripts/sync-fossil.sh, each a single file that states everything it does (host names, paths, the repository list, $HOME where the script read it) and finds its base directory from its own place in a checkout, as the scripts did. The precepts govern OCX, which is src/, spec/, tools/ and ci.cr; they do not bind scripts/, and the guards do not scan it. Agents do not change these files on any branch without the maintainer's approval on the record; each file says so at its top, and there is no other file their behaviour depends on. A release carries the shard.yml targets built from tools/ and no others, so a stale program in bin/ is never carried either (release 5349ffa94069 carried two). The maintainer's review of 2026-09-22 (thread cde80fe75b, 5797348fd9) backed out the first attempt, which put settings files and a reusable fossil invocation in src/ to satisfy precept I2 (Repository Portability) and precept C6 (Explicit Arguments); the maintainer then chose scripts/ as their place and kept them out of releases.
  23. Sessions are launched from the console. A setup or admin viewer starts one on a new or existing thread with a POST to the CGI. The CGI creates the workspace when there is none and starts the session detached from the request. The session sleeps on its record with task --wait and answers each new turn. Timers are for chores such as garbage collection, never for creating sessions. The maintainer's reasons (thread adfc87fba2, 2026-09-23): the manual steps were error-prone, he will not be tied to a console session, and a timer adds nothing here. This replaces the timer in the backlog's wake entry.
  24. An agent login is provisioned from the console, and its password is kept once per login in agents/<login>/password beside the release root. The account is created only on the repository whose console provisions it; the same password is set on the other members of its login group only where the account already exists, so provisioning never grants anything on a repository it was not asked for (the first cut created the account on all 25 members of this host's group, and the maintainer questioned it, 2026-09-23). Each repository keeps its own user table, and fossil user changes only the one it is given (www/caps/login-groups.md, tried on scratch repositories 2026-09-23). An account is retired on one repository by withdrawing its capabilities, since Fossil never deletes a user. A launch copies the password into the session's workspace, and a login may have any number of workspaces, each bound to one record of one project. Stated plainly: the Fossil server's user, and so the CGI, can read every agent's password, and each sandboxed session sees its own login's password, but no other agent's. The CGI is trusted at that level anyway, since it can operate on a served repository directly. The maintainer chose this over resetting the password at each launch (thread adfc87fba2, 2026-09-23), because resetting would break other running sessions of the same login and buys little. This amends decision 20: the console still reads a served repository query-only for everything it shows, and provisioning a login is its one write, made with Fossil's own fossil user.
  25. The console keeps its state on the record, and only a workspace keeps files. A launch writes the grant (when the record has none) and a note saying who started the session with what on its thread or ticket, as the agent. The console's Sessions list is derived from those notes, and a runner's default grant is the latest on the repository. A session's own state stays in its workspace directory (~/run, launch.log, launch.pid), since a workspace is sandboxed and works with a checkout anyway; the agent's password file stays as decision 24 put it. The public URL a session clones from and writes to is each repository's own email-url setting, set by its administrator, and a launch is refused without it. Earlier cuts guessed the URL from the request, three times wrongly, and kept launch files and a saved grant per runner beside the workspaces. The maintainer's words (thread adfc87fba2, 2026-09-23): "Only the workspaces should be getting state from the filesystem… Fossil is our abstraction to the storage wherever it can handle it", and "The email-url config property can be requested to be configured on each repository."
  26. Each work unit and each guest has a Fossil login of its own, made by an invite: a definition carrying a one-time password encrypted to the holder's public key, which the holder changes on Fossil's login page (its p capability) so that its clone remembers a password only it knows. The task child uses that clone directly, with no host-mediated operations; the sandbox, not a broker, keeps the served repository and other workspaces out of reach, and revocation or expiry removes the login's capabilities. This replaces decision 24's password file and its reason: a change breaks nothing when one login has one holder. Accepted cost: a password carried out of a sandbox works anywhere until its login is revoked or expires, and Fossil cannot limit a login to one branch. The maintainer's choice (thread 58d537bd67, ac5eaaf8ef, 2026-10-03), after asking for a design that uses what Fossil already provides.
  27. An invite's one-time password is handed over directly, in a channel the inviting person already shares with the holder; it is sealed to the holder's public key (seal) only when it must cross the record. The login's terms are its contact info, which only an admin can change, and the holder opens its record with its own first post and ends it with a closing post. This amends decision 26, which had every invite carry a sealed password. The reasons: the maintainer has only Fossil's web pages and no shell to seal with (0a85704e1b), the direct hand-off is dead once the holder replaces the password, and codex-cloud-1 joined and reviewed this way and found the documents disagreeing (c1815b2d32, 2026-10-03).
  28. The seal tool is removed, and an invite's one-time password is only ever handed over directly, never on the record. It had no use: the maintainer hands passwords over in a channel already shared with the agent, a unit's comes from the console that starts it, and nothing yet needs a password to cross the record. Crystal's standard library offers digests, HMAC, PBKDF2, symmetric ciphers and TLS but no public-key encryption, so seal bound libcrypto's lower-level calls itself; the maintainer judged that a sign of not knowing the standard library, and the tool unnecessary (236e40d65b, 2026-10-04). This amends decision 27. If a password must ever cross the record, the need comes first and the design follows it.
  29. There are no invites. A unit's login is made by a person in Fossil's own user pages. Whoever creates the unit's workspace needs admin or setup, because creating sets a fresh random password on that login and clones over HTTPS with it in the URL, in the same process; the password is never on the record and never in a file of its own, only in the clone's remembered remote. Setting it stops every older clone of that login from syncing, so a login has one live workspace, and a post that creates a workspace through the hook needs no password in it: the poster's capability is what allows setting one. Running a task in the workspace is a separate tool. The maintainer, 2026-10-04, in the session reviewing slice 1; this replaces decisions 24, 26, 27 and 28 for the ocx2 branch.
  30. Accepting and rolling back carry no token. Decision 19's token in the release root guarded the console's POST against a forged request from another site; the CGI now takes any change only from a POST whose Referer is its own page, as Fossil checks its own forms, since Fossil passes an ext program the Referer and not Sec-Fetch-Site (its src/extcgi.c), and from a terminal whoever can read the token can move the links anyway. So the token added a second lock in the browser and none at the terminal. The maintainer, 2026-10-05, in the session reviewing slice 1; this amends decision 19 and the token of decision 21 for the ocx2 branch.
  31. ollama.openbeagle.org is a staging server: its stakes are low (some agent logins and otherwise public data), its work is copied by Fossil's sync on immutable records, and it runs on isolated, untrusted hosts. On it, ocx2-cgi-next is the pre-release candidate and ocx2-cgi the active release candidate. The preview runs agent-built code as the server's account, by choice, bounded by the CI checks and by which agents are trusted. The web page does not offer deploy, which runs the checkout's own code; once a release is served, only the active release accepts, the pre-release candidate accepting only a node's first release, trusted on first use; either rolls back. Before any server holding private code or where downtime is costly, these gate: the preview as a separate OS user (or staging only), since checks enforced by the server's own account guard against mistakes and not a hostile build; acceptance recorded as an artifact holding the release digest, the full check-in, the node, the login and the time, made from the live page, with accept hashing the files itself; trust across nodes by a signature from a key held off the server; reproducible builds, tested by building one check-in twice in different places and comparing; and a per-agent or drop-only staged area in place of a shared one. The maintainer, 2026-10-05, in the session reviewing the CGI, after two outside reviews; this amends decisions 19 and 21 for the ocx2 branch.
  32. A tool is one typed definition, and every entry point is a generic walk over it (stone's review, part 3, 4bb787403b). A tool gives its name, summary, needed capability, a typed input and a typed answer, each field described once, and one method taking the dry-run flag; the CLI, the web page and MCP read their own input into the typed input and write the answer in their own layout, from its type alone, so nothing is written per tool for any entry point and nothing machine-structured comes from a second source (MCP's tools/list is MCP's rendering of the same types; ocx.dry-run is gone). Every answer carries the steps the tool took, or in a dry run would take, its result only when it ran to the end, and why it stopped. The flag is honored in one place, the step log a tool takes every step through: a check runs in a dry run too, an act only for real, and a failed step stops the run as a refusal naming it. The usage is the catalog as data. Inside, values are typed, JSON stays at the edge, a value is built by naming its fields, and every refusal is one exception (docs/crystal.md).
  33. Schemas are derived, never coded, and a log records operations (stone, after 4bb787403b). The tool usage is the tool list, and MCP's tools/list is its answer, rendered: one source, each tool with the schema of its input; help adds the context: the schemas of each output, of the context every tool receives, and of every answer, and where releases live. The catalog holds those derived schemas and nothing that restates them: no command-line usage strings, no list of options. dry_run is a field of every tool's input, so it appears in every derived schema, form and command line; the CLI's --json, --repository and --user are the CLI's own. An answer's log replaces its steps: each entry is a typed operation with its operands, such as take_release {root, name}, its schema derived from its type, with no prose and no outcome; a check is performed in a dry run too, a change only for real, and when a run stops its last operation is the one that stopped it. Caller is Context. The web page answers Markdown, which Fossil renders in its skin, so it can be walked by its headings.
  34. The web page keeps one page and changes how each element is drawn, the layout stone chose from six drawn on a design canvas with the staged release's real data (stone's review, part 4, 71e2b98cd6). Each tool is a card: its summary, the capabilities it needs marked held or not for the viewer, what it would do now when it changes something (a line for each operation: check or change, what it does as its type says, and its operands), and its form, which is its input: text fields with their descriptions, and each switch, dry_run and json among them, a checkbox described in its tooltip. The two written opening sentences are a strip of the context's facts. The page carries a small style block that takes its colours from the skin, with no > or &, which Fossil's Markdown escapes. A field's name reads with its underscores as spaces, muted beside its value; the list's needs is capabilities_needed. A tool that needs no capability changes nothing: its log refuses a change, so the page may run it from any request. The program's description stops at what it is, and the gate counts executables, since a tool is now one of the program's. A tool's own page shows a button back to the list, its answer and its own card, not the whole list again (stone, after eb9e464557: the full list there was superfluous).
  35. A card offers no form that would only be refused, the page knows its own site as Fossil knows its own, and each card links to where its tool is defined (stone, 2026-10-06, after fb21729d4a as served: the accept button was refused from the site's own page). A tool's form is left out when the viewer lacks what it needs or what it would do now stops; its name still leads to its dry run, which says why. Accept stops when the release offered is the one served already, since accepting it again changes nothing, so once a release is accepted its button is gone until the next deploy (stone, after a7a5e719eb as served). A POST is from the site's own page by the check Fossil makes of a POST to its own forms (cgi_same_origin): its Referer is Fossil's base URL or a page under it. Fossil's base is https when its HTTPS is on, which fossil server --https sets behind a proxy that ends TLS while REQUEST_SCHEME stays http, as on ollama.openbeagle.org; the check had compared REQUEST_SCHEME, so every POST from the page was refused. The CGI reads HTTPS and FOSSIL_URI, the repository's path, in its place, so a page of another repository on the same host is not this site's either. 07d7550765 had compared the host alone, which loosened decision 30's check; this keeps it. Because a tool's file is not always named after it, the list gives each tool's defined_in, the file and line that declare it, as the compiler saw them. A card links it as "(src)" to Fossil's file page in the repository the release's source note names, at the release's own check-in, with that line in view.
  36. deploy is renamed stage, and the module that stages a release Staging (the maintainer, 2026-10-07: "It is stage, not deploy in practice"; the name had been questioned since the reset). It never served anything: it checks, builds and stages a release as next, and only accept serves one. The old name also misled: an automated permission check took a staging for a production deploy and stopped it. The ocx tip's deploy and precept A4's "Staged Deployment" keep their names, the one being the tip's, the other naming serving as a step apart from building.
  37. Who sent an artifact is the user the server received it from, by Fossil's receipt. A node whose repository is a clone, such as a build host's, asks the server with OCX's receipt tool, over HTTPS, as its node user, and everything a run or a node writes reaches the server as its writer, from its own clone (docs/protocol-v1.md, section 8). The maintainer chose this, option (a), at the terminal on 2026-10-12: "go with option a". A clone had been found to record everything it pulls as received from its own user, which holds setup there (Fossil 2.27). His first leaning was signatures ("having agents produce a public/private keypair wouldn't be bad"), until he learned what they would need: OpenSSH's ssh-keygen on every node, since Crystal's standard library has no public-key signing (decision 28), and his own key for every run sent to a build host. Accepted cost: the server must run OCX, and a node on a clone decides nothing about an artifact until the server answers for it.
  38. A node user holds the least it needs, and never admin or setup. On the server's host it holds no capability, and no one holds its password, since the node writes into the repository itself and Fossil checks no capability for that. On another host it holds o to pull and i to push (docs/protocol-v1.md, section 10; tried on a scratch server, Fossil 2.27). A ci verdict counts if its run's node user sent it, whatever that user holds, and an audited verdict from a node user never counts. The maintainer asked at the terminal on 2026-10-12, "How is it setup to have least privilege and what are it's roles?" He was shown that the protocol gave a node user forum and ticket write it never used, and i where it needed none. His answer: "yes, make the proposed changes". ocx2 hook --node USER refuses a node user who holds a capability of their own.
  39. The hook acts on the list of artifacts Fossil gives it, and nothing else, reading the repository only to decide what it lists. A write made on the repository itself is acted on at once by the tool that wrote it. What the hook missed or failed on, and what a node's going down left open, is found by a deliberate check, never swept up by a later arrival. A node on a clone runs Fossil's backoffice after each sync, since a pull runs no hook (Fossil 2.27). The session's delivery mark goes with the sweep it served. The maintainer at the terminal, 2026-10-12, answering the three cases a re-read was meant to cover: stale arrivals ("why would we want to trigger a process execution. that sounds more likely to generate surprises than to meet expectations"), local writes ("the point of the hook is to handle the synchronization case, not local execution"), and failed runs ("detecting a failed hook can be a separate, deliberate process"); then "Please, have the hook stick to the list of artifacts it was given unless you have a better reason not to do so. Parsimony." This replaces the protocol's re-read at an interval, and the hook's every-run sweep of slice v1.3.
  40. OCX's code knows no agent. A runner other than command is named by the repository's wiki page sandbox/NAME, whose program, arguments, home files and environment the node runs: how a program such as Claude or Codex is run, its remote control included, is that page's data, and a run carries no field for it. Every program reads its run, {"run", "objective"}, as the first line of its standard input, and a session then its deliveries, a line of JSON each; a run's session alone says how long its program lives, and where a run is posted decides nothing: a task of many forum posts and a ticket's resolution are usage patterns, data in the runs people send. The maintainer at the terminal, 2026-10-12, in turn: "How an agent takes turns should be dependent on the task. I'd say the more typical approach should be to use terminal sessions with remote control for milestone-class tasks with multiple forum posts in the interaction. And for ticket resolution, a single process per turn should be the approach."; of a first form of this decision, which tied the way to a forum thread or a ticket (1016a51a6e): "Just let the OCX message dictate the life of the session ... That's just a usage pattern and doesn't need to live in the code at all. It is just data."; "Get rid of "remote" as an OCX message parameter."; and "there shouldn't be anything claude or codex specific in the CODE ... I don't know what you'd be doing outside of putting in command-line arguments onto the wiki page or putting something in the docs that is claude/codex specific." What is agent-specific, parsing a turn's response, live status and SSE, comes after v1 is complete, and Claude- and Codex-specific tests, outside the source ("I do want claude and codex specific TESTS, but not in our source code today").
  41. ci is no runner but a usage pattern: a session whose checkins names the branches, its program checking each check-in it is given in its own clone and recording a ci verdict on it as its run's user, from whom alone a ci verdict counts. The maintainer at the terminal, 2026-10-12: "Why is "ci" a special runner?", then "don't put into code what is just data." What the node's own checkout of each check-in attested, that a pass came from a checkout of exactly that check-in, goes with it.
  42. A run is written whole: its program and arguments in command, what it adds to its program's environment in env, and the host files it shares, such as an agent's sign-in, in homes. The runner field and the runner pages sandbox/NAME are removed, so a node reads nothing but the run to start its program, and started names no page. A pattern, by convention the wiki page run/NAME, holds such fields as defaults for create; of the served repository's own wiki page, create reads only the newest version that a user holding admin or setup sent, since a pattern names the program a node runs and the host files it binds. A program's environment is HOME, its workspace, USER, the run's user, and PATH, the host's system programs with the program's directory first when it is named by its path, the run's env over the last two, and nothing else. The maintainer, on the forum, 2026-10-13 (c6e8607e39), of patterns: "the wiki was just supposed to provide defaults to be overridden. The OCX message itself should be written fully expanded to make it as clear as possible"; of the environment: "Each variable should be strictly monitored, but I think a few are actually necessary, like PATH, HOME and maybe USER, no?", and at the terminal the same day, that "the blanket statement of clearing the environment was not giving proper consideration", without naming which variables to keep; the three above are the agent's choice. This amends decision 40: OCX's code still knows no agent, and how an agent is run is data, now in the run itself.
  43. A node on a host other than the server's, such as a build machine or a lab board, is OCX's node tool run on that host's clone. Each time it runs, it pulls and acts on the runs and stops among what the pull brought, as the server's node acts on what its hook is given (decision 39), and with --check on every run for it, as at its start; this replaces decision 39's backoffice run after each sync. It learns who sent each artifact from the server's receipt tool, signed in as its node user through Fossil's own login page (a POST to /login, whose cookie signs later requests in), and the tool's answer now carries each artifact's row number, which orders the artifacts of one receipt, so the clone sees the server's order. A run's program signs in as its run user with a password the node's file of passwords holds, readable by the node's account alone, since only the server's node can set a password on the server, and a node user holds neither admin nor setup there (decision 38). A run user works for one node, the node of the first run that an admin or setup sent naming it, so the server's node never resets a password another node holds; every node refuses a run whose user works for another. Each report a node on a clone writes it pushes at once, and goes on only when the server has received it from the node user, so started reaches the server before a launch. A clone records what it pulls with the server's address (Fossil 2.27, tried 2026-10-13), so a node's own reports are still known by their receipts' missing address. The maintainer, at the terminal on 2026-10-13: the reason for the thorough review of the OCX message definition was to add the build machine and lab boards as nodes; on the forum the same day (c6e8607e39), of the receipt service: "As long as you like the design, I guess I'm good with it." The maintainer also questioned naming a node by a Fossil user (c6e8607e39, A.2); the answer given (fc718dc03d) is that the user a receipt names is the only sender Fossil proves, and it stands until the maintainer decides otherwise. This answers items 1 and 2 of docs/review-v1.md's protocol questions.
  44. Protocol v1 is written as tables, and the tables are the standard: docs/protocol-v1.md, with docs/protocol-v1-fossil.md for what it rests on (Fossil as the medium, finding a message, who sent an artifact, verdicts, nodes, tools and versions). A run is built in layers a node processes outer to inner: workspace, a list of steps; sandbox, every field required; limits; and process, holding the command, its environment, manual, the subscriptions, deliveries (feed or start), terminal and keep. A string names something the node knows as $$name$$, every other character literal, and started records each value; the node adds nothing silently. The maintainer at the terminal, 2026-10-13: "If we think about the JSON structure like a network packet, our life might get simpler"; "Let's not allow any silent adds"; "Why not just something like $$VAR$$"; "the terminology in 13 seems mostly fine to me"; and "yes to draft being promoted". Of moving env to the process layer and the budget to the limits layer, out of the sandbox object of 62e45fcde6: "my previous sandbox object approval was just tentative as it looked reasonable. getting it implementable and at the right layer is more important." The draft was reviewed by a fresh reader and a spec-only implementer (docs/review-v1-tables.md), and by the battery, tests 20 and 21 (docs/battery/defs, batch 6). The code still follows the definition as it stood at a38dbeafca, though its comments now cite the new sections; docs/protocol-v1-limits.md and docs/protocol-v1-sessions.md are kept, marked superseded, until it follows the tables. This amends decision 42's fields.
  45. A run's limits layer has files, the largest file a process may write, a size, enforced by a shell's ulimit -f and ended budget by SIGXFSZ. OCX's own jobs in a sandbox, an import's git and fossil, are tasks: their sandbox and limits are written as a run's sandbox and limits layers, as data (src/ocx/tasks.cr), and planned and started by the code that starts a run's process, so bubblewrap's arguments are built in one place from generic fields that another implementation could read as well. An import keeps its limits on CPU, wall clock and its stream's size, and loses its memory cap, ulimit -v, which capped address space and not memory in use, until a node enforces memory with a cgroup, as for a run. The maintainer at the terminal, 2026-10-14: "i hope we have the pattern of calling bwrap in exactly one place and making the calls take the generic arguments that could be used by our own implementation. so, will you modify the custom bwrap environments used for these tasks to use the same implementation, but with specific data tables?", and of adding files: "yes to files". Since a5402d7f33 the code follows the tables, which amends the last sentence of decision 44.
  46. Protocol v1 is wrapped up as stable. docs/protocol-v1.md opens with what it is for, each problem it solves tied to the README virtue it serves, and its section 15 says what may be added within version 1 and what never changes; docs/protocol-v1-fossil.md's versions section now points there. A run with a key version 1 does not define, anywhere in it, is refused naming the key, where before only its workspace, sandbox and limits were and an unknown process key was ignored, so a newer sender's addition is never silently dropped by an older node; a report or stop with an unknown key is still read. The delivery guarantees move into section 9, and docs/protocol-v1-limits.md and docs/protocol-v1-sessions.md, superseded since decision 44, are removed: what a run may write in Fossil (no_password, branch, own_thread) was left out of the tables and stays out. The failures page follows the tables, its case numbers kept for the specs that cite them; the review and slice pages are marked as records; ARCHITECTURE.md places protocol v1 among OCX's layers, local models and their native agent among them, each pointing to where it is defined, so each part's rules are edited in one place. The maintainer at the terminal, 2026-10-14, in KEEP.md's words.
  47. Each topic is written in one place. knowledge-map.json names every document and data file of the tree once, with the question it answers and the topics it alone holds, and docs/knowledge-map.md is generated from it; README.md links the map instead of keeping its own table of documents, and AGENTS.md sends an agent to KEEP.md first and then to the map. A new precept, delineation, holds the map to the tree, every document reached from README.md by links, and every document named in another linked to it (spec/guards/delineation_spec.cr). The battery's records are an archive, kept as written, only its index held to this. Two design files that describe the design before protocol v1, docs/design/events.json and docs/design/sessions.json, are marked stale in the map until the maintainer decides what becomes of them. The maintainer at the terminal, 2026-10-14, in KEEP.md's words.
  48. Test records live outside the tree. The battery of uninitiated readers, its method, findings, every round's prompts, definitions, answers and transcripts, and the harness that ran them, is removed from the tree and read as it stood at check-in f68b57bd52 (the battery), linked from the knowledge map's records kept elsewhere; a new test record is a Fossil tech-note, linked from there. It stays the evidence behind protocol v1's words (decision 44). The maintainer at the terminal, 2026-10-14, in KEEP.md's words.
  49. What no longer describes OCX leaves the tree. docs/design/events.json, the registry of what starts each behavior, repeated the survey's events (docs/survey/functionality.json), and the causation precept now reads the survey, whose schema allows a person, a request, a hook, a session or the host as a cause, never a timer of OCX's own; docs/design/sessions.json and the schemas of state, session and workspace definitions described the design before protocol v1, and go with the guard that held them. AGENTS.md says how to work in a run, points a workspace the ocx release made to its earlier text, and sends an invited agent to the invite page. The maintainer at the terminal, 2026-10-14: "continue clean-up".
  50. PLAN.md is rewritten on protocol v1. It keeps the objective, now naming local models whose lower reasoning OCX tolerates, the precepts and the working rules; says where each function stands as built, pattern, missing, planned or to evaluate; says how each kind of agent works through runs, the native agent for local models and an agent's MCP surface marked as designs to settle with the maintainer, and agents that cannot hold a key, through their providers' MCP tunnels, as the next to design; and orders the phases as the maintainer did on 2026-10-14. What decisions 29 and 39 to 44 replaced goes: work units, the daemon, grants, bindings and the node-sync section, whose hook lesson docs/fossil.md keeps. The maintainer at the terminal, 2026-10-14: "get it done! Yes, rewrite plan.md now."
  51. A run's process may read the release its node runs, never write it: $$release$$ may be named in a sandbox's read_only alone, where before any of read_only, read_write and homes let it through the check that keeps a run off the node's own files, so a run could have rewritten the programs that confine it. The kernel enforces a read-only bind, so the check holds what it says; it is as good as the node's list of its own files. ARCHITECTURE.md and PLAN.md tell a stable definition from a complete implementation: the node builds all of protocol v1 but terminal. Codex's review of d8960f1251, passed on by the maintainer at the terminal, 2026-10-14.
  52. create is practical from the web page: only the run's objective is required; without a title, post or ticket the run opens a new thread the objective titles; without a node, in the pattern, the fields or the input, it goes to this host's node; and without a user, create makes one for it holding gio24, clone, read code, check in, read the forum and post there unmoderated, its password random and unshown. Not p: the node sets the run user's password itself, and a user who could change its own would change it out from under the node; not v, which grants whatever the repository gives developers. A pattern is a wiki page of the served repository by its name, or a URL. The page marks each field required or optional and, after a POST, holds what was submitted. The maintainer at the terminal, 2026-10-14, from use on the cleave project: "If I don't give "title", one should be made so that the default is a new forum thread. If I don't give "node", the CGI node should be used. If I don't give "user", a new account with minimal permissions ("pv4"?) should be used and generated"; "A URL is a URL. Something not a URL is not a URL"; "The required fields vs. optional fields are not clearly indicated on the form"; "The POST result page doesn't populate the previously submitted field contents."
  53. An edit of a post holding an OCX message is a new message, asking again, named by the edit's own hash; the request it edits stands until it is stopped. This is the maintainer's direction from their review of 2026-10-05 (10e85df270: "an artifact never changes, so an edit is a new OCX Record asking again"), which 7c019867e3, an agent's check-in, reversed without their word, making an edit ignored. The maintainer at the terminal, 2026-10-14, after an edited run on cleave did nothing: "I didn't make the check-in that reversed it", and "changing the design isn't the same thing as getting me aligned with the direction."
  54. OCX's MCP is offered over HTTP beside standard input and output: ocx2 --mcp-http HOST:PORT on this host's loopback alone, one JSON-RPC message posted to /mcp, the first step of the mcp-tunnel branch, whose plan the maintainer approved at the terminal on 2026-10-14: a remote agent that will not keep a Fossil login key syncs its own clone's check-ins through OCX's MCP, carried by its provider's tunnel, which connects out from the server's host, with no port opened (OpenAI's Secure MCP Tunnel; Anthropic's MCP tunnels route to an HTTP MCP server, this one).
  55. A run may use a secret it must not show, such as a tunnel's key: an admin sets it on the served repository with OCX's secret, as the setting ocx-secret-NAME of its config table, a setting of no group Fossil syncs, so no clone a run's user makes holds it (a clone of the file itself copies it whole, which only one holding the server's files can make). A run names it as $$secret:NAME$$ in its process's env alone; the node on the server fills it in there, refuses a run whose secret is not set, and writes its name wherever its value would be shown, in started and the kept logs. The maintainer at the terminal, 2026-10-14, on the plan for the mcp-tunnel branch: "I don't think we have a good way to put a KEY-FILE onto the server. An instance-only (not copied during clone) config setting seems like a better way to communicate it to the tunnel client."
  56. OCX is its own client of OpenAI's Secure MCP Tunnel: ocx2 --mcp-tunnel TUNNEL-ID long-polls the tunnel's control plane, connecting out alone, answers each JSON-RPC command as the MCP server answers a line, and posts each answer with its correlation, per the wire protocol of github.com/openai/tunnel-client, docs/protocol.md; its key comes from OCX_TUNNEL_KEY, which the one program's wrapper reads by value (precept C6's exception grows by that one name), set by the node from the run's secret (decision 55). It backs off a refused poll with a bounded sleep, listed among the timers in docs/precepts/exceptions.json, as the maintainer allowed when approving the plan, 2026-10-14 ("Allowed sleep"); and the maintainer, the same day: "Implement tunnel-client natively. the network based MCP server should be able to either listen on a port or long-poll using the secure tunnel-client protocol."
  57. An MCP server in a clone, as a run's process is, answers for its user as the server grants that user, asked of the server's receipt tool without signing in, not as the clone lists its cloner, setup, so the tools it offers and refuses are what an admin set for the agent; the server enforces them again on every push. The command line in a clone keeps reading the clone. Step 4 of the mcp-tunnel plan the maintainer approved on 2026-10-14: "acting as that agent's own Fossil user, limited by the capabilities you set".
  58. An agent that keeps no Fossil login key syncs through the call: OCX's sync takes its check-ins as a Fossil bundle in the call and answers one back, and the run's clone, signed in as the run's user, pushes and pulls for it. A bundle is read before anything is imported, its check-ins each on the agent's working branch, never trunk, and stated (precept C3); one that builds on check-ins the server lacks does not read, and the agent is told to push those first or export with --standalone. The maintainer at the terminal, 2026-10-14: "the commit-and-sync or really just sync is the ONLY operation that is required and it needs to go over the MCP connection, if we cannot invoke any code on the agent's machine. We can ask it to commit to the local repo first, but it'll need to do something like the FILE protocol to produce the data needed for the MCP-based sync."
  59. Saving an agent's own work for sync is guided where OCX cannot reach the agent's files and done where it can: commit_and_sync_guide, everywhere, answers the steps for the agent's own shell and a stated message filled from what it gives; commit_and_sync, for an agent running OCX's MCP on its own machine, commits on its working branch without syncing and answers the branch's bundle, and called remotely does nothing but point to the guide. A context now says whether its caller is remote (the web page, MCP over HTTP or a tunnel). The maintainer, approving the plan on 2026-10-14: "The local MCP should provide a tool that is aligned with the ocx.commit_and_sync_guide. It should not be allowed to be executed remotely and should just point agents to the guide when executed remotely."
  60. A remote agent is brought in by the pattern run/mcp-tunnel (docs/remote-agents.md): one run of it per agent, on the server's node, its process OCX's tunnel client in the run's own clone, acting as the run's user, the agent's own Fossil user, its tunnel's key the run's secret; a stop ends it. The maintainer at the terminal, 2026-10-14: "run/mcp-tunnel is the way to make a proxy to a remote agent." Step 7 of the approved plan; the proof, an agent's check-in landing through it, waits on the maintainer's tunnel.
  61. The tools are listed in groups, by what a person is doing: runs; an agent's work; the repository and its node; releases; and about this program. Each tool names its group as data (GROUP), so the web page, help and MCP's tool list order the tools alike; the web page heads each group with its title and opens with a bar linking each group and each tool. The maintainer at the terminal, 2026-10-15: "we need to organize the tools on the cgi page by group", and "there needs to be a top-bar to jump to the tool or group."
  62. A user holding R, a capability letter Fossil does not define and keeps, may send a run from inside a run of its own that an admin or setup sent and that the node has started and not ended; the run it sends asks no more of the host than that one (a sandbox, each path in its own workspace or within one that run binds as freely, the network and capabilities only as that run has them, no secret), and its user is one an admin or setup has already sent this node a run as, so no other user's password is set by its workspace steps. Protocol v1, section 11, row 1 grows by this. The maintainer, 2026-10-14: "we should be able to restrict that agent the same way we restrict other agents--through the capabilities we set for it", and on 2026-10-15 granted R to cleave's agent. An agent outside any run, as cleave's is, has no run to be held within, and is refused until the maintainer settles what bounds it.
  63. An agent working through a run can confirm where its work landed: receipt in a clone asks the server's own receipt tool, unsigned, as anyone may, and names the server it asked; sync answers each check-in it pushed as the server holds it, by its hash in full, its link, and the user the server's receipt names, the run's user. A ChatGPT agent on mechanical-turk, as run-c1740c5d, in docs/ocx-mcp-feature-needs-2026-10-15.md there, passed on by the maintainer on 2026-10-15: "Route receipt reads to the served repository from a remote clone context", and "Return published check-in IDs and verified document/check-in links with sync results".
  64. OCX's MCP names each tool ocx_ and its name, such as ocx_status, where it was ocx.status: letters, digits and _ are what every client takes, and ChatGPT's had already rewritten the dot. The maintainer, 2026-10-15, answering docs/agent-tools-proposal.md's first question: "don't care, just be consistent".
  65. An agent edits a run's own checkout through tools, a weak local model included, with no clone, Fossil or password of its own (docs/agent-tools-proposal.md): context, find, read, edit, test and save, and wiki_read, wiki_write, unversioned_list, unversioned_get and unversioned_put, in a new group of the list, the checkout. A path stays inside the checkout, its links followed to the deepest part that exists; read gives text exactly, its line numbers apart; edit replaces a text that appears once; the project names its tests in ocx-tests.json at its checkout's root; save commits on agent/, the user and eight hex digits, once each test has passed with exactly the checkout's changes, and pushes as the run's user; a wiki page is written against the version read, an unversioned file against the hash it replaces, after the server's receipt tool says the user holds y, since Fossil passes over what a user may not write and says nothing. Every tool's answer begins with ok, and a refusal may give next_step, what to do instead. The maintainer, 2026-10-15, answering the proposal: "don't care, just be consistent"; "our workspace clone is what i'd expect"; "make it unique like the runner name"; "what does the data show works best?"; "yes, include wiki and unversioned files (for binary file sharing). gain parity with trunk agent tools that justify themselves. continue to evaluate against our virtues."
  66. The battery measures the agent tools with local models: battery/main.cr runs saved tasks (battery/tasks: an objective, the checks that score it, the project and wiki pages its scratch server starts with) through Ollama's tool calling, OCX's MCP in the same process, each trial in a scratch project laid out as a run's workspace, scored from the server and never from the model's word, every exact request, tool answer and reply on its record; a call a model writes as text is taken, as a local agent's harness must, and counted. Its results are Fossil tech-notes, one a run, never edited. Its first live trial showed read's text reflowed by the answer's layout, so text of more than one line is now laid out as a block of its lines exactly, and edit's refusal of an empty old on a file that exists now says how to add to it. The maintainer, 2026-10-15: "can you make a crystal based battery to test the tools with local agents?", and of where results go: "instead of putting a battery result on a wiki, can you use a tech-note, since it won't be edited."
  67. A wiki page is changed as a file is, with wiki_edit: old, copied exactly from wiki_read, replaced by new, old appearing in the page once as it is now, so no one's newer change is lost; old empty adds to the page's end, making it if there is none. It replaces wiki_write, a page written whole against its version, which the battery's 7B model never used: it changed pages with edit as files (/wiki/Status), and once called a wiki_edit it had not been offered (run 2). From the same records, test with no name runs every test, as the model gave names of its own (test_license for license) 110 times; save reads a part that runs over lines as one line; and a path that is no file of the checkout points to the wiki's tools. The maintainer's direction for these tools, 2026-10-15: "gain parity with trunk agent tools that justify themselves. continue to evaluate against our virtues."
  68. ok and next_step stay in every tool's answer. Over the battery's four runs, qwen2.5-coder:7b passed 28 of 73 trials with them and 25 of 73 without, and the larger models passed alike either way (run 4): no measurable effect, while ok is the pass or fail the maintainer asked for and next_step costs nothing. The maintainer, 2026-10-15, of the proposal's question 4: "what does the data show works best?"
  69. The battery measures auditing too: --audit REPOSITORY has each local model rate check-ins against the README's four virtues as written, Clarity, Loose Coupling, Demonstrability and Parsimony, a score from 1 to 5 and a reason for each and a verdict, through Ollama's structured output; scored on seeded pairs (battery/audits/seeded.json), the same change clean and with one known fault against one virtue, on check-ins of the history with a verdict recorded, and on how steady its verdicts are over trials. The maintainer, 2026-10-15: "For all of the local models, I'd like to also judge them on their ability to rate a code change, specifically looking at code check-ins in the history, against the 4 virtues of clarity, interchangability, drift and parsimony. Ultimately, I want to put local models in to perform the audits, but I need an analysis of how well they can do the task." Interchangeability is the README's Loose Coupling, "can be replaced"; drift is what its Demonstrability guards against, "complex systems naturally drift toward failure".
  70. A build takes its check-in's files and holds no credential: the workspace step files writes the files of the check-in it names, resolved in the node's own repository, into the build's workspace, each executable as the check-in marks it, and leaves no repository, checkout record or password there; a run that never signs in names its user without an account, so create makes one only for a run whose workspace takes a password step. A build runs the change under test, code no one has vetted, so it must hold nothing that can write to the server: what it produces goes back through its node, under the node's one account. The maintainer, 2026-10-16: "for this remote agent, that seems like something it can handle itself. it shouldn't need the login of a node on the originator, except for the resulting posts it generates and possibly uv uploads, etc, based on the permissions set on that origination server. so, again, why would i need 2 accounts on my server for that remote instance? why is it even a question?", and of the plan built from it: "sure". A new step kind, as protocol v1, section 15 allows. Found on the way: a node wrote a finished run's kept logs through any link the run left in its workspace (cb9598612c).
  71. A build's pass or fail is recorded on the check-in it built: a run's process may ask for a verdict, and when its process has run and was not stopped, its node writes a ci verdict on $$checkout$$, as the node user, against the run, pass for an exit of 0, else fail and how it ended. A ci verdict now counts from the run's node as well as its user, which decision 38 said and the verdicts document had not. Refused on arrival without a step that sets $$checkout$$, and in a session, which runs on rather than once. Step 2 of the plan the maintainer agreed to on 2026-10-16 (decision 70).
  72. A build's files reach the server through its node: a run's process may name shares, each a path within its workspace and the name of the unversioned file it becomes, and when its process has run and was not stopped, its node shares each that is a regular file within the workspace, never a link, as the node user, on another host only when the server lets that user write unversioned files, since Fossil passes over what a user may not write and says nothing; a reply where the run is names each file shared with its hash, or why it was not. The run's sender chooses the names; the build only fills the files. Step 3 of the plan the maintainer agreed to on 2026-10-16 (decision 70): "it shouldn't need the login of a node on the originator, except for the resulting posts it generates and possibly uv uploads".
  73. A build machine makes its one account itself, and an admin's one press trusts it: node, run on a host with no file of passwords and given user, makes a random code, keeps it in the file, readable by its owner alone, and stops with a link to the server's node_user page naming the user and the code's SHA-256 hash, and the hash's first eight digits; each later round asks the server's node_password for the password, giving the code in a POST, and prints the link again until it gets one. An admin who opens the link sees node_user's dry run, what it would make and the digits to compare, with its form filled from the link, and one press makes the user, holding clone, read and check-in (goi), and unversioned files (y) when share is ticked, never admin or setup, with a random password the server keeps in a config row of its own, never synced and apart from the secrets a run may name. The machine then keeps the password in place of the code, makes its clone, and acts on every run for its node, as at a start, since its first pull brings none of those the clone holds already. No person types, chooses or sees a password, and no step on either host is by hand but the press. node_user refuses a name that is already a user it did not make, so a link cannot take over a person's account; trusted again by the same key, the user keeps its password, which its machine may hold. Two rules this needed: opening a link, a GET for the page, to a tool that changes something shows its dry run with its form filled, where it was refused, while JSON and a POST from elsewhere are still refused; and node, on the command line, is refused nothing for a clone not made yet, which it makes as Fossil's clone does, so it runs on a host that has no clone; every other tool still holds nothing on a repository Fossil cannot open. Found on the way: on Fossil 2.27 cloning needs g, and the specs' fixture lent it through nobody; the spec now runs against a server that lends no one anything. The maintainer, 2026-10-16: "don't forget to eliminate manual steps", "so the only click is the one to install the hook, right? if so, that's fine. for this remote agent, that seems like something it can handle itself", and of the plan, "sure". Step 4 of that plan (decision 70).
  74. Four tools bring an agent's work to the server, one for each place its files can be, under one rule. save: the files are in the run's own checkout, which OCX reaches; it commits and pushes as the run's user (decision 65). sync: the files are in the agent's own clone, which OCX cannot reach and which holds no login key; it takes the check-ins as a bundle in the call and pushes them from the run's clone (decision 58, the maintainer: "just sync is the ONLY operation that is required"). commit_and_sync: OCX's MCP runs beside the agent's own clone; it commits there and answers the bundle for sync. commit_and_sync_guide: the agent has a shell and no OCX of its own; it answers those steps as shell commands (decision 59, the maintainer: "The local MCP should provide a tool that is aligned with the ocx.commit_and_sync_guide"). The rule they now share (src/ocx/commands/stated.cr): a comment of a title and Intent, Expression and Demonstrates lines, each saying something, a part that runs over lines read as one; never trunk; and, where OCX commits, every change, new files included, once each test the project names has passed since the last edit. sync checks a bundle's comments by the same rule, so a comment save or commit_and_sync writes is one it takes. Before, commit_and_sync refused a part of more lines that save joined, left out new files that save added, and committed with tests unrun, and sync checked only that the three lines began. The maintainer's review, 2026-10-16: "These paths need an explicit keep-or-consolidate decision." All four stay. A ChatGPT agent on mechanical-turk reports the guide working and sync pushing and pulling (its docs/ocx-mcp-feature-needs-2026-10-15.md), and an agent's own clone and its run's checkout are different places, which a remote call must never take for one another (codex-cloud-2's review in the ocx2 thread, 2026-10-16: "local preparation and remote publication are complementary operations. Preserve their meanings.").
  75. A user holding R may send runs, and stop the runs it sent, within what each node's owner allows: a node reads, from r-senders.json in its node directory, what such a run's sandbox may have (the network, capabilities, paths to read) and caps on its limits, in a run's own shape; a run asking more is refused, naming what it asked, never fitted silently to the limits; a run from a user holding R alone may bind no home file, name no secret and take no password step, so it holds no credential, and its workspace must be a directory named for the run itself, ending in $$run$$, since a directory step empties its path and could otherwise empty another run's finished workspace; no path of it has .. in it; and a node with no such file takes none. Two reviews that tried to break this found both, and a fault older than R: a workspace step's path that climbed out with .. through a directory not yet made passed the check that keeps it within its place, for any run; the node now resolves .. before that check. It replaces decision 62's "from inside a run of its own", which an agent outside any run, as cleave's is, could never meet: its build to cleave's aarch64 node was refused on ticket 636b90d079. The maintainer, 2026-10-16, through cleave's Claude session: "guys, get together, get your story straight, get me a way to run build jobs. it ain't rocket science"; then, at the terminal, choosing "Yes, refuse past limits" over clamping a run to the limits and over leaving R as it was. The maintainer's "Let's not allow any silent adds" and "the node should be smart enough not to run the record request if it can't provide the restrictions requested" (KEEP.md) are why it refuses rather than fits. stop the tool still needs admin; a user holding R writes its stop as it writes its run.
  76. A node is a clone, or the server's own repository, running OCX on its machine, and not a user: the maintainer, 2026-10-16 and 17, "don't address a clone by its owning user. it makes sense for a clone to have an owner, but the 1:1 thing feels forced." Runs still name a node by a login in this pass (a later pass gives runs the clone's own identity), but the words stop equating the two: "node user" becomes "the node's login", the login OCX on that clone uses, which whoever has the command line there chooses: "the login chosen by the user with cli access should dictate the greatest capability extended to the OCX application, just like it is for cli usage of fossil". Asking for a login becomes general, in a group of its own, like Fossil's self-registration, but the user is made only when an admin approves: register, on the asker's own machine and its command line alone, makes a random code, keeps it with the login and the capabilities asked in its file of passwords, readable by its owner alone, asks the server at once, so a wrong URL shows on the first call, and prints a link naming the login, the capabilities and the code's SHA-256; every later call prints the same link, from what the file keeps, and refuses other capabilities; once approved, it collects the password. approve (was node_user), an admin's one press, makes that Fossil user holding exactly the capabilities asked, refusing a, s, u and v (which lend what Fossil's reader and developer hold), an empty set and any letter Fossil does not define but R; its input is login (not name, which Fossil takes from a page's query under /ext/ as the page's own path), key and capabilities, and its share switch is dropped, since the capabilities say y. It sets again only a user it made that still holds the password it set, so a user deleted and made again by hand under that login is never taken over (a review found node_user would). collect (was node_password) gives the password for the code, needing no capability and changing nothing; register sends the code in a POST's body, never in a URL. node becomes poll, its --user --login, and until its login is approved it does what register does, asking goi, and y only when given in --capabilities; the logic moved from node into register's file, not copied. hook --node USER becomes hook --login LOGIN. register needs no capability yet writes its file, so a tool now says whether it may change anything (changes?), which only one the web page and MCP withhold may say against its capabilities. The 8-digit fingerprint is dropped: the page says to open only a link the asker itself printed, and then only the asker's code can collect, as long as the hash in the link came from the asker. Kept for compatibility: the config rows ocx-node-key-LOGIN, so an approval pressed under the accepted release is still collectable; a file of passwords as node wrote it; and the command an installed hook runs, ocx2-hook --log FILE --node LOGIN %R, and the watcher's --node and --watch, so accepting this release breaks no installed hook. Not kept: a node's machine and the server join only on the same side of this change (the earlier release asks for node_password, this one for collect), so accept on the server first, then on each node's machine, where the timer's ocx2 node --user becomes ocx2 poll --login, as hook --node becomes hook --login on the server; a machine still waiting prints the new link with its old code. A code this release keeps carries its capabilities, which the accepted release does not read, until it is collected. The fields of OCX messages, a run's node among them, are unchanged. ARCHITECTURE.md is rewritten plain: six points and a simpler drawing. New words for the maintainer's approval: the node's login, register, approve, collect, poll, and the group "Logins"; on 2026-10-17 the maintainer approved register, approve, collect and poll ("i like those names"). The maintainer also said: "please don't go changing default account permissions", and that every new term needs their approval (KEEP.md).
  77. A run may name the clone that is to act on it, by Fossil's own identity for that repository file, its server-code. The maintainer, 2026-10-17: "don't address a clone by its owning user. it makes sense for a clone to have an owner, but the 1:1 thing feels forced", and of the identity: "i don't know why we rejected the clone id" (it was not rejected; decision 76 left it for a later pass). Protocol v1, section 15, allows a key whose absence means what version 1 says without it and forbids changing a key's meaning, so node still names a login and a key is added: a run's optional clone, the server-code of the node's repository that is to act on it, or a prefix of 10 to 40 lower-case hex digits, as Fossil names a check-in by a prefix of its hash. Absent, any node whose login is node acts, as before. Present, a node acts only if its login is node and its repository's server-code starts with clone; any other node of that login does nothing with the run, and does not refuse it, since the clone it names may take it; a clone of another form is refused by row 2 of the refusals, naming the key, by any node of the login, as a prefix of that form names no clone. started and ended carry clone, the writer's server-code in full, which a reader that does not know it ignores; for a run naming a clone, a report counts only if received from the node's login, as before, and its clone starts with the run's, so a report with none, or another clone's, does not count for it, unless the node wrote it into its own repository, as one before the key refused a run naming it; for a run naming none, reports count as before. A node answers lost only a started that is its own, names its repository or names none, never one another clone of its login wrote, whose process that clone holds (a review found a second machine on a login would answer every run in the first's care lost on its first poll). The server records only which login sent an artifact, so a report's clone is the node's word, and its login vouches for it. Stops are unchanged. The node reads its server-code from its repository (Fossil::Place#server_code, its config row server-code); a repository without one, which Fossil 2.27 never puts back once removed, takes no run naming a clone and writes reports with none. poll answers it, status shows the served repository's beside the hook's node and its limits, create --clone PREFIX writes it into the run, and run refuses a run naming another clone, naming both. Fossil gives a clone made over HTTP a server-code of its own, and a copy of the file, or a clone made from the file, keeps the one it had, so the rule for a node's repository is: clone it, don't copy it; a clone made again gets a new one, and runs naming the old one are answered by no node. A node on a release before this key refuses a run naming a clone (section 15), its ended carrying none, so the refusal does not count: update every node of a login before runs for it name a clone. A machine joining a login takes a copy of the first machine's file of passwords and does not register, since approve would set a new password; it joins only once every run for that login naming no clone has ended. This answers in part the maintainer's open question, c6e8607e39 (2026-10-13): "It hasn't been made clear to my why the identifiers provided by fossil aren't sufficient to define a node" (KEEP.md), leaving the maintainer to close it. Not changed, and left for the maintainer: a run user still works for a login, not for one clone of it (refusal row 5), and a node runs one run at a time for each user on its own host only, so two clones of one login may run one user's runs at once when runs name different clones. New term for the maintainer's approval: the key clone. On 2026-10-17 the maintainer approved the key, asking that it be clear it is optional: "yes, add, but make sure it is clear it should be optional."
  78. OCX's own agent for local models is one more entry of the one program, as the maintainer's order of 2026-10-14 asks (PLAN.md, phase 4): ocx2 --mcp-ollama URL --model NAME --steps N --system TEXT, named as a run's command, holds the conversation the battery measured, moved from battery/ into src/ocx/ollama.cr (Ollama.converse), so the battery and a run hold one conversation, and what the battery measures is this agent when the run's --system is the battery's words and its tools the checkout's group. Its tools are the checkout's group, as MCP serves them to the run's user, answered in the same process (Mcp.offered, Mcp.answer); a call to any other tool is refused, never run, since MCP would otherwise run any tool the run's user may call (a review found a model could post elsewhere and record a verdict). The model, its system's words and its step budget are the command's arguments, never OCX's code (precept C6, Explicit Arguments, and decision 40); its byte budget is the run's own process.keep.stdout, and its only clock the run's wall, since a step's read timeout is an argument the battery gives and the agent does not. Each reply is a line of JSON on its standard output, printed before any call it asks for runs, and each tool's answer a line after it, the first request and every reply exactly, so the exchange reaches the record whole, in the run's kept standard output when its process ends, not posted before each step as PLAN.md had it: watching it live is the live status the maintainer described over SSE (KEEP.md, 2026-10-16), and no session hearing the model's user is woken once a step. So it takes only a run keeping its standard output always, since the default keeps it only on failure, and a session only with deliveries: start, as it answers one delivery and ends. No request larger than the run keeps is sent, no line past it printed, and no call run without room for its answer, which is cut to fit and ends the conversation, so nothing a call does is missing from the record (a review found a save could land after the reply asking for it no longer fitted). No credential is given to the model, but a test ocx_test runs can read the clone's password (docs/backlog.md). Once the model calls no tool, its last words are posted as the run's user with post, a reply to the delivery's post or a change on its ticket, else where the run is; any other end is said on standard error, and the process exits 1. Delegation (ocx_delegate, ocx2 delegate) leaves PLAN.md and USAGE.md: no file of trunk at a46d73f5bf has it, and an agent sending a model's run conflicts with decision 75, as a run it sends takes no password step, so the model could neither post nor save as its own user; once a pattern such as run/gemma is written, a person can hand a subtask off with create --to POST in the delegating agent's thread (not yet tried), and an agent doing it itself is in docs/backlog.md. No protocol piece, message kind, key or glossary term is added; the one new name, --mcp-ollama, following --mcp-http, is for the maintainer's approval.
  79. A person is told when a running session was not given their post: when a post or ticket change holding no OCX message, from a user no run names as its node or process.user, reaches the thread or ticket of a session running in the node's care and section 9 of protocol v1 gives it to none there, the node replies to it, as its login, Given to no session:, naming each such run by the first ten digits of its hash, the first filter that held the post back, by its name in section 9 (after, not its own, where, heard), and whom that run hears (protocol v1, sections 8.2 and 9). On cleave, 2026-10-14, the maintainer posted as ec2-user to a session whose pattern heard stone, and filter 7 dropped each reply without a word: "I cannot tell if anything is happening after my latest reply". A session whose process is gone already shows why on the record, by its ended, and one not yet begun by having no started; a running one holding a post back by a filter said so only in its host's log. The node decides each reply in the same round, under the same lock, from the same running sessions that decide its deliveries, and writes it after every delivery, so it is true under any ordering and a reply that fails holds no delivery back; no session is given a node's reply, and no node replies to a user a run names as its node, so no reply is answered. It adds no key, message kind, tool or refusal, and keeps nothing on the node's host: a plain post, a v1 addition section 15 allows, which an older node does not write. A pattern hearing every user and leaving its prompt to choose whom to obey was passed over, since it moves the guard on who may instruct a run into a prompt and covers one pattern; and an opt-in key, since a run leaving it out stays silent, as the wrong pattern on cleave was. Trunk's src/ocx/unheard.cr, which parity-v1.md listed as not copied, needed a list of known agents and a password file; the node already writes as its login. It is step 8 of the order for landing trunk's parity deliverables, as the maintainer directed, built on their words of 2026-10-19: "please just start developing code again".
  80. A run may ask its node to remove its workspace once the server holds its work: "remove": "saved" on its directory step, its one value, a new term then needing approval, since approved with the key renamed remove_on (decision 103) (protocol v1, sections 3, 5, 10 and 11). Every workspace stayed on its node after ended, its clone remembering a password that, on a node on another host, never changes, and only a person removed it (parity-v1.md, trunk's workspace gc and the console's terminate). Once the run has ended, the watcher, still holding the run's and its user's locks, removes the workspace unless it holds work the server lacks: a change, a file Fossil does not track or a stash in the checkout a checkout step made (fossil changes --hash --differ, what a commit takes, and fossil stash list), or an artifact not sent in the clone a clone step made (its unsent table), Fossil reading them in a sandbox, as for any files a run wrote; ended gains workspace, removed or kept: and what it holds, and a run the node finds not in its care, answered lost, keeps its workspace, since the node cannot know what of it still runs. A run asking it without a sandbox whose ends_with_node is true is refused (section 11, row 2), so no process of the run outlives the wait: while the process runs the watcher is Linux's child subreaper, and after it exits it waits for each child it so took that is in a pid namespace of its own, a sandbox's init, whose end the kernel makes wait for every process of that sandbox; it waits for no child it started itself, since a wait for any child, as the design had it, takes statuses Crystal's own Process#wait waits for, and in a spec whose process is also the parent of the Fossil server it reads, it never returned. The key is named apart from process.keep, the kept logs, as the order for landing trunk's parity deliverables proposed in place of its designer's keep: unsaved. Trunk's rule, removing once a ticket is Closed or a thread has been quiet for N days, is a usage pattern and a timer, and its fossil changes --hash --extra lists untracked files only on Fossil 2.27, so it missed edits. Step 13 of that order; the run/claude page's step waits for the maintainer, who saves it.
  81. A run is written from inside a run with create, from the run's clone's checkout: its program, or its MCP, as ocx_create, pulls first, writes the run as a reply or on a ticket in the clone and pushes it, so the server receives it from the run's user, and its node decides it by who sent it (protocol v1, section 11, row 1), a user holding R within its owner's bounds (decision 75). create now needs admin or setup, or R (aR). From a clone it refuses title and a run with no to or ticket, since no tool starts a thread in a clone, and a run that would need a user made, since a run a user holding R sends takes no password step; its answer has no acted, as its node acts on it when it arrives. A wiki page pattern counts, from a clone, in its newest version, in the server's order, whose sender holds admin or setup as the server's receipt says, asked unsigned, as thread asks it: a clone's own receipts name its pulls, and its user table lists its cloner as setup, so neither may decide it, and a version the server has not received does not count. On the served repository a user is made for a run only for a caller holding admin or setup, which closes the web page's path where a user holding R alone, with a pattern taking a password step, would have one made. of sets the check-in each files step takes, after fields are merged, passed on as given for the node to resolve (section 5), and a run with no files step is refused. So an agent sends the build of its check-in as create --pattern run/build --to RUN --of CHECKIN, RUN its own run and CHECKIN the hash save answered (SCENARIOS.md, "An agent's change, built and judged", step 4). post still refuses text holding an OCX message: a post that quotes a run would otherwise be a request from whoever posted it, which a node cannot tell from one meant, and agents here quote runs often. It is step 10 of the order for landing trunk's parity deliverables (parity-v1.md), which kept that refusal over its design's deleting it, built on the maintainer's words of 2026-10-19: "please just start developing code again". A session being given its build's ended (step 6 of that scenario) is the order's next step, ends.
  82. A run whose process.terminal is true runs on a terminal its node holds, as protocol v1, section 8.1 defines, and a node no longer refuses one (decision 51 said it built all of protocol v1 but this). Its watcher makes a fresh pseudo-terminal, of no process's own, inherited by no program but the run's process, 50 rows by 200 columns, as ocx2's sessions had from 2026-10-08 to 10-12: its far end is the process's STDIN and STDOUT, and a pipe of its own its STDERR, which the watcher copies to what the terminal shows rather than into the terminal, so the watcher never waits on a terminal it reads itself. It types the run's first line and each delivery in, each ended by Enter, a carriage return, in one write; a session that feeds never gets an end of input, its one process reading every delivery as a typed line, but with deliveries: start, where the run and then each delivery start a process of their own, each such process gets an end of input, Ctrl-D, after its own lines, as without a terminal its input closes; what the terminal showed, escape sequences and carriage returns taken out, is the kept STDOUT. The code reuses src/ocx/terminal.cr of 65393cf672, removed with the pre-v1 tools in 485bbd7e69: the pseudo-terminal, its window, the copy and the plain text. What the removed sessions did that protocol v1 does not define is not done: the agent's own signal that its turn ended, which held each delivery back until then, the bracketed paste each was typed in as, and the screen file; how an agent is run, its arguments among it, is its pattern's data (decision 40), and TERM is the run's env, as every variable is (decision 42). Joining the terminal, attach, comes next. Linux holds a terminal's line to 4095 bytes for a program reading lines, so a longer delivery is cut there. The maintainer, 2026-10-19: "I want our terminal enabled interface with a long-lived session, not turn-based execution like we tried with codex", "you demonstrated it to me where I was able to connect a terminal session", and "I don't want a tmux dependency": the terminal is OCX's own, made by Linux, and no terminal multiplexer is used.
  83. A person at a node's host joins the terminal of a run's process with ocx2 attach RUN, as protocol-v1-tools.md defines attach, which 59db010dbe built for ocx2's pre-v1 sessions, restored on decision 82's terminal. The watcher binds a socket beside the run's lock, ROOT/node/runs/RUN.terminal (0600), before the process starts, removing first one a killed watcher left, and removes it when it lets the terminal go; so who may join is the account the node runs as, by the socket's permissions in the node's directory, no Fossil capability is asked, and the tool is offered on the command line alone, as register is, changing nothing in a repository. The person's window's size goes first, a line, which the run's terminal takes on; then their keys, their terminal raw and given back as it was, while they see what it shows. Ctrl-] goes no further and closes the connection's writing half; the node gives the terminal its own window, 50 by 200, again before it closes the connection, so attach returns only once that is done, answering joining: left; or the process ends first, which also closes it, joining: ended. One person joins at a time, a second waiting. What the terminal shows is sent to the person without waiting, so one who does not keep up, such as one whose terminal is paused, misses what was shown meanwhile and never holds the process back, where 59db010dbe wrote to them and waited. With deliveries: start each process has a terminal of its own, so a person joined to one is let go when it ends, and joins the next with attach again. The joining side is OCX's own code, which sends the person's window's size, then their keys, and copies what the run shows to their terminal, reading none of it, so the run cannot make it run anything, unlike a terminal multiplexer's client: a design for this step found a tmux client on the host could be made to run a command as root by the tmux server in the run's sandbox. What the run shows reaches the person's terminal as it is, which reads it as it reads any program's output. Not restored, as protocol v1 does not define them: a stop said over the socket (a stop is a message) and the screen file; nor is a window the person resizes while joined followed. The maintainer, 2026-10-19: "you demonstrated it to me where I was able to connect a terminal session" and "I don't want a tmux dependency".
  84. Each artifact holding an OCX message carries the Fossil tag ocx-message, its value the message's kind, and for a stop or a report the run it names after a space, by its hash in full (run, stop RUN, started RUN, ended RUN); one tag for every version, since each message carries its own version, which every reader parses and checks. Status took about 1.1 s on ollama because every status, and every round of a node, read and parsed every post and ticket change holding the mark of an OCX message the repository had ever held (96 on ollama, 1.27 MB), found by scanning the text of every forum post. The maintainer, 2026-10-19: "using the actual Fossil infrastructure to speed searches would be a better approach. If we used a "run" tag against every run record the hook finds (which we could add as a manual check-once process to back-fill now), it would take a lot of runs to make that search take very long. Let's just use the information the system gives us, rather than continuously trying to invent our own." Then: "er, more generically, an "ocx-message" tag"; asked whether versions need tags of their own, one tag for every version. Whoever writes a message with OCX tags it in the same turn at the repository, with fossil tag add --raw (create, stop, a node's reports; from a clone, pushed with it); a node's round tags each artifact it lists that holds a message and lacks the tag, or carries another value; the node's check (hook --login, poll --check) still reads every message, as decision 39 has it, and tags every one left untagged, or tagged otherwise, several in one control artifact with a T card each, put in and indexed as a post is, which is the back-fill. Status, a round and run's decision read by the tag, in one query, every run, as a run's user works for the node of the first run naming it (protocol v1, section 11, row 5), every ended, and what is about each run none is tagged as the ended of, reading only those texts, and then what is about a run for the node so tagged whose ended does not count; the watcher, before it starts a process, reads every message, as before, since a message whose tag is missing or wrong could make a run look unstarted and so be started twice. A tag is a hint, never an authority: anyone may add one, it only chooses what is read, each artifact read counts for the message it holds, its sender by its receipt as before, and a tag on an artifact holding no message is passed over. A message whose tag is missing or wrong is unseen by status and a round until a round lists it or the check tags it (protocol v1, Fossil, section 2). To keep the gate within its pace, a write that finds no artifact stored and left unindexed, by a read a checkout makes directly, no longer starts fossil to look for posts among them.
  85. A person who joins a run's terminal with attach has the view fit their window at once, without a key. The run's process starts in a session of its own whose controlling terminal is the run's terminal, by the host's setsid -c, outside timeout and bubblewrap, so the size the watcher sets on the terminal as they join, and its own, 50 rows by 200 columns, as they leave (decision 83), reaches the program as Linux tells any program its window changed, by SIGWINCH, and a full-screen program redraws to fit. Before, no session's terminal was the run's, so Linux told no one, and the program drew for 50 by 200 until a key made it draw again. Linux tells nothing when the size is unchanged, so one whose window is already 50 by 200 sees no redraw until a key. bubblewrap is not given --new-session for a process on a terminal, as it would take the program out of that session: own_session (protocol v1, section 6) is kept by the session setsid makes, whose one terminal is the run's own, and the terminal is still opened as none of the watcher's own (decision 82). As at any terminal, Ctrl-C and Ctrl- typed there now signal the program, and a terminal let go as its watcher ends hangs up its session (SIGHUP); a program that cannot be started is said so by setsid on the process's STDERR, exiting 127, as bubblewrap says so in a sandbox. A window the person resizes while joined is still not followed (decision 83). The maintainer, 2026-10-19, after attaching to the BQ2 Claude run: "The terminal view is ugly, but the first one worked once I used the arrow to get a refresh."
  86. A run may have its sandbox show its workspace at one path, the same for every run, by the sandbox key workspace_at, a path the run states, such as /workspace (protocol v1, section 6), while each run's workspace stays its own folder on the host. Claude keeps its trust per folder (projects[FOLDER].hasTrustDialogAccepted in ~/.claude.json), and each run's workspace is a new folder, its user and run in its path, so Claude asked again every run; with workspace_at, $$workspace$$, $$clone$$ and $$tree$$ read in the sandbox and process layers where the process sees them, so a run's home is /workspace and it starts in /workspace/tree each time. It is a key, absent meaning what version 1 says without it, and not the default for every sandboxed run, since changing where every run sees its workspace would change what $$workspace$$ means to a run already written, which only a version 2 may do (section 15); a key the run states also keeps a run saying all of its sandbox, the node adding nothing. Host paths stay host paths: read_only, read_write and homes name the host's, each seen at its place under workspace_at when it lies within the workspace, a share's at is read on the host, and started lists the values as on the host. The key reads no values, since a path that changed with the run would defeat it, and is refused unless absolute, other than /, with no . or .. part and no / repeated or at its end, or where nothing sets $$workspace$$. So that a checkout opens wherever its workspace is seen, every checkout OCX opens now names its repository by the path from itself, links followed, as Fossil keeps one it is given so (Fossil 2.27, db_repository_filename), where it named it by the host's full path, which a sandbox showing the workspace elsewhere does not have. Side effects: every run of a pattern with the key shares Claude's per-folder state, its trust and its folder of past sessions under ~/.claude/projects/, as one folder; the wiki pattern run/claude gains the key only when an admin writes it there. The maintainer, 2026-10-19: "Remapping the path inside the workspace so that it always looks the same makes a lot of sense. please add that to the backlog and do it when you get to it."
  87. A session may be given the ended of a run where it is: with the process key ends true, a new term then needing approval, since approved (decision 102), false when left out (protocol v1, sections 8 and 9), a session is also given each ended that counts written in its thread or on its ticket, such as the ended of the build its agent sent there with create (decision 81), as the line of the post or ticket change holding it. Filters 4, 5 and 7 of section 9 pass such an ended, though it holds a message and its sender is a node's login; filters 2 and 6 still hold, so it is given only where the session is and after its started. It counts by section 3's rule: written where its run is, received from the login the run names, and for a run naming a clone, by that clone; a heard user's forgery, a node's ended for another node's run and one elsewhere are not given, and no reply says why. Whoever is given it finds the build's kept logs, shared files and ci verdict already on the server, since the node writes them in section 8.2's order, each received before the next. On the server's repository Fossil's after-receive hook lists the node's own writes as it lists the rest, but only at a backoffice, which no write on the repository itself starts (decision 39), so the node starts fossil backoffice on it, detached, after each ended it writes there, where a hook is set: Fossil's lease runs it at once or queues it behind the one running, and the hook acts on Fossil's list alone, as before, so nothing waits on a clock and nothing is swept up; with backoffice-nodelay on, which hook's status shows, it gives up while a lease holds, and the ended waits for a later page, as every arrival there does. A node on another host gives only what its pulls bring, and never its own ended, which its pull's listing may hold when it wrote one meanwhile, so a session there is given another node's ended, never its own node's; an agent there names another node for its builds. So the agent of SCENARIOS.md, "An agent's change, built and judged", learns how its build ended (step 6), and its spec runs every step; CI on a branch becomes a pattern, a session sending a build of each check-in it is given, not yet tried. post still refuses text holding an OCX message (decision 81): an agent sends its build with create. The wiki pattern run/claude gains the key only when an admin writes it there, once a release holding it is accepted. It is step 11 of the order for landing trunk's parity deliverables (parity-v1.md), built on the maintainer's words of 2026-10-19: "please just start developing code again".
  88. A ci verdict counts only from the node of the run it names, and only on the check-in that run's started names (verdicts); the run's user, and a session's checkins, no longer make one count. Decision 41 had CI be a session checking each check-in it is given in its own clone and recording ci on it as its run's user; that runs the change under test beside the clone that remembers that user's password, which decision 70 forbids for a build, since "code no one has vetted ... must hold nothing that can write to the server": a check-in on a watched branch could push passes onto itself or onto any other. CI of a branch is now a pattern, run/ci: a session given each check-in on its checkins, each starting a process of its own (deliveries: start) that sends, with create from its clone (decision 81), a build of that check-in by its hash in full from the pattern run/build, as a reply to the session; the build's node takes the check-in's files, holds no credential, and writes ci on it as its login (decisions 70 and 71), so the session runs no check-in's code. This amends 41, whose session path goes, and 71, whose run-user path goes; no ci verdict from a run's user was ever written, as no pattern sent one, so no verdict on the record changes its meaning, and no code changes, since unaudited counts audited verdicts alone. The pages run/build, run/ci and run/bq2-build (trunk's job: its whole output kept as job.log, the ends of its reports and log posted on a failure; it builds data/sysroot afresh each run, until a user's cache keeps it, step 9), and run/claude with ends and a sentence of its prompt saying how to ask for a build, are written for an admin to save, once a release holding ends is accepted; SCENARIOS.md's "CI on each check-in of a branch" has its spec run every step past those done by hand. It is step 12 of the order for landing trunk's parity deliverables (parity-v1.md), built on the maintainer's words of 2026-10-19: "please just start developing code again".
  89. How an agent writes on the record is part of its pattern's prompt, data copied into each run, not OCX's code: the prompts of run/claude and run/codex gain trunk's conventions (src/ocx/conventions.cr), reworded: every reference a Markdown link with its full URL, built from $$server$$, which the node fills with the server's URL as it fills every value in command (protocol v1, section 2); a question for the maintainer a post of its own, written with ocx2 post POST QUESTION --for stone --options CHOICES, so OCX writes the question shape, as precept C4 has it, one at a time; the result first, what was verified and what was not, times in US Eastern, and README's four virtues. create copies the prompt into the run, so the run states what its agent was told, and a later edit of the page changes no run already sent. It answers the backlog's "a request's instructions wrapped in context for each kind of reader" for an agent: the wrapping is the pattern for that kind of reader. Each agent pattern holds its own copy, since one copy for all would need create to read a second page, which is code. The gate pins the one mechanism it relies on: a value in an argument of a run's command reaches its program filled in (spec/ocx/program_spec.cr). The pages are written for an admin to save, once a release holding post --for is accepted; the run/codex saved on the server holds its block between two backticks, which create refuses, and the version written for this step fences it with three backticks, as create reads. It is step 21 of the order for landing trunk's parity deliverables (parity-v1.md), built on the maintainer's words of 2026-10-19: "please just start developing code again". Of both pages as this step wrote them, decision 90 changes the question lines alone: ocx2 post POST QUESTION --for stone --options CHOICES --stakes STAKES --references LINKS, asked where the run is, so that its sessions are given the response; naming things in words, never by a hash, a number or a shorthand, what it refers to in --references, and the recommended option first; the response, given as a post, acted on by its note; a question answered some other way closed with ocx2 post QUESTION WHY --response; and several questions written on a wiki page, each ending in Answer:, which one question references, the page left alone until the response and then read with wiki_read PAGE --response RESPONSE; those versions wait for a release holding decision 90's post to be accepted.
  90. A question to a person is an OCX message, question, for a user as a run is for a node, and it waits until a response that counts, whatever else is posted after it (questions). The maintainer, 2026-10-20: "consider questions like a "run" targeting a person. The tool should help the person provide a suitable response to the prompt." A question post --for wrote (decision 89) waited only while it was the last thing its thread or open ticket said (e7251eb731, 69ac277430), so 0699f56b24 dropped from sight when an unrelated post followed it, and the page answered a question with a question back to its asker (bc118929ca). Now post TO TEXT --for USER writes {"ocx": 1, "kind": "question", "for", "question", "options", "stakes", "references"}, and post QUESTION TEXT --response writes {"ocx": 1, "kind": "response", "question", "note"} where the question is, an option's number as that option's words, each after Markdown saying it in words and tagged with its kind alone, decision 84's form. A response counts only from the user the question is for, or from its sender, closing it, written where the question is and public; the first that counts, in the server's order, closes the question, and nothing else does: not a later post, a third user's response, an edit, which is a question of its own (decision 53), nor its ticket closed. questions reads every question and response through the tag in one query and pairs each question with its first response that counts; a round and status read neither. A question names things in words: post refuses a hash (6 or more hex digits mixing digits and letters) or a link in its words, options or stakes, words past their bounds (200, 100 and 200 characters), a repeated option, a for naming the caller or a user the server lacks, and, for a question in a thread, a sender or for whose posts there Fossil holds for a moderator, so no response that counts is made public later at an earlier receipt; it refuses a response from anyone but the user asked or the sender, and to a question a response that counts has closed. A block without ocx, as post wrote before, asks nothing. The page's form writes the viewer's response. Both ways of giving a session its posts stay, chosen per run by deliveries: the maintainer, 33e7f8304e (2026-10-20), "There's no reason to restrict Claude sessions to one mode or the other. It is more contextual than that.", which answers the question of parity steps 16 and 22 (parity-v1.md) and removes the backlog's row on removing deliveries: start. Left to later check-ins of the same design: references expanded into a title, a sender, a time and an excerpt; a session given a question or a response as a post, which filter 4 of protocol v1, section 9 holds back until then; the page's cards and a sender's close form; a wiki page as a front end; and whether for may name several users, which waits on the maintainer. for names one user. New word for the maintainer's approval: response; question, already OCX's word, becomes a message kind.
  91. Anyone may respond to a question, and it stays open until its asker confirms it answered; every reference is a link. The maintainer, 2026-10-20: "i don't think we often need to filter by user. if we need to verify, then we verify, but we can pay attention to all users on a thread in most cases."; and "it isn't sufficient to show references by name. as previously stated, they should be at least links. in some presentations, they should include the key content as a preview. i'd say questions stay open until the asker confirms it is answered." This amends decision 90: a response counts when it is written where its question is and is public, whoever sent it, each shown with its sender by its receipt, so the asker can check when that matters; for says only whose page lists the question as waiting, and post no longer refuses a response from a user neither asked nor the sender. A question waits on the user it is for until they respond, whoever else has, so the person it targets can respond from their page (the maintainer, 2026-10-20: "The tool should help the person provide a suitable response to the prompt."); it is listed for its sender to confirm once anyone responds, and stays open until its sender confirms it answered with a response of their own, whose note says why, in words, never digits alone, and is then closed: only the sender's response closes it, and no response after that changes it. questions lists for its viewer what waits on them, what they sent that has a response, to confirm, and every other open question, each with its responses; the OCX page, /ext/ocx2-cgi, under the heading "Questions waiting on you", shows them with a "confirm answered" field for the sender, and after a press says in words what it did, never post's answer by hash. A wiki page a question references no longer has a version that counts (588785e8bd): the asker reads the page itself, with wiki_read or Fossil's history, and confirms, so wiki_read --response, the version named in post's answer and on the page after a press, and that rule go; post still refuses a for who may not read and edit the page, or whose edits there a moderator would hold. Every reference is a link, title included: on the page each title in "Read first" opens Fossil's page in a new tab beside its preview; the command line prints its full URL, the post's Markdown links it by its title, and the JSON for a client such as Stone holds link beside title and excerpt; previews stay on the page and in Stone. The maintainer's words of 2026-10-19 on hearing, "only hearing stone was stupid", and of 2026-10-20, "we can pay attention to all users on a thread in most cases", make the patterns run/claude and run/codex hear ["*"], written for an admin to save; --for stone stays, and their question lines gain the asker's confirmation, ocx2 post QUESTION "why it is answered" --response, once satisfied, or a clarifying question, in place of decision 89's wiki_read PAGE --response RESPONSE. Found by an adversarial review of decision 90's check-ins and fixed with it: a line of tildes in a question's words, its stakes or a response's note opened a fenced block hiding the message, so post escapes ~ as it escapes backticks and refuses text that does not read back as what it was made from; a hash in a reference's title reached the person, so titles are cleaned as excerpts are; a ticket's moderation could make a held response public late, so receipt says ticket_moderated and post refuses as it does for a thread; and a question's options read numbered at the command line, as each is a leaf, OCX::Choice, whose JSON is its words. A second adversarial review, of this decision's change, found and had fixed with it: a response's note showed a hash on the page and in the JSON for Stone, so each note's hashes are shown by their titles, as an excerpt's are; a hash naming nothing here, or several, stayed in an excerpt or a title, so it is cut to "…"; a third user's response took the question off its for's page, leaving them no buttons to respond with, so it waits on them until they respond; the sender's confirmation took an option's number as its note, so post refuses digits alone from the sender; a refusal after a press could name a hash, so its hashes are cut; and post's moderation refusals said a response "could count late", which the closing rule made untrue for the user asked, so each now says what a moderator holding it would do.
  92. What a tag, a moderator or the order of receipt can change is shown by specs that cross those boundaries. The maintainer passed on, 2026-10-20, ChatGPT's review of agent-tools through a91c8e8d8e, verbatim: "Priority: tags can hide valid lifecycle records. said_tagged_open excludes older started and stop records whenever any tag says ended RUN, before checking that report's sender or content. I reproduced the query in SQLite: tagging an unrelated artifact removed both records from its results. Keep those records until an authenticated ending is established."; "Question ordering needs an adversarial test. QuestionsCommand.asked checks the response's sender, question hash and location, but never checks that the server received the response after the question. A response received earlier could close a subsequently published question. Test that ordering explicitly against the documented state transitions."; and "For weaker reasoning, I'd prioritize tests that cross boundaries—untrusted tags versus authenticated reports, private versus public artifacts, and receipt order versus publication order—over more repeated readings of the same diff." The specs found three defects. A tag saying ended RUN on an artifact the reader does not read, such as a check-in, hid the run's started and its stops from every round and from status, since the reader learned which runs were so tagged only from the posts it read: a run with a started read as never started, and a stop for it answered it as if it had never started; a tag on a post holding no message, or on another user's ended, hid nothing. Now the one query that reads the runs names every run some artifact of any kind is tagged as the ended of, and the reader reads what is about each such run that no ended it read ends. The node's own ended, held private by Fossil as a post awaiting a moderator, ended the run for the server's node alone, where a node on a clone never holds it: now a message Fossil holds private counts for nothing until it is public, for a node as for a question (decision 90), and a held ended is given to no session whose ends is true; a session is still given a held post from a user it hears as it arrives, and on the server's node a post approved later reaches no round, both left in docs/backlog.md. And a response received before its question, the confirmation included, counted, as one written for it in a clone and pushed first would: now a response counts only when the server received it after its question. A moderator's approval keeps the receipt a post arrived by and receives nothing (Fossil 2.27, tried 2026-10-21), so what it approves counts as received when it arrived: a response held from before the confirmation and approved after it is one of the closed question's responses, and a confirmation held closes the question as of its receipt once approved, the responses received after it dropping out. Separately, the maintainer, 2026-10-21: "agents are mistakenly trying to use sync, when they should just use a regular fossil push." An agent in a run's checkout was told only that its check-ins reach the server as it commits, and found sync in the list, its summary beginning "Push your clone's check-ins"; now sync, commit_and_sync_guide and commit_and_sync begin by saying they are for an agent working outside the OCX server's host that keeps no Fossil password and end by telling one in a run's checkout on the OCX server's host that its commits push as it makes them, and to run fossil push if one did not, as AGENTS.md, docs/remote-agents.md and the patterns run/claude and run/codex, written for an admin to save, say too. An adversarial review of that change found that it still sent an agent in a run's checkout on a node on another host to sync, though that run's clone too is made as its user and remembers the password (protocol v1, section 5): now the summaries of sync, commit_and_sync_guide and commit_and_sync end "In a run's checkout, on any node? Your commits push as you make them; if one did not, run fossil push." and begin by saying they are for an agent outside every run's checkout that keeps no Fossil password, the guide's first step and commit_and_sync's next call say "in a run's checkout, on any node", and AGENTS.md and docs/remote-agents.md say so in their own words.
  93. Whoever sent the run whose user asked a question may confirm it answered, as its sender may (questions, section 2). The maintainer, 2026-10-21, chose it in the session's question form: "Run's sender (Recommended): Whoever sent the run (you, usually) may also confirm its questions. Works for every run, and patterns stay as they are." The adversarial review of aee6237c40 found, as its finding 7, that a run asks as a user made only for that run, so once the run ended no one could confirm its question, which stayed open for good. A confirmation now counts from a question's sender and, when its sender is a run's user, from the run's sender: the user who sent the first run naming that user, in the server's order, of the runs a user holding admin or setup sent, whatever became of it, as a run's user works for the node of that run (protocol v1, section 11, row 5; decision 84), by the server's receipt; a run from a user holding neither, or one that does not read as a run, names no one. The run's sender is usually the user the question is for too, so the first response of the user it is for is their response, an option's number written as its words, and a later one of theirs confirms it: on the OCX page their card waits on them with its buttons until they respond, then shows under "asked by you or your runs, responded to: confirm each answered", once "yours, responded to", with a field to confirm it. questions reads every run in its one query by the tag, with the questions and responses, and gives each question its confirmers; who may confirm is read from every run, whatever the viewer may read, so a question is open or closed alike to every viewer. post --response reads the runs wherever they are, from a clone as the server received them, and a refusal of a question closed already names who closed it. An adversarial review of 881bafb390 found two defects, fixed with it. Run users are reused, so the first run naming a user named its first sender for good: a question a later run asked, boss's, was confirmed by stone, who sent only an earlier run, and refused to boss, and a run from a user holding R gave its sender no say; and since each sender's capabilities were read as they are today, 26 questions stone had confirmed read open again once stone lost setup on a copy of the server. Now the run's sender is who sent the run that asked: of the runs naming its user, the one whose started that counts, public, from the run's node, the server received last before the question, whatever that sender may do since, as the node decided the run counted when it started it; a question asked before any run naming its user had started has no run's sender. questions and post --response read each started with the runs.
  94. A question names the run whose process asked it, in a key of its own, run, and nothing of which run asked is inferred (questions, section 2). This amends decision 93's rule; its choice stands, the maintainer's of 2026-10-21: "Whoever sent the run (you, usually) may also confirm its questions." An adversarial review of def06375db found that rule's inference, the run naming the asker whose started the server received last before the question, wrong twice: "A run that cannot ask questions still gives its sender the power to confirm them; a user holding only R can close a person's own question.", since a run from a user holding R never takes a password step, so its process can never write as its user; and "When two runs of one user overlap, the confirmer is whoever's run started last, not the run that asked.", as for two clones sharing a login, or a post a kept workspace pushes after the next run's started. Both reproduced on 63a9645afa before this change. The maintainer's standing rules leave no inference to mend: "Just let the OCX message dictate" (decision 40), and, 2026-10-18, "please don't go down any path with a possible race condition". So a question may carry run, the hash in full of the run whose process asks it, as a report's run names its run, and that run counts only when it is a run, public, that the server received before the question; its process.user is the question's sender; it takes a password step, so its process can write as that user; and its started that counts, from its node, public, where it is, and for a run naming a clone from that clone, was received before the question. Then its sender, by the server's receipt of the run, may confirm the question, whatever they hold today; otherwise only the question's sender may. questions reads it from the question's own key, and the inference and its rows are removed. post TO TEXT --for USER --run RUN writes it, RUN a run's hash or a prefix, found where the caller is, and refuses a run whose process.user is not the caller, or that takes no password step, and --run without --for. Of the ways a run's process knows its run, the one given outright is used: a run's process reads it on the first line of its standard input (protocol v1, section 8.1), which post, a program it starts, does not share, and a pattern can write it as --run $$run$$, which the node fills as it fills every value in a command (decision 89); the checkout's place is named by the run only as a pattern's directory step chooses, and a sandbox's workspace_at shows it as /workspace, and the values started lists could be matched to the checkout only by its path. So decision 93's "patterns stay as they are" no longer holds: the question lines of run/claude and run/codex gain --run $$run$$, written for an admin to save; a question asked without it is confirmed by its sender alone. The review's finding 3, USAGE.md still stating decision 93's first rule, is fixed with it; and its finding 4: a push that failed from a clone's checkout said "a later sync sends it", naming the tool decision 92 steers a run's agent away from, so post, a ticket change, a verdict and tag_messages now say "run fossil push to send it". An adversarial review of 2ea82d7e36 found a confirmation undone by an edit of a tag: questions read only the artifacts whose ocx-message tag stood with a kind it wanted, so any user holding i who cancelled the run's tag, gave its started another value, or cancelled the confirmation's, reopened a question the run's sender had closed, though a tag only chooses what is read (Fossil, senders and nodes, section 2). Now questions reads, in one query, every post and ticket change the tag was ever put on, whatever its value, cancelled or not, and the message each holds says what it is; a round and status still read by the tags as they stand. The review found too that the reader trusts the login a run names as its node, without checking what the run's sender could do when they sent it: one who names their own login as a run's node and writes its started may confirm a question whose sender names that run. Questions, section 2 now says so, and what the reader checks is unchanged.
  95. A watcher that fails at a step of its own after its run's started, before the run's ended, writes that ended itself: lost, its note naming the failure's class and the first line of its message, any password in a URL and any secret the run uses masked, once it has ended the run's process, if it still runs, as a stop ends it; then it raises the failure again, as before (protocol v1, sections 10 and 12; failures, case 33). If that write fails too, as when Fossil is down, the run is left with no ended, and the node's check answers it lost, as before (case 2). Decision 39 has a deliberate check, and nothing else, find what a failure left open, and no later arrival sweep it up. This is no sweep: the watcher reports its own run's end, holding the node's lock, as it reports budget or stopped, and reads nothing it was not watching already. Before, a failure after started left the run with no ended until the node answered it lost: at its check, as it starts or when run by hand, or when a stop for the run arrived. An adversarial review of 35b2b0febb found two such failures, each fixed in its own right in the same check-in: a watcher typing into a terminal its program had left full, which never let another fiber run once the program had exited (case 31), and a kept log whose place the program had taken with a directory, whose rename raised (case 32).
  96. A watcher that fails at a step of its own after its run's started writes the run's ended, lost, only once the run's program has ended, as after a stop: it ends the program, if it still runs, waits for it, and ends what it left in its process group (decision 97). It no longer raises the failure: it gives it whole, its message's every line and its backtrace, any password in a URL and any secret the run uses masked, to the run's log, its own standard error, and hands on its user's next run, as after any ended (failures, case 33). A run whose workspace is removed once saved keeps it, unread, its ended saying kept: what it holds was not read, as its watcher failed, as a run found not in the node's care keeps it: a failing node reads nothing more than it must (protocol v1, sections 3 and 5). This amends decision 95. An adversarial review of e9b388af16 found that its lost was written while the program still ran, so a waiting run of the same user could have its password reset under it (case 26), against the promise that a run that started gets exactly one ended, written once its program has ended; that raising kept the hook from handing on the user's next run, which waited for a node check; and that the lost of a run whose workspace is removed once saved gave no workspace and left it there, against section 5.
  97. A run's process with no sandbox starts in a session of its own, by the host's setsid, as one at a terminal does by setsid -c (decision 85), so it leads a process group of its own: a stop sends TERM to that whole group, and once the program has ended, by itself or past its wall, which timeout keeps on the program alone, the watcher ends with KILL what it left in the group, as a sandbox's processes end with its init (case 28) (protocol v1, sections 7 and 10; failures, case 34). The watcher is Linux's subreaper of what the run's program leaves while it watches the run, so what the program left is its child, and it signals the group only while one of its children not yet waited for is in it: Linux keeps a group's number while any process is in it, and a child's until it is waited for, so no other group that took the number is signalled. A gone watcher's program in a group of its own is ended by the node's check only when it started when its run's lock says: the watcher names each program it launches there with its start, in clock ticks after the boot, which no process that takes its number later, or after the host starts again, shares; and a run's lock's numbers are read only above 0, as a signal reads 0 as the sender's own group. A process that makes a session of its own leaves the group, and only a sandbox holds it. Before, a stop sent TERM to the program alone, and once the program had exited the watcher waited for every holder of its output: an adversarial review of e9b388af16 had sh -c 'sleep 40 & exit 3', unconfined, with a wall of 6s and a stop at 2 s, end stopped at 40 s, and with sleep infinity it would never have ended; the same at a terminal with a child that ignored the hang-up. The same review found that a node not running as root said removed of a workspace it could not empty, as a directory the program left that the node's account may not read: it says removed only once nothing is left at its path, and otherwise what is left.
  98. The console is a tool, console, offered at every entry point, and the web page draws nothing a tool does not answer. The maintainer, 2026-10-21: "Can I get some kind of console to help me see what the questions and runs all look like before I accept this new version? I ran questions and it told me nothing."; then: "running questions give garbage. i don't think i approved going around the consistent cgi/cli boundary to provide a console. my expectation was for you to give a console tool." This amends decision 18, whose order stands (the questions waiting on you, then the open questions, then what agents are doing and did), and decision 91's page. The OCX page drew its own question cards from the list, outside any tool, and wrote responses through a press field only it read; questions printed each question twice, as its post with the JSON block and as fields, with hashes in full; and a GET of questions, which needs 2r, showed a dry run with no result and, with json, was refused, as a tool needing a capability was taken to change something. Now console [--for USER] answers a person's view, shaped for reading: each question waiting on them with who asked and how long ago, its time in UTC beside it, where, what it changes, what to read first as links, each with its excerpt, an OCX message's block left out, and each option, the recommended first, then their own words, as a press of post; each they may confirm answered, with its responses and a press to confirm it; every other open question; then this host's node's runs that have not ended, each with where it is, who sent it, its user, where it stands and since when, and of those that ended the five the server received last; each empty list says none, and no hash is shown but in a link or a command line. From a clone's checkout it asks the server's receipt tool who sent each question and response, as post --response does, and shows no runs. questions is removed, as two tools showing the same questions would be one too many; its reading is post's and console's. A press is an element each entry point draws its own way: the command line as the exact post command, the page as a button posting to post, JSON as the tool, its input and whether it is the one recommended; a link reads as its title and full URL at the command line, its excerpt beneath, and on the page as a link by its title with its excerpt folded beneath. The command line, and MCP's text, print what any tool answered before its log, as the page does. A tool that writes nothing says so, whatever it needs, its log refusing any change, so the page runs it from any request, a GET among them, the viewer still holding what it needs; console is the first to say so. status keeps its runs for the node's operator, read by the same operation. The page's cards, their one form, the words typed kept across a press, the line "Your response is written." and a button back go with the path that drew them: after a press the page shows post's answer, and its bar leads back to console. No viewer is singled out: who sees a question is the viewer's capabilities, by the standing rule of a001dc306e: "Don't try to tell me that functionality should only be made available to 1 of them--we should resolve that with login/capabilities restrictions and sandboxing".
  99. What answers a question is a decision, and question and decision are the maintainer's words for its two kinds of OCX message (questions, section 3). The maintainer, 2026-10-21, chose on the question asking whether to approve response as the word for what closes a question and question as a kind of OCX message (9d82494f0c): "Approve question; find another word for response"; then, in a reply (117bbc193c) to the question offering answer, reply and decision: "decision sounds good." This amends decision 90, whose response it renames: the kind and its tag value, the type that reads and writes it, post's switch, now --decision, and console's presses, its decide, each writing a decision; the post post writes begins "Decision from USER:", and a question's post says "Decide: on the OCX page". The answers so far in the ocx2 thread were written as response, so a message whose kind is response is read as a decision, with the same keys, wherever a reader reads one: those still count, and close what they closed; OCX writes only decision. Neither word awaits approval now: GLOSSARY.md and protocol v1, section 13 drop their NEW TERM, and docs/backlog.md its row.
  100. console is a presentation, laid out for each entry point, and questions is back as data, the same at every entry point; status stays as it is. The maintainer, 2026-10-22: "sorry, i gave mixed messages around the console. my expectation was the console was fully optimized for the presentation given (cgi, cli or whatever), whereas questions and status stayed locked in a ui agnostic/compatible tool. but, i didn't want console just to be an extension of the default page. i'm still not sure what the default page should be. it could be the console or list, like it is now, but console should have no burden like list does to show all the tools. it should be clean and focused on how the data is consumed." This amends decision 98, which had console replace questions, laid out through the one generic layout as the list's first card. questions [--for USER] answers, through that layout and as JSON, each question's fields once: the post or ticket change asking it, where it is, its title and link, who sent it and when, whom it asks, its words, options, stakes and run, what it replies to and its references described, its decisions with their notes, and who may confirm it; it holds no post's text, nor its block beside the question it holds, and works on the served repository or in a clone's checkout, writing nothing. console reads that answer, and the runs as status reads them, with no second reading of the record, and each entry point lays it out its own way (USAGE.md, console): on the web a page of its own in Fossil's skin, with no list of tools, no answer around it (ok, tool, log), no form of its own and no dry run, one small link leading to the list: what waits on you as cards, each with who asked it and when in words, what it changes, what to read first as links with their excerpts folded, a button per option, the recommended marked, and a field for your own words; what you may confirm, with its decisions; every other open question on a line; the runs going and ended lately as a table. A button posts to post, as before, and the page comes back to console by a redirect, which a reload does not press again, saying the decision is written; a press refused shows console's page saying why. At the command line, and in MCP's text, the same sections with no answer around them: each question a short card, its options numbered with the exact post command, links as title and URL, each excerpt's lines quoted, the runs in aligned columns; --json gives the answer, its result the view's data, as for every tool. The default page stays the list, the maintainer not having decided; its header links console, which it draws no card of. console's page and text are laid out for a person and are no interface of data; questions' answer is (interfaces). What the reviews of decision 98's console fixed stands: no hash in its text but in links and commands, every link's target escaped, references only the links post writes, each record's words on one line, a press's input as post reads it over MCP, and decision for what answers a question (decision 99).
  101. console's layouts are chosen by the tool's name, and say each thing once; this amends decision 100 in its details, under the maintainer's words there, 2026-10-22: "my expectation was the console was fully optimized for the presentation given (cgi, cli or whatever) ... it should be clean and focused on how the data is consumed." A fresh reader and an adversarial review of decision 100's console found it fell back to the tool's page or the generic text, envelope and log, for input it did not take and for a dry run; told an MCP client to run shell commands; and said several things twice. So: a refusal of console, and a dry run, which shows nothing as console changes nothing, are one line on its page, and in its text one line, then next: where the tool says what to do instead; a dry run's line says it was one and, where console would stop without it, why, so it never reads as a refusal. Each question's call that answers it is said once, answer with: the post command at the command line, or over MCP ocx_post and its input as JSON, the call the client makes, the field to fill in as TEXT, its options numbered beneath, then or your own words as TEXT. What to read first is its references alone, as where it is says what it replies to; questions keeps replies_to. A section holding nothing has no heading, and one closing line names each such section. With no node known here, as for a program not released into a release root, the runs are one line, "Runs: none known here", and node is absent from the JSON, as status says why. The page is its title and one <html> block, which Fossil's Markdown keeps whole where it put any other HTML of a line's own inside a paragraph, <p><style> and <p><div> among it (block_tags in Fossil's src/markdown.c), its links under the repository's root as FOSSIL_URI names it (USAGE.md, console).
  102. ends is the maintainer's word for the process key of decision 87, with which a session is also given each ended that counts written where it is. The maintainer, 2026-10-22, decided the question asking whether to approve ends as the session key that also gives a session the ended of a build it sent (d6c96afe8e): "Approve ends" (a956e1dc15). It no longer awaits approval: GLOSSARY.md and protocol v1, section 8 drop its NEW TERM.
  103. The directory step's key remove is remove_on, its one value still saved, and saved and watcher no longer await approval. The maintainer, 2026-10-22, answered the question asking whether to approve saved for a run's work the server holds, as in remove: saved (1393389315): "there’s a missing implied modifier. Maybe “remove_on”: “saved”" (a42311d510); and decided the question asking whether to approve watcher for the process the node starts for one run (fa86f7cbb8): "Approve watcher" (7eefceaf57). The key takes their suggestion, and saved stays its value. Runs and patterns written before it may hold remove, so wherever a run is read, remove is read as remove_on where that is not given, and a step giving both is checked by each name, so, saved being the one value, the two say one thing; OCX writes only remove_on, in its examples and its specs' runs. create writes a pattern's fields as they are given, and neither pattern page on the server, run/claude nor run/codex, holds the key. Within version 1 this is an addition (protocol v1, section 15): remove keeps its meaning, and a run holding it asks what it did. GLOSSARY.md drops the NEW TERM of both words, and docs/backlog.md's row for saved keeps only the run/claude page's edit, which a user holding admin or setup saves.
  104. A run from a user holding R may bind the home files its node's owner lists in r-senders.json, under sandbox, as homes, each as written, and each user create makes for a run holds R beside gio24. This amends decision 75, under which such a run bound no home file, and decision 52, whose made user held gio24 alone. The maintainer, 2026-10-22: "Where am I at to enable these claude agents to generate codex runs (tickets or forum posts) to perform coding tasks that they can then review and merge? I hope that will start reducing my claude token usage." Asked how a Codex run sent by a user holding R signs in, they chose "Host's Codex home": the owner's file may list /root/.codex, which such a run binds read-write, as any run binds a home (protocol v1, section 6), so it has the host's Codex login, which it can read, use and change. A home the file does not list is refused, the note naming the home file; a path holding .., a home the file lists among them, is refused first, as every path of such a run is, the note naming the path and that it has .. in it (section 11, row 1). Of what a person would have to do for it, the maintainer said, the same day: "making things per-project that should be per-server is messed up. giving a large number of different intermediate steps to accomplish one goal is annoying. Let me know when one click on accept is going to give me the functionality I'm seeking"; "one link per ask isn't the ask"; "the contract on this box is as long as the development doesn't disrupt the server, let the agents go a bit nuts. I try to provide a bit of ratcheting on quality and guidance on design, but if I can't [get] what I'm asking for, the design is wrong"; and, of a different mechanism proposed in its place, "don't change the design... make the design work." So decision 75's design stays, with no new key or word: a session an admin sent, whose user create made, holds R, and may send runs from its clone (decision 81), such as a Codex run, within what each node's owner allows, writing only in its own workspace, at a path ending in $$run$$, sharing what it leaves through process.shares; a session whose process.ends is true is given that run's ended (decision 102). R alone still allows nothing: a node with no r-senders.json takes no run from a user holding it alone, such a run names no secret and takes no password step, and a caller holding it alone has no user made for a run (decision 81).
  105. A node reads r-senders.json from the release it runs, $$release$$, and no longer from its node directory: the file is in the tree, stage copies it into each release beside the programs and lists it in the release's inventory, and accept carries it with the rest, so the bounds within which a node takes a run from a user holding R alone arrive with a release, and a release without the file takes no such run. This amends decisions 75 and 104, under which the node read the file from its node directory. The maintainer asked, 2026-10-22, "Why not move r-senders.json to the release directory?", and to "Ship r-senders.json in the release, replacing the node-directory file?" answered "Yes, release only". Accepting a release is already the host owner's decision, so the bounds arrive with accept, versioned, reviewed and gated like code; an agent's sandbox cannot reach the server's release root, so a file in the node directory would be a step for the maintainer; the cost, which the maintainer accepted, is that every host accepting the same release gets the same bounds. The tree's file holds the least a Codex coding task, sent by a session from its clone, needs: the network; to read /usr, where /usr/local/bin/codex is, /bin, /sbin, /lib and /lib64, and the files name lookup and TLS read (/etc/resolv.conf, /etc/hosts, /etc/nsswitch.conf, /etc/ssl/certs); the home /root/.codex (decision 104); no capability; and caps of a wall of 4 hours, an hour of CPU for each process and 1024 processes, so such a run states each, within its cap. A run from a user holding R alone that asks more, such as a build reading /etc/group, is refused, the note naming what it asked (section 11, row 1). Found on the way: the hook's round, and poll, run and status, took the node's directory for the release root, so the $$release$$ they read named a directory under it that does not exist; each now takes the release root, as the watcher it hands a run to does, and reads the release that watcher runs.
  106. On a terminal, the node types a line, its Enter and an end of input each alone: once the program has read all typed before, or 10 seconds have passed, as when it reads none of it. This amends decision 82, under which a line and its Enter were typed in one write. Claude Code reads keys as they come, and takes a line and its Enter that arrive in one read as text pasted, submitting nothing: the maintainer, 2026-10-21, asked "Why didn't run-af0dbe70 see my post to the forum?", whose post, 1,167 bytes, sat in its input box until they pressed Enter, which "got it going", and suggested "just add some n after the JSON sent". Tried on this host with Claude Code 2.1.280, a line and a carriage return in one write was never submitted, short or long, nor with bracketed paste; the carriage return written on its own once the program had read the line was, short and long. The node asks the terminal's far end, opened through its near end, whether the program has read what was typed, and each of its reads wakes the wait, so nothing waits on time but the bound (docs/linux.md); a program reading lines, its terminal canonical, is never waited on, as Linux ends each line at Enter itself. So a session's deliveries, and the ended a session whose process.ends is true is given (decision 75), reach Claude Code as submitted lines, with no key pressed by a person. The maintainer, 2026-10-22: "Let me know when one click on accept is going to give me the functionality I'm seeking", and "don't change the design... make the design work."
  107. A run from a user holding R alone must have a sandbox whose ends_with_node is true, and may share a file only under a name holding $$run$$; a run an admin or setup sent is unchanged. This amends decision 75, whose bounds said neither. An adversarial review of the check-ins of decisions 104 to 106 found two ways such a run went past its bounds, each confirmed. First, it could escape its wall and its stop by setting ends_with_node to false: the node then leaves out bubblewrap's --die-with-parent, and the wall's timeout and a stop end only bubblewrap's outer process, so what runs inside runs on (the review's run with a 3 s wall ran 41 s), the wall r-senders.json caps bounded nothing, and such runs could pile up on the server. Tried again on this host, a run whose program left a child behind, under a 3 s wall, ended after 3.0 s with ends_with_node true and after 20.0 s, its child's whole sleep, with it false. Second, it could replace any unversioned file of the served repository: the node shares each file under the name the run gives it (fossil uv add FILE --as NAME, src/ocx/node/sharing.cr), and the bounds checked only that a share's path held no ... As a directory step's path must end in $$run$$, since that step empties its path (decision 75), a share's name must hold $$run$$, the run's own hash, which no other file's name holds, read as the node fills it: $$user$$run$$, whose text holds $$run$$ but which fills the user's name and no hash, is refused. Each is refused with its note naming it (protocol v1, section 11, row 1). The maintainer, 2026-10-22: "the contract on this box is as long as the development doesn't disrupt the server, let the agents go a bit nuts." Runs that outlive their wall and their stop, or that replace the server's files, would disrupt the server, so the bounds within which agents may go a bit nuts must hold. No new key or word: the tree's r-senders.json is unchanged, and the run/codex and run/claude patterns already end with the node and share nothing.
  108. A node takes one run from a user holding R alone at a time: while one is in its care, another such run waits, unstarted, with the note "a run from a user holding R alone is in this node's care, which takes one such run at a time", whatever its process.user, and starts once the first ends; and the tree's r-senders.json caps files at 256M, so such a run states a files limit within it, as it states its wall, CPU and processes. A run an admin or setup sent is unchanged: no such run holds it back, and it holds none back. This amends decision 75, whose bounds limited neither, and the file decision 105 shipped. An adversarial review of the check-ins of decisions 104 to 107 found four gaps, each confirmed: two in the Codex task's pattern and the session's steps that send it, which are data outside the tree, and two in OCX. First, nothing limited how many runs from users holding R alone ran at once: a run waits only for another run of its own process.user, which its sender picks, so a sender naming a new user for each run could start any number; a node cannot cap memory (row 8 refuses memory), and each sandbox's /tmp is a tmpfs of no set size, so such runs together could exhaust the server's memory. Second, nothing limited the size of a file such a run shares, and each share lands in every clone that fetches it. The maintainer, 2026-10-22: "the contract on this box is as long as the development doesn't disrupt the server, let the agents go a bit nuts", and "don't change the design... make the design work." Runs that together exhaust the server's memory, or shares that fill every clone, would disrupt the server, so the bounds within which agents may go a bit nuts must hold. The watcher of such a run takes the node's one lock of such runs, r-alone.lock in its node directory, beside its user's, under the node's lock as it decides the run, and the decision reads whether another watcher holds it, so the decision and the lock agree under one hold of the node's lock and nothing depends on timing; a round and status read the same lock. Once the run ends, its watcher hands on the first run waiting for its user and the first such run waiting, each to a watcher of its own (protocol v1, section 10). The files cap is enforced as every files limit is, by the kernel, ending a process that writes a larger file budget (section 7); a run leaving it out, or setting it above the cap, is refused, naming it (section 11, row 1). No new key or word.
  109. An admin gives a user R, or takes it away, with send_runs (NEW TERM, needs approval), at every entry point: given a user of the served repository, it adds R to the letters that user holds of its own, every other as it was, or with off true takes R away, through Fossil's own user capabilities command, and answers what the user may do then, as receipt reads it, inherited capabilities included. The maintainer, 2026-10-22 and 23: "the Fossil UI really fights the "R"." and "i can only add R from the cli. i need a helper to add it from here.", here being OCX's web page on the served repository; with "Let me know when one click on accept is going to give me the functionality I'm seeking", "one link per ask isn't the ask", and the box contract, "as long as the development doesn't disrupt the server, let the agents go a bit nuts". Each user create makes for a run holds R since decision 104, and approve may give it to a login it makes; any other user, those made before among them, had it only from an admin at the server's command line, since Fossil's own user page may drop a letter Fossil does not define when that user is saved there. On the OCX page a link send_runs?user=NAME shows its dry run, its form holding the user, and one press, a POST whose Referer is this site's page, adds R, as approve and accept take a change (decision 73); at the command line ocx2 send_runs NAME [--off]; over MCP ocx_send_runs, needing admin there too, which no run's user holds. It refuses, naming why, a user the repository does not have; Fossil's own users nobody, anonymous, reader and developer, whose permissions OCX never changes (decision 76: "please don't go changing default account permissions"); and a caller without admin, which setup holds. A user holding admin or setup may be given R, which changes nothing it may do, and the answer says so; where R is taken away but one of Fossil's own users still lends it, the answer says that too. A user holding R and neither admin nor setup still sends only what each node's owner allows in r-senders.json, one such run at a time on each node (decisions 75 and 105 to 108).
  110. A report the node stores on the server's repository whose index or tag then meets a writer outside OCX is written: the node goes on in the order of protocol v1, section 10, kept logs, shares, verdict and ended, each written once, and the next post written there indexes it, or leaves it so again while such a writer holds the repository, as a write that meets the writer at an earlier post's index goes on; the node's deliberate check decides nothing until it has indexed each, then tags what lacks its tag (failure case 35). The node starts no backoffice after an ended left so, as decision 87 has it do after each, since its hook would list it before it can be read. A write that stores nothing still fails, and a started left so fails its watcher, which launches nothing, since others must see it before its process launches. This amends decisions 95 and 96, under which such a write was a failure of the watcher's own. The live test on the server, 2026-10-23 at 00:32 UTC, release 3869da228e7d: a Codex task, run eabbfe740e in thread 682044729e, changed docs/backlog.md as asked and exited 0 in about 20 s; the watcher stored its kept logs, post da84f83b4d, but their index failed, "database is locked", as a writer outside OCX held the repository, and the watcher ended the run lost, ended f58c4d8268, before sharing its patch. Tried on this host, such a writer, an open BEGIN IMMEDIATE on the repository, fails Fossil's put and its index each at once, so nothing OCX puts is stored while it holds, and nothing here waits for it to let go (docs/fossil.md, "Forum posts"). The maintainer, 2026-10-22: "Let me know when one click on accept is going to give me the functionality I'm seeking", and "the contract on this box is as long as the development doesn't disrupt the server, let the agents go a bit nuts." A run that did its work and ended lost for another program's write gives the maintainer nothing of it, so it ends as it ran, with its share and its ended. A close of the temporary checkout a write on the served repository opens fails nothing either when it meets such a writer, as it leaves only Fossil's note of a checkout removed all the same.
  111. The CGI takes a change only from a POST whose Referer is OCX's own page: Fossil's base URL, then /ext/ocx2-cgi or /ext/ocx2-cgi-next, then nothing, / or ?; and send_runs adds R, or takes it away, in one SQL statement. This amends decisions 30 and 35, under which any page under Fossil's base URL was the site's own, and decision 109, under which send_runs read the user's letters and then set them whole through Fossil's user capabilities command. An adversarial review of 86190c5301, which added send_runs, found two defects, each shown by a run. First, a user holding check-in may commit an HTML page and its script, which Fossil serves raw under /doc as this site's own, and Fossil's script-src 'self' runs that script, so the page could post to send_runs, approve, accept or rollback as whoever opened it, its Referer under the base: through a real fossil server, a POST whose Referer was /doc/tip/evil.html gave a user R with an admin's login. Fossil's own user page refused the same POST, lacking its CSRF token, which Fossil checks beside the Referer and does not pass an ext program. Only the server's admin puts a program in the extension root, and Fossil answers every page under /ext/ocx2-cgi/ with that program, so such a Referer is OCX's own page and no page a user wrote; every tool that may change something is refused from a page under /doc, /wiki, /forumpost or another program under /ext. Second, send_runs read the letters and wrote the whole string back in two fossil processes, so a letter another writer gave the user between them was lost: eik, written between, became eiR. The maintainer, 2026-10-18: "please don't go down any path with a possible race condition". One statement now adds R where the user's own letters lack it, or takes every R out where they hold one, leaving every other letter as it is when the statement runs, and writes nothing where the letters already say what is asked; the answer reads the capabilities after it. No new word, and no protocol change.
  112. Each write OCX makes on a repository is one Fossil command, which stores all it writes or nothing: a post or a ticket change, with the control artifact tagging it ocx-message when it holds a message, written by OCX into a bundle file and stored by fossil bundle import BUNDLE --publish --force --user USER, which stores, indexes and tags them together; a control artifact of several tags likewise; a share (uv add), a verdict and a single tag (tag add), each one command already. A command that meets a writer outside OCX holding the repository, which Fossil fails at once, storing nothing, runs again once Fossil's sql shell has waited for that writer to let go, as SQLite waits on a lock, up to 15 s each time, four runs at most, then fails as before, a failure of the node's own (failure case 35; protocol v1, Fossil, section 5). This amends decision 110: no report is stored any more without its index and its tag, so none is taken as written while unindexed, Fossil's backoffice follows every ended the node writes on the server's repository again, as decision 87 has it, and the deliberate check's mend indexes only what an older release left. The live test on the server, 2026-10-23 at 00:32 UTC, release 3869da228e7d: a Codex task, run eabbfe740e in thread 682044729e, exited 0, and its watcher's kept-log post was stored, but its index met "database is locked" from a writer outside OCX, and the watcher ended the run lost before sharing its patch. Decision 110, of d9dd05e7dd, kept such a report as written and went on; that check-in's review by Gemma held that Fossil's backoffice would list an ended left so before it could be read, so none was started after one. An adversarial review of d9dd05e7dd found that this made things worse, each shown by a spec on a scratch repository: a stop after an ended left unindexed got a second ended, since rounds find runs and endeds by their tags and the next post indexed the leftover but tagged nothing, and a stop written outside OCX got lost written again; the hook listed an ended while it was unindexed, so no round read it, and Fossil's backoffice runs after any page that answers 200, so starting none did not help; a write that stored nothing, a put, a share, a verdict's tag or a started, still ended the run lost or left it unanswered, and the user's waiting runs were not handed on; a round whose own tag, reply or answer met the writer raised, so the hook handed no run; and a control artifact of tags left unindexed was never indexed. It proposed the one command, which it tried, and this change tried again with the wait (docs/fossil.md, "Forum posts"): fossil bundle append names artifacts by SHA1 whatever the hash policy, so OCX writes the bundle itself, naming each as fossil hash-policy says; fossil test-xfer, the server's sync handler, whose transaction would wait on the lock itself, crashed. The maintainer, 2026-10-22: "Let me know when one click on accept is going to give me the functionality I'm seeking", and "the contract on this box is as long as the development doesn't disrupt the server, let the agents go a bit nuts"; and, 2026-10-18: "please don't go down any path with a possible race condition". Nothing here depends on timing: a write goes through whole or stores nothing, and what it waits for is the writer's lock, never a time, so only how long it takes depends on that writer. A ticket change's text reaches Fossil in a file now, not as one argument, so a kept log over 128 KiB on a ticket run is posted too; and the temporary checkout a post on a repository named outright needed is gone with the two commands that needed it. This change's own review by Gemma (gemma4:31b), four rounds, held no finding; two misreads, that the wait fails at once under the writer and that the check's mend runs outside it, made the code's comments say why SQLite waits on BEGIN IMMEDIATE and that every command runs through it.
  113. A write that meets a writer outside OCX holding the repository waits for it outside OCX's own lock beside the repository, which each run of the write holds alone, so OCX's pages read on while it waits; and it fails only once writers have held the repository on past three waits of 15 s each, as before, or have taken it first before each of eight runs after a wait that ended as one let go (failure case 35; protocol v1, Fossil, section 5). This amends decision 112, under which that lock was held across all of a write's runs and the waits between them, and four runs in all were the bound however each wait ended. An adversarial review of 3c4c65c090 found two things, each on a scratch server or repository. First, confirmed: a write waiting so held OCX's lock, so every OCX page stalled while a writer outside OCX held the repository, against that lock's own rule that it is never held while waiting on another: a /ext/ocx2-cgi/questions page took 9.13 s while one report waited on a writer holding the repository for 10 s, and 0.03 s otherwise. Second, plausible: the wait gives the lock back before the write runs again, and a writer starting in that moment, about 10 ms in the review's trial, used up one of the four runs, so a run was lost after 4.5 s of writes outside OCX, against the 45 s decision 112 stated. Fossil 2.27 has no write from its command line that waits for the lock and keeps it, so the second is narrowed, not removed: what a wait waits for is still the writer's lock, never a time, but decision 112's "nothing here depends on timing" does not hold for the moment after a wait, and a run is still lost if writers start in that moment before each of eight runs, another write's wait of OCX's among them, as it holds the lock while it takes it and gives it back, or hold the repository on past the bound. A wait that ended with the writer still holding is told from one that ended as it let go by what Fossil's sql shell says: Error: stepping, database is locked (5), the same at once under .timeout 0 as after 2 s under .timeout 2000 (docs/fossil.md, "Forum posts"). A write path that begins with BEGIN IMMEDIATE, such as the server node's reports received through Fossil's sync handler, as a remote node's are, is a question in docs/backlog.md, with what still waits behind such a writer: status, which reads under the node's lock that a report write holds across its waits, and the check's mend, whose command waits within OCX's lock beside the repository.
  114. OCX's wait for those outside OCX holding what a write writes to let go is BEGIN EXCLUSIVE; ROLLBACK;, which waits for readers as well as writers, and in a checkout it is made with no -R, so it waits on the checkout's own database, .fslckout, as well as on its repository (docs/fossil.md, "Forum posts"). This amends decision 113, whose wait, BEGIN IMMEDIATE; ROLLBACK; on the repository named by -R, waited for a writer on the repository alone; its bounds, three waits of 15 s or eight runs again, and what it says remains after a wait, stand, for readers as for writers. An adversarial review of 211a7d966b found two things, each confirmed on a scratch repository with Fossil 2.27. First: a reader outside OCX holding the repository, an open read transaction, with no writer anywhere, lost a report write after 9 runs and 135 s: each run's COMMIT waited Fossil's 15 s for the reader and failed, database is locked: {COMMIT}, but no reader holds back BEGIN IMMEDIATE, so each wait went through at once and counted as one that ended as a writer let go, until the eight runs again were used up. Second: a writer outside OCX holding a checkout's own database, as a person's own fossil command there does, lost an add OCX made there in 0.19 s with no wait, since the wait named the repository alone, which no one held. Under BEGIN EXCLUSIVE a reader holding on past the wait's 15 s ends it as a writer does, Error: stepping, database is locked (5), so it counts as a try: in the review's trials a reader holding 20 s let the write store after 19.69 s, and one holding 150 s failed it after 105.19 s and four runs; and an add in a checkout whose database a writer held 5 s waited 4.77 s and went through. The cost: while a reader holds on, the wait keeps new reads of the repository waiting until it ends, up to 15 s, as each write's COMMIT waiting for that reader already did. Fossil::Locked's message says writers or a reader outside OCX held the repository, and, for a command in a checkout, or the checkout's own database.
  115. In a checkout, OCX's wait for those outside OCX holding what a write writes to let go is two waits, one after the other: fossil sql --no-repository :memory: there, on the checkout's own database alone, then, once that has gone through, fossil sql -R REPOSITORY, on its repository alone, each BEGIN EXCLUSIVE; ROLLBACK; (docs/fossil.md, "Forum posts"). This amends decision 114, whose one wait in a checkout, fossil sql there with no -R, opened the repository as its main database, then the checkout's own, then Fossil's settings, and its BEGIN EXCLUSIVE took their locks in that order, keeping each it had while it waited for the next: with only the checkout's own database held, it took the repository's lock at once and kept it, at each of its waits, so new reads of the repository waited behind it with no one outside OCX holding the repository at all, beyond the cost decision 114 named. An adversarial review of 39c646f7ad found this, confirmed on a scratch repository with Fossil 2.27: during such a wait a read of the repository and the page /timeline took 14.36 s, 0.01 s beside a wait by -R; while OCX's add met .fslckout held 70 s, 15 of 50 pages took over 15 s, the longest 29.99 s; and a person's command in the checkout, holding .fslckout and reading the repository as fossil commit does, lost its write of the repository, database is locked (5). With the two waits, in the review's trials, each of those 50 pages answered within 0.01 s, the add failing past three waits after 45.1 s; the person's write was kept and the add went through after 2.77 s; and a commit in a checkout whose repository a reader held 20 s was stored after 19.69 s. The main database of the first wait is an empty one, since naming .fslckout there opens it a second time and the wait waits on itself. The cost: in a checkout one wait takes up to 30 s, 15 s on each database, so decision 113's bound, three waits of 15 s each, is there three waits of up to 30 s each; the eight runs again stand. Fossil's settings database is still opened after the one waited on, so a writer holding it holds a wait too, as before. The specs' stand-in fossil now answers that OCX waits only once the wait's process sleeps, as SQLite sleeps between its tries at a lock another holds, so the wait has taken each lock it takes before that one; spec/ocx/fossil_spec.cr shows a read answered and the person's write kept, and each failed on 39c646f7ad.
  116. OCX's wait for those outside OCX holding what a write writes to let go detaches Fossil's settings database before it waits: each wait, by -R or in a checkout by --no-repository :memory:, is fossil sql given .timeout 15000, DETACH configdb;, then BEGIN EXCLUSIVE; ROLLBACK;, so it waits on its one database alone (docs/fossil.md, "Forum posts"). This amends decision 115, which said only that a writer holding Fossil's settings, still opened after the database waited on, holds a wait too: the sql shell opens the settings (configdb, $HOME/.fossil) after that database, and BEGIN EXCLUSIVE locks them in that order, so while anyone held the settings, another of the account's fossil sql sessions, reading or writing, or fossil commit itself, which writes them inside its transaction, the wait by -R kept the repository from every reader, and the wait in a checkout kept .fslckout, for up to 15 s: the cost decision 115 had removed where the checkout's own database was held. An adversarial review of 4228a3de0b found this, confirmed on a scratch repository with Fossil 2.27: with a reader holding the settings 10 s, the wait by -R took 9.75 s, and a read of the repository, the page /timeline and fossil status each 9.15 s; as a report write met a writer outside OCX holding the repository 3 s, another session in OCX's home holding the settings 20 s, pages waited up to 11.95 s, 0.01 s with the settings detached; and, new in 4228a3de0b, as an add and commit in a checkout waited for a writer outside OCX holding its repository under OCX's home, a person's fossil changes there took up to 13.45 s, 0.01 s on 39c646f7ad and with the settings detached. The specs had hidden it: the stand-in fossil's holder kept its settings in a home of its own; it now shares OCX's, as the account's own commands do, and so spec/ocx/post_spec.cr's post in a clone that a writer holds at its write's first run failed on 4228a3de0b. With the settings detached, in the review's trials, the person's write was kept and the add went through after 2.76 s; a commit in a checkout whose repository a reader held 20 s was stored after 19.7 s; and while an add met .fslckout held 70 s, each of 50 pages answered within 0.01 s, the add failing past three waits after 45.1 s. DETACH configdb; goes through under a home holding no settings yet, which the shell opens all the same. The cost: no wait of OCX's waits for a holder of the settings alone, which meets only a command writing them, such as fossil commit, whose own wait in Fossil, up to 15 s, waits for it (5.05 s for a writer holding them 5 s), so a run failing so counts as a run again, not a try: one held on past each run fails after about nine runs of 15 s, 135 s, where before it failed after about 105 s, four runs and three waits; nothing fails sooner. spec/ocx/writers_spec.cr shows a read of the repository answered within a second while a report's wait meets a writer holding the settings alone, which failed on 4228a3de0b.
  117. Every fossil command reads Fossil's settings as it starts, OCX's waits too, before their DETACH configdb;, so one keeping new readers of the settings out holds each of them, and a holder of the settings alone does not meet only a command writing them, as decision 116's cost said (docs/fossil.md, "Forum posts"). This corrects decision 116, whose wait stands. An adversarial review of aa650c80c4 found this, confirmed on a scratch repository with Fossil 2.27: with a reader holding the settings and a writer of them waiting at its COMMIT, as fossil commit waits when another of the account's commands reads them, which keeps every new reader out, fossil add, fossil bundle import and a wait by -R each failed after 30.03 s, database is locked at SELECT value FROM global_config WHERE name=$n, Fossil waiting 15 s there and 15 s at SELECT fts5(?1) before it. A wait failing so counts as a try, so the bound decisions 113 to 116 state, three waits of 15 s, or in a checkout of up to 30 s, then the command failing, does not hold there: each wait, and each run, may take twice 15 s more as it starts; on aa650c80c4 OCX's tag on a repository whose settings were so held 240 s failed after 210.22 s, four runs and three waits of about 30 s each. A writer holding the settings alone, not at its COMMIT, still meets only a command writing them, as decision 116 tried. The review also found a clone of a repository file that met a writer of the settings leaving its file, so its run again failed, file already exists. OCX clones only by URL (Workspaces.remote!, and poll's server, which it signs in to over HTTP), and Fossil takes away the file a clone by URL made as it fails so: on aa650c80c4 OCX's clone by URL met a writer holding the settings 20 s and cloned after 20.1 s, and one holding them 70 s failed it after 60.14 s, leaving no file, each wait between its runs finding no repository and counting as a try; so nothing in OCX changes for it. While OCX's commit runs again against a reader of the settings, the account's other fossil commands wait at their start, a question in docs/backlog.md. The comments of Fossil::LET_GO, Fossil::TRIES, Place#hosted and Place#clone say so.
  118. A run's ended is written only once its program and everything it left have ended: what an unconfined program left in a process group of its own, in its session, and what runs in a sandbox whose ends_with_node is false, end at the program's own end, at a stop and past the wall. This amends decisions 80 and 97, and the reason decision 107 gave for its rule. An adversarial review of 22d4decadd, decision 97's check-in, found two defects, each confirmed. First, a child that puts itself in a process group of its own, as a shell's job control does for each job (set -m, or any shell at a terminal), and as setpgid, Python's os.setpgrp and GNU timeout do, leaves the program's group but stays in its session, and the watcher ended only the program's group: such a child outlived the program's end, a stop and the wall, plain and at a terminal (set -m; sleep 17 & exit 3 at a terminal, with a wall of 6s, ended exited 3 at 17.2 s, and with sleep infinity would never have ended), and with its output sent elsewhere the run's ended was written at 0.2 s while it ran, so the user's next run could reset the password under it (case 26). Second, older: a sandbox whose ends_with_node is false ran on past a stop and its wall when not at a terminal, as a stop sends TERM to bubblewrap's outer process alone and timeout KILLs that alone, and without --die-with-parent the sandbox's init and its program run on: sleep 17, stopped at 1.8 s, ended stopped at 17.3 s, and under a wall of 3s budget at 17.3 s. Now, once the program has ended, the watcher, Linux's subreaper of what it left, ends with KILL each process group of the program's session that one of its own children not yet waited for is in, so never one that took its number, and each of its children that is a sandbox's init, which bubblewrap leaves as it ends, and with that init, as the kernel ends a pid namespace with it, every process of the sandbox; a stop sends TERM to the program and to each such group. It then waits for each by its pid, which KILL ends, and ends and waits for what each left as it ended, until nothing is left, before it says how the program ended, so ended comes only after and nothing waits on time: decision 80's wait for a sandbox's init, made for a run whose workspace is removed, is this wait, for every run. It waits by pid rather than on the CHLD trap, which is the whole process's, so a spec watching two runs in one process resets it, and it then forgets the program, so no later signal reaches a group or session that took its number. The node's check, finding a watcher gone, ends every process group of the session the program leads, when it started when the run's lock says. ends_with_node false now says only that the sandbox runs on when the node's process that watches the run ends, as when that process is killed: a stop, the wall and the program's own end end it either way, and a run from a user holding R alone must still have it true (decision 107), since what runs inside would otherwise outlive that process. What remains: a process that starts a session of its own, as a daemon does with setsid, leaves the program's session, and only a sandbox holds it (failures, case 34 and case 36; protocol v1, sections 6, 7 and 10). The maintainer, 2026-10-22: "the contract on this box is as long as the development doesn't disrupt the server, let the agents go a bit nuts"; a run whose program left a process running for ever holds its run's and its user's locks for ever, and a password reset under a running program breaks its work, so the promise must hold. The backlog's item on a stop that may not end a sandbox that does not end with the node is done.
  119. The node's check, finding a watcher gone, ends the process group the program it launched leads in the watcher's session, as GNU timeout leads one, when that program started when the run's lock says, besides the watcher's group and every group of a session the program leads (protocol v1, Fossil, section 5). This amends decision 118, whose check ended a program's groups only when it led its session: GNU coreutils timeout, the server host's, calls setpgid(0, 0), so a walled run in a sandbox, started as timeout -s KILL N bwrap ... in the watcher's session, leads its own group and not its session, and once its watcher had gone the check ended nothing and answered the run lost while timeout, bubblewrap and the sandbox ran on to the wall, so the user's next run, or another run from a user holding R alone, could start beside it. An adversarial review of aeabbd54e0 found this, confirmed with a stand-in built from GNU timeout's handling of processes: 1 s after the check, timeout and the sandbox's program still ran on aeabbd54e0, and were gone with this change; under BusyBox's timeout, which becomes bubblewrap in the watcher's group, bubblewrap, whose parent the watcher was, ends with it by its --die-with-parent, and its sandbox with it, either way. A review of 31c712a101 found that check-in's comments and docs/fossil.md blamed a slow start of a command, or of OCX's wait, on Fossil's settings alone: every fossil command reads the repository and a checkout's own database as it starts too, so one keeping new readers out of either, as a writer does at its COMMIT while a reader holds it, holds each command there, Fossil waiting 15 s at each of two reads of the repository or the settings, three of a checkout's own database. Tried with Fossil 2.27, 2026-10-23: with the repository so held, a wait by -R, fossil info, and in its checkout fossil add, fossil changes and the wait there each failed after 30.03 s, their last line not a valid repository; with a checkout's own database, its fossil add, fossil changes and wait each after 45.08 s, database is locked at an ATTACH, a wait by -R going through at once; with the settings, each of them after 30.04 s, in the checkout too. Nothing in OCX's code changes for it: a run failing so prints database is locked before that last line, which Place#hosted reads as a holder met, and a wait failing so counts as a try, as decision 117 has it. The comments of Fossil::LET_GO, Fossil::TRIES and Place#hosted, and docs/fossil.md, say so.
  120. An import never writes git's stream to a file, and from the web page it goes on detached, its outcome logged where status shows it. The maintainer, 2026-10-21: "My import of https://github.com/beagleboard/beagleboard-org keeps failing. The post times out, so I don't know what happened."; the tool's purpose, slice 39's (docs/slices-git.md), in the maintainer's words: "comfort that i can import projects and reliably serve development back to git with ocx2". Reproduced on the served host, the same code at the command line: the upstream, 3.0 GB packed on GitHub with 527,641 commits, stopped after 10 minutes at import_branches, "git in the import's sandbox stopped by its files limit (SIGXFSZ)", since git's fast-export stream was written whole to a file in the import's directory before Fossil read it, and passed the 8 GB a task's file could be; and the page ran the whole import inside its request, so the browser gave up long before, and the person never learned the outcome. Now git's fast-export writes into a pipe whose other end is fossil import --git's standard input, each in a sandbox of its own, as each was, reached through OCX::Git and OCX::Fossil::Place (precept I7), so no stream is kept and the largest file a step writes is the new repository itself; the pipe is unnamed, each end closed once its step has started, so either one's end ends the other at once, where a named pipe in the directory both sandboxes bind would have held git until its wall had fossil failed before opening it. A failure of either says both, git's first. Fossil's final VACUUM is left out (--no-vacuum): it copies the whole repository through a temporary file in the sandbox's /tmp, held in memory, for no gain on a repository the import wrote once (docs/fossil-git.md). Each step's limits are bounds against a runaway that a few-GB upstream fits: 48 hours of wall clock and as much CPU time, about three and a half times the slowest step's time measured, and for its largest file 24 GB, about twice the largest measured, or a third of the space free where it works as it starts, when that is less (ImportCommand.limits, Tasks.free), so what a step writes at once, the new repository and its journal, or the upstream's clone, leaves a third of it free. A limit of 64 GB on each file alone, as first built, let the new repository, its journal and the clone together pass the 96 GB the proof's disk had free. The proof, beagleboard-org imported from a scratch web page on the served host, niced, by this change's program, took 14 h 13 min in all and made its 527,641 check-ins, the export's branches the upstream's own commits: the clone 7 minutes, its pack 3.3 GB; git's fast-export 10 h 5 min, 11 minutes of CPU, a 50.6 GB stream; Fossil's import 14 h 4 min with 13 h 17 min of CPU, a 10.6 GB repository, its rebuild's journal reaching 2.5 GB and its temporary tables 1.1 GB in the sandbox's /tmp, since fossil import writes each check-in's whole manifest, 10,779 files for one of the upstream's 453,383 commits of November 2018 (docs/fossil-git.md); then two minutes to seed, place and join it. The first proof found the seeding of the git export's state slow and fragile: a transaction for each check-in, 399,783 of them took 33 minutes, and a reader of its database between two, the agent counting them, failed the import, database is locked, which it logged, leaving nothing; the state is now written in one transaction that waits on a reader, all 527,641 in 9 s. From the web page, after the same checks, the import starts detached, as the hook starts a watcher: the release's program, ocx2 --import-detached WORK, started by setsid, WORK the directory taken for it, whose job.json, its owner's alone, holds the served repository, the user and the input, never on its command line, its output in the release root's node directory, and the page answers at once with its process and where it writes; once it ends it appends its outcome, one JSON line in the form of the hook's log's (when, repository, name, took_ms, ok, stopped), to node/import.log beside hook.log, then releases its directory, the job with it, however it stopped, refused before it began too; and status shows setup the imports running, read from /proc, and the last outcome. A TERM, as when a person ends the process status lists or its host goes down, ends the processes the import runs, so the step running fails, its sandbox ending with it, under GNU timeout too, whose sandbox would have run on to its wall had the import alone ended, and its log begins no step more (Log#stop), so it stops as at any failure, discarding what it made, and logs its outcome. An import that KILL ends, as the out-of-memory killer does, runs nothing more, leaving its directory naming a process that has ended, mostly before it logged an outcome: status lists such a directory as left, and the next import of its name clears it. The command line answers once done, as before. Two imports of one name never meet: the directory an import works in, .NAME.import beside the served repository, is taken by an exclusive create holding the imports' lock, an flock on the served repository's directory itself, which only taking, releasing and reading those directories hold, never a fossil command, so taking one never waits on a fossil command, as it did on the new repository's turn, which fossil git export held for two minutes in the proof, and no lock file is left beside a repository an import failed to make; and it names the process importing with its start, as a run's lock names its program (decision 97), so a process that takes its number later is not read as it; a second import is refused while the process named runs, and once the repository or its export is there, and what an import that ended left, as when its host went down, is cleared. The page takes the directory for the import it starts before it starts it, so the refusal is the page's answer. Fossil's import takes the new repository's own turn, named as a clone names it, never that of a checkout its directory may be in. An adversarial review of the change, before it landed, found these defects, each fixed with it: a detached import TERM or KILL ended logged no outcome, and status then showed nothing of it; the 64 GB limit on each file; the page waited with no limit on the new repository's turn, and a failed import left that lock's file; a detached import refused at its first checks left its directory, its job in it; Fossil's import took its turn from a checkout above its directory, failing at one that did not read; and the documents gave the detached import's argument as its job.
  121. A watcher that decides its run waits hands that run on itself, once it has let go of its locks, when the run is then to start, as it hands on the next run of its user and, after a run from a user holding R alone, the next such run: its following reads its own run as well. This amends decision 108, under which only the watcher of the run waited for handed such a run on. An adversarial review of 3869da228e, decision 108's check-in, found a run left waiting, shown by a spec: two runs from a user holding R alone, of different process.user, handed to watchers together; the first's watcher takes the node's lock of such runs, and the second's decides its run waits, then, still holding that run's lock, takes the node's lock again to write its answer, which Crystal's blocking flock takes by trying again every tenth of a second, with no queue, so the first watcher, its run ending meanwhile, can take it first. Its following then read the second run in the node's care and handed on nothing, and the second's own following left its own run out, so the run waited, unstarted, until the node's deliberate check, since a round decides only the runs that arrived (decision 39). Each following runs under the node's lock once its own watcher has let go of its locks, so of the two, the second reads both let go, and hands the run on when it is to start; both may hand it on, and a watcher that finds another holding its run, or the run ended, writes nothing.
  122. A watcher turned away at its user's lock, as another run of that user is in the node's care, lets go of its run's lock in the same hold of the node's lock in which it found that user's lock held, rather than as its watch returns, so the run is not left waiting, unstarted, with no watcher to hand it on, as decision 121 has it for a run that waits for a run from a user holding R alone. An adversarial review of 488aecd89f, decision 121's check-in, found the gap: of two runs of one user, the second's watcher, turned away, let go of the node's lock but held its run's lock until its watch closed the file, and when the first run ended in that gap, its following read the second in the node's care and handed on nothing, while the watcher turned away returns with no following of its own; a spec holding that watcher there showed it, and a stress run left a run so in three rounds of twelve, and in none of twelve with the change. The run holding the user's lock lets go of it before its following reads the node's directory under the node's lock, so a take that fails comes before that reading, which then finds the run turned away free and hands it on when it is to start; a take after it takes the user's lock. Nothing is timed, and a watcher turned away still answers nothing.
  123. An import's step ends with the import, and is stopped once the space free where it works falls below two-thirds of what it was as the step started, and what an import KILL ended made outside the directory it worked in is discarded by the next import of its name, as that directory records it. This amends decision 120. An adversarial review of 905756db67, decision 120's check-in, found six defects, three serious, each shown by a run against a local upstream. First, a task's wall was timeout around bubblewrap, from outside, and GNU timeout, the server host's, leads a process group of its own and outlives its parent, so bubblewrap's --die-with-parent watched timeout, not the import: with a stand-in built from GNU timeout's handling of processes, 2 s after a detached import was sent KILL, timeout, both bubblewrap processes and git clone still ran, status showed the name left, and a second import of the name was accepted and ended ok while the first's git went on in the directory the second had cleared, to its 48-hour wall. Now a task's wall is kept inside its sandbox, timeout -s KILL SECS its command's start (Tasks.plan), so bubblewrap is the importer's own child and ends with it, the sandbox and its wall with it, whichever timeout the host has; a run's wall stays outside its sandbox, which the node's check ends as decision 119 has it. Second, a limit on each file a step writes, a third of the space free as it starts, bounded neither what a step writes in all nor imports running at once: on a 32 MiB file system, an upstream served by git's dumb HTTP, which fetches each object as a file of its own, four of 9 MiB each, left 0 KiB free, and four imports at once each failed with No space left on device. Now each task is watched while it runs (Tasks::Watch): the space free where it works is read every tenth of a second, a measure of it, not a cause, and once it has fallen below two-thirds of what it was as the task started, by the task's writes or any other's, the task is sent TERM and fails saying so, stopped by its space limit (SIGTERM); each file stays within a third as before. Third, the new repository was renamed into place before its export, public URL, patterns, hook and login group were set up, so an import KILL ended there left it served, half set up, and the next import of its name refused, is served already: now all but the login group are set up in the directory the import works in, and the repository is then placed by one rename, which the directory records first (placed), as it records the export once its directory is made (exported); the next import of the name, finding such a directory left, keeps it as it was left, outside the imports' lock discards what it records, the repository leaving the login group first, then clears it (discard_left). Fourth, two imports of one name from served repositories sharing the exports met there: one failed and its discard removed the other's export, which then reported ok with its export gone; now the export's directory is made by an exclusive create, and an import discards only the export its directory records. Fifth, a TERM during a fossil command run on the host logged fossil git:, which says nothing of it: a step that fails once the import is asked to stop now says why after what failed (Log#perform). Sixth, discarding the new repository left the lock beside it that its fossil commands took; it goes with the repository now. What remains: a step that writes more in a tenth of a second than the room it has left passes the two-thirds by that much before its TERM, as on a small file system written from the loopback; a KILL between the export's exclusive create and its record leaves the export unrecorded, and the next import of the name is refused, is there already, until a person removes it; and an import KILL ended after it joined the login group, before its outcome was logged, is discarded by the next import of its name like any other it left.
  124. The node's check, finding a watcher gone, ends what its run left once the process the watcher launched has gone too, and each sandbox a process of the run started in a session of its own; a run's lock names the host's boot, and a lock naming another boot names no process the check ends (protocol v1, Fossil, section 5; failures, cases 34 and 36). This amends decisions 118 and 119. An adversarial review of e06e736748, decision 119's check-in, found two older defects in the check, each shown by a stand-in watcher built from OCX's own code, killed once its program ran, at 30 places a run's processes are: under no timeout, GNU's or BusyBox's, at a terminal or not, unconfined or in a sandbox, in a session of its own or not, ending with the node or not. First, the check ended nothing once the process launched had gone, though its session or a group still held the run's processes: at a terminal always, since the watcher holds the terminal's near end, whose last close hangs the terminal up and ends the session's leader before any check, so a job in a group of its own ran on, and a sandbox that does not end with the node with it; not at a terminal, once an unconfined program had ended, its child and its job ran on, and once a sandbox's program had, its init and what it left, the init in the watcher's group or timeout's. Second, a sandbox in a session of its own that does not end with the node, not at a terminal, ran on past the check, and past its wall: bubblewrap's --new-session makes the sandbox's init lead a session of its own, in no group the check ended, so it ended bubblewrap's outer process alone, and GNU timeout with it, which kept the wall. Now the watcher names the host's boot, /proc/sys/kernel/random/boot_id, in the run's lock beside its own number, and the check ends nothing for a lock naming another boot, every process of which has ended. Once the process launched has gone, it ends, in the lock's boot, each process group numbered as that process or the watcher was, and each group of the session numbered as that process was, that holds a process started since it did, as the lock says it started: Linux gives no new process a number in use as a group's or a session's, so while a process the run left is in one, it is the run's (docs/linux.md). With every group it ends, the check first stops each process of it, so none waits for a child, which then keeps its number, then sends KILL to each child of them in a pid namespace below the host's, a sandbox's init, and with it, as the kernel ends a pid namespace with its init, every process of that sandbox, then KILL to the group. It never ends the group it is in itself, which a stop would hold for ever. A lock an older release wrote names no boot and is read as before. A sandbox that ends with the node, as every run from a user holding R alone has, left nothing either way. Tried 2026-10-23, the review's stand-in run against this tree: at the 30 places, 12 left processes past the check before the change and none after; with the program ended of itself before the check, not at a terminal, 9 of 15 before and 3 after, a process outside every run running on in each. What remains: those 3, a sandbox in a session of its own that does not end with the node whose bubblewrap outer process has ended before the check, at its program's end or past its wall, to whose init nothing the run's lock names leads any more; and one dependency on timing: once nothing the run left is in a group or a session numbered as the process launched or the watcher was, Linux may give that number to a new process, and the check would end a group that process leads, or its session's groups, holding a process started since the one launched. Within one boot that needs Linux to go round every number free first, up to 4,194,304 on the server's host; after a reboot the lock's boot no longer matches. Both are on the backlog.

  125. The maintainer, 2026-10-26 at the terminal, gave "go" on the page How a post reaches an agent. Also at the terminal on 2026-10-26: "A simple design is FAR more important to me than something that saves a few seconds in response." and "I'm happy to throw away code that we can do without." The rest of this entry is the agent's words, not maintainer quotations. The Fossil facts, daemon and removal were built in a4cceed157, ef83839803 and 8cac96ce10. Fossil stores arrivals without starting OCX work; the hook installed by OCX runs Fossil's own git export. One daemon per agent machine pulls whole clones and starts a watcher for each run it can act on; only the watcher holding that run's lock posts reports and launches its program. OCX's receipt page remains on the server, as do served writing tools whose removal is open in docs/backlog.md; the approved page's read-only server is not yet true of every tool. These parts of older decisions change, in the agent's words: decision 37 sent every node write from its clone; reports now go through Fossil's pages as the node user, with their senders still confirmed by the server's receipts. Decision 39 limited rounds to the hook's list, had tools act at once on local writes and left missed work to a separate check; now create and stop only write requests, the daemon pulls before deciding at start and after each chat answer, and after every complete pull it reads new messages and decides again each run it has not settled, so later pulls find missed work and runs left waiting. Decision 84 required node tagging and tag-based run reads in rounds, status and manual decisions; these now read message texts regardless of tags, and watcher reports have no tags, while tools still tag messages for questions. Decision 87 started backoffice after a server node's ended and excluded a clone's own node reports from deliveries; no node now starts backoffice, and each watcher reads confirmed ended reports after complete pulls, including its own node user's. Decision 103's approval of watcher and remove_on stands; its glossary's description of a watcher started by another watcher is replaced by a watcher started by the daemon or run. Decision 108's one-at-a-time limits stand, but an ending watcher no longer hands waiting user or R-alone runs to another watcher: the daemon's next complete pull finds their locks free and decides them again. Decision 121's waiting watcher no longer hands its own run on: it leaves no state, and the daemon decides that run again at later complete pulls. Decision 122's release of a busy user's run lock still holds; its need for another watcher to hand the run on goes, because the daemon decides the run again when a later pull finds its locks free. The implementing agent chose the following details, not further maintainer decisions: an ended the server's receipt page says the node user sent settles a run with no state file; for a run naming a clone, only an ended whose clone starts with the run's counts (protocol v1, section 3); daemon and watcher use the same decision table, with only the watcher posting; an unknown post is checked by sender, text and reply target at the next completed pull before retrying, and a failed daemon is restarted by the service manager after ten seconds, with no OCX retry timer; reports use Fossil's pages without tags; verdicts and shares alone use clone sync, with i and y required respectively; deliveries use server receipt order; each session answers its own held-back posts; run locks and state live under ROOT/node/runs, completion numbers under ROOT/node/completed/SERVER-CODE, and the daemon lock and log in ROOT/node. Protocol v1 defines the behavior and USAGE.md gives the commands.

  126. The maintainer, 2026-10-27: "we should be able to start the transition tomorrow afternoon." The rest of this entry is the agent's words, not maintainer quotations. The maintainer decided on 2026-10-27 and 28, restated here: the transition starts on the afternoon of Wednesday 2026-10-28 (EDT). The daemon runs on the server's host as a second install with a root of its own, never /var/lib/ocx/release2. The press makes one fixed run user per repository and kind of run, so no one sets passwords by hand. swk-ai's page may keep writing a run as the person typing, allowed for that app alone, with no code here. Simplicity matters far more than speed. OCX never writes as a person, swk-ai's page excepted. Fossil's default users nobody, anonymous, reader and developer are never changed. No node user or run user holds admin or setup. migrate moves run-work from the server's node to that daemon: it installs the export hook and sets Fossil's timeline chat user on every served repository; for each node an old hook named, it keeps the node's name, readies its letters and fixed NAME-KIND run users, and files their passwords privately with nodes.json and the service unit in the daemon's root. One such run runs at a time per repository and kind. It leaves other users alone unless named explicitly, preserves other hooks, lists changes in a dry run, and changes nothing on a second run with the same inputs. It supersedes the hand steps USAGE gave for moving from hook --login after decision 125; the migration instructions give the command. The reviewer links the implementing check-in after committing it.

  127. The maintainer, 2026-10-28, at the terminal, in order: "Revise the migration plan and transition runbook around this boundary: applications and agents act as Fossil peers. Only Fossil itself directly accesses the authoritative served repositories." "yes, a small, self-contained Crystal script in scripts directory that follows the rule of not updating without strict maintainer review/approval should be the approach." "Can you just call it scripts/start-ocx.cr?" From Slowpen's review, relayed by the maintainer: "Revise the migration proposal to avoid both a Fossil source patch and Admin credentials for agents or the daemon. Use a small, trusted, read-only receipt service written in Crystal." "Proceed with the simple design: the trusted Crystal receipt service may read the whole repository directory. Remove per-repository masking; that visibility is intentional. Agents, CGI and the daemon must not gain access to those database files or Admin credentials." "Keep the service read-only, without network access, and expose only the fixed receipt interface. Establish the daemon's actual confinement, not merely hidden paths." "Before switching the live service, demonstrate on a scratch deployment that receipts and the console work, writes to served repositories fail from the receipt service, and agents cannot open those repositories. Verify that agent sandboxes still start. Keep a clear rollback point." "Avoid new mechanisms for hypothetical corner cases. Report any failure of those core boundaries before proceeding." The rest of this entry is the agent's words, not maintainer quotations. The read-only receipt service in scripts/receipts.cr reads the served repositories and answers only its fixed receipt interface. The maintainer's launcher, scripts/start-ocx.cr, has three modes: CGI, receipt service and daemon. For receipt, the launcher sends the identity line and passes a connection to the confined CGI, which sends only the request and reads the answer; the CGI has no repository, fossil program or network. The daemon runs as its own OS account through the fixed launcher, in a mount namespace holding its root, homes and read-only system files, without served repositories; it retains the host's process namespace so watchers outlive a daemon restart. E6 is the temporary passthrough of other CGI tools to the release, until the CGI talks to the daemon; that boundary is not yet complete. USAGE.md gives migrate's explicit launcher and account arguments; docs/migrate.md holds preparation, scratch checks and rollback.

  128. The maintainer, 2026-10-29: "Don't tie into systemd... let me worry about how start-fossil gets invoked." "start-fossil should call start-ocx to start the ocx2 daemon and receipts daemon in their sandboxes." After a review that renamed them: "i like the name changes." "let's get this staged and moving!" The rest of this entry is the agent's words, not maintainer quotations. The reviewed scripts are renamed: start-fossil to serve, start-ocx to confine, receipts to receipt-reader, and servers.json to hosts.json. serve starts both daemons through confine, so migrate writes no systemd unit. The --start-ocx and --account options are removed. scripts/README.md describes the scripts; docs/migrate.md gives the admin's steps.

  129. The maintainer, 2026-10-29: "for the individual information cards, think about how to keep them data driven and pluggable, so they are easy to change over time". The card tools read JSON definitions from card/NAME wiki pages and answer them from indexed Fossil columns across named repositories. The shared examples give the same requests and parsed answers to Crystal and Stone's C bridge. Later amendment, the maintainer, 2026-10-31: "having specifically prepared files from ollama seems wrong. if any project can provide cards, that seems fine, but if ollama is special". Definitions now come from every named repository, and a card is identified by project and name.

  130. The maintainer, 2026-10-29: "should not we be done with migrate and remove it? agree we should turn import into project to give an on-boarding tool." The one-time move by migrate is done. project makes a new served repository from git or empty, then readies its agent users, daemon clone entry and ticket fields.

  131. The maintainer, 2026-10-31, asked how a session could stage, then chose the mechanical way: "yes, please." A session sends a stage run naming a pushed check-in; the node clones it anonymously, builds it and runs ocx2 stage with only the release root's staged/ writable. $$releases$$ names that root; r-senders.json lists the path an R-sent run may write besides its workspace. The maintainer still accepts from the page. The pattern gives the run and USAGE.md gives the command.