Book a call
BUILD30mVERIFIED 2026-08-05 · CLAUDE CODE 2.1.221 · CODEX CLI 0.146.0 · GITHUB COPILOT CLI 1.0.78 · ANTIGRAVITY CLI 1.1.10 · KIMI CODE CLI 0.31.1

The repo that explains itself to an agent

Your setup works because of things that never leave your laptop. Build the committed layer that answers somebody else's agent, and a drill that shows whether it does.

What you end up with

The index file is a root file with less than 20 lines, we've added some pointers instead of writing more. The actual content is available in separate documents, they wait in peace until somebody needs them. A pointer file only for the harness that needs one — out of the six we've probed, that was only Claude Code, which doesn't find an AGENTS.md in the root of the repo on its own. Also, the clone-drill script, a test that runs the repo against a fresh clone rather than against our local working copy.

Not implemented: skills, hooks or MCP servers. These are other topics, it's good to have them but they don't help if a new person can't run tests on their first day here.

It took us around half an hour, 50% of which was spent on removing already existing things.

The reader isn't you

Your setup works, and that's the problem, because the context that comes with the repo is indistinguishable from the context that comes with your machine. The repo contains everything git stores (the source code) but not these:

  • the conversation we had about deploying

  • the instructions file in your home directory

  • your shell history

  • the configuration you've set up to tell the model what to choose

If you clone the repo, none of this is there. Also, the way your agent differs from your colleague's agent is that it doesn't have any of these.

We've checked six harnesses and two didn't find the AGENTS.md in the root of the repo even though we had one — for Claude Code we needed to create a separate pointer file for it and for Antigravity CLI it ignored it entirely.

It's not only about "less context", it's also about the fact that if sth is there, doesn't mean it will find it. We've created a repo with a real document in it that contains an actual answer (that you run make unstick-staging to deploy to staging) and then asked our agent in this directory what to do before deploying to staging — and it replied UNKNOWN even though the file was in its working dir. Then we've added a single line in the index saying what the file is called and have asked again — and it returned the right answer, with the part copied from the file, also saying that it hasn't found this target in Makefile.

It's all about the same file, the same directory and the same question, the only thing that differed is this line in the index. This way agents don't find things, they find things that are pointed to.

Push and pull

There are two types of pieces of information, everything that can be found in a repo falls into one of these types, and the goal is to assign every fact to one of them:

  • push — gets loaded before any prompt (the memory file, its imports, always-active rules), occupies the context until the end of every session that loads it

  • pull — everything that's findable and readable when sth needs it, zero cost until read

It's easy to fall into a trap here, because for example Claude Code documentation says that splitting the memory file into @path imports is good to keep things tidy but doesn't change the context as they get loaded with the memory file. An import is push disguised as pull.

On one of our repos Codex answered an index-resident question in 535 tokens, asking it to read a document cost it 4,353 tokens — in that one session.

For push things, they need to be such that if somebody were to make a mistake it would be costly:

  • command nobody will think about

  • a folder with generated code

  • any decision making it hard for the agent to improve smth it's not supposed to improve

For pull things, these are things that might be needed in one out of twenty sessions — specifics of the test suite, conventions used for endpoints, release process or rationale behind the queue configuration.

Write the index, not the manual

The index file contains five headings now:

  • Commands

  • Landmines

  • Decisions

  • Where things go

  • Where the detail lives

MARKDOWN
## Where the detail lives
- Running and debugging the test suite: `docs/testing.md`
- Adding a new endpoint: `docs/endpoints.md`
- Why the queue is configured this way: `docs/queue.md`

The fifth one lists question-shaped pointers to paths of documents (about the test suite, about adding an endpoint and about the queue). Remember:

  • to point to questions, not subjects — "Running and debugging the test suite" rather than "Testing", agents appear with a question and closer wording means less thinking for them

  • to point to paths using backticks — they're code verifiable, titles aren't; in Claude Code backticks also help avoiding treating an @path as a launch-time import which defeats the purpose of it

  • to list one thing per line — if you feel like you need to write more than a line, create a document and point to it from the index instead

