Skip to content

Dependencies ​

The harness calls the tools below. A tool is required when a subcommand, a hook, the pre-push hook, CI or an installed git hook calls it. An optional tool serves one frontend or one job; the used for column also says what stops without it. Each minimum comes from one source, which the cell names: a check in the code, a CI pin, a [compat] entry, or the release note of a feature that the code uses. Where no minimum exists, the cell gives the version that the harness is tested with.

The tools are in two tables. You provide holds the three tools that a machine needs before the clone of the harness. The second table holds every other tool: harness setup puts each one that the profile chooses on the machine. The used for cell of each row starts with who needs the tool: every machine, a component of [setup], or a frontend. Below the tables, What each tool does says what each tool does, why the harness needs it, and where the harness calls it. setup-reference.md gives each step of harness setup and how to do it by hand. The install of the harness needs Julia only where the profile chooses the component julia, as the paragraph after the second table says.

You provide ​

harness setup runs on Python, its environment comes from uv, and git makes the clone of the harness. So these three tools come first, and you get them yourself, before the clone.

On macOS, Homebrew installs them. Homebrew is the package manager that harness setup calls on macOS, so it is a prerequisite too, and no row of the tables. Its installer, from https://brew.sh, needs an administrator once. Then this line installs the three tools, with no administrator:

bash
brew install python@3.14 uv git

On Ubuntu, this line installs Python, its venv module, git and curl. It needs an administrator: a user who may run sudo. Without sudo, ask an administrator to run the line for you.

bash
sudo apt-get install python3 python3-venv git curl

Ubuntu 26.04 packages no uv. The first run of bin/harness outside a session fetches the uv release that it pins into ~/.local/bin, and checks its sha256 first, as uv says.

toolminimumused for
Python≥ 3.14, requires-python in pyproject.toml, in the Python environment, for which uv fetches it; bin/harness itself starts on 3.9 or laterneeded by every machine: runs the harness command, the guard hooks and the pre-push hook
uvtested with 0.13.5, the release that bin/harness fetches on Linuxneeded by every machine: makes the Python environment that the harness command runs on, from uv.lock
gittested with 2.54.0needed by every machine: reads and pushes the repositories of the tree, makes the worktrees of Claude Code, and clones the pushed commit for the pre-push hook

harness setup installs ​

harness setup installs each tool of this table that the profile chooses. A row that starts with needed by every machine is installed on each machine. A row that names a component or a frontend is installed only where [setup] of the profile chooses it: components holds the components, and frontends the frontends. The six Julia packages come with the Julia environment that harness install --apply makes, and harness setup runs that step. A tool that is installed and meets its minimum is not installed again.

