Skip to content
deramond.dev
Blog · 2026-10-07

How I maintain my open source projects with Claude agents

A technical deep dive: a conductor session paces Claude agents under written rules, and I get one question at a time. How it works, how to build yours.

Julien Déramond30 min readAI · Open Source · Maintainers · Claude CodeCC BY-SA 4.0

I maintain about ten repositories on my spare time, and each one has an issue tracker that grows faster than my evenings:

Since Saturday 3 October 2026, a set of Claude agents does most of the work on them. I call the whole thing the orchestrator. One Claude Code session in it, the conductor, plans and paces. Background agents, each on the model tier its task needs, do the work in each repository under written rules. A small command-line tool with a SQLite file holds the state they share. And I get one question at a time.

In short

Claude agents now do most of the work on my repositories, and they’re organized around one limit: my review time. One Claude Code session, the conductor, plans and paces. Background agents on four model tiers do the work under written rules. A small command-line tool over SQLite holds the state, and I get one question at a time. On Bootstrap, every open item is analyzed and a fix is prepared when one is needed, in a repository shared with Mark Otto, but nothing is posted without a maintainer.

How to read it: the first three sections give the idea. From budget pacing to safety, it’s the machinery, for anyone who wants to build one; How to replicate it is the short version. Step two is the Bootstrap story.

013 to 5 October 2026

Writing code isn’t the bottleneck, reviewing it is#

When I rebranded everything I publish, Claude opened about 80 pull requests in eight days. The writing was never the slow part. I was. Every pull request is a review, every question is a context switch, and I do both between two Bootstrap reviews or from my phone in the subway.

So the whole setup is designed around one constraint:

My attention is the scarce resource, not the tokens and not the code.

Every rule below exists to protect it:

  • agents never wait on me in a way that blocks other work;
  • what waits on me is capped per repository;
  • what reaches me is one decision, explained well enough to answer without opening anything;
  • what only I can do by hand is in one separate list.

Tokens come second: a weekly budget that’s paced, not burned on day one.

Protecting my attention also means not spending it on relaying. Once agents write the code, the easy role left for the human is the one Niklas Gruhn calls a meat proxy. The proxy pastes an issue into Claude, its answer into a comment, a reviewer’s feedback back into Claude, the next diff into GitHub, and adds nothing of their own on the way. It’s the question every software factory raises: what’s left for the human? I want as little of that relaying as possible. Carrying things between Claude and GitHub is the agents’ job. What reaches me should be what only a maintainer can add.

The shape: a conductor, agents, and one small database#

The conductor, the agents and orcJulien answers questions and reads his to-do list through the conductor, a Claude Code session, and reviews and merges pull requests on GitHub himself. The conductor dispatches background agents on four tiers: scout on Haiku, worker on Sonnet, engineer on Opus and architect on Fable. Each agent works in a git worktree and pushes from it when the repository's policy allows. The conductor and the agents read and write all their state through orc, a command-line tool over one SQLite file, and orc's sync reads GitHub without any model tokens.01 Me02 Conductor03 Agents04 RepositoriesJulien/inbox · /todoconductorClaude CodescoutHaiku · lowworkerSonnet · mediumengineerOpus · higharchitectFable · xhighGitHubworktreeorcone SQLite file: tasks, events, questions, usagereview · mergepushnext · asklog · donesync
Figure 1 · I only talk to the conductor and review on GitHub; everything else meets in orc.

The conductor is a Claude Code session opened in a private repository. Its CLAUDE.md is the conductor’s protocol: plan, dispatch, pace the budget, bring me questions. It never writes code itself. It’s the only session I talk to, from my laptop or, with Remote Control, from my phone.

The agents are Claude Code subagents started in the background, one per task. Each one is a Markdown file in .claude/agents/. The front matter sets its model and effort. A few lines of instructions all say the same thing: read the worker protocol first and follow it.

---
name: engineer
description: Senior engineering tier (Opus). Features, non-trivial bugs, substantial PR reviews.
model: opus
effort: high
---
You are the engineer tier, handling substantial engineering. Understand the codebase
conventions before changing anything, keep diffs focused, and write the review/PR text
a maintainer would respect.

Read docs/worker-protocol.md first and follow it exactly. The task id is in your dispatch prompt.

The worker protocol (docs/worker-protocol.md, about 460 lines) is the contract every agent follows: how to load a task, where to work, what it may write on GitHub, how to describe a pull request, how to ask a question, and how to end a run. It’s the most important file in the repository, and the one I edit the most.

orc is the state. One Python file, about 3,200 lines, standard library only, over one SQLite file. Everyone goes through it: the conductor to plan, the agents to report, and me through slash commands in the conductor. Nothing lives in a chat transcript. If the conductor’s session ends, a new one reads the database and carries on.

What orc stores#

There are six tables. Most of the logic is in how they’re read:

Table What it holds
tasks one per GitHub issue or pull request worth tracking, or per job added by hand: its repository, kind (review, my-pr, backlog-issue, task…), status (new, ready, running, blocked, needs_user, prepared, done, dropped), priority, tier, goal (“what done means”), branch and worktree, and what it waits on
events an append-only log per task: every dispatch, every progress note, every state change
questions the decisions waiting on me, with their context, options, recommendation and answer
usage snapshots of my Claude plan’s usage meters, for pacing
meta, ease the last sync time, overrides, and ratings that let /todo suggest the easiest issue to pick up

SQLite in WAL mode, with a 30-second lock timeout, is enough for four agents and a conductor writing at once.

How a task becomes work#

A task is dispatched with a one-line prompt:

Task <id>. Mode: prepare. Skills: deramond-brand, brand-diagram.

The agent runs orc show <id> for the goal, the history and any question I already answered, then orc policy <repo> for the rules of that repository. Then orc worktree <id> gives it a git worktree on its own branch, outside my own clone. Agents never touch my checkouts. I have local branches and work in progress everywhere, and a background agent switching my branch would be a disaster.

Every run ends with exactly one outcome:

orc done <id> "<one-line result>"          # goal achieved
orc prepared <id> "<state>"                # branch built locally, waits for review room
orc refined <id> "<what changed>"          # issue sharpened, back to ready
orc block <id> "<why>" --waits-on owner/repo#N   # waits for a PR, an issue, a release or a day
orc ask --task <id> -q "<one question>" -c "<context>" -o "<option>" -o "<option>" -r 1

That’s the whole interface between the agents and the rest of the system. An agent that ends without an outcome is retried, and moved up a tier after two attempts.

The life of a task in orcA task starts as new, from the GitHub sync or added by hand. Triage makes it ready, or drops it when it is not worth doing. The conductor claims a ready task and it becomes running, with one agent on it. The run ends with exactly one outcome: done with orc done, prepared with orc prepared (a local branch waiting for review room), blocked with orc block and what it waits on, or needs_user with orc ask. A refined issue, or a run that ended without an outcome, goes back to ready. A blocked task comes back when its wait is over, a needs_user task when Julien answers, and a prepared branch when review room frees up.Life of a taskOne outcome per runnewfrom syncreadydispatchablerunningone agentdroppednot worth itdoneorc donepreparedorc preparedblockedorc block --waits-onneeds_userorc asktriageclaimtriagerefined · no outcomewait over · answered · review room frees
Figure 2 · every run ends with exactly one outcome, and every outcome but done comes back on its own.

The loop#

The conductor works in ticks. One tick:

  1. Budget. Read the usage meters (get_usage), record them with orc usage, ask orc budget how much work this moment can afford.
  2. Sync, if the last one is older than two hours: orc sync pulls review requests, my pull requests, issues assigned to me and, for repositories that opt in, the whole open backlog. New items are new until a cheap scout triages them.
  3. Plan. orc next --json returns what to dispatch now, each with its tier, model, effort, run mode and skills, within the free slots, the per-repository limits and the review limits.
  4. Dispatch each one as a background agent, then orc start <id> with the agent’s id.
  5. On completion, read the last few events (orc show <id> --events 5), not the agent’s whole transcript. The conductor’s context is shared by everything, so it stays lean.
  6. Inbox. If I’m here, show me the top question.
  7. Chain: tick again while there’s dispatchable work.
One tick of the conductorA tick runs seven steps in order. One, budget: read the usage meters with get_usage and ask orc budget what this moment can afford. Two, sync with orc sync if the last sync is older than two hours. Three, plan with orc next --json. Four, dispatch: orc claim, launch the background agent, then orc start. Five, when an agent is done, read its last five events with orc show. Six, show the top question if Julien is here. Seven, chain: tick again while there is work ready. Then the conductor waits until an agent finishes, or 5 minutes while Julien is active and 25 minutes when he is away, and starts again at the budget step. In manual mode each tick proposes the plan and waits for his go; in autopilot it runs on its own. Past 300,000 tokens of context, the conductor stops dispatching and a fresh session takes over.One tick01 Budgetget_usageorc budget02 Syncorc syncif older than 2 h03 Planorc next --jsonwhat fits now04 Dispatchorc claimAgent · orc start05 Agent doneorc show <id>--events 506 Inboxtop question,if I'm here07 Chaintick again whilework is readyWaitagent finishes, or5 min · 25 min awayManual: each tick proposes the plan and waits for my go.Autopilot: /loop /tick, until the weekly target or /manual.Past 300,000 tokens of context: no new dispatches, and a fresh session takes over.
Figure 3 · the conductor's loop: seven steps, then a wait until the next agent finishes or the timer runs out.

In manual mode, each tick proposes the plan in full sentences and waits for my go. In autopilot, the conductor runs /loop /tick and paces itself. It wakes up on every agent completion, and otherwise every 5 minutes while I’m active (I answered a question, commented on or merged one of its pull requests, or wrote to it in the last 15 minutes) and every 25 minutes when I’m away. It never asks me anything in autopilot. Questions wait in the inbox, and one push notification goes out if a question blocks the most important work.

The conductor resends its whole context on every tick. Past 300,000 tokens, it stops dispatching, lets the running agents finish and asks for a fresh session. Nothing is lost: the state is in orc.

The Claude desktop app, Code tab, in its dark theme. The sidebar lists sessions, with the Conductor session selected and marked 3 running, and Remote Control on. The conductor answered /tick: three tool calls (get_usage with the weekly, 5-hour and Fable meters, orc budget saying mode steady, 3 workers, max tier engineer, and orc next), a sentence saying the week is at 41% with 43% of it gone, three background agents running (a worker rebasing a dtgraph pull request, an engineer refining a Transtyle issue, an engineer analyzing a Bootstrap pull request), and a note that the rebase finished.
Figure 4 · a tick in the conductor session: budget, plan, three agents dispatched, one already back. Mock rendering, not a screenshot.

Priority you can explain#

orc ranks tasks with a deterministic score, so orc explain <id> can say why something is first. It adds up:

Part Points
the kind a review requested from me 60, an issue assigned to me or a task added by hand 50, a pull request of mine 40, a backlog issue 20
the repository’s weight, from its policy 0 to 30
label boosts, from the policy regression +10 on Bootstrap, priority: high +30 in my branding repo
age up to +20 for a review waiting on me, +5 for a backlog item with recent activity, −10 for a pull request of mine stale for six months
the rest +10 for a follow-up in a chain, a manual boost from the conductor, +100 for a pin

So a review requested from me on Bootstrap, labelled regression, starts at 85: 60 for the kind, 15 for the repository, 10 for the label. It climbs while it waits.

Following open pull requests costs no tokens#