The documents should be written with a goal to be read, not to make yourself feel better. They should be single-purpose, single-screen and contain the failure sentence — for example, on one of our projects make test alone says could not connect until you run make db once. Every Claude Code run we did printed that sentence next to its answer without being asked, and that sentence is the reason the document exists.

Verbs the agent can read

The most valuable thing when it comes to task runners is that you can have one that has a target list that documents itself. For example, just --list outputs such a thing directly from the recipe's comments. That way you get:

TEXT
Available recipes:
    db   # Start the test database container
    test # Run the test suite (needs `just db` first)

and a Makefile gets you the same thing — a ## comment per target and a help that greps them out:

MAKEFILE
help:  ## List tasks
	@grep -E '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed 's/:.*##/ —/'

db:  ## Start the test database container
	@docker compose up -d postgres

test:  ## Run the test suite (needs make db first)
	@cargo test

because the CI runs the same targets so the list can't lie, but prose documentation in the repo can be outdated for a year. Also, we've asked a cold agent what tasks are available and it turned out make help needed an approval it couldn't get in that session, so it read the Makefile instead and returned the same three targets.

It's all about the fact that the list is placed somewhere readable rather than just runnable. Well, not always — Antigravity headless mode denied a file read outright for want of an allow rule — but wherever the gate is on running the command, a readable target list walks straight past it.

The clone drill

We've created a script called clone-drill, it's a test that runs the repo against a fresh clone of its HEAD in a temp directory and asks a cold agent questions that you ask on your first week here:

  • There are two sources of local self-deception — files you haven't commited and files you've added to .gitignore. A clone doesn't have any of these.

  • The one it doesn't address is the home-directory configuration, because a clone on your machine is still on your machine — for that one, give the drill to somebody else and see what they get.

It's a two-part script:

  • It runs the free part which makes sure that every backticked path in the index points to an existing file in the clone (you might need to tweak this part to suit your index)

  • Then it runs the billed part asking these questions, we've limited it to three or four

You can run it like this:

BASH
./scripts/clone-drill.sh

Or:

BASH
AGENT='codex exec' ./scripts/clone-drill.sh

And it costs around one credit to ask three questions in the harness that shows price. In the harness that shows tokens it's 1.5k–4.4k per question so you can't run it on every commit but if you ever change the shape of the repo, it's reasonable to do. Also, we've set this script up to assess the minimum:

  • If the repo doesn't have an answer to a question, we instruct the agent to reply with UNKNOWN and the script checks if it says that

  • That way we can say if the answer is there or not but we can't say how good it is — that part is your eyes, not the exit code

In the first version we were checking the entire response but it turns out that Codex includes the prompt in its transcript so two correct answers were marked as failed. That's why we changed it to check only the last line.

Loaded and sufficient are different failures

The canary is a different kind of test, it's good to have one (to make sure the file makes it to the model) but this test aims for sth else — to make sure that the context that comes with the repo and the context that's findable in it is enough. A repo might pass the canary and still won't be able to answer any question:

  • Loading sth is binary, this or that, it either makes it or doesn't

  • The other one, having enough context, is where the week goes

IN YOUR HARNESS

In Claude Code

Claude Code loads CLAUDE.md instead of AGENTS.md (which is outlined in the official docs). It's reasonable to make AGENTS.md the main index and then import it in CLAUDE.md. For example, the CLAUDE.md file could look like this:

MARKDOWN
@AGENTS.md

## Claude Code
Use plan mode for changes under `src/billing/`.

This way it's easy to differentiate between importing a file and just referring to it. We've tried both. With the @AGENTS.md import, starting a new agent and asking it about sth from the index, it responded without using any tool (even though it could have). With the prose line instead — "Project notes for this repo live in AGENTS.md. Read it when you need them.", an instruction to read it, in plain English — it returned UNKNOWN when we asked it the same question.