toolminimumused for
ghtested with 2.102.0needed by every machine: protects the default branch of each repository on GitHub, in harness ci-protection
gitleaks≥ 8.21, the check in .githooks/pre-pushneeded by every machine: finds secrets in the commits of a push, in the pre-push hook and in CI; a machine that never pushes to the harness can do without it, so harness doctor gives a WARN, not a FAIL, without it
Node.js≥ 20.16 (20.x), ≥ 22.3, for process.getBuiltinModule, as the API documentation says: https://nodejs.org/api/process.html#processgetbuiltinmoduleidneeded by every machine: loads the guard extensions of oh-my-pi and OpenCode in the hook probe of harness test, and builds the documentation site with npm and VitePress
shellchecktested with 0.11.0needed by every machine: checks the shell scripts, in the pre-push hook and in CI
actionlint≥ 1.7.12, the CI pin in .github/workflows/test.ymlneeded by every machine: checks the GitHub workflow files, in CI
rsynctested with 3.5.1needed by every machine: copies a package without its .git directory, for the test runner and the mutation tests
Julia≥ 1.13, [compat] in Project.tomlneeded by the component julia: runs the Julia scripts: the formatter, the test runner, the mutation tests, the workflow check and the installed git hooks
ExplicitImports≥ 1.15, [compat] in Project.tomlneeded by the component julia: finds the names that a package uses without an explicit import, in the script explicit-imports.jl and the package audit
JSON≥ 1.10, [compat] in Project.tomlneeded by the component julia: writes the reports of the test gate and the mutation tests
JuliaFormatter≥ 2.14, [compat] in Project.tomlneeded by the component julia: formats Julia code, in harness format, the installed pre-commit hook and the test gate
JuliaSyntax≥ 1.0.2, [compat] in Project.tomlneeded by the component julia: parses Julia code to make the mutants of a mutation test
TestEnv≥ 1.103.7, [compat] in Project.tomlneeded by the component julia: runs the tests of a package in its test environment, for the test runner and the mutation tests
YAML≥ 0.4.17, [compat] in Project.tomlneeded by the component julia: reads the CI workflows
fatou≥ 0.22.0, the CI pin in .github/workflows/julia.ymlneeded by the component julia: lints the staged Julia files before a commit, and rejects a mutant that uses an undefined name
Kaimontested with 2.10.0, the version of its app projectneeded by the component julia: gives an agent a Julia session and tools for Julia code; without it, the update job stops with an error, and the agents have no Julia session
juliauptested with 1.18.9needed by the component julia: installs Julia, and updates the Julia releases in the update job; without it, the job cannot update them
Claude Codetested with 2.1.295needed by the frontend claude: reads the layer that harness install writes into ~/.claude/, and runs the skill-trigger test, which checks which skill loads on a query; without it, neither happens
OpenCodetested with 2.0.26needed by the frontend opencode: reads the configuration that harness install writes into ~/.config/opencode/; without it, nothing reads that configuration
oh-my-pitested with 18.8.6needed by the frontend omp: reads the configuration that harness install writes into ~/.omp/agent/; without it, nothing reads that configuration
Antigravityno minimum; harness setup prints its step and installs nothingneeded by the frontend antigravity: reads the layer that harness install writes into ~/.gemini/config/; without ~/.gemini/, the install writes no file there
RTKtested with 0.51.0needed by the component rtk: shortens the output of shell commands in OpenCode; without it, the plugin passes each command unchanged
jqtested with 1.7.1needed by the component jq: reads the session data for the Claude Code status line; without it, the status line shows no values
chezmoino minimum; on Ubuntu without an apt candidate, the pinned release 2.73.2 of setup/tools.tomlneeded by the component dotfiles: applies the private copy of the dotfiles kit on each machine; without it, the kit cannot run

For a reader who writes no Julia. The rows of the component julia serve Julia code only: the formatter, the test runner and the mutation tests, the git hooks and workflows that harness githooks and harness workflows install into Julia packages, the workflow check of harness ci-protection, the update job, and the Julia session of an agent through Kaimon. Without them, a reader who writes no Julia loses these tools and nothing else: the instruction layer, the settings, the guard hooks, the leak checks and the warning about drift work the same. Leave julia out of components in [setup] of the profile. Then harness setup installs none of these tools, and harness install --apply makes no Julia environment and needs no Julia. With the component julia, or with a profile that has no [setup], which chooses every component, harness install --apply makes the Julia environment of the scripts, and without julia it stops with an error.

The tables leave out the system tools: the POSIX shell utilities, such as awk, sed, grep, ps, kill, id and mktemp; the tools that macOS supplies, launchctl, security, osascript, lsof, pgrep and tar; and the tools of the GitHub runner that the CI jobs call, curl, tar and sha256sum. On Linux, the update job julia-update.jl calls lsof, systemctl and notify-send in place of the macOS tools, and finds each on the PATH. Without lsof its server check fails; without notify-send it writes the log line only. On Linux, the Claude Code sandbox needs bubblewrap, bwrap, and socat. The settings template sets failIfUnavailable, so without them Claude Code does not start. On Ubuntu, harness setup installs curl, bubblewrap, socat and lsof on every machine, in its apt-get line.