Once an agent opens a pull request, its task blocks on it (--waits-on owner/repo#N). From then on, orc checks it on every listing with plain gh calls. No model runs until there is work to do.

How an open pull request is followedWhile a pull request opened by an agent waits for review, orc checks it with plain GitHub calls on every listing. Failing CI on a new commit queues a ci run: a worker twice, then an engineer, then a question. A comment from Julien queues a feedback run that adds commits to the same branch. A comment from anyone else becomes a question for Julien, and nothing changes until he answers. Another pull request merging in the same repository queues a rebase run, dispatched before anything else. When the pull request merges, the task is done, or a close run does the issue's closing steps.01 orc sees, without tokens02 and queuesCI fails on a new commitci runworker twice, then engineer, then a questionJulien commentsfeedback runnew commits on the same branchsomeone else commentsa question for Juliennothing changes until he answersanother PR merges in the reporebase runahead of every other dispatchthe PR mergestask doneor a close run if its issue is still open
Figure 5 · an open pull request is followed by plain GitHub calls; a model only runs when there is work.

Two rules here matter more than they look. Rebases go first, even past the free-slot count: a pull request that needs a rebase can’t be reviewed or merged, and it holds the review room, so it blocks the whole repository. And only my comments are instructions. The agents post as me, so orc tells its own comments from mine by the attribution line every one of them ends with. Anyone else’s comment becomes a question for me instead of a change.

Waits aren’t only pull requests. A task can wait on npm:<package>@<version>. I publish releases by hand and the publish shows in my to-do list. Once the registry has the version, the item clears itself and the task wakes up to propagate the release. Or on until:<date>, for a policy rule like “one new Open {re}Source article every two weeks at most”.

Budget pacing and four tiers#

I’m on Claude Max 20x, which has three meters: a weekly limit for all models, a 5-hour window, and a separate weekly bucket for Fable. The goal is to finish each week near 95% of the weekly limit and never hit the wall before the reset.

orc budget compares the weekly meter with where it should be at this point of the week (time elapsed × 95%) and picks a mode:

Mode When Parallel agents Highest tier Minimum priority
stop the weekly target is reached 0 worker 90
conserve more than 10 points ahead of pace 1 engineer 55
steady-low 3 to 10 points ahead 2 engineer 40
steady on pace 3 engineer 0
push more than 5 points behind, or the last 24 hours with over 10% left 4 architect 0

On top of that, the 5-hour window halves the number of agents above 70% and pauses everything above 90%, until its reset. The Fable bucket has its own pace: ahead of it, the architect tier is kept for tasks that clearly need it; empty, nothing goes above engineer. There’s also a burn mode, every slot filled and engineer work moved to Fable, but only I can switch it on (/autopilot-burn). Pacing alone never goes past push.

Weekly budget pacingOver one week, from one reset to the next 168 hours later, the pace line rises from 0 to 95% of the weekly limit: time elapsed times 95%. The gap between the weekly meter and that line picks the budget mode. For example, a meter at 52% when 40% of the week is gone is 14 points ahead of the pace line's 38%, which means conserve. More than 5 points behind pace is push: 4 agents, up to the architect tier. From 5 behind to 3 ahead is steady: 3 agents, up to engineer. From 3 to 10 ahead is steady-low: 2 agents, priority 40 and up. More than 10 ahead is conserve: 1 agent, priority 55 and up. At the 95% target, stop: nothing new starts. Overrides: in the last 24 hours with more than 10% left, push; the 5-hour window halves the agents at 70% and pauses them at 90%; burn only when Julien switches it on.01 One week of the weekly meter100%95%0%resetnext reset · 168 hpace: time elapsed × 95%meter: 52% at 40% of the week14 ahead: conserve02 Mode, from the gap to pacepush> 5 behind4 agentsup to architectsteady−5 to +33 agentsup to engineersteady-low+3 to +10 ahead2 agentspriority ≥ 40conserve> 10 ahead1 agentpriority ≥ 55stoptarget reached0 agentsnothing startsbehind paceahead of paceLast 24 hours with more than 10% left: push. 5-hour window at 70%: half the agents; at 90%: pause.Burn (every slot filled, engineer work on Fable) only when I switch it on.
Figure 6 · the gap between the weekly meter and the pace line picks the mode; the example meter is illustrative.

The four tiers are four subagent files:

Tier Model, effort Used for
scout Haiku, low triage, CI status, summaries: anything read-only and mechanical
worker Sonnet, medium well-specified changes, rebases, CI fixes, docs, dependency bumps, small reviews
engineer Opus, high features, tricky bugs, reviews of non-trivial PRs, anything that needs judgment
architect Fable, xhigh cross-cutting work, long chains, a task that failed twice

The routing rules:

  • the tier comes from the task, then from the repository policy (per kind, or per label), then from the kind’s default;
  • triage decides the tier of the work, not of the triage: a scout can never be assigned anything that writes code, content or a pull request (orc set refuses it);
  • rebases and closing steps always go to a worker; a red CI gets a worker twice, then an engineer, then a question for me;
  • an agent that’s out of its depth stops early and hands the task up a tier: a cheap stop beats an expensive flail;
  • the budget mode caps the tier last.
How a dispatch gets its tierThe tier is the first one set among four places: the task's own tier, set by triage or by Julien; the policy's tier for one of the task's labels; the policy's tier for the task's kind; and the kind's default in the configuration. The run mode can replace it: rebases and closing steps always go to a worker, and a CI fix goes to a worker twice, then to an engineer. Then two caps apply. Burn, switched on only by hand, moves engineer work to the architect tier. The budget mode caps the tier last: worker when the weekly target is reached, architect in push, engineer otherwise. The tier names the agent file, which carries the model and the effort. A scout never gets work that writes code, content or a pull request, and a task that failed twice moves up a tier.01 First set wins02 Run mode03 Caps04 Agenttask.tiertriage, or melabel_tierpolicy, per label[tiers]policy, per kind[kinds]config defaultunsetunsetunsetrebase · closealways workerciworker ×2, engineerany other runkeeps the tierburnby hand: engineerwork → architectbudget modestop → workerpush → architectelse → engineercapengineer.mdmodel: opuseffort: highA scout never gets work that writes code, content or a pull request: orc set refuses it.A task that ends twice without an outcome moves up a tier.
Figure 7 · the tier comes from the task, then the policy, then the kind; the run mode and the budget have the last word.

Per-repository policies#

A repository without a policy is synced, so I can see it, but never worked on. A policy is a Markdown file with a TOML front matter. An onboarding skill researches the repository first, then writes the policy with me, one question at a time. It’s never invented.

The front matter holds what the code reads. Here’s this site’s:

repo = "julien-deramond/deramond.dev"
enabled = true
default_branch = "main"
weight = 20                   # 0–30 priority bonus relative to other repos
max_parallel = 1              # concurrent tasks in this repo
skills = ["accessibility", "seo", "web-design-guidelines", "deramond-brand", "brand-diagram"]   # excerpt
watch = ["issues", "prs"]     # the whole open backlog, not only what's assigned to me

[autonomy]                    # auto | ask | never   (unlisted = never)
push_branch = "auto"
open_pr = "auto"
open_draft_pr = "auto"
mark_ready = "never"
comment = "ask"
label = "ask"
close = "ask"
merge = "never"

[commands]
install = "pnpm install --frozen-lockfile"
typecheck = "pnpm check"
build = "pnpm build"

The body holds what the agents read: context and rules in plain words. For this site: “text in my voice opens as a draft pull request, for me to rewrite, and is never marked ready”. This post is one of those.

Every GitHub action is auto (do it), ask (file a question and stop) or never. The spread across my repositories says a lot about trust:

  • my Bootstrap test bench: almost everything is auto. It exists to find bugs, its issues follow a fixed taxonomy, and its CI catches mistakes.
  • this site: pushes and pull requests are auto; comments, labels and closing issues are ask.
  • my other tools (dtgraph, Component Anatomy, Transtyle, Bootstrap Tokens): every write is ask, except refining Transtyle’s own issues. They have users.
  • Bootstrap: every write is never. More on that below.
  • merging: never, in every single policy. I merge.
What agents may do on GitHub, per repositorySix GitHub actions for four kinds of repositories, each auto, ask or never. On the Bootstrap test bench, pushing, opening a pull request, commenting, labelling and closing are auto, and merging is never. On this site, pushing and opening a pull request are auto, commenting, labelling and closing are ask, and merging is never. On my tools, every action is ask except merging, which is never; Transtyle also lets agents refine its issues on their own. On Bootstrap, every action is never.pushopen PRcommentlabelclosemergeBootstrap test benchits CI catches mistakesautoautoautoautoautoneverThis sitedrafts in my voiceautoautoaskaskaskneverMy other toolsthey have usersaskaskaskaskaskneverBootstrapanalysis onlyneverneverneverneverneverneverOne exception among my other tools: Transtyle lets agents refine its issues on their own.auto: do it · ask: file a question and stop · never: don't, and don't ask.
Figure 8 · the same six GitHub actions, four levels of trust. Merging is never, everywhere.

The policy also carries the repository’s commands and skills. It says how many agents may work on the repository at once, which labels to watch or skip, and whether code from outside contributors may be run (run_untrusted_pr_code, false everywhere).

Review limits#

This is the part that makes the whole thing livable. Each repository has a max_open_prs, one by default: the number of agent pull requests that may wait on my review there. There’s no global cap, so a pull request waiting in one repository never holds back another.

A full repository doesn’t stop the work. It changes what the work produces:

How a ready task picks its run modeA ready task that would end in a pull request first checks the review room in its repository. If fewer pull requests are open than max_open_prs allows, it runs in full mode, or ship mode for a branch already prepared, and the agent pushes and opens the pull request. If the repository is full and fewer branches are prepared than max_prepared allows, it runs in prepare mode: the branch is built locally and not pushed. Otherwise it runs in refine mode: no code, the agent researches and sharpens the issue. Work on pull requests already open, and tasks that never open a pull request, skip these checks.01 Task02 Review room03 Run modeready taskwould open a PRopen PRs < max_open_prs ?prepared < max_prepared ?full · shippush, open the PRpreparelocal branch, no pushrefineno code: sharpen the issueyesnoyesnoRebases, CI fixes, feedbackand no-PR tasks skip this.
Figure 9 · a full repository doesn't stop the work, it changes what the work produces.
  • prepare: the agent builds the branch exactly as for a pull request (commits, checks green, title, description and labels drafted) but doesn’t push. Up to three prepared branches per repository.
  • ship: when room frees up, a prepared branch is rebased, checked again and pushed. Branches I already approved go first.
  • refine: when enough branches are prepared, agents stop writing code and make issues ready instead. They read the code the issue touches, do the research, sharpen the scope and the acceptance criteria, fix the labels and split what’s too big.

Work on pull requests that are already open (CI fixes, rebases, my feedback) doesn’t count against the limit, and neither do no-PR tasks: audits and analyses whose output is issues, comments or drafts. Questions are never limited.

One question at a time#

Every decision that is mine goes through orc ask, and the protocol is strict about how it’s written, because I come to it cold, between two other things, remembering nothing about the task:

  • the context is four to eight full sentences. They say what the item is about and why it exists, what the agent did, where things stand (checks, what wasn’t verified), and why it needs me rather than the agent;
  • it cites the evidence: commits, the files that matter, screenshots, the draft holding the exact text I’d approve;
  • one question, two to four options, a recommendation.

/inbox shows me the top one. orc gathers the evidence itself, with git and gh only: the issue or pull request body, the task’s commits and changed files with their GitHub links, the full text of its drafts, the screenshots. The conductor turns that into a brief: what it’s about (with the issue’s real title and a quote of its key line), what was done (links on every word that can carry one), what I’d be approving (quoted from the draft), and why it’s my call. Then comes one question. The brief is repeated inside the question box, because on my phone the box is all I see.

The conductor session after /inbox. A brief in three bold-led paragraphs: what it's about (the deramond.dev issue asking for this article), what was done (a first draft with three diagrams, checks passing) and why it's Julien's call (publishing the repository, a cleaned-up template, or keeping it private). Under it, a question card asks whether the private repository should be published, published as a template, or stay private, with three options, each with a one-sentence consequence; the first, keep it private, is marked Recommended and selected, with Skip and Submit buttons.
Figure 10 · a question from /inbox: the brief first, then one question with its options and what each one does. Mock rendering, not a screenshot.

The order is mine too: questions whose answer pushes, opens a pull request or releases something come first, then direct work, then background work.

Answers are recorded with orc answer. The task goes back to ready, and the next run follows the answer.

Two rules took a few iterations to get right. The first: short cards with ids and no links don’t work. The brief format came out of my standing complaint:

Give me links all the time.

The second: nothing waits on me outside orc. An agent refused a command, a policy that needs a change, something to do by hand: each is filed the moment it comes up, never left in a chat message I’ll scroll past.

And one list of what only I can do#

/todo is the other place, and it never holds questions. Only what I must do with my own hands:

  • Dependabot verdicts, first: a read-only run weighs what changed between the versions against how the repository uses the package, and says whether the pull request is safe to merge;
  • pull requests ready to review, and only once their CI has finished green: a red one goes back to an agent first;
  • manual actions an agent asked for: a screenshot behind a login, a test on a device, npm publish;
  • the next issue to pick up in my Bootstrap test bench. Worker runs rate open upstream bugs from 0 to 10 on how easy they are to reproduce and fix, and the easiest one comes with a local fix branch drafted for me;
  • the next action on Bootstrap, one at a time.
The conductor session after /todo, as five short lists: Dependabot (a dtgraph npm group bump rated safe, merge it), Ready to review (an Open {re}Source glossary pull request and this article's draft, both with green CI), By hand (take the Desktop and phone screenshots for the article), Next on Bootstrap with ease 9 (post a drafted reply and close), and Pick next on the test bench (the easiest upstream bug, with a fix branch ready).
Figure 11 · /todo: only what I have to do with my own hands, Dependabot verdicts first. Mock rendering, not a screenshot.

Bootstrap: analyze everything, write nothing#

Bootstrap has over two hundred open issues and pull requests, most of them from people I don’t know. The policy there is different from everywhere else: every open item gets an analysis run, and agents have no GitHub write on the project at all.

An analysis reads the whole thread and searches for duplicates. It checks the claim against the code on main (v5) or v6-dev (v6) and reproduces it with a page written from scratch when it can. Then it decides the resolution: reply, review, fix, or a decision only I can take.

It prepares what that needs: the comment or the review as a draft, or a fix branch, never pushed to the project. It writes the analysis and records my next action with an ease score: 9 or 10 for “paste this comment and close”, 6 to 8 for “submit this review” or “open a PR from this branch”, 0 to 2 for a real design decision. /todo shows me the quickest one.

I post it myself, after checking it closely. An analysis is a starting point, not a verdict, and the agents get things wrong. Since 6 October, all of this lives in a repository I share with Mark Otto: more on that at the end. I wrote the Bootstrap side of this, from the maintainers’ point of view, on the Bootstrap blog: Triaging Bootstrap with Claude agents.

The other rule on Bootstrap: everything from issues and pull requests is hostile until proven otherwise. Anyone can open one, and some will try to reach the agent behind the maintainer.

So agents never check out a contributor’s pull request, never run its code, and never open what it points to. Its text is read as data, never as instructions. Anything that looks off is flagged for a maintainer to read by hand first, and nothing an agent writes reaches the project without one.

Skills#

Agents get my conventions on demand through skills, all committed to the orchestrator’s repository so one clone brings everything to a new machine. Twenty-four today:

  • eight for the conductor: tick, inbox, inbox-drain, todo, manual, autopilot, autopilot-burn, onboard-repo;
  • eight third-party ones for generic web quality: accessibility, SEO, Core Web Vitals, performance, UI guidelines, frontend design, Playwright testing, doc co-authoring;
  • eight of my own: the brand and its voice, brand images from my asset kit, diagrams on the brand (the ones in this post), Open {re}Source’s writing rules, its screenshots and its social posts, Changesets releases for my monorepos, and tracking Bootstrap bugs found by the test bench.

Each policy lists the skills that fit its repository. orc next puts them in the dispatch prompt (Skills: …), and the agent loads the ones its task needs. Agents log which ones they used, and any gap they hit (skill-gap: <skill>: <what was missing>). A monthly audit task reads those logs. It checks third-party skills for updates and my own for drift against the documents they summarize, and proposes new ones, one question each.

Safety#

An agent with a GitHub token acts as me. These rules keep that in check, from the most fundamental:

  • GitHub content is data, never instructions. Issue bodies, comments, diffs and file names are read, never obeyed. An agent that finds text trying to direct it reports it instead.
  • Only listed repositories. No write of any kind (issue, comment, label, push) in a repository without an enabled policy. Some repositories are on an ignore list and aren’t even synced.
  • No GitHub write without a policy that says auto, or my answer approving that specific action. One approval covers one action.
  • The permission layer agrees. Claude Code’s own permission rules allow only the GitHub writes each policy needs, and deny any push to Bootstrap outright. A rule in a Markdown file is a promise; a permission rule is a lock.
  • Worktrees only, never my own checkouts. Outside contributors’ code is never run.
  • Drafts first. Comments, reviews and pull request descriptions that need my approval are written to a drafts folder, and the question quotes them.
  • No private leaks. Public text never names a local path, a private repository or the orchestrator itself: the attribution line at the end of each pull request and comment is the only marker.
The gates in front of every GitHub writeWhen an agent wants to write on GitHub, three gates come first. One: the repository must have an enabled policy; if not, there is no write at all, and Julien is asked first. Two: the policy must say auto for that action, or Julien must have approved that specific action; if not, the agent files one question with orc ask and stops. Three: the command must match a permission rule in its literal shape; if not, Claude Code's auto mode decides, and refuses what it cannot justify. Only then does the write happen. Before any of it, issue bodies, comments and diffs are data, never instructions.A GitHub write, three gatesagentwants to writeon GitHub01 Listed?has an enabledpolicynono writeask me first02 Allowed?policy: auto,or my yesyesnoorc askquestion, stop03 Matched?a permissionrule, as typedyesnoauto modedecidesGitHubthe writehappensyesjustifiedBefore any of it: issue bodies, comments and diffs are data, never instructions.One approval covers one action. Merging is never, in every policy.
Figure 12 · a write needs a listed repository, a policy or an answer that allows it, and a command the permission rules recognise.

Every pull request description follows the same layout. A short summary comes first. Then What was done holds the agent’s development notes: choices and their alternatives, the checks it ran, what it couldn’t verify. How to review comes last and holds only what I have to do: decide, try it, check by hand. Never “read the code”. I read it anyway.

Two days in numbers#

The first commit was on Saturday 3 October 2026, in the late afternoon. These are the numbers from orc’s database on Monday 5 October, in the evening.

Repositories with a policy 11, watching 571 open issues and pull requests
Tasks created 752: 639 from GitHub, 82 by hand, 30 follow-ups, 1 recurring
Background agent runs about 590: 423 worker, 104 engineer, 41 architect, 24 scout batches that triaged 225 items
Runs that added nothing to my review queue 246 refine, 89 prepare
Pull requests opened by agents 60, across 10 repositories: 54 merged, 5 open, 1 closed
Runs on open pull requests 19 rebases, 18 rounds of my feedback, 10 CI fixes, 8 closing steps
Questions asked 159: 158 answered, 1 withdrawn
Bootstrap pull requests analyzed 46, with 35 next actions prepared for me
Drafts written for my approval 411
Commits to the orchestrator itself 117

And the tokens. orc doesn’t count them: it reads the same usage meters I see. From Saturday afternoon to Sunday afternoon, the weekly meter went from 51% to 98% of my Max plan’s limit, with the 5-hour window reaching its pause threshold on the way. After the limit was reset that Sunday, the night that followed used another 39% of the weekly limit and 69% of the Fable bucket. Two days of this is most of a week’s allowance. Pacing exists for exactly that reason.

What worked, and what didn’t#

What worked:

  • The state outside the chat. A conductor session can die, be rotated or be cleared, and nothing is lost. The same goes for any agent.
  • Review limits. I never open GitHub to forty pull requests. One per repository waits on me; the rest is prepared, refined, or waiting its turn.
  • One question with a brief. I answered 158 questions in two days without opening a single issue to remember what it was about.
  • Writing the rules down. The protocol and the policies do more for quality than any prompt. When an agent does something wrong, the fix is a rule, committed, and every later agent follows it.

What didn’t, or not yet:

  • Most rules came from mistakes. 117 commits in two days, and most of them fix a behavior I noticed. Questions were written as fragments of ids. Agents woke themselves up with their own comments. Branch names leaked the setup on GitHub. A hung gh call froze the conductor. A pull request was found twice and tracked by two tasks. It’s young, and it shows.
  • It’s expensive. Most of a week’s allowance in two days. Pacing keeps a week from ending on Tuesday, but a backlog this size is still more work than one plan pays for.
  • Review is still the bottleneck. The agents prepare more than I can review. The difference is that it now waits in branches and sharpened issues, not in my head.
  • Every agent reads a long protocol. Four hundred and sixty lines, at the start of every run. Skills moved a lot of it out. The rest is the price of consistency.

How to replicate it#

The repository behind all this stays private, so there’s no template to fork. Here are the pieces instead, in the order I’d build them again, starting small:

  1. A state store everyone writes through. A CLI over SQLite, with tasks, events and questions. Start with add, show, ready, done, block, ask and answer. Make it deterministic and boring: the agents are the creative part, the state must not be.
  2. A worker protocol. One file every agent reads first: load the task, work in a worktree, respect the policy, end with exactly one outcome. Write it before the first agent runs, then edit it every time an agent surprises you.
  3. One policy per repository, starting with everything on ask and merge = "never". Loosen one action at a time, when you’ve seen the agents do it right.
  4. Tiers as subagent files, each with its model and effort. Two are enough to start: a cheap one for triage, a capable one for the work.
  5. A conductor protocol in the CLAUDE.md of the repository the conductor runs in: the loop, the hard rules, how to talk to you. Run it in manual mode for a while: watching it propose each step teaches you what to write down.
  6. Review limits before autopilot. Without them, the first autonomous night ends with more pull requests than you can read in a week.
  7. The inbox. Write the rule about how questions are written early. It’s the part you’ll use every day.
  8. Budget pacing last, once you know what a normal day costs.

Start small

Start with one repository you own and know well, on manual mode, with a scout and a worker.

The setup grows by rules, not by code. The code is about 3,200 lines of plumbing. The rules are what make it trustworthy.

026 and 7 October 2026

Step two: sharing the Bootstrap backlog with mdo#

Everything above is step one: a setup that works for me, alone. Its Bootstrap part had an obvious limit. Everything the agents prepared stayed with me: analyses in the orchestrator’s private state, drafts in its drafts folder, fix branches in worktrees on my laptop. It reached one person, through my /todo. But I don’t maintain Bootstrap alone. Mark Otto, mdo, its co-creator, looks at the same backlog, and he had none of it.

So on Tuesday 6 October, in the evening, the Bootstrap part moved to a place we both see: twbs/triage, a private repository in the Bootstrap organization. The next morning, it learned to prepare a fix for every issue that needs one, and to put competing fixes side by side. The method stayed the same. What changed is where it writes, for whom, and how much it prepares.

How the Bootstrap backlog is shared through twbs/triageAgents read the open issues and pull requests of twbs/bootstrap with read-only GitHub calls and never write there. An analysis run writes the item's folder, issues or prs then its number, with the analysis, the drafts and meta.json, and publishes it to the private twbs/triage repository with orc triage-publish. When a fix is worth preparing, the fix branch starts from main or v6-dev and goes to twbs/triage as issue or pr, number, base, with orc triage-push. Every two hours, orc sync refreshes the list of open items and the sync date there, without a model. The maintainers read all of it in the dashboard, on GitHub or, for Julien, in /todo, and every post, review or merge on twbs/bootstrap is theirs, by hand.01 twbs/bootstrap02 Agents03 twbs/triage04 Maintainersissues and PRsread-only ghanalysis runorc triage-publishissues/<n>/, prs/<n>/analysis, drafts, meta.jsonfix branchorc triage-pushissue-<n>-<base>from main or v6-devorc syncevery 2 hours, no modeldata/open.jsonopen list, sync datedashboard, GitHuband my /todopost, review, merge: by hand
Figure 13 · agents read Bootstrap and write only to twbs/triage; every write on the project is a maintainer's, by hand.

What changed#

  • One folder per item. Every open issue and pull request has issues/<n>/ or prs/<n>/, with the analysis (analysis.md for an issue, review.md for a pull request), the exact text to post (answer.md), the description of each pull request to open from a fix (pr.md, pr-2.md, pr-3.md), and meta.json: status, resolution, next action, ease, when it was analyzed, when the item last moved on GitHub, the open pull requests that address it, and its fix branches.
  • Fix branches are pushed there. A fix starts from twbs/bootstrap’s own main (v5) or v6-dev (v6), in one or two commits that cherry-pick cleanly, and goes to twbs/triage as issue-<n>-<base>. orc triage-push pushes only commits I authored, never a contributor’s, at most three, only to twbs/triage. It refuses a commit the same task already pushed under another branch name. A push to the project itself stays forbidden, in the policy and in the permission rules.
  • Publishing is one command. orc triage-publish runs the repository’s own check on the folder (valid meta.json, the main file present, no hidden Unicode, no local path). Then it commits that folder alone and pushes it to main, under a lock, because three analysis runs may publish at the same moment.
  • Sync keeps it current. Every orc sync also runs the repository’s sync script, with read-only gh. It updates the list of open items, the open pull requests that close or mention each issue, and the “last sync” line of its README. An item whose GitHub activity is newer than its analysis is flagged, and the same activity wakes its task. The next run rewrites the folder from scratch, and git keeps the old version.
  • Mark’s items and mine are analyzed too. Until then, the policy skipped our own issues and pull requests: they were ours to handle. In a shared backlog, a review of his pull request is useful to me, and the other way round.
  • Nothing there names my setup. twbs/triage has its own CLAUDE.md and README, written for a maintainer, or any agent, who has never heard of the orchestrator. They cover the steps of an analysis, the format of each file and the security rules. Commits read issues/37807: analysis or Sync with twbs/bootstrap. No task id, no local path, no orc.
  • Bootstrap moved up. Keeping that backlog analyzed and in sync now comes right after the Dependabot reviews in orc next, ahead of the orchestrator’s own work.

The move itself was a one-off export. A script read the existing analyses and drafts from orc’s state and wrote one folder per item. It stripped every reference to the setup (draft paths, task ids, local paths) and listed any line that still matched a pattern of the setup’s words for a last look. It also pushed the fix branches that held only my commits. 190 items went in, in two commits. Fifteen minutes later, the analysis runs wrote straight into the twbs/triage clone, and the script was deleted.

A fix for every issue that needs one#

Before the move, a fix branch was the exception: the analysis prepared one when the fix was obvious. For two maintainers deciding together, a written fix is worth more than a paragraph describing it. So the rule changed on Wednesday morning:

  • Every issue whose resolution changes code or docs gets at least one fix branch: a confirmed bug, a docs fix, a plausible feature kept open for v6, a design decision between implementations. No branch for what won’t change anything: by design, duplicate, can’t reproduce, won’t fix, suspicious.
  • Even when someone already opened a pull request. A contributor’s or a maintainer’s pull request doesn’t replace ours. It’s something to compare ours with. Ours is written from scratch on the upstream base, never from their code. If it ends up close to theirs, the analysis says so.
  • One branch per direction, three at most. When there are several reasonable ways to fix it (a maintainer writing “we could do A or B” on the thread is two), each direction gets its own branch, its own commits and its own pr-<n>.md. The analysis compares them, open pull requests included, in a Directions part, and recommends one. Directions aren’t invented to fill a quota: one clear fix is one branch.
  • A review can come with an alternative. When the review of a pull request concludes that another approach would be better, that approach is prepared too, as a pr-<n>-<base> branch.
  • The check enforces it. npm run check refuses a fixable issue without a branch, and an analysis written since that morning without its fixable, direction and pr_file fields.

Issue 37807, container queries for the flexbox grid, shows what that gives. Mark has a draft pull request that goes much wider. The analysis wrote two branches on v6-dev, in two directions: columns driven by their container by default, as in v6’s CSS Grid, or the same behind an opt-in Sass option. Its folder, as it lands on GitHub:

issues/37807/
  analysis.md   what it is, where it stands, findings, directions, recommendation, next action
  answer.md     the status comment to post on the issue
  pr.md         container-driven columns by default, from issue-37807-v6-dev (recommended)
  pr-2.md       the same behind an opt-in Sass option, from issue-37807-v6-dev-2
  meta.json     status "action", ease 6, fixable, open PR 42784, two branches

A dashboard anyone can run#

On GitHub, the folders read like any Markdown. To go through two hundred of them, twbs/triage comes with a small dashboard: one Node server and one page, built on Bootstrap’s own CSS with the v6 color tokens on top, in light, dark or automatic mode. Run npm start and the whole backlog is in the browser, quickest first, grouped by ease, with filters by status and by fix: Open PR for items a pull request on twbs/bootstrap already addresses, Triage fix for items with a branch prepared here. j and k step through the list, / searches, and on a phone the list and the item are two screens.

The Bootstrap triage dashboard in its dark mode. The top bar reads 223 of 224 analysed. On the left, a search field, All, Issues and PRs tabs, a Quickest first sort, status chips (Action 178, Decision 34, Waiting 6, Not analysed 1, Closed on GitHub 5) and two fix filters, Open PR 26 and Triage fix 80. Under them, the group 9 to 10, paste and close or merge, with 86 items, each with its ease, number and title, such as pull request 42991, Fix docs build on Windows; the planned outcome under each title is blurred. On the right, Pick the next item, with a card proposing pull request 42991, its outcome blurred too, then the meaning of each status, of the two fix badges and of each ease band.
Figure 14 · the twbs/triage dashboard: every open Bootstrap item, quickest first. The planned outcomes are blurred: they aren't posted yet.

An item opens on its next action, with a button that copies the drafted answer. For issue 37807, it says to post a status comment and keep the issue open, then which pull request to open from which branch, or to let Mark’s land.

The dashboard on issue 37807, Grid System should support CSS Container Queries for responsive column sizing, status Action, with an Open PR badge and a 2 triage fixes badge, labels feature, v6 and backport-to-v5, analysed on 7 October 2026. The list on the left is narrowed to this one item. A Next action panel says: fix branches ready, status comment, keep open until 42784 lands or a direction is chosen. It details: post answer.md on 37807 and keep it open; to close it for the flexbox grid, open a PR on v6-dev from issue-37807-v6-dev, container-driven by default, or issue-37807-v6-dev-2, opt-in, or let 42784 land. Ease 6 out of 10, with Copy answer and Open 37807 buttons. Below, the start of the Fixes section.
Figure 15 · one item: the next action, the answer to copy, and which fix to open.

Under it, Fixes puts every way to solve the item side by side. Our branches come with their direction, commit, pull request description and the commands to cherry-pick them into a twbs/bootstrap clone. The real pull requests each link to their own review.

The Fixes section of issue 37807: 2 triage fixes, 1 open PR, as three cards. Two triage fix cards from v6-dev: issue-37807-v6-dev, container-driven by default (as v6 CSS Grid), marked Recommended, and issue-37807-v6-dev-2, opt-in Sass option, default off, each with its commit link, PR description and Copy commands buttons. One open PR card, with a dashed border: 42784, Draft, Extend container-query grid to the flexbox grid, by mdo, on v6-dev, named by the analysis, with Open and Its review buttons. Below, tabs for the analysis, the answer and each PR description, and the start of the analysis, which quotes mdo on the issue: container queries are coming to v6.
Figure 16 · competing fixes for one issue: two triage branches, and Mark's draft pull request.

The analyses quote hostile content, so the page that shows them must not run it. The server listens on localhost only and renders Markdown without raw HTML. It loads nothing from outside (its Content Security Policy blocks every other origin) and makes hidden Unicode characters visible. Any link that doesn’t point to github.com/twbs or getbootstrap.com becomes inert text.

Day to day#

  • The agents work as before. A new item, or new activity on one, is found at the next sync and analyzed in the next tick. The folder is published and its fix branches are pushed. On Wednesday morning: 224 open items, 223 analyzed, 120 fix branches on 80 of them, 31 with competing directions, 26 already addressed by an open pull request.
  • I still get one next action at a time in /todo, the quickest first, now with links to the item’s files on twbs/triage. I act on GitHub myself, or tell the conductor “post it”. It posts answer.md as is, or fetches the fix branch from twbs/triage and pushes it where I say. Either way, my activity on the item wakes its task, and the folder is rewritten if anything is left.
  • Mark needs no agent and no orchestrator: a git pull, npm start (after one npm ci), or the folders on GitHub give him the same backlog, with the same drafts, branches and comparisons. What he answers or merges shows up at the next sync, like anyone’s activity.

Prepared, never automatic#

The agents do the reading, the checking, the drafting, and now the fixing, in as many directions as the issue deserves. If you use Bootstrap or contribute to it, though, here’s the part that matters: none of it reaches the project without a maintainer going through it precisely. That’s why it’s semi-manual on purpose.

The agents make mistakes. They don’t know Bootstrap’s history well: why a selector is written the way it is, what was tried and reverted years ago, what we’ve planned for v6. And they tend to add code rather than refactor. They add one more rule on top of the existing ones, where the best fix would rethink the component with all of Bootstrap’s specifics, and the web platform’s, in mind. Part of that isn’t on them. Much of that knowledge lives in our heads, in old threads and in decisions never written down, so it isn’t in their context either.

An analysis is where we start, not what we ship.

Maybe we’ll find a way to make them smarter, starting with giving them that missing context. Until then, every comment, review and merge on Bootstrap is a maintainer’s, by hand.

What comes next#

Today, all of this runs on Claude, and as far as I know only on Claude: the subagent files, the skills, the slash commands, the usage meters orc budget reads, the permission rules. The parts that matter most aren’t tied to it, though. orc is a Python file over SQLite, and the protocol and the policies are Markdown. At some point the setup has to become agent-agnostic, and OpenCode, an open source coding agent that works with many model providers, is one way to get there.

Agent-agnostic also means that not every step needs a frontier model. Two experiments are on my list. The first is local models. A scout’s work (triage, summaries, CI status, rating how easy an issue is) is mechanical enough to run on my own machine, for free, without touching the weekly meter.

The second is Jev, a model by TypeSafe AI built for decisions rather than text: given a state and the allowed answers, it returns a typed choice with a calibrated confidence. The setup is full of questions shaped like that. Which tier does a task need? Does an item look suspicious? Which status and ease does an analysis land on? Is something worth a notification on my phone? Today each one costs an agent run, or hides in the middle of one. A typed answer with a confidence would let the clear cases go fast and cheap, and send only the uncertain ones to a bigger model, or to me.

Meanwhile, Claude Code’s own Remote Control fits the way I use it now. The conductor runs on my laptop, and I drive it from my phone, one question at a time, wherever I am. So: let’s see what happens.

A phone showing the Claude app connected to the conductor session through Remote Control. Under the header, Conductor, Remote Control on a MacBook, the /inbox brief says an Open {re}Source branch adds a glossary page with 78 terms and passes its checks. A question card asks: push the glossary branch and open its pull request? The recommended option, push and open the PR, is selected; the other is keep it prepared. Skip and Submit buttons, and a reply box at the bottom.
Figure 17 · the same inbox on my phone, through Remote Control. Mock rendering, not a screenshot.

For a while, I’m staying on Claude anyway. I got six months of Claude Max 20x for free through Claude for Open Source, a program for open source maintainers. It’s the plan this whole setup runs on.

The agents read, check and draft while I’m at work, asleep or in the subway. What’s left for me is the part that needs a maintainer: deciding, not relaying.

You can’t create time. This comes close.

Now, back to the next Bootstrap action in my to-do list.