The official docs state that imported files are being expanded and loaded into the context during initialisation, so if we were to put the path of the file in backticks it would just be a piece of text. But without them it's an import.

Which files are shared vs. personal:

CommittedYours
CLAUDE.md, .claude/CLAUDE.mdCLAUDE.local.md
.claude/rules/*.md~/.claude/CLAUDE.md, ~/.claude/rules/
.claude/settings.json.claude/settings.local.json

If you use Claude Code to create settings.local.json in a repo that doesn't have an entry for ignoring this file in .gitignore, it will be added to the user's global git excludes, so everything that's stored there is not visible in the repo and for other collaborators (which is what you want, but it's easy to lose track of which part of the config is personal).

The script is run with the default config: ./scripts/clone-drill.sh

In Codex CLI

The AGENTS.md file is placed at the root of the repository, so there's no need to create a pointer file for it. We've run a cold codex exec in a fresh clone and the index reached it unaided: 535 tokens for the question the agent answers without accessing any docs, 4,353 tokens for the question that requires it to open a doc first. These two figures reflect the two types of costs.

What doesn't travel is your config. codex --help describes -c as overriding "a configuration value that would otherwise be loaded from ~/.codex/config.toml", and the examples it gives are model and sandbox_permissions — a per-user file outside the repository. So if somebody clones your repo they'll get your instructions but none of your defaults; everything your teammates should have must be part of the index, never in your personal config.

If you run into any issues, make sure to run codex doctor before blaming the repo — it reports the installation, config, auth and runtime status.

To explore this, run the clone-drill script with the agent set to Codex's exec mode.

BASH
AGENT='codex exec' ./scripts/clone-drill.sh

Just be aware that it logs the prompt to its transcript (that's why we check only the last line of the response) — initially we were counting two right answers as UNKNOWN just because it was included in the echoed prompt.

In GitHub Copilot CLI

Copilot reads AGENTS.md files "stored anywhere within the repository", and "the nearest AGENTS.md file in the directory tree will take precedence." The index in the root of our repository reached it out of the box after we ran copilot -p in a fresh clone, no need to use any of the --allow- flags, as we don't need to do any tooling during the process.

Also we have two more files created for the entire repository and set up in version control, so that the whole team is using it — .github/copilot-instructions.md (repo-wide) and NAME.instructions.md in .github/instructions/ (path-scoped). Per GitHub docs, these work in an additive way, so it's best to keep them short with index pointing to them.

But especially the first one is crucial, as it applies to all requests made in the context of our repository, including GitHub chat and code review — so if we have a company that already paid for Copilot, the majority of team members will be subject to it using this file, even if they don't use terminal.

To test it, run the drill with copilot set as the agent:

BASH
AGENT='copilot -p' ./scripts/clone-drill.sh

You can see the costs of every run in the footer — 1.02 AI Credits for the entire three-question drill. It's the cheapest way to estimate what the paid part would cost you before you decide to actually go for it.

In Cursor

We have 2 types of instructions in Cursor, they're positioned on push/pull axis differently.

  • We can create AGENTS.md files (and nest more of them in subfolders)

  • There's also .cursor/rules directory with .mdc files (versioned as part of the project), each of them containing a rule definition that declares its own activation condition:

    • always-on — for rules that should always be applied

    • glob — for rules that should be applied if the current path matches a glob pattern

    • ai — for rules that the AI decides to apply

    • @ — for rules that should be applied only if they're explicitly mentioned using @

  • The "always-on" is the only push mode in terms of this classification, the other 3 are pull modes and differ by what triggers them

    • other harnesses can scope instructions too, but Cursor is the one that makes the trigger an explicit property of the rule itself

    • thanks to this you can even move entire docs/ tree into .cursor/rules/ directory, but in a disciplined manner of not creating any "always-on" rules unless really necessary

  • Keep in mind that an "always-on" rule counts like a line in the index — it costs you every session

  • The extension matters too, for example if you create an api-guidelines.md file under .cursor/rules/ Cursor won't find this rule as it assumes that a rule must have a frontmatter

In the terminal:

BASH
AGENT='agent -p --output-format text --trust' ./scripts/clone-drill.sh
  • As you can see, we need to provide the --trust flag here and that's what the drill is about, making sure you're aware of this requirement

  • If you clone it freshly, the directory is not trusted so Cursor won't run cursor-agent inside it and will just print its path with info that you can either run it interactively or pass the --trust, --yolo or -f flag to proceed

  • Here we should go for the --trust, as per its help message it trusts the current workspace without asking and doesn't do anything else. The other two flags (--yolo and -f) are actually aliases for --force, which is a "whatever" flag that allows every command to be run unless there's some specific rule against it

In Antigravity CLI

It's good to try it before using. For example, on v1.1.10 after cloning with AGENTS.md in the repo root we asked agy -p for a string that's present only in this file. It responded with UNKNOWN. Then we created GEMINI.md file with some another string and tried again. Still UNKNOWN. It looks like none of these files was loaded into LLM during the headless run. In fact, we tested it on v1.1.9 too and got the same UNKNOWN in both scenarios, so this is not bound to a single release. Not sure what's going on behind the scenes here, but for now, we'd say treat it as unverified until you check, not as not working (unless you face the same). We were testing the headless -p mode only.

The pull direction has a gate of its own. We tried to make it read the file itself and that didn't work either, the output said:

TEXT
a tool required the "command" permission that headless mode cannot prompt for, so it was
auto-denied. Add an allow-rule under permissions.allow in settings.json (e.g. command(<target>)).

So on that run the tool it needed was denied before it could read anything, and the fallback route was shut too. This means that to make it work we would need to whitelist this particular narrow permission under permissions.allow in settings.json (not --dangerously-skip-permissions, which the message suggests and which basically disables any restrictions).

All the user data is located under ~/.gemini/ (settings, trusted folders, credentials, list of projects etc), skills are under ~/.agents/skills/. That means that compared to any other entry this is the biggest difference between one person's setup and another's as everything is stored locally in the user's home, not in the repository.

To test it, run clone-drill script with agy -p as an agent:

BASH
AGENT='agy -p' ./scripts/clone-drill.sh

You should be asked questions that your index should know the answers to and they will return UNKNOWN until you make sure that the project file is loaded. That's a finding about the setup, not a bug in the script.

In Kimi Code CLI

AGENTS.md in the root of the repo, read with no pointer file. We ran a cold kimi -p in a fresh clone and it not only answered from the index, it said where the answer came from: "Canary is 4412-albatross per AGENTS.md." Free provenance, and a fast way to tell whether an answer came from your index or from the model making things up.

But be mindful that we have another AGENTS.md in ~/.kimi-code/ (which Kimi docs call global agent instructions, specific for Kimi), which is located based on KIMI_CODE_HOME and is a per-user config being used in every project you open. The project facts stored there are available to you only and aren't available to anyone else — that's the very thing we're aiming to address here; so it's best to use it for preferences

Running kimi doctor shows all the configs that are being validated by it, including their paths, so if you wonder what home directory is used, it's the fastest way to check, before you explore the project for the missing instruction

So let's use the shared clone-drill script, and pass Kimi's non-interactive command as an agent:

BASH
AGENT='kimi -p' ./scripts/clone-drill.sh

Kimi puts that provenance line before its answer, so the script's last-line grading matters here for the same reason it does on Codex.

When six people edit it

If you have multiple maintainers, the index becomes an infrastructure and the problem changes from potential mistakes to potential lack of ownership. To address these things:

  • List the context files in CODEOWNERS so the review request lands on somebody automatically and they notice the stale pointer — not to block the merge, that's a separate branch-protection setting you don't need here

  • Attach the free part of this script to some other push automation so somebody can run it even when the team is busy (it doesn't need any model, any credentials or the network), keep the billed part for cases when you change the shape of the repo

  • Make sure that every new line in the index replaces an existing one or is a pointer (so it doesn't turn into a manual and each future session becomes a bill)

  • Find out which of your files are personal — in most harnesses there's one you commit with the project and another you keep in your home directory, and the block above names the ones we could verify for yours. If you skip this, there might happen:

    • A — your personal preferences in the index file will change people's sessions without them noticing

    • B — your project's facts in your home-directory configuration won't be in the repo and therefore won't be in their sessions

What we'd leave out

We don't think these are good ideas:

  • A per-person private context file — if a repo only explains itself to the people who already know it, it doesn't explain anything

  • Importing documents as an insurance — it turns the pull layer into push at full price in every session, and you don't notice it because nothing breaks

  • Pointing to an unwritten document — we expected it to sink the answer, instead the agent went looking, found the right document anyway and told us the pointer was stale, so the cost is a search rather than an answer. Still good to add to the free part of the script so somebody doesn't pay for it

  • Running the drill on every commit — it bills a model, so run it when the shape changes and leave the everyday load to the pointer check

THE FILEscripts/clone-drill.sh
BASH
#!/usr/bin/env bash
# Ask a cold agent the questions this repo is meant to answer — inside a clone of HEAD, so nothing
# you have left uncommitted or gitignored can answer them for it.
#
#   scripts/clone-drill.sh                      # default agent: claude -p
#   AGENT='codex exec' scripts/clone-drill.sh
#   INDEX=CLAUDE.md scripts/clone-drill.sh      # if your index is somewhere else
#
# Edit QUESTIONS to match your repo. Keep them to what a new colleague asks in week one.

set -euo pipefail

read -ra AGENT_CMD <<<"${AGENT:-claude -p}"
INDEX=${INDEX:-AGENTS.md}

QUESTIONS=(
	"How do I run the test suite? Answer with the exact commands."
	"What in this repo is generated or off-limits to edit by hand?"
	"Where does a new endpoint go?"
)

# The agent is told to say this when the repo does not answer. It is the only part a script can grade.
SUFFIX='If this repo does not tell you, reply exactly: UNKNOWN.'

clone=$(mktemp -d)/repo
trap 'rm -rf "$(dirname "$clone")"' EXIT
git clone --quiet "$(git rev-parse --show-toplevel)" "$clone"
cd "$clone"

# Free half: every path the index points at should exist in the clone too. The filter takes any
# backticked token that looks like a path — tune it if your index names things it doesn't.
missing=0
if [ -f "$INDEX" ]; then
	while read -r path; do
		[ -e "$path" ] || { echo "dangling pointer: $path"; missing=$((missing + 1)); }
	done < <(grep -oE '`[A-Za-z0-9_./-]+`' "$INDEX" | tr -d '`' |
		grep -E '/|\.(md|txt|toml|json|ya?ml|sh|py|rs|ts|tsx|go|rb|sql)$' | sort -u)
	[ "$missing" -eq 0 ] && echo "pointers: all resolve"
else
	echo "no $INDEX in the clone — is it committed?"
	missing=1
fi

unanswered=0
for q in "${QUESTIONS[@]}"; do
	printf '\n? %s\n' "$q"
	answer=$("${AGENT_CMD[@]}" "$q $SUFFIX" </dev/null 2>&1 | tail -n 20)
	printf '%s\n' "$answer"
	# Only the last line is graded: Codex echoes the prompt back, and the prompt says UNKNOWN.
	grep -qw UNKNOWN <<<"$(grep -v '^[[:space:]]*$' <<<"$answer" | tail -n 1)" &&
		unanswered=$((unanswered + 1))
done

printf '\n%d dangling, %d of %d unanswered\n' "$missing" "$unanswered" "${#QUESTIONS[@]}"
exit $(((missing + unanswered) > 0))
j / k to move between lessons