What each tool does ​

Each section below describes one tool of the tables, or the six Julia packages together: what the tool does, what the harness uses it for, why the harness needs it, and where the harness calls it. The sections follow the order of the tables.

Python ​

Python is the language of the harness command, the guard hooks and the hook probe. The command uses the standard library and one package, PyYAML, with which harness wiki-lint reads the frontmatter of the prose repositories; uv.lock pins it. The tools that check the Python code come from the dev group of uv.lock, which only a profile with develop = true in its [setup] table installs.

Every subcommand runs on the interpreter of the Python environment of the harness, which bin/harness makes with uv. The pre-push hook runs bin/harness test once, which runs on that interpreter. CI runs it on Python 3.14 and the latest 3.x. The guard hooks are Python scripts, which Claude Code, OpenCode, oh-my-pi and Antigravity call before a tool call; the commands that harness install writes for Claude Code and Antigravity run them on the environment's interpreter.

The harness needs Python 3.14 or later because 3.14 is the one version that it is tested on, in its environment, in CI and in the pre-push hook. The python3 of the machine may be older, as on Ubuntu 24.04, which has 3.12: from 3.9 on, bin/harness starts on it, and its first run outside a session lets uv sync download Python 3.14 into uv's own store for the environment. Then only a hook that starts outside harness on the python3 of the PATH needs 3.14: one that you run by hand, and the pre-push hook of a clone of the harness that you push from, which refuses the push on an older python3. harness doctor reports the older one as a WARN. The profile and the model tables are TOML, which tomllib of the standard library reads. A shell script cannot parse TOML or JSON safely, and a compiled tool needs a build step on each machine.

Call sites: every subcommand (bin/harness); the pre-push hook (.githooks/pre-push); the guard hooks, which Claude Code calls (~/.claude/settings.json), OpenCode calls (adapters/opencode/plugins/guards.ts), oh-my-pi calls (adapters/omp/guards.ts) and Antigravity calls (~/.gemini/config/hooks.json).

uv ​

uv makes a Python environment from a project and its lock file. The Python environment of the harness is at ~/.local/share/research-harness/python/, or at $RESEARCH_HARNESS_PYTHON, an absolute path. The first time that bin/harness runs outside a session, it makes the environment with uv sync --locked --no-dev from pyproject.toml and uv.lock, and from then on it runs every subcommand on the environment's interpreter. harness install --apply syncs the environment again when it lags uv.lock, with the dev group where [setup] of the profile sets develop = true, and harness doctor checks it. The environment is made on Python 3.14 (UV_PYTHON overrides it), and uv downloads a managed 3.14 where none is installed.

The harness needs uv because the environment comes from a committed lock, so that every machine and CI run the same versions of the same packages.

On macOS, install uv with Homebrew: without uv, bin/harness stops and names brew install uv. On Linux, without uv on the PATH or in ~/.local/bin, bin/harness fetches release 0.13.5 into ~/.local/bin and checks its sha256 first. A session never runs uv: there, without the environment, bin/harness runs the subcommand on the interpreter that started it, and prints one line that names harness install --apply.

Call sites: the start of every subcommand (bin/harness); harness install (lib/harness/install.py); the CI job python (.github/workflows/test.yml), which runs uv sync --locked --no-dev before the suite.

git ​

git is the version control system. Each repository of the research tree is a git repository.

The subcommands that work across the tree use git: harness githooks reads each repository's configuration, harness clone and harness repos clone, fetch, pull and push each repository, and harness leaks --commits reads the commits of a push. The hook hooks/worktree.py makes the worktrees of Claude Code, and the pre-push hook clones the pushed commit and tests it.

The harness needs git because its work is on git repositories: the hooks, the commits and the pushes are git concepts. No other tool reads them.

