Adapters
An adapter is the part of the harness that belongs to one frontend. The neutral sources in agents/, skills/, rules/, instructions/ and commands/ name no frontend. Each adapter turns them into the configuration of its frontend, and adds the files that only its frontend reads. The sources of an adapter are below adapters/<frontend>/: adapters/claude/ for Claude Code, adapters/opencode/ for OpenCode, adapters/omp/ for oh-my-pi and adapters/antigravity/ for Antigravity.
Each adapter has one Python module, adapter.py. lib/harness/frontends.py loads the four modules by path, in the order Claude Code, OpenCode, oh-my-pi, Antigravity. Each module gives its own subcommands, its part of harness install, its test cases for harness test, and the restart line that the install prints after a change. The other files of an adapter are instruction text, configuration and extension code. harness install --apply copies or renders them into the configuration directory of the frontend. The architecture describes what each frontend receives, and the harness command describes the loader.
The sections follow the four frontends: Claude Code, OpenCode, oh-my-pi, then Antigravity. In each group, the module adapter.py comes first, then the files that it installs.
claude/adapter.py
The adapter of Claude Code. It writes the Claude Code layer of harness install, and it gives the subcommands harness settings and harness trust.
The layer goes into ~/.claude/. The five neutral directories install at their own path, adapters/claude/ at the top, hooks/ into ~/.claude/hooks/, and the tree instructions at their own path. An agent and a SKILL.md get two values rewritten: the model tier of model: becomes the model of the [claude] table of models.toml, and each tools: item its Claude Code name. A hook keeps its mode, and every other file is installed with mode 0644.
The first file of the layer is ~/.claude/settings.json. The adapter merges four sections of the settings template into this file: permissions, hooks, sandbox and modelSettings. It adds each other key of the template, and each env variable, that the file does not have. It writes only those values again, through ownedjson.py, and keeps every other byte of the file: the other keys and values, the layout and the text of each number. It keeps the old file as a backup, and creates the file if it is missing. The dry run prints the diff of those sections after the line of the file. After the layer, the adapter writes the stamp ~/.claude/.harness-install.json.
The adapter exits with status 2, before it writes a file, in these cases:
- two sources hold the same path;
- a source or an installed path is a symbolic link;
- a source holds
settings.json; settings.jsonis not a JSON object thatjsonio.pyreads, such as one with a key twice in one object, or one of the four sections in it is not an object.
An installed file with no source is an EXTRA line with its removal command. The permission rules of the other settings files of the research tree are a warning.
harness settings has four subcommands: surface, compare, selftest and twins. surface reads the session transcripts below ~/.claude/projects/ and counts the Bash commands that reach a prompt. harness trust marks the research root and each git repository below it as trusted in ~/.claude.json. It skips .worktrees/ and .scratch/. It writes only the entries that it adds, through ownedjson.py, and writes a symlinked ~/.claude.json through to its target. A session cannot read that file, so run it in your own terminal.
fixtures/ holds the expected rendered output of two agents for Claude Code, which the cases compare.
claude/CLAUDE.md
The global instruction file of Claude Code. harness install copies it to ~/.claude/CLAUDE.md, and every Claude Code session reads it at the start.
Its first lines import RTK.md, instructions/core.md and instructions/research-tree.md, the last from the tree instructions. The rest holds the mechanics of Claude Code only:
- the worktrees, which the
WorktreeCreatehook makes, andEnterWorktreeandExitWorktree; - the trust of a new clone, which needs
harness trust --applyfrom the user; - a workflow for fan-out over many repositories;
- the wait for a long command: run the command that gives the answer in the background, never a
sleep; gitandghas the whole command, so that they run outside the Claude Code sandbox;- the sandbox as the security boundary, and the deny list as protection against accidents.
OpenCode, oh-my-pi and Antigravity do not read this file. They read opencode/OPENCODE-DELTA.md, omp/OMP-DELTA.md and antigravity/ANTIGRAVITY-DELTA.md in its place. A rule for every frontend goes into instructions/core.md, not here.
claude/RTK.md
The facts about RTK, a proxy that rewrites shell commands so that their output uses fewer tokens. Under Claude Code, the PreToolUse hook rtk hook claude does the rewrite. harness install copies the file to ~/.claude/RTK.md.
The file tells the agent:
rtk run,rtk proxy,rtk curlandrtk wgetare denied;rtk hook check '<cmd>'shows whether a command is rewritten;- in Claude Code, a pipeline or a redirection turns the rewrite off for the whole command;
headandtailare not rewritten;rtk grepandrtk rgstop at a limit per file, so read the first and the last lines;- use the
Readtool when the exact bytes matter.
Four frontends read it, each its own copy. CLAUDE.md imports ~/.claude/RTK.md. OpenCode lists its copy in the instructions of opencode.jsonc. OMP-DELTA.md imports oh-my-pi's copy, but oh-my-pi runs no rewrite. Antigravity loads its copy, ~/.gemini/config/rules/RTK.md, as a rule with trigger: always_on, and runs no rewrite either.
claude/statusline-command.sh
The status line of Claude Code. The script reads the session data, as JSON, from its standard input with jq. It prints one line: the model, the effort, the input and output tokens, and the use of the five-hour and the weekly rate limits. A use below 50 % is green, below 80 % yellow, and from 80 % red. The five-hour limit also shows the time until it resets.
harness install copies the script to ~/.claude/statusline-command.sh, with mode 0644. The settings template does not name the script. Point the status line setting of Claude Code at the installed copy.
The script uses POSIX sh, jq and date +%s only, so it runs on macOS and Linux. Without jq the line shows no values.
opencode/adapter.py
The adapter of OpenCode. It writes OpenCode's part of harness install, and it gives the subcommand harness permissions.
The install writes into $OPENCODE_CONFIG_DIR, or ~/.config/opencode if it is not set. The directory must exist. The adapter writes:
opencode.jsonc, rendered with the profile, with a backup of a file that it replaces;AGENTS.md, fromOPENCODE-DELTA.md;agents/<name>.mdfor each agent ofagents/;- the plugins
plugins/*.tsand the renderedplugins/guard-paths.json; - its own copies of the files of the Claude Code layer that OpenCode reads:
RTK.md,instructions/,rules/, the guard hooks inhooks/and the skills inskills/.
In each text except AGENTS.md, a path below ~/.claude/ names the same path in OpenCode's directory, so OpenCode reads its own copies. The install also removes the links in ~/.agents/skills/ that an earlier install wrote, because OpenCode reads that directory too.
The port of an agent maps the model tier through the model tables of models.toml, which OpenCode shares with oh-my-pi, with the keys of [opencode] over them. It turns tools: and skills: into a permission: block, and adds an "Under OpenCode" section before the body. A council is a set of copies of an agent on other models, and a seat is one of these copies. The description of a seat with verify = true says that it also judges each verify round, a later critic round that checks only the fix. The install refuses to replace an opencode.jsonc that holds a literal Authorization value, unless you give --force.
harness permissions [--apply] writes the permission block of opencode.jsonc and plugins/guard-paths.json from the ask and deny rules of the settings template. Its report shows that every rule is emitted or dropped with a reason.
fixtures/ holds the input agents, their expected rendered output, a settings file, and the expected permission block, path list and report. The cases compare the output with them.
opencode/opencode.jsonc
The configuration template of OpenCode. harness install renders it with the profile and writes it to opencode.jsonc in OpenCode's configuration directory.
It sets the default model and the model for background work. Its instructions list RTK.md, instructions/core.md and instructions/research-tree.md, because OpenCode does not follow a file reference in an instruction file. The template names them below ~/.claude/, and the install writes the paths of OpenCode's own copies. The provider entries come from providers.toml beside the profile, as profile.md maps them. Before them, the install writes one entry for each provider of the context_limits of models.toml, with that limit. When a provider is in both, the install stops with exit status 2. The mcp entry connects to the Kaimon server at http://127.0.0.1:2828/, which launchagents/kaimon.plist starts. The token comes from ~/.config/kaimon/auth-token when OpenCode reads the file, so no secret is in the repository. experimental.subagent_depth is 2, so that a builder that the dispatcher starts can start julia-branch-verifier or a reader, and an editor can start a writer. The dispatcher is the session that runs the build-part or build-reviewed skill. OpenCode allows one level by default.
The permission block is generated. Do not edit it by hand: run harness permissions --apply, which writes it from the settings template. OpenCode takes the last rule that matches, so each ask comes before each deny. The allow rules of the template are not ported. OpenCode matches a path pattern against the path relative to the project root, so each **/X read rule is followed by its root form X, which plugins/guard-paths.json leaves out. external_directory starts with "*": "deny": OpenCode's built-in rules allow every path, and a file tool is refused outside the allow entries that follow.
OpenCode has no sandbox. This permission block is its only control, and a command inside a string argument escapes it.
opencode/OPENCODE-DELTA.md
The global instruction file of OpenCode. harness install writes it as AGENTS.md in OpenCode's configuration directory. There it takes the place of ~/.claude/CLAUDE.md. The core rules, RTK.md and the tree instructions load beside it through opencode.jsonc.
It states the facts of OpenCode:
- which rule file to read before which kind of work, because OpenCode loads no rule by itself;
- there is no sandbox, and
external_directoryis the one boundary; - pass
workdirand absolute paths, and do notcdinside a command; - there is no session scratchpad, no worktree hook and no background run;
- the plugins
rtk.tsandguards.tstake the place of the hooks; opencode --autoremoves every prompt, so do not use it to push, merge or release;- how to call the agents, and the council of critics of
build-part.
The file loads into every session, so keep it small. opencode.jsonc gives its budget as 5,120 bytes.
opencode/plugins/env.ts
An OpenCode plugin that sets environment variables for every shell command. OpenCode has no env key in its configuration, so the plugin sets the variables in the hook create.before of the shell.
It sets the variables of the env of the Claude Code settings:
JULIA_PKG_USE_CLI_GIT=true: Pkg uses thegitprogram and not libgit2.JULIA_NUM_THREADS=2andOPENBLAS_NUM_THREADS=2: a part runs up to three critics and a suite at the same time, and without a limit each Julia process starts one BLAS thread per core.
It does not port CLAUDE_CODE_MAX_OUTPUT_TOKENS, which has no meaning for OpenCode.
harness install copies the plugin to plugins/env.ts in OpenCode's configuration directory. The hook probe hooks/probe.py loads it under node, so it holds JavaScript syntax only.
opencode/plugins/guards.ts
The OpenCode plugin of the guard hook. On every tool call it runs the install's copy of the guard package in hooks/guard/ of OpenCode's configuration directory, with --from opencode and the interpreter that guard-paths.json beside the plugin names under python. It sends the event and the session's directory as JSON on standard input, and throws the reason of an answer that blocks, which refuses the call. The guard's table checks the shell call and every Kaimon call kaimon_<tool>, and lets every other tool pass; Guard hooks describes each check, the path list and its list in $OPENCODE_CONFIG_DIR, and the refusal of an in-place edit such as sed -i.
The plugin fails closed. A list that is missing or holds no string python, a guard that cannot start, exits with another status than 0, prints no valid answer or does not answer within 10 s refuses the call. The guard refuses every call when its list cannot be used, and so does a $OPENCODE_CONFIG_DIR/guard-paths.json that is present and cannot be read or is not JSON. A check that would ask refuses too, and the model must ask the user in chat.
The path list checks text only. A path that a command builds at run time passes it.
opencode/plugins/guard-paths.json
The deny list of the guard's path check under OpenCode. It holds the read denies of OpenCode's permission block: the Read denies of the settings template, and one deny that only OpenCode needs, its own data directory.
harness permissions --apply writes the file from the settings template. Do not edit it by hand. The paths hold {home}, so harness install renders the file with the profile and writes it to plugins/guard-paths.json in OpenCode's configuration directory, with the environment's interpreter under python.
A read deny binds only the file tools of OpenCode. With this list, the guard refuses a shell command that names the same path, for example a cat of a credential file.
The oh-my-pi adapter reads the list too. Its deny becomes the deny of oh-my-pi's path list.
opencode/plugins/rtk.ts
The OpenCode plugin of RTK. RTK has no hook of its own for OpenCode, so this plugin does the rewrite. On each shell call it runs rtk hook check <command>. Exit 0 gives the rewritten command, which replaces the command of the call.
The plugin never fails a command. When RTK is absent, fails or takes more than 10 s, the command runs as it is. It calls rtk from the PATH of OpenCode, as the hook of Claude Code does. harness install warns when no rtk is on the PATH; the warning does not change its exit status.
The rewrite differs from the hook of Claude Code: the plugin also rewrites the first command of a pipeline. So grep -n p f | cat stops at the limit of rtk grep. For a complete search, use the grep tool.
harness install copies the plugin to plugins/rtk.ts. OpenCode runs the plugins in the order of their names, so guards.ts checks a command before rtk.ts rewrites it.
omp/adapter.py
The adapter of oh-my-pi. It writes oh-my-pi's part of harness install and gives no subcommand of its own.
The install writes into oh-my-pi's agent directory, $PI_CODING_AGENT_DIR, or ~/.omp/agent if it is not set:
harness.yml, an overlay over oh-my-pi's ownconfig.yml, which the install never writes (see below);modelRoles.defaultinharness.yml, themediummodel of the model tables, and an emptyretry.fallbackChainsentry for each model of an agent or a council seat, so that a provider error does not move an agent to another model. A model whose chain inconfig.ymlnames only the same model through another provider keeps that chain;models.yml, after a backup: the providers ofproviders.tomlbeside the profile, and each model of thecontext_limitsofmodels.tomlwith its limit ascontextWindow. The install stops with exit status 2 when a limit is on a provider ofproviders.toml;extensions/guards.tsand the path listextensions/guard-paths.json;AGENTS.md, fromOMP-DELTA.md;rules/<name>.mdfor each rule that the Claude Code layer installs, withpathswritten asglobs;mcp.json, with the Kaimon server alone and a request timeout of 300 s, above Kaimon's own move of a slow eval to the background after 240 s;agents/<name>.mdfor each agent ofagents/, with its model and itsthinking-levelfrom the model tables and the section ofUNDER-OMP.md, and a copy for each seat of its council;- its own copies of
RTK.md,instructions/, the guard hooks inhooks/and the skills inskills/. In each text exceptAGENTS.md, a path below~/.claude/names the same path in oh-my-pi's directory.
harness.yml sets the approval mode yolo and denies the eval tool. It writes the Bash rules of the settings template as bash.patterns: every deny, then every ask as prompt, then every allow. A command that matches no pattern runs under yolo; the guard extension refuses a gh pr create that names no repository and blocks one outside the own organisations. oh-my-pi reads the file when PI_CONFIG_FILES in ~/.omp/agent/.env names it, and the install warns until it does. oh-my-pi has no env setting either, so the install also warns, with the lines, until the .env or the environment sets each variable of the template's env except the CLAUDE_CODE_* variables.
oh-my-pi takes the first pattern that matches, so this order keeps the precedence of Claude Code. The model tables are the ones that OpenCode reads, with the keys of [omp] over them, so the agents of both frontends run on the same models. The install exits with status 2 when a rule source is outside the grammar of render_rule or in a subdirectory of rules/, and when the table TOOLS does not map a tool. An installed rule, agent or skill with no source is an EXTRA line.
The adapter has no fixtures/ directory. Its cases run the install on a scratch HOME.
omp/OMP-DELTA.md
The global instruction file of oh-my-pi. harness install writes it as AGENTS.md in oh-my-pi's agent directory, byte for byte. Its first three lines import oh-my-pi's copies of RTK.md, instructions/core.md and instructions/research-tree.md, below ~/.omp/agent/.
It states the facts of oh-my-pi:
- there is no sandbox, and
bash.patternsand the guard extension are the two controls; - oh-my-pi runs no RTK rewrite, so a command runs as typed;
- the approval mode is
yolo, and theevaltool is denied; - what the guard extension refuses;
- there is no session scratchpad, no worktree hook and no background run;
- read a
CLAUDE.mdbelow the working directory yourself; - read a rule through
rule://<name>before work on a file that its globs match; - the skills are oh-my-pi's copies in
~/.omp/agent/skills/, read throughskill://<name>; - each agent's model is in its frontmatter, and a council seat needs its provider's credentials;
- the dispatcher continues an agent by starting a new one, never by a write to
agent://, and runs a command that a sub-agent returns for approval; - the council of critics of
build-part.
The cases check that the file is no larger than OPENCODE-DELTA.md.
omp/UNDER-OMP.md
The text of the "Under oh-my-pi" section that the oh-my-pi adapter puts before the body of an agent. The section replaces each Claude Code mechanism that the agent uses.
The file has one level-2 section for each mechanism: intro, skills, worktree, agent, bash, write, pr and mcp. render_agent takes intro, then the section of each mechanism that the agent uses: preloaded skills, worktree isolation, the Agent tool, the shell tool, the write tool, a body that opens the pull request, or a Kaimon tool. bash has a sub-agent return a command that needs approval to its caller; write forbids a write to another agent; pr has the builder return gh pr create to its caller. In skills, {skills} becomes the names of the agent's skills. An agent that uses none of these mechanisms gets no section.
Edit this file to change what every ported agent reads under oh-my-pi. When a section that the adapter needs is missing, the install exits with status 2.
omp/guards.ts
The guard extension of oh-my-pi. harness install copies it to extensions/guards.ts in oh-my-pi's agent directory. On every tool call it runs the install's copy of the guard package in ~/.omp/agent/hooks/guard/, with --from omp and the interpreter that guard-paths.json beside the extension names under python, sends the event, the working directory and the kind of the agent as JSON, and returns the answer {block, reason} when it blocks. The guard's table checks each tool call that a glob of bash.patterns cannot check:
bash: the shell checks of the guard, the working-tree check of the sandbox check among them, and the path list on the words of the command and on its working directory;mcp__kaimon_<tool>: the Kaimon check;readandgrep: thedenylist on the path;editandwrite: thedenylist, then theeditlist, then theasklist, on each path that the call names;writein a sub-agent: a pathagent://<name>, which it refuses, because the write wakes a parked agent. oh-my-pi gives a sub-agent's contextagent.kind: "sub".
A word with a glob character reaches only the names that its glob segment matches, so a pattern such as /[0-9]+/ reaches no denied path below /.
The path list is guard-paths.json beside the extension. The deny and edit lists also get ~/.omp/**. The copies of the skills, the rules and the instructions below ~/.omp/agent/ stay readable. The match ignores case. An ask match and an "ask" answer of a check refuse the call, and tell the model to ask the user in chat.
The extension fails closed: a list that is missing or holds no string python, a guard that cannot start, exits with another status than 0, prints no valid answer or does not answer within 10 s blocks the call. The guard checks text only. A path that a command builds at run time passes, and the find and ast_grep tools are not checked.
antigravity/adapter.py
The adapter of Antigravity. It writes Antigravity's part of harness install and gives no subcommand of its own.
The install writes below ~/.gemini/, once Antigravity has made that directory; without it, the plan holds no file and one warning. The desktop app, the IDE and the CLI agy read the one directory ~/.gemini/config/. The adapter writes:
config/hooks.json, with oneSessionStarthandler, which runsinstall-drift.pyand gives its warning to the session, and onePreToolUsegroup with the matcher*, which runs the guard package with--from antigravityby its absolute path on every tool call, each on the interpreter of the harness's Python environment;- the Claude Code layer's copy of the guard package in
config/hooks/guard/and ofinstall-drift.pyinconfig/hooks/, and the path listconfig/guard-paths.json; config/GEMINI.md, fromANTIGRAVITY-DELTA.md;config/rules/<name>.md:RTK.mdand each file ofinstructions/as a rule withtrigger: always_on, and each rule ofrules/with itspathsastrigger: glob;config/skills/<name>/, the skills, eachSKILL.mdwithout itsmodel:;config/agents/<name>.mdfor each agent ofagents/, with its model from the table[antigravity]ofmodels.toml, its tools from the tableTOOLS, and the section ofUNDER-ANTIGRAVITY.md;config/mcp_config.json, with mode 600 from its first byte and no backup: the file as it is, every other byte kept, with the entrykaimonset to Kaimon's URL and its token from~/.config/kaimon/auth-tokenwritten out, because Antigravity expands no shell form in a header. With no token, the install leaves the file as it is and gives one warning;- three keys of the CLI's
antigravity-cli/settings.json:permissions,toolPermissionandenableTerminalSandbox. The merge keeps every other byte of that file (ownedjson.py), and backs up the file it replaces; - the same permission rules in the desktop app's grants,
userSettings.globalPermissionGrantsofconfig/config.json: each rule once in its list,allow,denyorask, after the entries there. The merge keeps every other byte of the file, so a minified file stays minified, removes no entry, and backs up the file it replaces. The app's grants per project, inconfig/projects/, stay as they are.
In each text except GEMINI.md, a path below ~/.claude/ names Antigravity's copy, and RTK.md and the files of instructions/ are in config/rules/. Antigravity loads no glob rule of config/rules/, so the delta lists each rule with the files it applies to.
The permission rules are the settings template's: a Bash entry becomes a command(…) rule, a Read or Edit path a read_file(…) or write_file(…) rule, and an allow host of the network lists a read_url(…) rule. A * of an allow rule in regex form matches no text that ends a segment, so the rule never matches across ;, |, &, a substitution or a redirection. A path with a glob that Antigravity cannot express is dropped, and the guard's path lists cover it. Each Kaimon tool of the template, mcp__kaimon__<tool>, becomes mcp(kaimon/<tool>) after the other rules of its list, in deny before the rules of denyWrite below: the app needs one grant per Kaimon tool, and the wildcard mcp(kaimon/*) has no effect there. A Kaimon tool that the template does not name gets no rule, and the app asks for it. Last in allow, the first word of each command of the template's sandbox.excludedCommands, which Claude Code runs outside its sandbox, becomes a command(<word>) rule, unless the list holds it: command(gh), command(git), command(glab) and command(ps), in the CLI's settings and in the app's grants. No unsandboxed(…) rule is written. The guard applies the template's Bash denies and asks before these rules, so git push --force and git add -A are still refused, and gh pr merge still asks. The guard's path list refuses a write to config/config.json by a tool that writes or edits a file, so an agent cannot widen its own grants with one. A command that writes the file gets ask from the guard, not deny.
The guard hook is the guard package of hooks/guard/ run with --from antigravity: its Antigravity table reads each tool call and answers with a decision. It runs the eight shell checks and the working-tree check of the sandbox rule on run_command, the Kaimon check on a Kaimon call, and the deny list of guard-paths.json on each path argument and each word of a command. A glob in a word, a recursive command, a cd, the Cwd of a call and the SearchPath of a search also refuse a directory that holds a path of the list. On run_command it applies the template's Bash denies and asks, from the lists bash_deny and bash_ask, to each segment of the command, the bodies of $(…), backticks and ( … ) included. A rule matches the words of a segment from any word, so an assignment, a wrapper such as env or a keyword such as then before the command does not hide it, and a quoted word is one word. On a tool that writes or edits a file, multi_replace_file_content included, it also applies the edit list, the template's Edit denies, and the ask list, its Edit asks. The deny list holds the files of the layer itself. A refusal becomes deny, an ask force_ask, and a pass ask, which leaves Antigravity's own rules in force. It fails closed: a payload that it cannot read, a path list that is missing or malformed, and a relative path of a call with no workspace give deny, and a guard that raises prints no decision, which Antigravity reads as a block. A run_command that it does not deny, a force_ask included, also gets "overwrite": {"BypassSandbox": …}: true only when the guard's lift check lifts the command, where Claude Code's sandbox rule lifts it, the whole line matches a closed grammar and none of the screens applies, else false; a deny and a call of any other tool carry no overwrite.
Last in deny, each entry of the template's sandbox.filesystem.denyWrite, the paths that Claude Code's sandbox does not let a command write, becomes a read_file(<path>) rule, with ~ as the home directory, unless the list holds it, when the entry is at or below the research root. An entry outside the research root, such as ~/.gitconfig, renders no rule and no warning: the app's sandbox mounts only the workspace, so a sandboxed command cannot write there anyway. The guard lifts git out of the app's sandbox, so a .git/hooks or .git/config that a sandboxed command could write would run outside it. The rule is a read_file deny because of a probe in the app. A write_file deny of a path below the workspace hid much more than its path, all of ~/Research/.scratch in the probe. A read_file deny of <repo>/.git/hooks and <repo>/.git/config stopped a sandboxed command from reading .git/config and from writing either path, and left the rest of the workspace readable. It also hides the two paths from a sandboxed read. A sandboxed git cannot read ~/.gitconfig and fails anyway, and a lifted git runs outside the sandbox; a sandboxed tool that reads .git/config itself, such as a library that finds a repository's remote, can no longer read it. The app honours no wildcard in a path, so an entry ~/Research/**/<rest> becomes one rule <repo>/<rest> for each directory at or below the research root that holds a .git directory, nested repositories included. The walk is the one of harness trust: it skips .git, .scratch, .worktrees and node_modules, and follows no symbolic link. A worktree, whose .git is a file, is covered by its main repository. The rules name each repository by its real path. One warning of the dry run names the number of repositories that these rules cover. A repository cloned after the install is covered after the next install, and the dry run then shows the changed number and config/config.json as a change. Any other entry with a wildcard whose directory before the first wildcard is the research root, above it or below it, gives a warning that names it, and no rule.
The install exits with status 2, before it writes a file, in these cases among others:
- the CLI's
settings.json, the app'sconfig.jsonormcp_config.jsonis not a regular file or not a JSON object thatjsonio.pyreads, such as one with a key twice in one object; - the app's
userSettingsorglobalPermissionGrantsis not an object, or itsallow,denyoraskis not a list; - two sources hold the same path;
- an instruction file or a rule holds an include
@[…](…)or has 24,000 bytes or more, or the global file and the always-on rules together reach the budget that they share, 80,000 bytes; a rule withpathsloads only for a matching file, so it is not in the budget; models.tomlhas no[antigravity]table, or the table does not give each tier one of the wordsinherit,flash,flash_liteandpro;TOOLSdoes not map a tool of an agent.
A council of models.toml gives one warning for each agent that it names, and writes no seat. An installed rule, skill, hook or agent with no source is an EXTRA line.
fixtures/ holds the expected frontmatter and "Under Antigravity" section of four rendered agents, which the cases compare.
antigravity/ANTIGRAVITY-DELTA.md
The global instruction file of Antigravity. harness install writes it as ~/.gemini/config/GEMINI.md, byte for byte, in place of ~/.claude/CLAUDE.md. The core rules, RTK.md and the tree instructions load beside it as always-on rules.
It states the facts of Antigravity:
- the two controls: the terminal sandbox of
agy, with the rules of itssettings.json, and the guard hook on every tool call, in the app and inagy; - the guard hook decides the sandbox of each command, so the agent does not set
BypassSandbox; - in the app, a call of a subagent that needs a grant shows the app's dialog, as a call of the main agent does;
- Antigravity runs no RTK rewrite, so a command runs as typed;
- there is no worktree hook: make the worktree with
git worktree add; - the
Agenttool isinvoke_subagent, a subagent replies throughsend_message, and no agent sets an effort; - each agent's model is the Antigravity tier of
[antigravity], and a council has no seat; - read each
CLAUDE.mdon the path to the working directory yourself, because Antigravity reads none; - read a rule of
~/.gemini/config/rules/before the work that it names, because none loads by itself; - the skills are Antigravity's own copies in
~/.gemini/config/skills/; - there is no session scratchpad.
antigravity/UNDER-ANTIGRAVITY.md
The text of the "Under Antigravity" section that the Antigravity adapter puts before the body of an agent. The file has one level-2 section for each mechanism: intro, skills, worktree, agent, background, mcp and pr. render_agent takes intro, then the section of each mechanism that the agent uses: preloaded skills, worktree isolation, the Agent tool, a run_in_background in the body, a Kaimon tool, or a body that says to open a pull request. In skills, {skills} becomes the names of the agent's skills. An agent that uses none of these mechanisms gets no section. When a section that the adapter needs is missing, the install exits with status 2.