My Self-Tuning AI Harness

Claude Code, extended instead of wrapped in an app: one command for every project and account, work that moves to another account at a limit, subagents on a benchmarked model table that can retune itself, and a harness that reads its tools' news and repairs itself.

A rain-soaked hall: a black slab with one glowing cursor sends blue threads to tiers of glass modules, ringed by gauges and sliders, as light flows from an amber cell to a blue one.
0:00
/23:55
Two Letters: the whole harness, in 24 minutes.

TL;DR

  • No app. My AI harness is Claude Code itself, extended through its own extension points (global instructions, skills, subagents, hooks, plugins, statusline, settings, headless mode) for every account and every project.
  • One front door. ai updates Claude Code and asks two questions: where (which project) and as whom (which account, ranked by what each has left). Then it starts Claude at max effort and stays behind it as a supervisor.
  • A limit moves the work instead of stopping it. Near a plan wall, the session finishes its step and ends the turn. The same conversation then resumes on another account under the same session id. If no account has room, it keeps working until it's refused, then carries on by itself after the reset.
  • Big work is written down, then divided. A long request is kept word for word and planned with typed dependencies. It is split across role agents, each with its own model and effort. It counts as done only when a verifier passes and an independent review agrees.
  • Two small graphs, no upkeep. The plan's graph says what can run now, what can run in parallel and what decides the finish. The decision graph says which rule is current and what it replaced. Both are read from files that exist anyway.
  • The money, measured. One point of my $200 plan's weekly window absorbed about $24 of API-priced work, so the plan carries about $10,500 a month. My last 30 days on both accounts, paid as credits, would have cost about $9,600.
  • The model for each role, benchmarked. 378 script-graded runs across models and efforts couldn't separate the setups (all 99% or better, Haiku included), so the hand-set table stays until harder tasks prove a cheaper one safe. When real work keeps failing where the benchmark can't see it, the role climbs a rung.
  • It fixes itself, inside fences. Once a day it can take one evidenced problem and fix it on a sandboxed copy. It runs every test and an independent review, and only then lands the fix and tells me. Some files and rules it can never touch. A big improvement can become an article, through every review gate, after a window in which I can veto it.
  • It reads the news first. Every morning it checks what changed in Claude Code, Anthropic's models and prices, and the wider industry, verifies every quote against the saved page, and turns what matters into its own to-do list.
  • Visible on purpose. Every switch is announced, logged and done at a turn boundary with the unmodified binary. Automatic switching is a gray zone in the terms, and one line turns it off.

Explain it like I'm five

Claude Code is a very good worker with no memory of yesterday. Every time you call it, it walks in knowing nothing about your house rules, where the tools are, or which jobs are half done.

The harness is the workshop around that worker:

  • The same rulebook hangs over every bench, and every bench opens the same drawer of tools.
  • A big job is written on the wall word for word, then cut into cards. Arrows say which card has to wait for which. Cards with no arrow between them go to several helpers at once.
  • Someone who didn't do the job checks it before anyone calls it done.
  • Every worker wears a badge with an allowance that refills every few hours and every week. A meter at the bottom of the screen shows what's left.
  • Sometimes a badge runs low in the middle of a job. The worker finishes the sentence and puts the pen down. Someone with a fresh badge sits at the same bench, with the same notes, and keeps writing. When every badge is empty, they wait for the refill and carry on.
  • Every morning the workshop checks its own pipes and fixes one leak on a copy of the building. The fix goes in only after a second inspector signs off.
  • The workshop tests which helper does each kind of job best for the least money, keeps score, and swaps a helper who keeps getting it wrong.
  • Before work starts, it reads the morning paper about its own tools and pins what matters on its to-do board.

Start here: five minutes

My first session of the day

1. Start. I type ai in any terminal. It shows the installed Claude Code version. When there's a newer release, it installs it in the background with a progress bar. It judges the update by the version before and after, never by an exit code alone. Offline, it says so and goes on. Meanwhile it asks two questions (arrow keys, digits, Enter):

⚑ ai · Claude Code 2.1.310 · up to date
  Which project?
❯ 1  dotfiles · AI harness
  2  <my personal knowledge repo>
  3  <a work project>
  4  here: <current folder>
  5  home
  6  other path…
  Which Claude account?
❯ 1  a@example.com  5h 62% ↻10:20  β˜… recommended
  2  b@example.com  5h 97% ↻08:40  near its limit

(Names and numbers are placeholders; the real rows also show the plan and the 7-day window.) The usage numbers refresh while the project menu is open, and the account list re-ranks when fresh numbers arrive.

