Writing

Keep the harness, borrow the model

A Codex plugin that hands one task to Claude Fable, Opus, Sonnet, Haiku, or Sakana Fugu without leaving the session. What it does, what it refuses to do, and the environment bug I found building it.

Dogukan Tuna · · 5 min read

Read in Turkish

Keep the harness, borrow the model
Contents
  1. What I built
  2. What I delegate, and what I check
  3. The environment bug
  4. What Windows taught me
  5. What this doesn't buy you
  6. The fine print

Most days I have Codex open with three or four things in flight. Some of them I hand over and stop watching. Some of them I read line by line before anything lands. That split is most of how I work, and it holds up fine until I want a different model to look at something.

Then the friction starts. I open a second terminal, paste in the plan or the diff, read the answer, paste the useful part back. Every one of those hops throws away what the session already knew. I did it for about two weeks before admitting how silly it was.

What I built

A Codex plugin with two tools, ask_claude and ask_fugu, and eight models behind them. Fable 5.1, Opus 5, Sonnet, and Haiku come through Claude Code. Fugu, Fugu Ultra v1.1 and v1.0, and Fugu Cyber come through the fugu profile Sakana's installer already wrote into my Codex config.

No API keys anywhere. Claude runs on the subscription I already pay for, and the Fugu side reuses a provider block that was sitting in ~/.codex/config.toml before I started. If you have both CLIs installed and logged in, the plugin has nothing left to set up.

Codex stays in charge. There is no scheduler here, no plan state, nothing running in the background. One tool call is one turn on one model, and then it comes back.

What I delegate, and what I check

Opus gets the things where being wrong is expensive: an architecture call, a security-sensitive change, a bug that has already survived one round of debugging. Fable gets plan critique and anything where the writing matters. Fugu gets a second opinion when I suspect I have talked myself into something, since it routes across a different set of frontier models than the two I use all day.

Haiku gets the boring work, and I check almost nothing it does.

What I always read is the diff. More on that below.

The environment bug

This one I did not go looking for.

I have claude-fugu installed, which points Claude Code at Sakana's gateway using nothing but ANTHROPIC_* environment variables. Tidy design, until something spawns Claude Code as a child process. A child that inherits my shell can ask for Opus and be answered by a completely different model, quietly, with a reply that reads perfectly normal.

The fix was to stop filtering the environment and start building it. The child gets an allowlist and nothing else: PATH, HOME, locale, SHELL. Anything not on that list never reaches the process. The adapter then reads modelUsage back out of the reply and refuses it if the model that answered is outside the family I asked for.

If you are writing anything that shells out to an agent CLI, check this on your own machine. A blocklist only stops the variables you thought of.

What Windows taught me

My Codex Desktop runs on Windows while my repos, my Claude login, and the Fugu profile all live in WSL. So the plugin has a bridge mode: Codex on Windows launches the server through wsl.exe, and the server translates the paths Codex sends it.

wslpath handles C:\Users\me\repo and refuses \\wsl.localhost\Ubuntu\home\me\repo, which is exactly the shape a WSL-hosted project produces. I had to write that case by hand. When the path names a different distro than the one I am running in, the server refuses instead of guessing, because guessing drops a model with write access into a directory that looks identical and is not.

Then I ran the test suite under a Windows interpreter, and it found three things Linux never would have.

claude_route imported pwd at module scope. That module does not exist on Windows, so the server died on import before answering anything. The plugin had never worked there and I had no idea.

The server read stdin with the locale encoding. MCP is UTF-8 on the wire, but Python below 3.15 opens stdio with the platform codepage, so one Turkish character in a prompt was enough to break a session.

And my own path check was wrong. I refused \\wsl.localhost\... whenever the server was not inside WSL, except native Windows reaches that share directly. I had broken the setup I wrote the bridge for.

One failure was my fault in a different way. My first test wrote under %LOCALAPPDATA%, and the terminal I was in redirects that to a sandboxed directory WSL cannot see. The bridge looked broken for ten minutes and was fine the whole time.

What this doesn't buy you

It is not a second agent. Nothing here keeps state between calls, so the model you ask has no memory of the last thing you asked it and cannot see your conversation. You pay for that in prompt length: every call carries its own context or it gets a worse answer.

It also does not make the second opinion correct. Two models agreeing means you asked twice. When they disagree, the gap between them is where I learn something.

The fine print

Every call runs with full permissions in whatever directory you pass. Claude gets all its tools and bypassPermissions. Fugu runs unsandboxed. Nothing pauses to ask you anything.

That is what I want from a delegate, and I would rather write it down than bury it in a config file. The workdir argument is a grant, not a hint. What comes back is a report on changes that already exist on disk, so read the diff before you trust it.

147 tests, green on Linux and on native Windows. macOS should work and I have no Mac to prove it on, so the README says exactly that.

Share:XLinkedIn