Setup on macOS
This page gives each step from a clean Mac to a working harness. For each step it says what the step does, why the harness needs it, and how you see that it worked. Installation in the README gives the short form of the same steps. setup-linux.md is the page for Linux.
The steps use ~/Research/Harness as the path of the checkout. Introduction explains the words that this page uses, and the glossary there has an entry for each.
Without Julia code. If you write no Julia, you can skip the steps and lines that this page marks. You still need Julia itself, because the install runs it once.
The exit codes. Each verb of harness ends with an exit code, which your shell keeps in $?. Exit 0 means that nothing would change or that the verb succeeded, exit 1 that a dry run found something to change, and exit 2 an error. Each step below names the code that it expects, with its meaning.
The dependencies
What it does. These commands install every required tool that the harness calls. Homebrew installs all but Julia, and the installer of juliaup installs Julia. Dependencies lists each tool, its minimum version and what the harness uses it for. Its section What each tool does says why the harness needs each tool, and where it calls it.
brew install python python@3.11 git gh gitleaks node shellcheck actionlint coreutils rsync
brew install jolars/tap/fatou
curl -fsSL https://install.julialang.org | shWithout Julia code, skip the second line: fatou checks Julia code only.
The third line downloads the installer of juliaup and runs it. The installer asks you to confirm its settings; with its default settings, it installs juliaup and the current Julia release, and adds them to the PATH in your shell's start file. Open a new terminal after it, so that your shell finds julia. The six Julia packages of Dependencies need no command: the first install adds them.
Why. harness runs on the python3 of the PATH, which must be 3.11 or later. python supplies a current python3, and python@3.11 supplies python3.11, which the pre-push hook also runs. coreutils supplies timeout, which macOS does not have. You need Julia even if you write no Julia code: the first install instantiates the Julia environment of the harness's scripts, and it stops without julia. The optional tools of Dependencies serve one frontend or one job each. Install them when you need that frontend or that job.
Check. Each command prints its version: Python 3.11 or later, and Julia 1.13 or later.
python3 --version
julia --versionThe clone
What it does. These commands clone the repository and put its bin/ directory on the PATH, so that your shell finds the command harness.
git clone https://github.com/michakraus/ResearchHarness.git ~/Research/Harness
export PATH="$HOME/Research/Harness/bin:$PATH"Add the export line to your shell's start file too, for example ~/.zshrc, so that each new shell finds harness.
Why. Every step after this one uses harness, and the installed configuration names files of the checkout.
Check. harness --help lists the verbs. A Python older than 3.11 makes harness stop with exit 2, an error, and the message names the version that it found.
harness --helpThe profile and the model tables
What it does. These commands copy the example profile and the example model tables of examples/ into your configuration directory.
mkdir -p ~/.config/research-harness
cp ~/Research/Harness/examples/profile.toml ~/.config/research-harness/profile.toml
cp ~/Research/Harness/examples/models.toml ~/.config/research-harness/models.tomlThen open ~/.config/research-harness/profile.toml in an editor. Replace the example path /home/example with your home directory, for example /Users/me, in each value. The key harness then holds the absolute path of the checkout, for example /Users/me/Research/Harness. profile.md names each key and table, and the comments in the two files say what each value is.
The key repository_roots names the directories that hold the repositories of your research tree. Each entry must be a directory. With the example values, these commands make them:
mkdir -p ~/Research/Packages ~/Research/ExperimentsWhy. The profile holds every value that names a person, an institution or a machine, so the repository holds none. The model tables give the model of each tier for each frontend.
Check. harness get prints the value of one key. It must print your home directory, not /home/example.
harness get homeThe tree instructions
What it does. These commands make the directory of the tree instructions, with one empty file in it. The profile key tree_agents gives the path of the directory; the commands use the example value.
mkdir -p ~/Research/Environment/Agents/instructions
touch ~/Research/Environment/Agents/instructions/research-tree.mdWhy. The tree instructions hold your own instruction text about your research tree. harness install installs them into ~/.claude/ beside the sources of the harness. The directory has the layout of ~/.claude/. adapters/claude/CLAUDE.md imports instructions/research-tree.md, so put the facts about your tree into that file. The directory must exist, else harness install stops with exit 2, an error. architecture.md says how the tree instructions relate to the harness and the profile.
Check. harness get tree_agents prints the path of the directory, and the directory holds the file.
harness get tree_agents
ls ~/Research/Environment/Agents/instructionsharness install --apply
What it does. harness install writes the configuration of each frontend from the sources of the harness, the profile and the tree instructions. It needs two directories: ~/.claude/skills/ and the OpenCode configuration directory, $OPENCODE_CONFIG_DIR, else ~/.config/opencode/. On a new machine, make them before the first install:
mkdir -p ~/.claude/skills ~/.config/opencodeThen run the dry run. It prints each file that it would install, and it exits 1, which means that it would change files:
harness installRead the plan, then make the change:
harness install --applyWhy. Each frontend reads its own configuration directory, never the checkout. The install copies each agent, skill, rule and instruction file to the place where its frontend reads it.
The first --apply also instantiates the Julia environment of the scripts, so it downloads the Julia packages of Project.toml. The warnings after the plan name what the install does not do for you. For example, the Kaimon entry needs a token file.
Without the OpenCode directory, harness install stops with exit 2, an error, and tells you to start OpenCode once. Without ~/.claude/skills/, it stops with exit 2, an error, and says that the path is not a directory.
Check. Run the dry run again. Its last line is 0 change(s) to make., and it exits 0, which means that the installed files are the files of the sources.
harness installClaude Code
harness install writes the Claude Code layer into ~/.claude/. Its first file is ~/.claude/settings.json: the install writes the sections permissions, hooks, sandbox and modelSettings of the file from the settings template settings/settings.proposal.json, rendered with the profile. It keeps every other key of the file, such as the model that you choose in Claude Code, and it keeps the old file as a backup, settings.json.bak-<date>-<time>. If the file does not exist, the install makes it, with these four sections only. Then it writes the instruction files, the agents, the skills, the rules, the commands and the hooks, and last the stamp ~/.claude/.harness-install.json.
These sections are the permission settings, the hooks, the sandbox of Claude Code, and the effort and auto-compact window of each model. Without them, Claude Code runs with its own defaults: no guard hook refuses a command, and no sandbox limits a shell command. An /effort or /autocompact that you type holds until the next install, which replaces it. The settings come first, so that a new deny rule is in place before the files that it protects. The dry run prints the difference of the four sections after the line of the file, for example:
~/.claude/settings.json REPLACE (backup: settings.json.bak-20261010-175004)
--- ~/.claude/settings.json
+++ settings/settings.proposal.jsonRead this difference before each --apply. security.md describes what the settings stop, and how to check a change of them. The installed settings refuse an edit of ~/.claude/settings.json from a session, so you run the install yourself, in your own terminal. The install reads the file when it makes its plan, so do not change the model of a running session while --apply runs: Claude Code writes the file then, and that change is lost.
If ~/.claude/settings.json is not a JSON object, is a symlink, or holds one of the four sections as a value that is not an object, the install stops with exit 2, an error, and writes no file.
OpenCode
harness install writes the OpenCode configuration into the OpenCode configuration directory: opencode.jsonc with its permission block, the plugins, the global instruction file AGENTS.md and the agents, and OpenCode's own copies of the instructions, the rules, the guard scripts and the skills. It removes the links in ~/.agents/skills/ that an earlier install wrote. architecture.md describes the adapter.
oh-my-pi
harness install writes the oh-my-pi configuration into its agent directory, $PI_CODING_AGENT_DIR, else ~/.omp/agent/: harness.yml with the permission layer and the default model, models.yml, the guard extension, AGENTS.md, the rules, the agents, mcp.json, and oh-my-pi's own copies of the instructions, the guard scripts and the skills. It never writes oh-my-pi's own config.yml. oh-my-pi reads harness.yml over config.yml only when PI_CONFIG_FILES names it, so add this line to ~/.omp/agent/.env once; the install warns until it is there:
PI_CONFIG_FILES=~/.omp/agent/harness.ymlarchitecture.md describes the adapter.
git config core.hooksPath .githooks
Only to change the harness. A user who only installs the harness skips this step.
What it does. These commands turn on the pre-push hook of this repository, once in the checkout:
cd ~/Research/Harness
git config core.hooksPath .githooksWhy. The pre-push hook runs the leak checks and the tests before a push to main. development.md describes the hook.
Check. git config core.hooksPath in the checkout prints .githooks.
The check that it worked
Run the harness's own test cases. The last line gives the number of cases, and it must say 0 wrong:
harness testAt the start of each new Claude Code session, a hook compares ~/.claude/ with the sources. A session that starts with no warning has the installed layer of the sources. When a source changed after the last install, the session starts with a warning about drift. The tutorial starts your first session.