Call sites: the subcommands (lib/harness/githooks.py, lib/harness/profile.py); the hook hooks/worktree.py; the pre-push hook (.githooks/pre-push).

gh ​

gh is the command-line client of GitHub. It calls the GitHub API with the credentials of the user.

harness ci-protection calls gh api to protect the default branch of each repository on GitHub.

The harness needs gh because branch protection is a setting on GitHub, which only the API changes. gh holds the user's credentials, so the harness stores no token of its own.

Call site: harness ci-protection (lib/harness/protection.py).

gitleaks ​

gitleaks finds secrets in text: tokens, keys and other values with a known shape. It reads a directory, the commits of a git repository, or standard input.

The pre-push hook runs gitleaks over the commits that a push sends, over their messages and over the tree of the pushed commit. The CI job leaks does the same with gitleaks 8.30.1 at a fixed checksum. The configuration .gitleaks.toml adds a pattern for home paths.

The harness needs gitleaks because the repository is public, and a leak is permanent once it is pushed. The patterns of gitleaks for known secret shapes are maintained upstream; the harness's own harness leaks finds only the private strings of the profile. Version 8.19 added the git, dir and stdin commands (release note). Version 8.21 is the first with several allowlists for one pattern, [[rules.allowlists]], which .gitleaks.toml uses (release note).

Call sites: the pre-push hook (.githooks/pre-push); the CI job leaks, which pins 8.30.1.

Node.js ​

Node.js runs JavaScript outside a browser.

harness test runs the hook probe, which loads the guard extension of oh-my-pi and the OpenCode plugins under Node.js and checks their decisions. Without node, a test case of the probe fails.

The harness needs Node.js because these extensions are JavaScript, and their frontends run them in a JavaScript runtime. The plugins take the Node.js built-in modules from process.getBuiltinModule, which Node.js 22.3 and 20.16 added.

On macOS, Homebrew installs Node.js. On Ubuntu, harness setup installs the nodejs and npm packages where the apt candidate of nodejs meets the minimum, as on Ubuntu 26.04. Ubuntu 24.04 packages Node.js 18, so there it installs the Node.js 24 release that setup/tools.toml pins from nodejs.org, after a check of its sha256: it unpacks the whole tree into ~/.local/share/research-harness/node/<version>/, because npm runs from that tree, and links node, npm and npx into ~/.local/bin.

The documentation site is a VitePress project, and its build runs under Node.js too: npm installs the packages that docs/package-lock.json pins, and VitePress builds the site and draws the call graphs. Only the docs build needs these packages; the harness itself does not.

Call sites: the hook probe of harness test (hooks/probe.py); the docs build (docs/package.json, .github/workflows/docs.yml).

shellcheck ​

shellcheck finds errors in shell scripts: a missing quote, an unset variable, a construct that does not work in the given shell.

The pre-push hook and the CI job lint run shellcheck -S warning over each tracked file with a sh or bash shebang, for example the shared git hooks and the status line script. The pre-push hook skips shellcheck when it is missing, because CI runs it.

The harness needs shellcheck because the git hooks run in every repository of the tree, and an error in a hook can let a commit through unchecked. The shell does not report most of these errors itself.

Call sites: the pre-push hook (.githooks/pre-push); the CI job lint.

actionlint ​

actionlint checks GitHub Actions workflow files: the syntax, the expressions and the shell scripts in the run: steps.

The CI job lint runs actionlint 1.7.12, at a fixed checksum, over .github/workflows/.

The harness needs actionlint because GitHub reports an error in a workflow only when the workflow runs. actionlint finds the error before the push.

Call site: the CI job lint (.github/workflows/test.yml).

rsync ​

rsync copies a directory tree, with patterns for the files that it leaves out.

run-tests.jl and mutate.jl use rsync to copy a package without its .git directory and without a manifest that another Julia version made. The tests of run-tests.jl in CI make such a copy too.

