Login
ocx-node
Login

An OCX node in a container. It keeps a clone of each approved project, runs agent and CI requests sent to its node login, and replies where each run was sent. It holds no admin credentials. It uses the daemon and register tools.

Start

The container's hostname, aarch64-1, picks its entry in scripts/hosts.json. Its entry point is serve, which starts confine daemon as on the server, and a Fossil server on loopback that serves no repository, which keeps serve in the foreground.

docker compose build
docker compose up -d

Add a project

With the release's own ocx2, as USAGE.md says under "Run a node". Name the project once:

P=ollama

Ask the server for the node's login on it, and an admin of the repository opens the link this prints and presses once:

docker compose exec ocx-node /opt/ocx/current/ocx2 register \
  https://ollama.openbeagle.org/$P /opt/ocx/node/credentials/$P.json --login aarch64-1

The running daemon reads the project password directory each minute: once the login is approved, it collects the password, makes the clone and serves it. No restart. To watch it: docker compose logs -f ocx-node.

Let it run Claude sessions

A session's run user needs a login of its own, whose password the node keeps under runs in its file. Ask for it, and approve its link:

docker compose exec ocx-node /opt/ocx/current/ocx2 register \
  https://ollama.openbeagle.org/$P /opt/ocx/node/$P-session.json --login aarch64-1-$P-session --capabilities gio24R

Once both logins are approved, collect both passwords, then keep the run user's in the node's file:

docker compose exec ocx-node /opt/ocx/current/ocx2 register https://ollama.openbeagle.org/$P /opt/ocx/node/credentials/$P.json
docker compose exec ocx-node /opt/ocx/current/ocx2 register https://ollama.openbeagle.org/$P /opt/ocx/node/$P-session.json
docker compose exec -e P=$P ocx-node sh -c 'cd /opt/ocx/node/credentials && jq --slurpfile s "../$P-session.json" ".runs[\$s[0].user] = \$s[0].password" "$P.json" > n && mv n "$P.json" && chmod 600 "$P.json"'

Each run reads the file as it starts, so the next session sent with --user aarch64-1-$P-session runs. No restart.

Sign in the agents once

The container's user is uid 1000 and has /root as its home. Sign in through the container; the home volume keeps both sign-ins:

docker compose exec ocx-node codex login --device-auth
docker compose exec ocx-node claude

In claude, use /login. The existing session patterns and the Codex task pattern bind /root/.claude, /root/.claude.json, and /root/.codex as needed.

Send runs

Admins may send runs. Users holding R may send runs within r-senders.json: network, listed system paths and agent homes, no capabilities, at most 6 hours and 4096 processes. A request beyond those bounds is refused. An R run must end with the node, name no secret or password step, and use a workspace ending in $$run$$. The node takes one R run at a time. See USAGE.md for the full rules.

From a session's checkout, commit and push the check-in the next run should read. Send an agent run with an existing session or Codex task pattern:

ocx2 create "OBJECTIVE" --pattern URL --node NODE --to POST --of CHECKIN

URL is the published pattern's content-addressed raw URL. NODE is this container's login on that project, POST is a post in the session's thread, and CHECKIN is the pushed check-in. A Codex task needs nothing more, so any agent holding R may send one. A Claude session's pattern takes a password step, which an R run never may, so only an admin sends one, naming the run user made in "Let it run Claude sessions" with --user.

For CI, publish patterns/ci.md as a tech-note attachment and use its content-addressed raw URL:

ocx2 create "Check CHECKIN with CI" --pattern URL --node NODE --to POST --of CHECKIN

It runs nice -n 19 crystal run ci.cr from that check-in's files and keeps the last 4 KB of stdout and stderr in the reply, including output on success. Copy the pattern and change the command for another project's checks.

Host and release

The host needs unprivileged user namespaces for bubblewrap. On Debian, kernel.unprivileged_userns_clone=1; on recent Ubuntu, kernel.apparmor_restrict_unprivileged_userns=0. The compose file permits the needed namespaces and mounts with seccomp=unconfined, apparmor=unconfined and systempaths=unconfined; it does not use --privileged.

The image builds the release the server serves, read from the server's status page as it is built. When the server accepts a new release, run docker compose build && docker compose up -d.

This image was written without building it: this checkout's machine has no container runtime. Outside a container, as an unprivileged user, confine daemon's sandbox was checked to give the daemon a writable home and to let it start a run's own bubblewrap sandbox inside it.