blog/model-agnostic-repos/

What changed when I stopped choosing a model per repo

One set of instructions, skills and hooks that Codex and Claude read exactly the same way: I tried to make my models converge in my repos.

It started as a cost question. I wanted to run Codex on a repo I had been building with Claude Code, and found the repo was written for Claude Code alone, hard to read for anything else without a rewrite. Earlier in 2026 that would not have bothered me, since I was keeping the two apart on purpose, one auditing and the other executing. They now share the same files, and I cannot tell their output apart.

Migrating the repo came before testing anything, so what follows is what that migration involved and what came out of it. The gap between the two models has narrowed enough that what now decides the quality of the output is the repo they work in, and a good model in a badly kept repo gives a poor result. This is a few weeks of use on personal repos, in a field where positions age quickly.

The repo

The experiment runs on a repo called teach/. I built it to relearn SQL and BigQuery properly, and it is organised as one mission per subject: a set of lessons, each covering a single concept as a self-contained HTML page, exercises I work through on my own, and a short record written after each session of what actually stuck and what did not. The first mission covers SQL and BigQuery.

The teaching method behind it is not mine. The teach skill comes from Matt Pocock’s work at github.com/mattpocock/skills, copied into the repo and adapted to how I work. What follows is about the configuration around it, not the method itself.

Three mechanisms

Making that repo readable by another agent came down to three places in the tree.

teach/
├── AGENTS.md                    canonical instructions
├── CLAUDE.md                    one line: @AGENTS.md
├── .agents/                     readable by any agent
│   ├── hooks/notion-mirror-stale.py      the logic, with a CLI
│   └── skills/
│       ├── teach/SKILL.md
│       ├── write-lesson/SKILL.md
│       └── publish-notion/SKILL.md
├── .claude/                     Claude Code wiring only
│   ├── settings.json            hook registration, nothing else
│   ├── hooks/posttooluse-notion-mirror.py   adapter, no logic
│   └── skills -> ../.agents/skills          relative symlink
├── scripts/check-links.py       permanent check
└── sql-bigquery/
    ├── AGENTS.md
    └── CLAUDE.md                one line: @AGENTS.md

Instructions. One document carries the project instructions, AGENTS.md. The file Claude Code looks for, CLAUDE.md, holds a single line that imports it, so nothing has to be kept in sync between the two, and the same pair sits at every level of the repo that had its own context file.

Skills. A skill is a written procedure an agent follows, for writing a lesson or publishing one. They live in .agents/skills/, and .claude/skills is a relative symlink committed to git, so a fresh clone restores it on its own. An agent with no /name trigger opens the same file as ordinary Markdown and nothing fires, so each skill has to be readable on its own, in a straight line, without depending on being invoked.

Hooks. A hook is a script an agent runs automatically after an action, here to flag that a published lesson no longer matches its source. The logic and the conditions that trigger it are shared, moved out into .agents/hooks/ behind a command line interface. What differs between agents is the execution: each has its own way of declaring a hook and passing it an event, so there is one adapter file per agent. Claude Code is the one that set the format, and the agents that come after it are likely to land on something close, but until they do, each of them needs its own adapter. The adapter calls the core over the command line instead of importing it. Every trigger therefore goes through the same interface any other agent would use, which keeps that interface exercised rather than leaving it a side path nobody tests.

The migration skill

Doing that conversion once by hand is manageable. Doing it again on the next repo, from memory, is where it goes wrong, so the procedure became a skill of its own, migrate-agnostic.

Its first rule is that the migration never ships in one go and never without explicit approval. The files it touches are the ones agents load automatically, so a mistake does not surface as an error. It produces an agent working without context, silently, and the work it returns looks normal until you read it closely.

The rest of the skill is the sequence and the guard rails around it:

  • an inventory first, which modifies nothing: where the context files are, where the skills are, which hooks are declared and where, and which files elsewhere point at this repo by path
  • a plan submitted and approved before anything is touched, short enough to fit on one page, and a plan that does not fit signals a repo to migrate in several passes rather than a plan to lengthen
  • the execution on a dedicated branch, one repo per pass, starting with the least critical one available
  • a rollback written in advance, with the parts it cannot undo named explicitly
  • a permanent check left behind in the migrated repo, which verifies that the internal links still point at something and that no context file has been left orphaned

One line of the skill carries the same idea as that first rule: a check that cannot fail checks nothing. Both are about the same failure mode, a setup that looks healthy because nothing is complaining.

What came of it

Once the repo was agnostic, I ran Codex on it. It picked up the work where Claude had left it, in the same format, and I did not have to rework what came back. The method followed, the structure of the files, the shape of the exercises: across the lessons I gave both of them, nothing in the output tells me which one wrote it.

Codex wrote a handful of lessons and their exercises, plus an addition to one of the skills, and none of the progress records. The material itself is where the two are indistinguishable. The difference sits in the conversation around it. When I am stuck during an exercise and ask for a hint, Codex answers more directly, closer to handing over the answer than to walking me back to it. That probably owes more to the lighter model and the lower effort level I run it on than to anything in the tools.

Much of the comparison between models turns on that kind of thing, on which one has become more verbose or more terse with the latest release. The gap is real when the model is handed a vague request in a repo that tells it nothing. Once the environment is laid out and the way of working is written down, the difference has been close to imperceptible in my experience.

Cost

Running two models on the same repo is a cost decision once they produce the same thing. The cheaper one takes whatever does not need the best, and on a repo written one lesson at a time, that adds up. None of that saving is available if the repo only speaks to one of them, which is the point of the whole exercise.

Resilience comes with it, and that part has not changed. A model that is down, rate limited or simply weak on a given task no longer stops the work.

Where this stands

All of this comes from personal repos, run over a few weeks, on work that matters to me but carries no production risk. The conclusion holds for now, and the next generation of models could make it wrong in either direction, either by pulling apart again or by making the configuration around them matter even more than it does today. The ground here moves every few weeks, and one person’s experiment on personal repos only goes so far, so if you are running something similar and seeing something else, I would like to hear about it.