The harness needs rsync because the copy must leave out paths by pattern, which a plain cp cannot do.

Call sites: scripts/run-tests.jl, which the Julia tests in CI call through scripts/run-tests-test.jl; the script cases of harness test (lib/harness/script_cases.py).

Julia ​

Julia is the language of the research code in the tree. The harness's tools for Julia code are Julia scripts too: the test runner, the mutation tests, the formatter run and the workflow check.

harness install instantiates the Julia environment of the scripts. harness format and harness ci-protection run Julia scripts. The installed git hooks run Julia to check the format, the Unicode form and the load of a package before a commit, and the test suite before a push to main. The CI job test runs the Julia test files of the harness on Julia 1.13.

The harness needs Julia because a tool that reads, runs or rewrites Julia code must parse it as Julia does. Only Julia's own parser and loader give that result.

Call sites: harness install (lib/harness/install.py), harness format (lib/harness/fmt.py) and harness ci-protection (lib/harness/protection.py); the installed git hooks (githooks/pre-commit, githooks/pre-push); the pre-push hook (.githooks/pre-push), which skips it when it is missing, because the CI job test runs the Julia tests.

ExplicitImports, JSON, JuliaFormatter, JuliaSyntax, TestEnv and YAML ​

These six Julia packages are the dependencies of the Julia scripts, in Project.toml. harness install --apply installs them into the environment ~/.local/share/research-harness/julia/, and each script adds that environment to its load path. ExplicitImports finds the names that a module uses without an explicit import. JSON reads and writes JSON. JuliaFormatter formats Julia code. JuliaSyntax parses Julia code into a syntax tree. TestEnv activates the test environment of a package. YAML reads YAML.

The scripts use them as follows. explicit-imports.jl uses ExplicitImports. gate.jl, mutate.jl and fatou-lsp.jl use JSON for their reports and messages. harness format, the installed pre-commit hook and the format step of gate.jl use JuliaFormatter. mutants.jl uses JuliaSyntax to make the mutants, and mutate.jl uses it to read the names in a mutant. run-tests.jl and mutate-worker.jl use TestEnv to run a package's test files in its test environment. verify-workflows.jl uses YAML to read the workflows.

The harness needs these packages because each does a task that Julia's standard library does not do. A copy of their code in the harness would be larger and less correct.

Call sites: ExplicitImports, githooks/explicit-imports.jl; JSON, scripts/gate.jl, scripts/mutate.jl and scripts/fatou-lsp.jl; JuliaFormatter, harness format (githooks/format-tree.jl), the installed pre-commit hook (githooks/pre-commit.jl) and scripts/gate.jl; JuliaSyntax, scripts/mutate.jl and scripts/mutants.jl; TestEnv, scripts/run-tests.jl and scripts/mutate-worker.jl; YAML, harness ci-protection through githooks/verify-workflows.jl. harness install instantiates all six with Project.toml.

fatou ​

fatou is a linter and a language server for Julia code.

The installed pre-commit hook runs fatou lint on the staged Julia files and prints its findings as advice. mutate.jl runs fatou lint to reject a mutant that uses an undefined name, and fatou-lsp.jl uses its language server. The CI job test installs fatou 0.22.0 for the tests of mutate.jl.

The harness needs fatou because it reports an undefined name from the source alone. Julia reports such a name only when the code runs.

Call sites: the installed pre-commit hook (githooks/pre-commit.jl); scripts/mutate.jl; the CI job test (.github/workflows/julia.yml).

Kaimon ​

Kaimon is an MCP server that gives an agent a Julia session and tools to search and change Julia code.

The update job julia-update.jl updates Kaimon, checks that it loads, and reports when its server must restart. The oh-my-pi, OpenCode and Antigravity configurations hold its MCP entry.

The harness does not need Kaimon to run. Without it, the update job stops with an error, and an agent has no Julia session and no Kaimon tools.

Call site: the update job (scripts/julia-update.jl).

