The sdd plugin is about what gets built and how it’s verified. Its sibling, delegation, is about who does the work. It started as a few subagent definitions and some notes on model tiering, and grew into a plugin that can also send work outside the Claude account I’m sitting in.

Matching the model to the work

The core of the plugin is a set of agents, each tied to a model tier that fits the kind of work it does:

  • a scout for read-only searches (“where is this used?”) and a test runner that runs a suite and reports, both on the cheapest tier;
  • an implementer that executes a settled design, on the workhorse tier;
  • a code reviewer on a stronger tier;
  • an architect on the flagship tier, for genuinely hard design, and only when I’ve agreed to pay for it.

The rules that come with it are short:

  • Pay flagship prices for the thinking, workhorse prices for the typing. The architect produces a design a cheaper model can carry out.
  • Never escalate silently. The expensive tier has to be asked for, with a reason.
  • Don’t hand a vague brief to a cheap model. It will produce confident, wrong code that costs more to undo than writing it yourself.

I also measured fan-out once instead of guessing. Splitting one mechanical job across three agents finished about twice as fast and cost about twice as many tokens. Parallel agents buy time, not savings, and you wait for the slowest one. That changed my default from “parallelize everything” to “parallelize when someone is waiting”.

Three ways out of the session

A subagent’s model setting picks a model from the same provider and the same account. It can’t send work anywhere else. So the plugin adds three “lanes”, each a small script with a thin agent in front of it:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#e8eef7', 'primaryTextColor': '#1a1a2e', 'primaryBorderColor': '#4a6fa5', 'lineColor': '#4a6fa5', 'textColor': '#1a1a2e', 'edgeLabelBackground': '#f8f9fc', 'clusterBkg': '#eef2f9', 'clusterBorder': '#4a6fa5' }}}%%
flowchart LR
    S["Claude Code session"] --> A["Another vendor's harness<br/>e.g. Antigravity<br/>(a second opinion)"]
    S --> B["A second Claude subscription<br/>(questions or real work)"]
    S --> C["A local model<br/>(bulk work that gets checked)"]
    B -. "returns a diff" .-> S
    C -. "returns a diff" .-> S
  • Another vendor’s harness, like Google’s Antigravity, answers one read-only question about the repository: a second opinion from a different model family, looking at the real files.
  • A second Claude subscription can answer a question or do real work in the repository. It can’t commit. The diff is what comes back, and I review it.
  • A local model on my own hardware takes bulk work, but only work that something will check, like a test suite. A small model’s mistakes are confident and hard to spot. That’s fatal when nothing checks the result, and harmless when a test does.

The second subscription also keeps a reserve: past a usage level I choose, it refuses work, and if usage can’t be read, it refuses rather than guessing.

The lane nobody picked

Here’s the story I wanted to write this post for.

About a week after the lanes were ready, I counted the subagent dispatches in one project: 540 dispatches, and not one to any lane. Everything was set up. Permissions were granted, the plugin was enabled, every lane probed as ready. Meanwhile, the agents described by the kind of work they do had been picked 65 times.

The cause was obvious once I saw it. A session picks an agent by matching the task in front of it against each agent’s description. My lane descriptions led with who pays: “runs on a second subscription”, “costs no quota”. But “billed somewhere else” isn’t a property of any task. No task ever looks like “something that should be billed elsewhere”, so the lanes never matched.

The fix was to rewrite every lane description to lead with the work it suits (“implements a settled design across several files and returns a diff to review”, “runs a suite or makes a change that a command can verify”) and push billing to the end. The session rules got a matching habit: name the lane you’re using in one line and go, without stopping to ask, because a default I can veto is better than a question I have to answer.

The lesson goes beyond this plugin: describe tools by the problems they solve, not by their properties. A model matches tasks to descriptions. If your description doesn’t sound like a task, nothing will match it. It’s too early to say how much the rewrite changed the numbers. I’ll write that up once there’s enough data.

Things that looked fine and weren’t

Most of the bugs in this plugin had the same shape: something looked configured, reported success, and did nothing.

  • Shared logins that logged each other out. The first design shared one login between several config directories. Refresh tokens can only be used once, so the first refresh quietly invalidated the others, and eventually every directory was signed out. Each directory now has its own login. I found that by breaking a real login, not by reasoning about it.
  • A setup check that always passed. It printed a clear verdict and then exited successfully no matter what, so every script relying on it was reading a constant.
  • A relay that answered instead of refusing. On the cheapest tier, the agent meant to forward questions to Antigravity would answer them itself when Antigravity wasn’t installed, and report success. Moving it up a tier fixed it.

The fix was the same each time: stop trusting the status, and go check the behavior.

The task loop

The newest piece is a procedure for what happens after a review comes back with problems: fix, review again, with a hard cap on rounds. Later rounds go to a fresh agent on a stronger model rather than the same one, because an agent that’s already failed tends to defend its first approach. At the cap, the remaining issues get a decision instead of another loop.

Its first real run proved its own point. The work passed its tests, and review still found that it didn’t meet the spec, plus a crash the change had introduced. Passing tests aren’t proof the work is right, and a fix needs reviewing as much as the work it fixes.

Where it’s at

delegation lives next to sdd in my private plugin marketplace. It’s mostly prompts and a handful of shell scripts.

If I had to sum it up: routing work between models is easy to configure and hard to get used. The configuration was never the problem. Whether the model would ever choose it was.