"Other path…" takes a path with Tab completion. A path that doesn't exist yet becomes a new project:

  • I confirm the name.
  • I pick standard or multi-repo (a cockpit that holds a work project's repositories).
  • ai new creates git, the folder layout, a knowledge base, its search collections, a memory store and a private GitHub repository.

Then Claude starts in that folder: Opus 5.5, max effort, permission prompts off, full screen. ai waits behind it.

2. Talk. One tab, one task.

  • A question gets an answer.
  • A task gets a short plan and the work.
  • A long spec, or "do this until it's done", is written down verbatim and planned with dependencies. It goes to agents or a loop, then gets verified, reviewed and landed.
  • Anything I type while it works is appended to the same request as the next message.
  • status? gets up to five lines from the plan and progress files, added by a hook; Claude answers from them.

3. Read the bottom of the terminal.

dotfiles βŽ‡main* β”‚ a@example.com β”‚ Opus 5.5 max β”‚ ctx β–°β–±β–±β–±β–± 21% β”‚ 5h β–°β–°β–°β–±β–± 62% ↻10:20 β”‚ 7d β–°β–°β–±β–±β–± 41% ↻Wed 21:00 β”‚ credits $4.20

That line is this session and its account (the statusline section below goes segment by segment). Under it, every running agent's row shows its job, model and effort, time and cost next to what it's doing. Above the prompt, a band shows everything else: this session's total time and cost, its headless runs, the project's loops, other projects, and anything that waits for me.

4. Let it run. Near a limit it moves to another account at the end of a turn and tells me. Before a compaction it writes a checkpoint. Harness changes reach every new session the moment they land. Once a day, unattended, it reads what changed in its tools and fixes at most one problem in itself.

5. When something is off: ai doctor. If a hook misbehaves in every session, touch ~/.local/state/ai-workspace/hooks/OFF stops every harness hook at once; deleting the file turns them back on. The dashboard band is a plugin, not a hook: /dash off hides it.

The deep dive

Why a terminal tool and not an app

The obvious thing to build was a desktop app: a window with my projects, a meter per account, a button for work that runs overnight. I didn't build it, for four reasons.

Wrapping is chasing. Claude Code ships every week: tools, permissions, subagents, compaction, caching, MCP, the terminal UI itself. An app that wraps it has to follow every release. An app that replaces it has to rebuild all of it. A harness made of Claude Code's own extension points gets most of each release for free, and ai installs the newest one before every launch.

The extension points are enough. Everything I wanted from an app fits in these:

Extension pointWhat it does for me
Global instructionssay how to work
Skillshold the procedures, loaded only when needed
Subagentscarry their own model and effort
Hookssee every prompt, tool call, stop and compaction; can add a line of context, block a prompt or keep a session working
Pluginspackage hooks and additions to the interface; mine draws the dashboard band above the prompt
Statuslineshows anything I can compute
Headless moderuns the same agent from a loop or a timer

It isn't tied to my terminal. The harness lives in each account's config directory. A session opened from the desktop app or an IDE gets the same rules, skills, agents, hooks and statusline. It misses only what ai adds around the process:

  • the update and the menus;
  • the supervisor, so no live account switch;
  • max effort, which can only be set per launch, so such a session runs at xhigh.

Claude Code maintains it. Changing the harness is a Claude Code session in ~/dotfiles, reached from any project through the harness skill. Its change is tested, reviewed and landed like any other code. The daily self-improvement worker is a headless Claude Code run. When a result has to look like a product for someone else (a report, a dashboard, a page), Claude publishes an artifact. The harness never becomes a web app.

What's left for me to own: rules, procedures, one hook dispatcher, a statusline, a launcher with a supervisor, a loop engine, a dashboard plugin and some registries. After the first night's build, the Python came to about 33,000 lines in 72 modules, guarded by more than 1,350 tests. The core uses the standard library only, so it runs on a fresh Mac with only Apple's command line tools.

Four design rules:

  • One implementation per mechanism. Projects supply data (a manifest, profiles), never copies of code.
  • Hooks never raise and never block a prompt because of a directory or an account. Guards fail open.
  • Every automatic action is visible (a line, a notification, a log entry) and, where it can be, reversible. An automatic article's newsletter can't be unsent, so the article waits six hours for my veto.
  • Facts in the docs come from the code or a named source. Estimates say so.

One repository that every session loads

Everything shared lives in one folder, ~/dotfiles/ai, in a private repository. One command, ai apply, builds it into every account's Claude config directory, idempotently. ai doctor checks the result.

~/dotfiles/ai/                      a private git repository
β”œβ”€β”€ bin/ai            β†’ ~/.local/bin/ai                 symlink
β”œβ”€β”€ policy/CLAUDE.md  β†’ <config dir>/CLAUDE.md          copy
β”œβ”€β”€ skills/<name>/    β†’ <config dir>/skills/<name>      symlinks
β”œβ”€β”€ agents/<name>.md  β†’ <config dir>/agents/<name>.md   symlinks
β”œβ”€β”€ githooks/         ← core.hooksPath of ~/dotfiles and new projects
β”œβ”€β”€ config/           accounts Β· projects Β· models Β· harness Β· qmd Β· profiles
└── lib/ai/           hooks Β· statusline Β· launcher Β· supervisor Β· usage
                      exec Β· loops Β· review Β· graph Β· improve Β· …
~/dotfiles/home/ mirrors ~ :
  .claude/settings.json β†’ <config dir>/settings.json
  Library/LaunchAgents  β†’ ~/Library/LaunchAgents

<config dir> is ~/.claude for the default account and ~/.claude-<id> for every other one. The settings file is a symlink, and its harness hooks and statusline all call ai. The background jobs are loaded with launchctl.

  • Skills: spec, ralph, autoship, second-opinion, sessions, takeover, knowledge, graph, new-project, blog, image, video, pdf and harness.
  • Agents: scout, researcher, implementer, reviewer and verifier.
  • Code: the entry point is a small sh wrapper around python3 -I, and each module's docstring is its contract.

Projects bring data, not code. A project adds its own CLAUDE.md and one manifest, .ai/project.json. The manifest lists the project's name and type, knowledge folders, search collections, memory store, checks, loop adapter and profiles. Every hook reads the nearest manifest, so one implementation serves every project. Before this rebuild, the same search guard existed in several copies that had drifted apart.

There are two project types:

  • standard, one private repository;
  • multi-repo, a cockpit whose work repositories sit under repos/. Harness work never touches those, and the search index never includes them.

Changes apply themselves.

  • A commit, merge or checkout in ~/dotfiles runs the harness git hooks, which run ai apply --quick. That refreshes the links and the CLAUDE.md copy, and takes a few milliseconds when nothing changed.
  • A change to a background job or to the search collections starts one ai apply --full in the background.
  • ai runs the quick apply at every launch, and the first hook of every session repairs drift on a running machine.
  • New sessions read new policy at once. A running session keeps the instructions and hook entries it loaded until it restarts. The code behind ai hook is live immediately, because every harness hook entry calls that same command.
  • Tests run under a sandboxed HOME. There, machine-wide steps (launchctl, a detached full apply) refuse to run unless AI_APPLY_ALLOW_MACHINE=1.

Where things live.

WhatWhere
shared machinery, policy, skills, agents, registries, templates~/dotfiles/ai/, in git (private)
project specifics<project>/CLAUDE.md, <project>/.ai/project.json and <project>/claude-memory/, in the project's private repo
work code<cockpit>/repos/*, a work project's own repos, never touched by harness work
private state: ledgers, usage cache, loops, sessions, checkpoints, findings, inbox~/.local/state/ai-workspace/, not in git
tool runtimes (Remotion, Python environments)~/.local/share/ai-studio/, not in git; ai video setup recreates them

Old names keep working. The commands this replaced (ai-workspace, ai-ralph, ai-second-opinion, ai-qmd, ai-graph) are now shims. Each one calls ai legacy NAME and logs the call, so running sessions and background jobs don't break. A shim nobody called for 14 days becomes a finding that proposes removing it.

A session's life

Every hook event in the shared settings.json runs one command, ai hook. The only other entry approves file edits without a prompt; deny rules still apply, because Claude Code evaluates them whatever a hook returns. The statusline is ai statusline. One session, in order:

1. Launch. ai resolves the project and the account. It sets CLAUDE_CONFIG_DIR, or leaves it unset for the default account. It removes ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, CLAUDE_CODE_OAUTH_TOKEN and any inherited effort or subagent-model pin from the environment. It sets CLAUDE_CODE_NO_FLICKER=1 and AI_SUPERVISED=1. Then it starts claude --permission-mode bypassPermissions --effort max --session-id <uuid> as its child.

  • Permission prompts are off in my interactive sessions. The unattended worker that edits the harness never runs that way.
  • With no terminal on stdin there are no menus: it uses the current folder and the best account.

2. SessionStart (10-second budget).

  • The session is recorded, so parallel sessions can see each other, and landed worktrees are swept.
  • After a compaction or a resume, Claude gets a pointer to its checkpoint, and an attached loop is re-bound.
  • Logins that expire soon are flagged.
  • Once a day, one line says what the harness fixed lately.
  • Drift is repaired with ai apply --quick, in at most two seconds.

3. Every prompt (UserPromptSubmit, 10 seconds). In a supervised session, a typed switch or stay answers a pending account offer. Then the router sorts the prompt and opens or appends the request ledger (next section).

4. Every risky tool call (PreToolUse, 5 seconds, on Bash, edits, Grep and MCP tools). It runs guards only, at a median cost of about 30 ms (41 ms for git and edits):

  • no git command or edit that would trample another live session's uncommitted files;
  • no grep over the knowledge folders; query the search engine instead;
  • no chat posts from a batch configured not to post. Reads stay open.

5. After every write, edit or shell command (PostToolUse): the failover check in supervised sessions, and the usage ledger.

6. The statusline refreshes after every response and every ten seconds.

7. Around a compaction. PreCompact writes the checkpoint. PostCompact records the compaction. The next SessionStart points Claude back at the checkpoint.

8. At every stop (Stop, 15 seconds).

  • An attached loop keeps the session working while its verifier fails.
  • A pending account switch wins over the loop and marks the session ready for the supervisor.
  • A refused request (StopFailure) holds the account. If another account has room, it asks for an immediate switch.
  • A finished subagent (SubagentStop) is counted once in the usage ledger.

9. SessionEnd (1 second): the session record and the ledger close.

Every event except PreToolUse also runs the limit watch.

The hook contract.

  • One JSON payload in, at most one JSON object out.
  • Always exit 0, never raise, and keep output under Claude Code's 10,000-character limit.
  • Every part (sessions, ledger, router, failover, checkpoint, loops, usage, apply) is imported lazily inside its own try/except.
  • A failing part is skipped and leaves one line in an error log: the exception type and the line it came from. The message itself is never logged, because it could carry prompt text.
  • The harness's own helper runs, such as the /usage probe, set AI_WORKSPACE_HOOK_ACTIVE, and the hook returns at once.

One rule sits above the rest: no hook refuses a prompt because of a directory or an account. Nothing reads the account from the current folder. A hook blocks only one kind of prompt: a typed switch or stay answering a pending account offer. That rule has a story, near the end.

How work gets divided

For the rebuild I measured my last 60 days, and I work in two styles:

  • Big spec, fed as it runs. In 27 of 130 sessions I handed over a big spec, often pasted, and kept feeding it follow-ups while Claude worked. Those 27 sessions held 78% of all my prompts. In half of the ones that opened with the spec (10 of 20), I had to nudge with "continue" or "why did you stop".
  • Ping-pong. Everything else: short questions, short answers.

The harness has to handle both without me choosing a mode.

Routing, without a model. A UserPromptSubmit hook computes cheap features of the prompt:

  • length, pastes included;
  • imperatives, autonomy words, pointers, parallel words;
  • question form and status words;
  • whether a spec or goal is active.

It picks one of six routes (Q, S, T, W, F, L) with no model call and no network, adds one short line at most (up to five for a status question), and leaves the decision to the model. The policy names two more routes that Claude judges by itself (B, N).

RouteLooks like, and what the hook does
Q questiona question, no imperative. Nothing.
S statusstatus?, so?, "where are we". The hook adds up to five lines from the plan and progress files, and Claude answers from them. A control word such as "continue" passes untouched.
T follow-upa short prompt while this session has an active spec. Appended verbatim to the request ledger as the next numbered message.
W one taskan imperative of moderate size. Nothing; the reply starts with a short plan.
F fan-outparallel words, research over many sources. One line naming the agent roster.
L big speclong prose with imperatives and autonomy words, or a ticket plus an imperative. Opens the request ledger with the prompt as Message 1; one line points at the spec procedure.
B batchticket ids, an issue list, "autoship". No hook route: a ticket plus an imperative routes as L, and Claude starts the batch loop.
N new topica long idle on a big context, or an unrelated request. No hook route either: Claude says in one line that /clear is cheaper; I decide.

Prompt text goes only into that session's own request ledger. The route log keeps the route and the features, never the words.

Escalate on behavior, not only on the prompt. Prompt text isn't enough: in my data, prompts under 50 characters still led to big work 15% of the time. So Claude also watches the turn, by a rule in the policy (no code times it):

  • past 30 minutes or 100 tool calls, the turn gets plan and progress files;
  • past two hours, or after a second "continue", the task moves to the big-spec route.

The big-spec route opens the request ledger and follows the flow under "Orchestration" below. Unknowns become questions or risks, never guesses, and the ledger and the plan are re-read before every major step and before "done".

Orchestration

Different models and efforts serve two goals at once: spend less, and keep the quality at the top. Max effort everywhere is the easy way to guard the second and give up the first, so I benchmarked the five role agents in the cast below; the result is under "Cost optimization, measured".

The cast

The cast. The main session (Opus 5.5, 1M context, effort max) plans, delegates, integrates and decides. It hands work to five role agents: scout (Sonnet 5.5, high), researcher (Opus 5.5, high), implementer (Opus 5.5, xhigh), reviewer (Opus 5.5, max) and verifier (Sonnet 5.5, medium). Built-ins inside the session, both overridden: Explore runs as a scout, and general-purpose runs Opus 5.5 at xhigh (Plan and forks still inherit the main session's model). Headless work outside the session: loop workers (xhigh; goal iterations max), the second opinion (max, isolated, no tools) and the improvement worker (xhigh, inside a sandbox).
The cast, from the film: who does what, on which model and effort.
AgentModel / effortTools Β· writesWhy
main sessionOpus 5.5, 1M context / maxall Β· writes what it decidestalks to me and decides; quality first, by my choice. Its cost is mostly its context (below).
scoutSonnet 5.5 / highRead, Grep, Glob, Bash Β· writes at most one result filereading at half of Opus's price; on the benchmark Haiku passed too, for a tenth of the cost
researcherOpus 5.5 / highRead, Grep, Glob, Bash, WebSearch, WebFetch, Write Β· writes its findings filejudgment about sources and dates; Sonnet at medium matched it on the benchmark
implementerOpus 5.5 / xhighRead, Grep, Glob, Bash, Edit, Write, NotebookEdit, WebFetch, WebSearch Β· writes in its own worktreereal changes with tests; max cost 2.7 times as much for the same score
reviewerOpus 5.5 / maxRead, Grep, Glob Β· writes nothinga missed bug lands; a protected floor that the learning loop never lowers by itself
verifierSonnet 5.5 / mediumRead, Grep, Glob, Bash Β· writes at most one result fileruns the checks and reports exactly what they show; Haiku at low matched it
ExploreSonnet 5.5 / highas the scout Β· writes at most one result fileClaude Code's built-in explorer, overridden to run as a scout (it inherited max)
general-purposeOpus 5.5 / xhighall Β· writes anythingthe catch-all, overridden the same way (it inherited max)
Plan, forksthe main session'sthe main session'sPlan left as built; a fork carries the whole conversation, so it keeps the main model
loop workersOpus 5.5, 1M / xhigh; goal iterations maxall Β· writes in their worktreelong unattended work
second opinionOpus 5.5, 1M / maxnone Β· returns its opinionisolated: no tools, memory or project instructions
improvement workerOpus 5.5 / xhighinside a sandbox Β· writes in a sandboxed copylands only through tests and an independent review

How a big job flows

Everything but the main session works from a written brief.

How a big job flows: from the request ledger (REQUEST.md, every message verbatim), the main session briefs two scouts and a researcher in parallel; each writes a findings file and returns at most 30 lines, and the findings feed PLAN.md with typed dependencies. Briefs go to three implementers, each in its own worktree on disjoint files; the main session integrates their diffs, a verifier runs the checks, an independent review reads the whole brief, and only then does the work land.
One big job from request to landing (example names).

1. The ledger. My request goes into REQUEST.md, word for word, before any work. Every follow-up becomes the next numbered message. The rebuild's ledger grew to 67 messages, 27 of them from the first night.

2. Research fans out. Scouts and researchers run in parallel, each with a narrow brief, and each writes its findings to a file. The main session reads the files, not the agents' transcripts.

3. The plan. PLAN.md gives every item an id, an acceptance check and typed dependencies (needs:). At plan freeze, ai review reads it.

4. The graph decides the order. ai graph plan turns the dependencies into waves, the ready set and the critical path. A wave goes to parallel agents on disjoint files; an implementer works in its own worktree. Each item runs inline, with one role agent, with parallel agents or as a loop.

5. Integration. The main session reads every result, reconciles the interfaces and runs the whole test suite.

6. Verification. A verifier runs the checks and reports exactly what they show. For a loop, a strict verifier written before the work decides "done", and nothing else does.

7. Independent review. Before landing, ai review gives the complete brief to a fresh, isolated Claude at max effort with no tools. Only then does the work land, with a report and a notification.

How the agents coordinate

  • Files, not chat. A brief names the goal, what's already ruled out, the files to read, the acceptance check and where to write the result. Agents return at most 30 lines; the full result is in the file.
  • One writer per file. Readers change no project files (a scout writes at most one result file). Parallel writers work on files that don't overlap, or in separate worktrees.
  • Fresh contexts over borrowed ones. A subagent starts with its brief, not my conversation. A fork starts with the whole parent context, so a fork is only for work that needs the whole conversation.
  • Budgets. About 100 tool calls for a reader and 150 for an implementer; then it writes what it has and returns.

This build, as the worked example

The rebuild that produced this article followed this flow, in one session, and its transcripts give the numbers.

PhaseAgents
Research11 launched in 126 seconds, 15 running at once after one agent forked four more
Implementation6 launched in 94 seconds, then the test port (4 forks), the batch engine and the content engines: 19 agents open at once at 20:46
Reviews5 isolated reviews of the design and the code: 85 findings, 13 of them high
Fixes and moves4 agents on separate repositories: the batch engine's fixes, my personal knowledge repo, the film studio and a work project's cockpit
Articlean independent draft and ten pre-publish reviews

In total: 36 subagents (26 launched by the main session, 10 by subagents), 37 isolated reviews and one headless loop iteration. The briefs had a median of 4,421 characters. At list prices the night came to about $1,003 of API-equivalent work: the main session $257, the subagents $664, the reviews $82 (estimates, not bills).

Two notes. First, every response in the build ran on Opus 5.5 at max. Second, the agents were general-purpose agents and forks, not the roles above: the role agents only arrived with the landing at 00:05.

The probe

After the landing I checked the role table: a probe from a clean login shell launched every agent type without a model or effort parameter and read each subagent's transcript.

AgentObserved in its transcript
main sessionOpus 5.5, max
scout Β· verifierSonnet 5.5, high Β· Sonnet 5.5, medium
researcher Β· implementer Β· reviewerOpus 5.5, high Β· xhigh Β· max
Explore (Claude Code's built-in)Opus 5.5, max: the main session's model and effort
general-purpose, Plan, a forkOpus 5.5, max: the main session's

The roles held. The probe found two problems:

  • The built-in Explore agent runs on the main session's model, so every built-in exploration was a top-price read. A user agent named Explore overrides it (the docs say so, with model: haiku as their example); mine now carries the scout's profile, and a later probe saw it run as designed. general-purpose inherited max too; its override came later, from the cost data (see "Interactive work at max effort").
  • Pins outlive the settings. A settings env value is written into the session's process environment at launch. The rebuild's own session started before I removed the old effort and subagent-model pins, so it kept them all night. They didn't change its numbers, since its agents ran on the main model anyway, but they would have overridden every role. Now ai and ai exec drop inherited pins from every session they start, a pinned session says PINNED SESSION when it starts, and ai doctor warns about pins in its shell.

Graph engineering, explained

For months my setup had something called graph engineering in it, and I couldn't have told you what it bought me: the measurement below says it wasn't beating plain search. So the rebuild had to make it useful or remove it. It did a bit of both.

The useful version: give a plan or a set of records explicit, typed edges, so a program can answer questions about order and what's current. Search finds pages that sound like your question. A graph follows the arrows from the answer: what has to happen first, what can happen at the same time, what replaced what.

There's no graph database and nothing to curate by hand. Both graphs are read from files the harness keeps anyway, and only big plans carry one; a small task stays a list.

The execution graph answers "what can run now?" A big plan gives every item an id and its prerequisites:

- [x] 1.1 Layout
- [ ] 1.2 (needs: 1.1) Accounts
- [ ] 1.3 (needs: 1.2) Launcher
- [ ] 1.5 (needs: 1.2) Statusline
- [-] 2.9 Dropped idea

There are three edge kinds, the same ones my batch loop uses:

  • needs means the prerequisite must succeed (requires_success);
  • after is only an order (ordering_only);
  • requires_terminal accepts done or dropped. It goes in a fenced block whose info string is deps, one edge per line, such as 3.1 requires_terminal 2.9.

ai graph plan PLAN.md computes four things:

  • ready now: open items whose prerequisites are satisfied;
  • waves: groups that can run in parallel (topological levels);
  • critical path: the longest chain of open items, which bounds the finish;
  • blocked: items whose required prerequisite was dropped, everything waiting on them, and why.

Here is the real output on the rebuild's own plan of 43 items, early on:

3 done, 40 open
ready now: 0.2, 1.1
  wave 1: 0.2, 1.1
  wave 2: 1.2, 1.8, 1.9, 5.3
  wave 3: 1.3, 1.5, 1.7, 1.10, 2.1
  ...
critical path: 1.1 β†’ 1.2 β†’ 1.3 β†’ 1.4 β†’ 1.12 β†’ 1.14 β†’ 1.15
               β†’ 6.1 β†’ 6.3 β†’ 6.2 β†’ 6.4 β†’ 6.5 β†’ 7.1

The critical path is the list of things never to let wait: items off it had slack, items on it didn't. Two more details:

  • --json gives the same data to tools, and --mermaid draws it;
  • unknown ids, cycles and unreadable deps lines are errors, never guesses.

The decision graph answers "which rule is current, and what did it replace?" Every decision record starts with front-matter. Only the newer record names its edges; the reverse edges are derived, so there is nothing to keep in sync:

id: harness-v2
date: 2026-10-25
topics: [harness, accounts, effort, loops]
supersedes: [per-directory-accounts, source-graph-catalogs]   # trimmed
amends: [ralph-loop, claude-code-only]
implements: [ai/lib/ai/accounts.py]
CommandAnswers
ai graph current accountswhich decisions about accounts are in force, newest first
ai graph history per-directory-accountsthe chain of rules that replaced one another
ai graph why ai/lib/ai/accounts.pywhich decisions govern this file
ai graph checkdangling edges, cycles, bad dates, implements paths that don't exist

Here is the record of this rebuild with its edges; ai graph mermaid draws the whole graph.

Decision graph: the 2026-10-25 record supersedes the directory account rule and the hash-pinned graph catalogs, and amends the Ralph loop and Claude-Code-only records.
One record and its edges.

ai graph history per-directory-accounts then answers the question a search can't: the directory rule is no longer current, and the record above is what replaced it.

Three rules keep the decision graph honest:

  • metadata never grants permission and never replaces reading the record;
  • chronology is not causation, so there is no "caused" edge;
  • a decision becomes current only through a record that says so, never by repetition.

Why the old graph went. From September I kept hand-curated catalogs: nodes with claims and pinned source hashes, refreshed after every change. When I measured them, they lost: "It did not beat plain QMD discovery (the needed sources were reached on 29 of 31 test questions without the graph, 28 of 31 with it), so QMD stayed the baseline." (Total Recall: How QMD Works, 2026-10-16). They answered a retrieval question that search already answered, and they cost upkeep. The new graphs answer what search can't (order, and what's current) and cost nothing to keep.

Loops that finish

Both loop modes use the Ralph technique (Geoffrey Huntley, 2025): run the same prompt again and again, each run starting fresh and reading its state from files, until an objective check passes. It is one engine with two modes. They share the private state layout, an account chosen per run, waiting out limits, STOP, notifications and the ledger.

ai loop goal: one objective, one verifier. A goal is a folder of files:

  • REQUEST.md, verbatim, with follow-ups appended;
  • PROMPT.md;
  • PLAN.md, with ids and needs;
  • progress.md;
  • the verifier, goal.json and STOP;
  • the iterations and logs.

The session I'm talking to is attached: its Stop hook keeps it working while the verifier fails. It may stop when the verifier passes, when STOP exists, when its account is held, or after stall_limit identical states.

Then the driver takes over. It is a launchd job that starts fresh headless iterations while the session is quiet:

  • each iteration picks an account, and after a refusal the next one fails over at once;
  • when every account is held, the driver waits for the earliest reset;
  • it stops for STOP, success, an iteration budget or a stall.

After an account switch, the attachment follows the session into its new process. stop and resume are mine. The harness never creates or removes STOP, and a loop I stopped stays stopped. The rebuild itself ran as one goal, and this article is one of its items.

ai loop batch: a graph of tracker items. This is my batch engine, made generic. Items come from an adapter: plan-file (built in), linear or github-issues. The adapter is declared in the project's .ai/project.json, together with:

  • its workspaces (repos and base branches);
  • the review host and merge policy;
  • named mutexes;
  • a prompt overlay, which is the project's own worker contract;
  • the notifier and the report.

The engine brings the rest:

  • typed dependencies (requires_success, requires_terminal, ordering_only);
  • one worktree and one lease per item;
  • gates and an oracle exit-code contract;
  • self-healing that may never touch the verifier;
  • checkpoints, a sanitized console digest and a report.

The controller runs as its own launchd job. Workers run through ai exec, each on an account chosen at launch. Every item's pull request gets an independent review before merge. Engine updates reach a running batch only through a stopped, reviewed refresh.

Spend bounds. A loop is a machine for spending tokens, so each kind has a ceiling:

  • Goal loops stop for success, STOP, an iteration budget or a stall.
  • Batch loops carry spend guards I learned the expensive way. One overnight batch on another provider spent 57,500 credits in about 100 minutes, about 88% of it on gate audits of tickets that couldn't move. Now: one preparation audit per blocked item, re-audits at most hourly, one audit worker at a time, and a worker cap.
  • The improvement worker gets one finding a day, at most 200 turns and two hours.
  • A review gets one attempt.

The general rule is no spend without progress: a stalled loop stops and says why.

Accounts and the wall

I run Claude on more than one subscription; today, two accounts. The old rule picked the account by folder. The new rule is that I pick at launch, from a list ranked by live usage, and any account can work in any project. When the account I'm on approaches its wall, the work moves.

The registry. ai/config/accounts.json lists every account with these fields:

FieldMeaning
identityid, email, config_dir, org_uuid, plan and label: who it is and which plan
creditsallowed: may keep going on its own usage credits; plan-only: never
prioritytie-break
failover_targetmay it receive work automatically

One more line, failover.mode, is auto, confirm or off.

Exactly one account uses the default config directory. Every other one uses its own literal path, because Claude Code keys the stored login to that exact string: the Keychain item is Claude Code-credentials- plus the first 8 hex characters of the path's SHA-256. That's why CLAUDE_CONFIG_DIR=~/.claude is a different, logged-out state.

Two commands add an account:

  • ai account add ID EMAIL creates the directory, wires the harness into it, and defaults the account to plan-only;
  • ai login ID runs the native login there.

The harness never exports a token variable, and never runs /login inside a session to change accounts. It reads a credential in exactly two places, both read-only requests to Anthropic's API that I asked for: the prepaid credit balance, which has no native source (at most every 30 minutes), and a usage reading when Claude Code's own /usage probe fails. For those it takes that account's existing, unexpired access token from the Keychain into memory and sends it only to api.anthropic.com. It never refreshes, writes or logs it, and AI_USAGE_API=0 turns both reads off.

Knowing what's left. ai accounts shows each account's two windows, their resets, the credits and the state. Readings come from these sources, newest first per window:

  • the statusline payload of live sessions on that account, after every response;
  • Claude Code's own /usage, run headless in that account's directory: no model turn, no tokens, about three seconds, with Claude Code using and refreshing its own login. It runs every 15 minutes for idle accounts, and right before a choice for any candidate whose reading is older than five minutes;
  • fallbacks: the OAuth usage endpoint (with the token above), then Claude Code's own cached last reading;
  • refusals seen by hooks and headless runs, which hold the account until its reset.

A reading never goes down inside a window, because usage only grows there, and it expires at the reset. Only structured signals count: the statusline's rate_limits, a StopFailure error, and a headless rate_limit_event or api_error_status. Free text never counts. One account's window never raises a warning in a session on another account.

Choosing. First, some accounts are out: those not logged in, those refused, and those past 100% unless their credits can pay. The rest are ranked by headroom, min(100 βˆ’ 5h, 100 βˆ’ 7d), where a 5-hour window resetting within 30 minutes counts as reset, and at least 25 points is preferred. On near-ties, the account last used for this project wins; then priority decides. Unknown usage ranks after known-good. An account with failover_target: false is never recommended or picked automatically, unless it's the session's own.

The switch, step by step. Does the open session get reconnected to the new account? No: nothing reattaches a running process to another account. A running Claude Code keeps the config directory and the login it started with, and /login inside it would change the login for every session on that directory, which the harness forbids. So the supervisor ends the process at a safe moment and starts a new one on the other account. The new process resumes the same conversation under the same session id. In the terminal, it reads as one session with a banner in the middle.

1. Each account is a directory. ai runs Claude as a child process on one account's config directory and stays behind it as the supervisor.

2. Watch. After every response the statusline gets both windows. At 90% of either, I see one line naming the account that would take over (shown to me, not added to Claude's context).

3. Ask. At 95%, or right after a refusal, the hook looks for a target with at least 25 points of headroom. Then it checks the brakes:

  • the session has been on this account for at least 30 minutes; a refusal skips this check;
  • there have been no more than three switches in five hours;
  • waiting isn't cheaper. If the 5-hour window binds and resets within 10 + 10 Γ— (context / 100k) minutes, the session waits instead: 30 minutes at 200k of context, 90 at 800k.

If everything passes, the hook writes a switch request and tells Claude once: ACCOUNT SWITCH PENDING, finish the current step, update the plan and progress files, end the turn. Only now does the statusline show the pending switch, ⇄ β†’ email. In confirm mode I answer switch or stay.

4. Wait for the boundary. When the turn ends, the Stop hook marks the session ready; a pending switch wins over an attached loop. The supervisor waits until Claude is idle, with no tool and no background task running. Then it checks the target again: dwell and ranking, logged in as the registered email, still room, and the conversation not open in another process.

5. Hand over. A checkpoint is written, as before a compaction. Then the supervisor:

  • stops Claude with SIGTERM (SIGKILL after 10 seconds if it hasn't exited; at the prompt it exits in under a second), and Claude Code restores the terminal;
  • copies the conversation's files into the other account's project folder, the /rewind checkpoints included, because transcripts live inside each account's directory;
  • gives the other account the trust I already granted this folder on the first one. It sets the hasTrustDialogAccepted flag in that account's .claude.json, written only while no Claude runs on that account.

Without that trust grant, a folder the other account never opened would stop the resumed session at the trust dialog, and in an unattended switch nobody would answer it. The first live test ran straight into that.

6. Resume. The supervisor prints ⇄ ACCOUNT SWITCH from β†’ to (reason), sends a notification, logs the switch, and starts claude --resume <same id> "<continuation note>" on the other account, with every original flag and the session's permission mode, so bypass stays bypass. If a refused turn caused the switch, Claude Code continues that interrupted turn by itself.

7. After. What stays the same: the conversation, its history, the session id, the plan files, and an attached loop, which follows. What changes: a new process, a cold prompt cache, and the earlier thinking blocks are gone. For ten minutes the statusline shows ⇄ from email.

It ran end to end on 2026-10-26, in a test session that asked Claude three small sums, one per turn. The first answer came on one account. Then an automatic switch moved the session to the other, and Claude's first words there were "The previous answer (SEVEN) is complete, so I'm waiting for your next request." The new process had the whole conversation. The second answer came there, and a typed switch moved the session back for the third. The session id stayed the same throughout, the switch log holds both moves, and both transcripts hold every answer. The test found two gaps, both fixed the same hour: the trust dialog above, and launches from inside a session that inherited the parent session's markers (which, among other things, switch transcript saving off).

Sequence of an account switch: Claude on account A reaches 95%, ai asks it to end the turn, checks account B, stops A with SIGTERM and resumes the same conversation on B.
An account switch at a turn boundary: a new process, the same conversation.

Near the wall. In order of preference:

1. Another account has room: the switch above. Loops and headless runs move at their next launch.

2. Usage credits can pay. An account marked credits: allowed keeps going if Claude Code says credits are usable, and one USAGE CREDITS note says so. My plan is to use credits rarely; "Lever 5: plans, not credits" below shows why.

3. Nobody has room: LIMIT WATCH, and the work keeps going. It warns at 95% and goes critical at 98%. The session doesn't stop, and doesn't end its turn to wait. Instead it:

  • keeps its files current, commits and pushes;
  • writes one handover record: task, what's done and verified, what's next in order, loops, open risks;
  • tells me in one line what it saw and when the window resets;
  • continues.

When requests are finally refused, the open session waits and continues the task by itself after the reset. That's Claude Code's own autoContinueAtUsageLimit, on in my settings, for resets within 24 hours. The supervisor never exits Claude during that wait, because exiting would cancel it. Loops wait in their launchd drivers and continue at the earliest reset. Nothing falls back to another provider.

Two cases still need me:

  • a machine that slept across the reset waits for Enter;
  • a weekly window more than 24 hours away on every account, outside a loop, waits for a person.

Headless runs. ai exec, loops, reviews and the improvement worker choose an account at every launch: the caller's own while it has room, otherwise the best. A refused -p run is repeated once on the next account, but only if no tool ran yet. Otherwise it exits with code 75, and a loop's next iteration picks another account.

What a switch costs. Prompt caches belong to one organization. So the first request on the new account re-reads almost the whole conversation at the cache-write price instead of the cache-read price. Earlier thinking blocks are dropped too; both effects were measured on 2026-10-25. At Opus 5.5 list prices, with cache reads at $0.20 and one-hour cache writes at $8 per million tokens (API-equivalent estimates, not bills):

ContextFirst turn after a switch
200k tokensabout $1.51, against $0.04 for the same turn on a warm cache
800k tokensabout $6.31, against $0.16 on a warm cache

That cost is why the brakes exist: switch late, switch rarely, preferably when the cache is already cold, and wait when the reset is close enough.

Who did what. Every headless launch writes one line to accounts/launches.jsonl in the private state folder: time, account, how it was chosen, project, reason, exit code, refusal, target. Every interactive switch writes one line to failover/switches.jsonl. Prompts are never in either file. The usage ledger attributes every response to the account whose directory holds the transcript, never to the folder the shell happens to be in.

The terms, plainly. I read them before shipping this (pages fetched 2026-10-25).

  • Claude Code's legal page says OAuth "is designed to support ordinary use of Claude Code and other native Anthropic applications", and adds: "Advertised usage limits for Pro and Max plans assume ordinary, individual usage of Claude Code and the Agent SDK."
  • The same page says "developers may not collect, store, or intermediate Claude.ai credentials or session tokens". My harness reads my own token for the two read-only requests above. That is the part of it closest to that line, and AI_USAGE_API=0 switches it off.
  • The usage policy, under "Do Not Abuse Our Platform", lists among what users may not do: "Coordinate malicious activity across multiple accounts, or create multiple accounts, to avoid detection, circumvent product guardrails, or generate identical or similar inputs that otherwise violate our Usage Policy", and to "Intentionally bypass or interfere with capabilities, restrictions, or guardrails established within our products".
  • The consumer terms, which cover Pro and Max, forbid automated access "Except when you are accessing our Services via an Anthropic API Key or where we otherwise explicitly permit it". Headless claude -p is a documented part of Claude Code, and the limits sentence above names "the Agent SDK", so I count my loops as permitted. That is my reading, not Anthropic's statement.
  • When the weekly windows were announced in July 2025, Anthropic's email to users, in a copy a user posted on Hacker News, named "advanced usage patterns like running Claude 24/7 in the background" among what was "impacting system capacity for all".
  • Thariq Shihipar, then on the Claude Code team, wrote on 2026-02-18 that "it's not against terms of service to have multiple MAX accounts", adding that using them to resell tokens is.
  • No clause I found names automatic switching to keep working past a limit.
  • One Hacker News user reported a permanent ban right after automatic switching with a third-party tool (February 2026; I couldn't verify it).
  • One blog (MetricNexus, February 2026) reported bans of people holding several Max plans.

My reading, and it is only mine: one person with two accounts is ordinary.

So the harness switches visibly, at turn boundaries, with the unmodified Claude Code binary. There are no token pools, no proxies and no credential swapping, and every switch is logged. Which accounts may receive work automatically is one flag per account, and failover.mode is one line.

Cost optimization, measured

Where the money goes

From the last 30 days of Claude Code's own transcripts before the rebuild (it keeps about 30), priced at list prices (estimates, not bills):

FindingNumber
Input tokens that were cache reads98.5%
Share of the cost that was cache reads58%
Requests above 400k tokens of context42% of responses, 61% of the cost
A response at 800k+ context vs one under 200kabout 4Γ— the cost
Subagents42% of the cost
An average general-purpose subagent104 responses, context growing to 267k on average
A forkstarted at about 225k tokens, almost all of it the parent's conversation
A prompt after more than an hour idlerewrote about 490k tokens of cache (100 cases)

Two things follow. Context size drives the bill more than anything else, and subagents are over two-fifths of it.

Interactive work at max effort

A fair worry once the roles exist: most of my work runs in the main session on Opus 5.5 at max, and no delegation math touches it. The ledger answers part of it: the last 7 days to 26 October, 15:51, both accounts, every response the hooks recorded, priced at list prices. Two caveats. The week includes the rebuild's long session, so it leans toward big contexts. And the ledger has recorded subagents only since 00:05 on 26 October, when the code that records them landed; before that it holds the main chain only, so the main sessions' share below is an upper bound. The rebuild night's own transcripts alone put its subagents at $664, more than all the subagent rows below together.

WhereShare of the costWhat it went on
main sessions (interactive)69% ($1,459 of $2,114)the context: cache reads $856, cache writes $365; output $237, thinking included ($121)
subagents without a role (general-purpose, forks)13%they ran on the main session's model and effort
implementer subagents9%β€”
headless runs (loops, reviews, the benchmark)8%β€”

All the thinking, at max, was $121 of the main sessions' $1,459, about 8%. Context was 84%: the average main-session request carried 465k tokens and cost about $0.16, most of it for the context. At 50k tokens the context part would cost about a tenth as much. So the biggest lever I can measure for interactive work is the size of the session. Max effort may also add turns, and each turn re-reads that context; this table can't tell the two apart. Two changes came out of the table:

  • general-purpose now has the same kind of override Explore got: Opus 5.5 at xhigh (set in its agent file; live probes saw it run at xhigh under a main session at max and under one at low). Before, every general-purpose agent inherited the main session's max.
  • The dashboard shows what this session has cost so far, so a long, expensive session is visible while it grows.

Lever 1: context

Compaction at half the window, files over chat, fresh contexts and /clear between tasks; "Context management" below has the details. The lever I haven't pulled: compacting at 300k tokens instead of 500k. By my estimate that would cut a long session's cost by about a quarter, and it trades away detail the model can still see. That is a quality decision, so it's mine to make, not the harness's.

Lever 2: who does the work

  • Reading on a cheaper model. Sonnet 5.5 costs exactly half of Opus 5.5 on every price column (input $2 vs $4, output $10 vs $20, cache reads $0.10 vs $0.20 per million tokens; Anthropic's pricing page, read 2026-10-26). Up to 100k tokens of prompt, Haiku 5.5 costs a twentieth of Sonnet's input and output price and a tenth of its cache-read price; above that it costs five times as much, a quarter of Sonnet's input and output price.
  • Effort where a miss costs most. Anthropic's effort guide: max is "Absolute maximum capability with no constraints on token spending"; the Claude Code docs warn it "may show diminishing returns and is prone to overthinking"; and the effort guide for Sonnet 5.5 says "Use xhigh or max only where your evals show a quality gain."
  • Checks guard the cheap work. A cheaper role's result is read by the main session, its code is tested, and big work is reviewed at max before it lands.

Lever 3: benchmark the table

The benchmark. ai bench roles runs each role as a real Claude Code subagent on tasks shaped like the harness's own delegated work: 26 of them, 16 standard and 10 hard. Scouts answer questions about the code, verifiers run test suites with traps in them, implementers make real changes against hidden tests, reviewers read diffs with planted bugs, researchers look up documented facts. A script grades every run, never a model. Every run's cost comes from its own transcript at list prices, and every setup runs each task three times.

The rule: written before the first run, guarded after the first results. The original rule: take the best score, allow three points of noise, pick the cheapest setup inside that band. On the morning of 26 October, after the first results, two independent reviews found its hole. On tasks that everyone passes, "the cheapest within three points" is always the cheapest model, which proves nothing about real work. So a change now also needs every task run three times for both the candidate and the current row, a cheaper setup that fails on score (proof that the tasks can tell setups apart), and the pick winning 80% of 2,000 bootstrap replicates. Otherwise the row stays. The guards can only block a change, never cause one; under the original rule alone, the cheaper candidates would have won.

The result. 378 runs (397 attempts, $88.57 API-equivalent, 9.4 agent-hours). The 19 extra attempts are runs the benchmark threw out and repeated: stopped when the sweep was paused for account limits, killed, or contaminated.

RoleKept Β· candidate for harder tasksScore, kept Β· candidateCost per run, kept Β· candidate
scoutSonnet 5.5 high Β· Haiku 5.5 medium1.00 Β· 1.00$0.085 Β· $0.008
verifierSonnet 5.5 medium Β· Haiku 5.5 low1.00 Β· 1.00$0.041 Β· $0.003
implementerOpus 5.5 xhigh Β· Sonnet 5.5 high1.00 Β· 1.00$0.81 Β· $0.13
reviewerOpus 5.5 max Β· Sonnet 5.5 high1.00 Β· 1.00$0.75 Β· $0.08
researcherOpus 5.5 high Β· Sonnet 5.5 medium1.00 Β· 1.00$0.19 Β· $0.07

Every setup scored 99% or better, Haiku included. Effort moved the bill, not the score: an implementer at max cost $2.21 a run, at xhigh $0.81, at medium $0.23, all at the ceiling. A benchmark that can't tell setups apart can't justify a cheaper one, so the guarded rule kept every row. The candidates wait for harder tasks, and the learning loop below watches what real work does.

One finding came from the runs themselves. Twice, the implementer at xhigh came across the benchmark's own task folder, where the hidden tests live, in a command's output. The benchmark scans every run's tool inputs and results for the task folders and their key material, and excludes a run that touched them; it threw both runs out as contaminated and ran the task again.

Lever 4: the table learns

The table isn't final; it can change by itself, under these rules:

  • Every delegated result is checked. By policy, the main session checks each result; when a check fails, it retries the task one rung up the role's ladder (its next stronger model or effort) and records the outcome with ai roles note. The ledger knows each subagent's role, so ai report and ai roles status show cost and failures per role.
  • Once a day the improvement run looks at the evidence. It raises a finding when a role fails too often (at least 8 checked results in 14 days, more than 20% failed or escalated), when the guarded benchmark has picked a cheaper setup that isn't applied yet, or when a model or a price changed. Then it acts on one role: it re-runs the benchmark for that role and applies only the guarded decision, through the sandboxed tests, an independent review and a notification.
  • When the benchmark can't reproduce what real work shows, the row climbs one rung on its ladder instead, through the same gates, and real use measures it again. It never climbs down on real-use evidence; only the guarded benchmark lowers a row. It never touches the floor (the main session, the reviewer, the second opinion).
  • If a climbed row still fails, the harness stops and files the question for me: model strength is probably not the cause.
  • Unattended benchmarks never browse. A benchmark task that browses the web could meet a page that tries to steer an agent running with full permissions, so the two roles with web tools, the researcher and the implementer, are re-measured only when I run ai roles tune myself; until then, a finding about either waits for me.

Lever 5: plans, not credits

The question behind adding accounts: is it cheaper to hold several Max plans than to pay for usage credits once one plan is full?

Usage credits are billed at API list prices. My Max 20x plan is a flat $200 a month with a 5-hour and a 7-day window. So everything hangs on one number: how much API-priced work does one plan absorb before its weekly window fills?

I measured it. The 7-day window resets at a fixed time, so I took everything my main account did since the last reset, priced it at list prices from Claude Code's own transcripts (main sessions, subagents and isolated reviews), and divided by the percentage the window showed.

One account, four days of real workValue
API-priced work since the weekly reset$870.95
Weekly window used36%
One weekly pointβ‰ˆ $24
One full weekβ‰ˆ $2,400
One month (Γ— 4.35 weeks)β‰ˆ $10,500 of API-priced work for a $200 plan

Two short windows during the build agreed: $24 to $26 a point. At the end of the build the same window read 49% for $1,186.36: $24.21 a point. Two notes on the method. My pricing tool prices each model at its own list price and counts a model it doesn't know without pricing it; in this window every response ran on Opus (5.5, plus 74 responses on 4.8), so nothing went unpriced. And it is a lower bound: chat on the web and desktop apps shares the same limits but leaves no transcript on my laptop.

OptionPer month, and the capacity at list prices
my last 30 days as credits (both accounts, measured)β‰ˆ $9,600: what I used
one Max 20x plan$200 for β‰ˆ $10,500 of capacity; heavy weeks still hit the wall
five plans$1,000 for β‰ˆ $53,000
six plans$1,200 for β‰ˆ $63,000

The plans win by more than an order of magnitude. Why five or six plans? Those 30 days were Claude alone: in the 60 days before the rebuild, another provider served 79% of all my responses, and since it left the harness that work runs on Claude too. And my usage isn't even: a single heavy week is what hits the wall.

The caveats:

  • A window is a quota, not a price list. The provider sets it and can change it. These numbers describe four days on one machine with my mix of work.
  • A switch isn't free, but dozens a month would still be small next to the gap above.
  • The terms are the real constraint, not the math. The table is a calculation, not advice to buy five plans, and today I run two accounts.
  • Credits still win for a short spike on an account you already have, and they need no switching.

Context management

Context is where the cost and the quality go; the numbers are in "Where the money goes". Two more: auto-compaction fired near 967k tokens (the median), and the median interactive session started at 67k tokens before any work.

What the harness does about it:

  • Compaction at half the window. Auto-compaction runs near 500k instead of near the 1M limit. autoCompactWindow: 500000 is set at the top level and for the Opus model.
  • A checkpoint before every compaction and every account switch. It is written privately (mode 0600) and holds the time, folder and account; the request ledger, the attached goal and its progress; the last ten prompts verbatim; the files edited this session; and the git state of the repositories involved. To find those ten prompts after a long autonomous stretch, it reads the transcript backwards in 2 MB steps, bounded at 48 MB and 1.5 seconds.
  • My words survive the summary. After a compaction or resume, the first hook injects a three-line pointer to the checkpoint, so a summary never replaces what I actually wrote.
  • Big work lives in files: the ledger, PLAN.md and progress.md, re-read before major steps.
  • Lean instructions. One global policy of about 220 lines. Procedures load through skills, and only when needed.
  • Subagents get narrow briefs, write results to files and return at most 30 lines.
  • Cold caches. Claude suggests /clear for an unrelated request, or after a long idle on a big context.
  • Transcripts are kept 90 days instead of 30 (cleanupPeriodDays: 90), because measurement and self-improvement need them. Thirty days measured 5.48 GB.

My rules of thumb:

  • pick model and effort at the start, and change models only through subagents, because a model switch rebuilds the cache;
  • /clear between unrelated tasks;
  • compact only while the cache is warm;
  • prefer a fresh session over resuming a huge idle one;
  • read targeted ranges, and send bulk reading to a scout.

The statusline, segment by segment

One line under every session, refreshed after each response and every ten seconds (refreshInterval: 10). It reads left to right: where β†’ who β†’ what β†’ how full β†’ how much is left β†’ money β†’ alerts, with a small bar for the context and both windows, and thin separators. It is about this session and its account only; everything else has its own place (below). In my terminal a Nerd Font icon leads each segment (AI_ICONS=unicode draws plain ones); the examples here leave the icons out.

dotfiles βŽ‡main* β”‚ a@example.com β”‚ Opus 5.5 max β”‚ ctx β–°β–±β–±β–±β–± 21% β”‚ 5h β–°β–°β–°β–±β–± 62% ↻10:20 β”‚ 7d β–°β–°β–±β–±β–± 41% ↻Wed 21:00 β”‚ credits $4.20
SegmentMeans, and where it comes from
dotfiles βŽ‡main*the project: its registry name, else the folder, or ~ at home. Then /repo in a nested repo of a multi-repo project, /worktree in a linked worktree, the branch, and * when tracked files changed. From the project registry, the repo's HEAD, and git status -uno cached for 10 seconds.
a@example.comthe account this session runs on, each in its own color. From the config directory that holds the session's transcript, never the shell's folder.
Opus 5.5 maxmodel (purple) and live effort (orange at max, yellow below). From Claude Code's statusline payload.
ctx β–°β–±β–±β–±β–± 21%context in use, with a five-cell bar (compaction near 500k of 1M). From the payload.
5h β–°β–°β–°β–±β–± 62% ↻10:20the 5-hour window used, with its bar and its local reset time. From the payload, else the account's usage cache.
7d β–°β–°β–±β–±β–± 41% ↻Wed 21:00the same for the 7-day window.
credits $4.20the prepaid credit balance, shown only above $0. From the account's balance, cached.

Usage colors, for the numbers and their bars: green below 70%, yellow from 70%, red from 90%.

Alerts, appended at the end of the line:

AlertWhen
⚠ 5h 96% or ⚠ 7d 97%a window passed the switch threshold, 95%
⇄ β†’ other@emaila switch is pending for this session; it happens at the end of the turn
⇄ from other@emailfor ten minutes after the session moved here
LIMITthe account refused a request and the hold is active
⚠ login β‰  registrythe config directory is logged in as a different email than the registry says
driftthe last automatic ai apply failed (ai doctor says why)

On a narrow terminal, the bars go first, then the reset clocks, then credits, then the icons, then the email's name and the branch are shortened (the domain stays); the context share and both windows always stay. A part that fails is left out and logged. The command always prints one line.

Why the cache left the line. Every request repeats the whole conversation so far, and the provider keeps a prompt cache: when the start of a request matches one it processed recently, it reuses that work, which is faster and much cheaper. An earlier line showed cache 99% Β· 48m: the share of input served from cache, and how long it stays warm. That segment never asked me to do anything, so it went. The cache still decides the bill (see "Where the money goes"): after more than an hour idle, the next request writes the whole context again.

The agent panel and the dashboard

Agent panel rows. Below the prompt, Claude Code lists the running subagents, one row each. Its native subagentStatusLine setting lets a command rewrite those rows from live data. Mine (ai statusline --subagents) leads with the role and the job the agent was given, then its model and effort, its time, what it has cost so far and what it is doing right now:

● implementer Β· Add a retry to the usage probe Β· Opus 5.5 xhigh Β· 4m Β· $0.40 Β· Editing usage.py
● scout Β· Map the hook wiring Β· Sonnet 5.5 high Β· 1m Β· $0.05 Β· Reading hooks.py

The effort is always the one the agent really runs at: a built-in that inherits shows the main session's effort, never the word inherit. When the row gets narrow, the activity goes first, then the cost, then the time; the role and the job stay, the job cut short at a word.

The dashboard above the prompt. The statusline answers "where am I, and how much is left", the agent panel "what are my subagents doing". Neither shows the rest: the headless runs this session started (some on another account), the project's loops and other sessions, other projects, and what all of it costs. So every session draws a band above the prompt. It took four versions: the first showed the whole machine and read as noise from another project; the second scoped it but repeated the statusline and the agent panel; the third gave every fact one place and was too plain to scan; the fourth keeps that order with labels, icons, colors and aligned columns.

The bottom of a Claude Code terminal, top to bottom: the dashboard band above the prompt, with bold colored labels and an icon for each kind of work (MAIN, HEADLESS, LOOPS with a progress bar, SESSIONS, ELSEWHERE, NEEDS YOU, NEXT, TOTAL) in aligned columns; the prompt box; the statusline with an icon per segment and small bars for the context and both usage windows; the permission mode line; and Claude Code's agent panel with two subagent rows.
The terminal as the code draws it, from its built-in example (example accounts).

The band in a busy session (the built-in example):

● MAIN         1h42m Β· ● 2 agents Β· β–Ά 2 headless Β· ~$17.70 incl. agents + headless
β–Ά HEADLESS 2   exec    film scene builder   Opus 5.5 xhigh   12m   ~$2.10   Render scene 4   ⇉ b@example.com
               review  second opinion       Opus 5.5 max      3m   ~$0.90   Read the diff
↻ LOOPS 1      dash-v2   β–•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–β–β–β–β–β– 7/12   βœ— 2 checks failing   driver   8h20m   ~$3.80
β—‡ SESSIONS 1   1 other session Β· busy
β—† ELSEWHERE    work  β—‡ 1 busy session Β· β–Ά 5 headless
⚠ NEEDS YOU    work  "release" loop has waited 2m for your answer
β—· NEXT         06:00 ai watch Β· 06:40 ai improve Β· inbox 4 Β· ⇉ b@example.com 5h 10% Β· 7d 5%
Ξ£ TOTAL        agents worked 4m Β· headless worked 15m Β· all sessions today ~$41
RowShowsWhen
● MAIN (orange)this session: how long it has run, how many agents (●) and headless runs (β–Ά) it has going, and what it has cost so far, all of them includedalways
β–Ά HEADLESS n (purple)one row per headless run this session started, in aligned columns: kind (exec, review), job, model and effort, time, cost, what it is doing, and ⇉ with the email when it runs on another account; a red row when it failed, until my next promptwhile there is one
↻ LOOPS n (green)this project's loops: a bar with the plan's step, βœ— failing checks in red, the driver, time and costwhen there is one
β—‡ SESSIONS n (cyan)this project's other sessions: busy or idle, and for how longwhen there is one
β—† ELSEWHEREother projects in one row, icons with words (work β—‡ 1 busy session Β· β–Ά 5 headless), then +N morewhen another project is busy
⚠ NEEDS YOU (amber)anything that waits for me and isn't already a row here, such as a loop waiting for an answer, with its age; red for a failureuntil it is handled
β—· NEXT (amber)the next scheduled runs (the morning watch, the improvement worker), the inbox, and the other accounts' windows after ⇉always
Ξ£ TOTALthe agents' and headless runs' summed working time, and today's API-equivalent cost over every sessionwhen this session started something

Who a run belongs to is decided by who started it, not by its folder or account: the live session that launched it, then the run or loop that launched it, then the machine for the watch and the improvement worker. Each kind of work has its own color and icon, failures are red, and anything that waits for me has its own ⚠ NEEDS YOU row. /dash all widens the band to every project; /dash off hides it. ai dashboard --all prints the whole tree in any terminal, project by project, with a total line.

Every number comes from files the harness already has. ai dashboard --json reads the session's transcripts incrementally (a cache of byte offsets, so a 1M-token session is never re-read), the live record each headless run writes while it lives, the loop states and the usage cache, in about 45 ms. Costs use one price table and deduplicate responses by message id: API-equivalent estimates at list prices (the ~), never a bill, checked against Claude Code's own cost record for a finished loop iteration. The band is a Claude Code plugin, the kind Claude Code calls a mod: a small TypeScript module draws it above the prompt every few seconds, and the shared settings load it in every session of every account.

Self-improvement, inside fences

The inbox. Problems that sessions and the watch report go into one private list, ai inbox: what happened, the evidence (a path, a command, the session), and whether the unattended worker may take it ([auto]) or only I can decide (--manual). In the first night after the landing, sessions filed fourteen items: a review that stalled for an hour on an open stream, a macOS privacy prompt that froze a loop driver, a search guard that couldn't see other projects' folders, a flaky test. Three were fixed and closed before the morning; the rest wait their turn.

How problems get in. Four ways:

  • Sessions report them. Any session that hits a harness problem (a bug, friction, waste, a stale rule) runs ai inbox add "…" --evidence "…". Small, safe fixes happen at once in a background worktree, and land with tests and a review like any harness change. The rest wait in the inbox for the daily worker.
  • A daily scan finds them, with no model call. It reads the usage ledger (including the share of requests above 400k tokens), hook errors (the same one three times in seven days becomes a fix), calls to retired command names, failed applies, ai doctor failures and warnings, blocked or silent loops, failed reviews and the inbox.
  • I say so, in any session. The harness skill routes the change to ~/dotfiles.
  • The morning watch finds them in the world outside: see below.

A finding has a stable id, a severity (fail, warn, info), the evidence and a suggestion. It's marked resolved when it stops appearing.

The daily run. A background job runs ai improve daily at 06:40 and, if no account had room, again every hour until 12:40: a scan, then at most one fix a day.

1. Gates before any token. The run goes ahead only if all of these hold, and otherwise it skips and says why:

  • no STOP file exists (my kill switch; the harness never creates or removes it);
  • no other run holds the lock, and none ran in the last 20 hours;
  • an open finding is marked safe to fix automatically;
  • an account has room after a fresh usage probe.

2. A copy, never my checkout. The runner exports origin/main, without the secrets folder, into a temporary folder. It commits it there as a starting point so the worker can diff. Then it runs the whole suite once to record which tests fail in a sandbox anyway (macOS refuses setuid programs such as ps inside one).

3. One worker, fenced. One headless Claude, Opus 5.5 at xhigh, fixes that one finding with a regression test and docs, and ends with a result line. Its limits:

  • at most 200 turns and two hours, with its whole process group stopped on timeout;
  • acceptEdits, never bypass;
  • user settings only, so a settings file inside the copy can't grant it more;
  • no MCP servers, no connectors, no web tools, no tokens in its environment;
  • Claude Code's Bash sandbox: no network, nothing under the home folder readable, writes only in its own folder, never retried outside. If the sandbox can't start, the run doesn't either.

That list looked complete. Then a probe on 2026-10-25, on Claude Code 2.1.310, showed a file tool writing into my home folder. The cause was my own settings: a hook that approves every file edit, which the worker inherited. With hooks off, acceptEdits refused the same write (rechecked on 2026-10-26). The Bash sandbox never saw it, because the write didn't go through Bash. So the file tools got their own fence: no reads outside the folder, and deny rules for writes anywhere under ~, which Claude Code enforces whatever a hook says.

4. The runner takes files, not code. After the worker, the runner gives up if STOP appeared, or if anything it watches outside the folder changed: its own last-run record, the ai entry point, the improvement module, and the settings file and its link. It never runs git with the copy's configuration. It reads the folder only as file contents, using the dotfiles repository's own configuration and a private index, and builds one commit on origin/main. No repository hooks run, ever.

5. The runner checks, not the worker. Before anything lands, all of these must hold:

  • Protected files are untouched, renames included: the improvement module and its tests, the account registry, the harness settings, its own background job, and the chain the runner trusts while landing.
  • The permissions, hooks and env of the shared settings.json are unchanged.
  • No symbolic links or submodules were added.
  • The global policy may improve, but its "Truth" and "Boundaries" sections stay byte-identical, and its STOP, verifier and no-downgrade sentences stay.
  • The model and effort floor (main session, reviewer, second opinion) is unchanged.
  • There are no fewer tests, at most 600 changed lines, and nothing that looks like a credential.
  • The whole suite passes on an export of exactly that commit, under macOS's sandbox-exec: no network, nothing under the real home readable, writes only in a throwaway folder that is also HOME. No test may fail that passed at the start.
  • An independent second opinion ends with VERDICT: approve.

6. Land or step aside. If main moved, the change is merged onto it, then checked and tested again. If everything passes, it's pushed without force and applied; the finding and its inbox item close, and a notification says what landed. If any gate fails, the commit stays on an improve/<stamp> branch, pushed for me unless it might hold a credential. An inbox item says why, the finding waits seven days, and a notification says it didn't land.

Why so strict? An unattended change to the tool every session runs on can fail in two ways: a bad change breaks every session at once, and a clever change can weaken its own rules. The list of things the worker can never touch came from the first safety review.

The first real run (2026-10-26). At 06:40 the run picked one item: replace the harness's ps calls. The worker fixed it in its copy in 159 turns. Then the runner could not take the files, and the fix nearly went with the worker's temporary folder. Claude Code leaves an empty .claude/.cc-writes/ folder where it works, my global git ignore lists that folder, and the take named it in an exclude pathspec. Git refused that pathspec after staging. The runner filed its own failure in the inbox. The leftover index still held the worker's changes, so the fix was recovered onto a branch. The runner now takes files with only the repository's own git config and never names litter on the command line. It logs every git call with its full error, and keeps the worker's changes as an archive whenever it gives up.

The morning watch. At 06:00, forty minutes before the improvement run, ai watch daily reads its sources:

  • Collect, without a model. A fixed list of primary sources: Claude Code's changelog and documentation index, Anthropic's news and release notes, the model and pricing pages, and the versions of the tools the harness runs on. Each page is saved with its fetch time and hash, and compared with yesterday's copy.
  • Read, with one model. One headless Claude, on the researcher role's model and effort, reads the changes and the day's industry news and writes a report: what changed, why it matters to this harness, and a proposed action. It may search and fetch the web, but it has no shell and no MCP servers, reads only its run folder, and writes only its report and its item list.
  • Verify, without a model. Every quoted sentence must appear in a saved page, or its item is marked unverified and can't become automatic work.
  • Act. Each verified change that matters becomes an inbox item, so the 06:40 run can take it. A new model or a new price also raises a role-table finding (Lever 4). One notification a day says what changed.

Its rehearsal. The first run, a rehearsal that filed nothing, fetched all 13 sources and the 228 English documentation pages in 93 seconds. The reader took 155 seconds, 28 turns and four web searches, about $1.16 at list prices. It found ten items, from a Claude Code release that hardens the Bash sandbox to Haiku 5.5's availability, and the verifier found every quote on a saved page. All ten were low impact, so none would have reached the inbox. Before it landed, two independent reviews found problems (two high and five medium, then one medium); all were fixed, and a live probe checked that its fences hold.

Articles about big improvements. When a landed improvement changes what the harness can do (a new capability, a measured change of the role table, a move to a new Claude Code feature or model), and nothing was published this way in the last week, a fresh reviewer is asked whether it is worth an article. If it says yes, the blog workflow runs end to end without me (independent draft, build with the privacy scan, pre-publish reviews until clean, email checks), the post waits as a draft, I get a notification, and it publishes six hours later unless I hold it (ai blog hold). Pure tech only, by the same rules as this post.

The role table learns. The same loop tunes which model and effort each role runs on; "Cost optimization, measured" has the details and the numbers.

What I see: one line at session start, at most once a day and only when something is new; the watch's daily digest; a notification for every landed or refused fix; ai improve status, ai watch report and ai inbox list, when I ask.

Knowledge

Every session, on every account, shares one local search engine: QMD. It is one SQLite index with full text (BM25), vector embeddings from its own small local models, and re-ranking, available as a CLI and an MCP server. A background job keeps its MCP server running over HTTP on localhost. ai apply --full registers it in every account, so every session in every folder has the same qmd tools.

qmd query "how does failover work"    # hybrid; the one to use
qmd search "exact words"              # full text only

One pair of collections per repository: its curated content/ and its generated data/. Each has a short root context that travels with every result and carries nothing private. ai new adds a new project's pair. Code under a cockpit's repos/ is never indexed.

The layout is the same everywhere:

  • content/ holds curated knowledge, with an overview.md per area, read first and kept under about 200 lines;
  • data/ holds machine-written or verbatim records, never edited by hand;
  • tools/ holds code.

QMD before grep, judged by scope. A guard denies any grep, rg, ag, ack or git grep whose scope covers a project's knowledge folders, and tells the agent to query QMD instead. The scope is the searched paths, never the current folder alone and never words in the command. A search with no path covers the current folder if it's recursive; otherwise it's a stdin filter. So:

  • ps aux | grep node inside content/ is fine;
  • a pathless Grep from tools/ is fine;
  • rg x from the repository root is denied, because it covers content/ and data/.

Reading a known file is always allowed, and code searches name a code path. A project may keep its own guard instead. Each block is logged with the rule, never the search.

Fresh and clean. After every commit, merge and checkout in a repository with collections, the git hooks queue an index request. One shared, locked worker runs qmd update then qmd embed, coalescing requests from every project.

ai qmd cleanup first takes a verified backup: an online SQLite copy, an integrity check, and row counts compared table by table. Then it runs QMD's own cleanup under the indexer's lock. Its first run, during this rebuild, removed 245,285 orphaned embedding chunks and 1,265 stale document records. The index went from 1.8 GB to 1.4 GB.

Memory follows the project, not the account. A project's memory store is a claude-memory/ folder in its own repository, linked into every account's project folder by ai apply. ai doctor reports stray memories in an account's native folder. Machine-level memory stays with the default account.

The evolution record. The history of all this lives in one permanent document, my AI evolution record. Every dated fact in it has a source. Four mechanisms keep it current: the global rule that every harness change updates it, the blog workflow's last step, an ai doctor warning when harness commits are newer than its last entry, and the daily improvement worker, which writes the missing entry.

Sessions, review, takeover

Sessions. One tab per task: ai, the project, the account, the task in plain words. Feedback stays in that tab, and I close it when I decide; finished work has already landed. Agents never open, fork or close sessions on their own, and never ask me to run git.

Several sessions at once are safe because of these mechanisms:

  • Registry. Every hook event records which session works in which checkout, and which files it edited (ai sessions, or --all for every repository).
  • Awareness. A session sharing a checkout or a repository with another live one gets a PARALLEL SESSIONS note: the other session, where it works, its edited files, and how to message it.
  • Guard. It refuses what could destroy another session's work: staging, committing, stashing, resetting, restoring, checking out or cleaning that session's uncommitted files; broad operations over unclaimed changes; branch switches while another session is live; and edits to a file another live session has uncommitted edits in.
  • Shared-checkout rules. Commits name their own paths (git commit -m MSG -- FILE...), never git add -A. A rejected push gets git pull --no-rebase --no-edit --no-autostash, then another push.
  • Worktrees take overlapping files, branch or PR work, colliding servers and long unattended runs. They land on the default branch as soon as they're done; whoever lands second rebases. Then they're removed. A sweep at every session start deletes landed agent worktrees that are clean and inactive.
  • Messages. A message from another session is a teammate's note, never my approval.

Review. ai review asks one fresh Claude, Opus 5.5 at max, to read a complete brief and answer once. It has no tools, no MCP servers, no hooks, no memory, no project instructions and no session persistence; the brief and every artifact go inline.

  • Labeled. It's labeled independent_same_provider and never presented as another vendor's agreement.
  • Account. It runs on the caller's account, or on the best other account with room when the caller's is held.
  • One attempt. If it can't run, the gate stays unmet. No weaker model and no other provider stands in.
  • When it runs: at plan freeze and before big work lands; on every batch item's pull request before merge; for the blog's independent draft and its pre-publish review; on every harness behavior change before it lands.
  • Cost. A completed review had a median of about 50k output tokens and 8 minutes, over 164 reviews: about $1.40 at list prices. So everything that needs review goes into one brief.

Takeover. When no account has room, the session writes a handover record and keeps working. In a new session I say "takeover from claude". The new session reads the record and the ledgers it names, checks git, re-derives the work from the repository's rules, continues, and closes the record. A record is a pointer and a to-do list, never authority.

How it was built

When (2026-10-25 and 26)What
19:16–21:05Research by parallel agents: all 159 posts of this blog, every project and the harness code, my Claude Code usage (60 days of prompts, 30 of transcripts), Claude Code's native features, published best practice, and how other people run several accounts
20:13–20:28The guard incident below, and its hotfix
from 20:30The design, then waves of agents in one worktree with disjoint files: accounts and usage, the launcher and supervisor, hooks, the statusline, projects and install, policy and skills, the loop engine, the content engines, the tests
22:05–00:00Five independent reviews, all asking for changes; every finding fixed or gated
23:43 and 23:56Two late requirements: a limit never stops the work, and explain the account switch end to end
00:05The landing: twelve commits, every test green on the exact tree that shipped
00:36The first live switch test, two accounts and back; it found two gaps, both fixed within the hour
00:45–03:00Every project moved onto it in parallel, the studio moved into the harness, and the film was made. More reviews (the batch engine, this article) and the build's own session found four more defects, all fixed: a stale verifier result shown as current, a search guard blind to other projects' folders, and two checks of the blog engine
from 03:00The film went onto this page; after a final independent review, it was published
from 06:36The second round: I asked for the orchestration in depth and for every model and effort to be measured, not guessed. Live probes, the role benchmark, the learning role table, the morning watch, the agent panel rows, the dashboard band, automatic articles, this film rebuilt in a new look, and this article deepened

One session, one ledger, one verifier. Before any code, the goal got a design, a plan with typed dependencies and a strict verifier. The critical path went first.

Five independent reviews. Each one was a fresh, isolated Claude reading the complete brief, and each asked for changes:

  • Design against the request: a gate for "every non-content check passes" before this article could claim done, and hooks that fail open with a kill switch.
  • The launcher: a crash path, terminal restore on every exit, signals passed to Claude, a width-safe statusline.
  • Accounts and failover: never switch onto a stale or unknown reading, the 25-point headroom rule, and a switch that can't fork a conversation.
  • Safety and boundaries: the sandboxed improvement worker, file contents instead of the worker's git, the protected chain, a QMD guard judged by scope, and no prompt text in any log.
  • The loops: a hard spend bound per item, stall detection that ignores the loop's own bookkeeping, and a verifier that a loop can't quietly change.

The guard that refused me. At 20:13 my own harness started refusing my prompts with "WRONG CLAUDE ACCOUNT", in the very session doing the rebuild. It was an old guard of mine, from when the folder chose the account, and it misfired: it judged a session by the shell's current directory, which follows every cd. The session had started in my home folder, on the right account, and had then changed directory into another project's folder; from then on, every prompt was "on the wrong account". I had to report it from a second session, and it took several messages to get out of it.

A hotfix on main stopped every refusal by directory that evening. The rebuild turned it into the rule above: nothing refuses a prompt because of a directory or an account. That rule is now a regression test in both directions, and a check in the verifier that decided whether the whole job was done. No hook reads the account from the folder anymore; the statusline and the ledger take it from the config directory that holds the transcript.

The landing. The work was built on its own worktree branch. It went to main through the same gates as any big work: the tests, the goal's verifier and an independent review. Because a commit in the dotfiles applies itself, landing was the deployment: every new session on every account picked it up. The account switch was proven live about half an hour later.

What it still can't do

  • Only supervised sessions switch. A session opened without ai follows every rule but has no supervisor. At a wall it waits for the reset instead of moving, and it runs at xhigh.
  • Part of the usage picture is undocumented. The plan-window fallback and the prepaid balance come from endpoints Claude Code calls but doesn't document. They are read-only, cached and never blocking; a failure shows as unknown.
  • A busy target can still ask about trust. The trust flag is written only while no Claude runs on the target account. Otherwise the banner warns that Claude Code may ask once, and an unattended switch would wait at that dialog.
  • Waiting out a wall still needs me in two cases: a machine that slept across the reset, and a weekly reset more than 24 hours away on every account, outside a loop.
  • Automatic switching is a gray zone in the terms. I kept it visible and one line away from off.
  • Headless runs depend on today's claude -p. Claude Code's docs say that --bare, which "doesn't use your subscription login", "is the recommended mode for scripted and SDK calls, and will become the default for -p in a future release." When that lands, the launchers need its opt-out.
  • Running sessions lag: they keep what they loaded until they restart.
  • Mac only. launchd, the Keychain and sandbox-exec are load-bearing.
  • The reviews come from one vendor: independent and isolated, but not a second company's model.
  • The role table hasn't moved yet. The benchmark kept every row without showing that the pricier setups are needed, and a different workload may need a different table. The 500k compaction window and the failover thresholds are still set from my ledger, not from a benchmark.
  • The watch reads what it's told to read. Its source list is fixed in a file; news elsewhere reaches it only through the reading model's own web searches, and an unverified quote never becomes automatic work.

Appendix: the commands

CommandDoes
aiupdate Claude Code, pick project and account, launch and supervise
ai PATH, ai PROJECT, -a IDskip the project or account menu
-e LEVEL, -c, -r [ID], --no-update, -- ARGSone launch at another effort; continue or resume; skip the update; pass flags to claude
ai --self-test --cwd DIRprint the menus and the launch decision as JSON, launch nothing (about 60 ms)
ai doctor, ai apply (--quick, --full)check, or repair, the whole installation
ai updateupdate Claude Code
ai accounts, ai account add ID EMAIL, ai login IDusage per account; add one; log one in
ai exec -- claude -p …a headless run on the best account, with refusal handling and a ledger line
ai loop goal with init, attach, start, status, verify, log, stop, resumethe verifier-gated goal loop; stop and resume are mine
ai loop batch with init, plan, start, status, console, stop, resume, report, refresh, …the batch loop (ai loop batch --help for the rest)
ai review --caller claude --brief FILE --context FILE … --output FILEone independent second opinion
ai graph plan PLAN.md, current, history, why, check, mermaidthe two graphs
ai improve status, scan, daily; ai inbox add, list, doneself-improvement and its inbox
ai bench roles, ai bench report DIRmeasure every role's model and effort on tasks shaped like the harness's own work; the decision
ai roles status, note, tune ROLE, syncthe role table that learns: outcomes, re-measurement
ai watch daily, status, reportthe morning watch over Claude Code, Anthropic and the industry
ai blog auto, holdarticles about big improvements; hold one
ai dashboard --all, --json; /dash all, /dash offthe dashboard band: every project in any terminal, its data, and the band in a session
ai report --days 1, 7, 30usage, reviews and harness activity
ai qmd request, status, doctor, cleanupthe search index
ai new PATH, ai projectsa new project; the registered ones
ai sessions, ai takeover, ai statuslive sessions; handover records; a one-screen summary
ai blog, ai image, ai pdf, ai videothe content engines
ai hook, ai statusline, ai statusline --subagentswhat Claude Code itself calls (the last one rewrites the agent panel rows)
touch ~/.local/state/ai-workspace/hooks/OFFevery harness hook off at once; delete the file to turn them back on