juliaup ​

juliaup installs and updates the Julia releases, and selects a release per command.

The update job julia-update.jl runs juliaup update and finds the installed channels in the configuration of juliaup. harness setup runs the juliaup installer, which installs Julia too, and runs juliaup update for a Julia below its minimum.

The harness needs juliaup for the update job, and harness setup needs it to install Julia. Without it, the job cannot update the Julia releases.

Call sites: the update job (scripts/julia-update.jl); harness setup (lib/harness/setup.py).

Claude Code ​

Claude Code is Anthropic's coding agent for the terminal. It is one of the four frontends that the harness configures.

harness install writes the Claude Code layer into ~/.claude/: the agents, skills, rules, instructions, commands and hooks, and the permission settings of ~/.claude/settings.json. harness skill-triggers --apply runs claude -p to measure which skill loads on a query.

The harness does not need Claude Code to run. Without it, nothing reads the layer in ~/.claude/, and the skill-trigger test cannot run.

Call site: harness skill-triggers --apply (lib/harness/skill_triggers.py).

OpenCode ​

OpenCode is an open-source coding agent for the terminal. It is one of the four frontends that the harness configures.

harness install writes the OpenCode configuration into ~/.config/opencode/: the permission block, the agents, the plugins, the global instruction file, and OpenCode's own copies of the instructions, the rules, the guard hooks and the skills.

The harness does not need OpenCode to run. Without it, nothing reads that configuration.

Call site: harness install (adapters/opencode/adapter.py).

oh-my-pi ​

oh-my-pi is a coding agent for the terminal. It is one of the four frontends that the harness configures.

harness install writes the oh-my-pi configuration into ~/.omp/agent/: the permission layer, the guard extension, the rules, the agents, the models and the MCP entry.

The harness does not need oh-my-pi to run. Without it, nothing reads that configuration.

Call site: harness install (adapters/omp/adapter.py).

Antigravity ​

Antigravity is a coding agent with a desktop app, an IDE and the CLI agy, which read one configuration directory. It is one of the four frontends that the harness configures.

harness install writes the Antigravity layer into ~/.gemini/config/ once ~/.gemini/ exists: the instructions, the rules, the skills, the agents, the guard hook and the permission rules of the app and of agy. harness setup installs no Antigravity: with the frontend antigravity, it prints the step under By hand, to install the app from https://antigravity.google/, start it once, then run harness install --apply.

The harness does not need Antigravity to run. Without ~/.gemini/, the install writes no file there and gives one warning.

Call site: harness install (adapters/antigravity/adapter.py).

RTK ​

RTK (Rust Token Killer) rewrites shell commands so that their output uses fewer tokens: for example, it replaces cat with its own reader.

The OpenCode plugin rtk.ts asks rtk hook check to rewrite each shell command.

The harness does not need RTK. Without it, the plugin passes each command unchanged, and the output of a command is longer.

Call site: the OpenCode plugin adapters/opencode/plugins/rtk.ts.

jq ​

jq reads and transforms JSON on the command line.

The Claude Code status line script reads the session data from JSON with jq.

The harness needs jq only for the status line. A shell script cannot parse JSON safely. jq is a small tool: macOS supplies it, and the Linux distributions package it.

Call site: the status line script (adapters/claude/statusline-command.sh).

chezmoi ​

chezmoi manages the dotfiles of a user from a source directory, on several machines.

The dotfiles kit of the harness, examples/dotfiles/, is a chezmoi source with placeholder data. Its private copy keeps the profile, the model tables and the commit of the harness the same on each machine, and chezmoi applies it there. The scripts of the kit, such as dotfiles-update, run chezmoi. The dotfiles command gives the steps.

The harness does not need chezmoi to run. Only a user who keeps several machines the same with the kit needs it, and that user chooses the component dotfiles.

Call sites: the scripts of the kit (examples/dotfiles/dot_local/bin/).