{
  "parts": [
    {
      "name": "hook",
      "key": "repository",
      "where": "repository setting",
      "holds": [
        "the after-receive hook that wakes sessions when a record is added"
      ],
      "made_by": "install the wake hook",
      "removed_by": "install the wake hook"
    },
    {
      "name": "agent login",
      "key": "login",
      "where": "repository setting",
      "holds": [
        "the login's account on each repository of the login group, and its one password kept on the host"
      ],
      "made_by": "provision an agent login",
      "removed_by": "withdraw an agent login"
    },
    {
      "name": "workspace",
      "key": "agent login × project",
      "where": "record",
      "holds": [
        "a workspace definition, the first entry on the record it was added on: login, project, branch, and its whole sandbox",
        "a terminated note on that record when it ends",
        "on the host, made from the definition: the clone, the tree, binding.json",
        "it outlives its sessions: each session reuses the clone and tree; only Terminate or gc removes it, and gc skips a workspace a running process names"
      ],
      "made_by": "add a workspace",
      "removed_by": "terminate a workspace"
    },
    {
      "name": "session",
      "key": "workspace: at most one at a time",
      "where": "record",
      "holds": [
        "a session definition on the workspace's record when it starts: mechanical (a command) or llm (runner and model), and whether it runs to completion or stays running",
        "a stopped note when it ends",
        "a launch note, and an end note with its exit status",
        "on the host: its process, `workspace serve`, whose command line names its workspace and release; the wake pipe; the log"
      ],
      "made_by": "start a session",
      "removed_by": "stop a session"
    },
    {
      "name": "listener",
      "key": "session × record or branch",
      "where": "record",
      "holds": [
        "a listen note by the agent on the record listened to, or on the workspace's record for a branch; an unlisten note ends it. On a thread or ticket it feeds a `stays` session; on a branch it launches a `completes` one",
        "on the host: the listening index"
      ],
      "made_by": "add a listener",
      "removed_by": "remove a listener"
    }
  ],
  "layout": [
    {
      "path": "AGENTS/LOGIN/password",
      "part": "workspace",
      "is": "the login's one password"
    },
    {
      "path": "AGENTS/LOGIN/projects/PROJECT/clone",
      "part": "workspace",
      "is": "the Fossil clone, the agent's own and never shared, cloned over the network as the agent (6 s for ollama, 35 MB, measured 2026-09-26); a clone of the file on the host would copy every login's password hash into the agent's sandbox"
    },
    {
      "path": "AGENTS/LOGIN/projects/PROJECT/tree",
      "part": "workspace",
      "is": "the checkout, on the branch the workspace definition names"
    },
    {
      "path": "AGENTS/LOGIN/projects/PROJECT/binding.json",
      "part": "workspace",
      "is": "login, project code, remote, branch, home thread"
    },
    {
      "path": "AGENTS/LOGIN/projects/PROJECT/session/wake",
      "part": "session",
      "is": "the named pipe the hook writes"
    },
    {
      "path": "AGENTS/LOGIN/projects/PROJECT/session/listening/RECORD",
      "part": "listener",
      "is": "an empty file per listened record naming its kind, rebuilt from the agent's listen notes when a session starts and written when one is added or removed"
    }
  ],
  "listeners": [
    {
      "kind": "thread",
      "names": "the thread's root post",
      "wakes_on": "forum-post",
      "lookup": "listening/ROOT",
      "does": "feeds a stays session"
    },
    {
      "kind": "ticket",
      "names": "the ticket's hash",
      "wakes_on": "ticket",
      "lookup": "listening/TICKET",
      "does": "feeds a stays session"
    },
    {
      "kind": "branch",
      "names": "a branch name, by a listen note naming it on the session's own record",
      "wakes_on": "check-in",
      "lookup": "listening/branch-NAME",
      "does": "launches a completes session"
    }
  ],
  "decisions": [
    {
      "question": "How many sessions may a workspace run?",
      "choice": "one at a time; a second for the same project is a second agent login",
      "why": "one tree, so no two runs edit it at once",
      "evidence": [
        "f1a3f457f2",
        "28a0f2abfa"
      ]
    },
    {
      "question": "Where are listeners recorded?",
      "choice": "as JSON listen and unlisten notes by the agent on the record listened to; the workspace keeps only an index rebuilt from them",
      "why": "state on the record, visible where people post; and the listen note is where the session starts answering, so it never answers a thread's whole history",
      "evidence": [
        "7f13b6da5e",
        "28a0f2abfa"
      ]
    },
    {
      "question": "Which branch does a listener's work go on?",
      "choice": "the workspace's one branch, named by its definition; a listener chooses none",
      "why": "one tree has one checkout",
      "evidence": [
        "7f13b6da5e",
        "28a0f2abfa"
      ]
    },
    {
      "question": "How cheap must a workspace and a session be?",
      "choice": "a workspace is made once per agent and project, by a network clone as the agent (seconds), and reused by every session after; a session is a process and a pipe in it, with no clone; a short session to fix one bug starts on its ticket or thread and stops when done",
      "why": "the maintainer: clones and workspaces kept cheap, a short-lived session cheap",
      "evidence": [
        "a079dce491"
      ]
    },
    {
      "question": "May agents share a clone?",
      "choice": "never; each workspace, and so each agent login, has its own",
      "why": "the maintainer: sharing a clone between agents should never be considered",
      "evidence": [
        "a079dce491"
      ]
    },
    {
      "question": "Is mechanical work (CI, review, a long build) a session?",
      "choice": "yes: a session whose runner is a command, not a model, under a login of its own (ci, reviewer, builder), with its own workspace and listeners; CI and review listen to a branch's check-ins, a build to its ticket; the command's end posts or tags its result",
      "why": "one shape for everything OCX runs, instead of separate job machinery",
      "evidence": [
        "5179d11ec3",
        "472a4955ad"
      ]
    },
    {
      "question": "In what order is it built?",
      "choice": "agent sessions (project workspace, JSON grant, listeners on threads and tickets) first; then CI as the first command session, with branch listeners",
      "why": "the maintainer's choice",
      "evidence": [
        "472a4955ad"
      ]
    },
    {
      "question": "Where are a workspace's bindings stated?",
      "choice": "in a workspace definition, the first entry on the record it was added on; the host's binding.json is a copy made from it, and a terminated note ends it",
      "why": "the maintainer: the biggest gap is that no place clearly defines the workspace bindings; the grant mixed the workspace, the session and its listeners",
      "evidence": [
        "26b7cdcdfd",
        "3fa96ac171"
      ]
    },
    {
      "question": "What does a session say it is?",
      "choice": "a session definition on the workspace's record: mechanical (a command) or llm (a runner and model), and whether it runs to completion or stays running; the grant is retired",
      "why": "an agent is either mechanical, performing exact code steps, or llm, using a model to define its activities",
      "evidence": [
        "26b7cdcdfd",
        "3fa96ac171"
      ]
    },
    {
      "question": "How are the console's pages laid out?",
      "choice": "one focused page per step: hook, agent logins, workspaces, a workspace, starting a session, a session; each shows one thing and offers its few actions",
      "why": "keep the individual console pages focused",
      "evidence": [
        "3fa96ac171",
        "78bea0bd5a"
      ]
    },
    {
      "question": "Where is a workspace's sandbox stated?",
      "choice": "whole, in the workspace definition on the record: network, read-only and read-write paths, homes, and PATH; the workspace's templates carry one too, and the console adds the workspace's own directory. Only what is the same for every workspace (/proc, /dev, /tmp, the workspace as HOME, its peers) and the program a session runs are not stated",
      "why": "every session's confinement is then stated on the record, where anyone can read it; the host builds bwrap from it and nothing else",
      "evidence": [
        "43ce0ee965",
        "46913d970d",
        "60b14a4324"
      ]
    },
    {
      "question": "Who is the parent of a session?",
      "choice": "one OCX daemon, `ocxd`, per agents root, started idempotently by the console and the hook (a flock on AGENTS/ocxd.lock admits one), under Fossil's sandbox; not systemd, and not start-fossil.sh. A launch is a post: the hook hands it to the daemon, which starts `workspace serve` as its child in its own process group, reaps its exit and puts the end on the record. Accepting a release makes it re-exec in place, keeping its PID and its children",
      "why": "one parent that sees every start and end as an event, without depending on systemd or adding a service to maintain; the old task-worker daemon's lessons: one launch site, process groups, a lock on the shared disk",
      "evidence": [
        "c4521cbb97",
        "14d8477f84",
        "027446fbe9",
        "1b2388982d"
      ]
    },
    {
      "question": "How are running sessions and active workspaces found?",
      "choice": "from the process table, by command line: a workspace is active while a process names it (`workspace run|start|serve KEY --root ROOT`, or bwrap's `--bind WS`); a process is identified by PID and start time. It covers console sessions, terminal sessions and orphans alike; gc never collects an active workspace; ~/run and launch.pid go",
      "why": "the OS is the one record of what runs; /proc/PID/cwd is not readable under the bq2 namespace, but command lines are",
      "evidence": [
        "c4521cbb97",
        "14d8477f84",
        "027446fbe9",
        "1b2388982d"
      ]
    },
    {
      "question": "Is a stopped session restarted automatically?",
      "choice": "no: nothing is killed when the daemon dies and nothing is relaunched by itself; a console button restarts the stopped `stays` sessions, on the current or the previous release. The console flags a session running an older release than the current one, saying it can be stopped and restarted on the current release",
      "why": "an automatic relaunch risks a kill-relaunch-kill cycle that is hard to leave, and a bad release must be escapable by rolling back",
      "evidence": [
        "c4521cbb97",
        "14d8477f84",
        "027446fbe9",
        "1b2388982d"
      ]
    },
    {
      "question": "Does the daemon ever start a terminal session?",
      "choice": "no: terminal sessions (`workspace run` at a terminal) are found through their command lines and listed as terminal, never started or restarted; the daemon refuses a launch into a workspace a running process already names",
      "why": "a session at a terminal needs its terminal; starting another beside it would edit one tree twice",
      "evidence": [
        "c4521cbb97",
        "14d8477f84",
        "027446fbe9",
        "1b2388982d"
      ]
    },
    {
      "question": "How do the console and Stone learn the state?",
      "choice": "by polling JSON status the console serves (hooks, agent logins, workspaces, sessions, listeners), derived from the record and the process table; the daemon's events make that state definitive, not the polling",
      "why": "clients such as Stone need one readable source; events keep it true",
      "evidence": [
        "c4521cbb97",
        "14d8477f84",
        "027446fbe9",
        "1b2388982d"
      ]
    },
    {
      "question": "What ends a session?",
      "choice": "its lifetime: a `completes` session exits once its report is on the record, and a question it asked keeps it running until answered; a `stays` session runs until an `OCX stop`. The report is an llm session's done or failed report on its own record, or a mechanical session's result: the `ci` tag, green or red with the command's last line, on the check-in it tested, and a red one also said on its own record (agent-sol, d38004971c). The daemon reaps it and puts the end on the record; the Sessions page shows \"completed: REPORT\"",
      "why": "a short session, such as a bug fix, costs one Start and ends by itself",
      "evidence": [
        "d71c9d4bd1",
        "fe709740df",
        "3f6abf8cc4",
        "5d940f5510",
        "257b282d36",
        "d38004971c"
      ]
    },
    {
      "question": "Where does a session work and report?",
      "choice": "on its own record, the one it was started for (a bug's ticket, say): its definition, work and report go there. The workspace keeps its own record for its definition, and a session on another record no longer rebinds the workspace",
      "why": "the workspace definition stays where it was stated, and each session's work is on the record it was for",
      "evidence": [
        "d71c9d4bd1",
        "fe709740df",
        "3f6abf8cc4",
        "5d940f5510",
        "257b282d36"
      ]
    },
    {
      "question": "Does CI stay running between check-ins?",
      "choice": "no: a branch listener launches rather than feeds. Each check-in on a listened branch makes the daemon launch the mechanical `completes` session, which tests the newest untested check-in, tags it and exits. A check-in during a run is refused as a launch (the workspace is active), and the run's end launches it again while an untested check-in remains. A listener on a thread or ticket feeds a `stays` session",
      "why": "an idle CI process waits on nothing but a wake; per-check-in sessions each have their own start and end on the record, and no timer",
      "evidence": [
        "d71c9d4bd1",
        "fe709740df",
        "3f6abf8cc4",
        "5d940f5510",
        "257b282d36"
      ]
    },
    {
      "question": "How does anyone know a session cannot hear its record?",
      "choice": "listening is a state read from the process table: a session is listening while a process holds its wake pipe (`workspace serve`, or `task --wait` for a session at a terminal). Status and the Sessions page show a running session that is not listening, and the hook notes on the record a person's post that reached a session not listening (the pipe has no reader)",
      "why": "a post reached nobody while the session looked idle (104c163383); a session that cannot hear must look different from one that is ready",
      "evidence": [
        "104c163383",
        "d7da4387c8"
      ]
    }
  ],
  "migration": [
    "Start a session makes the project workspace when there is none; the per-record workspaces under AGENTS/LOGIN/workspaces keep running until stopped, and collect workspaces removes them after.",
    "bin/wake looks up listening/ first and workspaces/RECORD/wake second, until no per-record workspace remains.",
    "Once the release that reads a sandbox is accepted, each record's workspace definition is posted again with its whole sandbox, and the workspace/NAME templates carry one; a definition without a sandbox is refused, not read as the old tools and homes.",
    "Sessions running before the daemon (their parent PID 1) are found by their command lines like any other, listed with their release, and stopped by `OCX stop`; the daemon never adopts them.",
    "~/run, launch.pid and Sandbox.running_in's --chdir scan are removed once the process-table reading replaces them."
  ],
  "runners": [
    {
      "kind": "model",
      "names": [
        "claude",
        "codex",
        "native"
      ],
      "on_wake": "a turn: the model answers what waits on its login"
    },
    {
      "kind": "command",
      "names": [
        "command"
      ],
      "on_wake": "launched by the daemon for a check-in on a listened branch: runs the session's command in the sandbox on the newest check-in with no result of its login's, tags it ci green or red with the command's last line, says a red one on its own record, and exits; it answers no post"
    }
  ],
  "definitions": {
    "workspace": {
      "schema": "docs/design/workspace-definition.schema.json",
      "example": {
        "ocx": "workspace",
        "login": "ci-ocx",
        "project": "ollama",
        "branch": "ocx",
        "sandbox": {
          "network": true,
          "read_only": [
            "/usr",
            "/lib",
            "/lib64",
            "/bin",
            "/sbin",
            "/etc/ssl",
            "/etc/pki",
            "/etc/ca-certificates",
            "/etc/resolv.conf",
            "/etc/hosts",
            "/etc/nsswitch.conf",
            "/etc/passwd",
            "/etc/group",
            "/opt",
            "/var/lib/ocx/release/current"
          ],
          "read_write": [
            "/var/lib/ocx/agents/ci-ocx/projects/ollama"
          ],
          "homes": [],
          "path": [
            "/var/lib/ocx/release/current",
            "/usr/local/bin",
            "/usr/bin",
            "/bin"
          ]
        }
      }
    },
    "session": {
      "schema": "docs/design/session-definition.schema.json",
      "example": {
        "ocx": "session",
        "kind": "mechanical",
        "command": [
          "sh",
          "-c",
          "shards install && crystal run ci.cr"
        ],
        "lifetime": "stays"
      }
    }
  },
  "pages": [
    {
      "view": "1. Hook",
      "shows": "whether the wake hook is installed on each repository of the login group",
      "does": [
        "install or remove it"
      ]
    },
    {
      "view": "2. Agent logins",
      "shows": "the agent logins and whether each has an account here",
      "does": [
        "add a login",
        "withdraw one"
      ]
    },
    {
      "view": "3. Workspaces",
      "shows": "a short list: login, project, record, state",
      "does": [
        "open one",
        "add one"
      ]
    },
    {
      "view": "Add a workspace",
      "shows": "login (pull-down), record (completion of ten recent, or a new thread), branch (completion), the sandbox from a workspace template, with the workspace's own directory added read-write",
      "does": [
        "post the definition and make the workspace"
      ]
    },
    {
      "view": "A workspace",
      "shows": "its definition, its session, and the end of its log",
      "does": [
        "start a session",
        "terminate the workspace"
      ]
    },
    {
      "view": "Start a session",
      "shows": "kind (mechanical or llm), then the command or the runner and model, from a session template; lifetime",
      "does": [
        "post the definition and start it"
      ]
    },
    {
      "view": "4. Sessions",
      "shows": "this project's sessions from the process table: login, record, how it was started (console or terminal), state, and its release, flagged when it is older than the current one",
      "does": [
        "open one",
        "restart the stopped sessions that stay, on the current or previous release"
      ]
    },
    {
      "view": "A session",
      "shows": "where it stands (working or idle), a link to its listeners with their count, and the end of its log",
      "does": [
        "stop it"
      ]
    },
    {
      "view": "Its listeners",
      "shows": "what the session listens to besides its own record",
      "does": [
        "add a listener: kind pull-down, then completion of ten recent",
        "remove one"
      ]
    },
    {
      "view": "Status (JSON)",
      "shows": "hooks, agent logins, workspaces, sessions and listeners, for Stone and the console",
      "does": [
        "nothing: it is read"
      ]
    }
  ],
  "daemon": {
    "process": "`ocxd`, one per agents root, under Fossil's sandbox (the bq2 namespace, which shares the host's PID namespace); the parent of every session it launches, each in its own process group",
    "started_by": [
      "ensure the daemon runs from the console",
      "ensure the daemon runs from the hook"
    ],
    "files": [
      {
        "path": "AGENTS/ocxd.lock",
        "is": "flock held by the one running daemon; on the shared disk, since /var/run is per namespace (2e2901ce80)"
      },
      {
        "path": "AGENTS/ocxd",
        "is": "its pipe: post hashes from the hook, requests from the console"
      }
    ],
    "reads": [
      "launch a session",
      "record a session's end",
      "re-exec the daemon"
    ],
    "never": [
      "relaunches a session by itself",
      "starts a session in a workspace a running process already names",
      "starts a terminal session",
      "kills its sessions when it dies"
    ]
  }
}
