← Writing

Dispatch vs Adoption: How DevPilot Sends Issues to Agents

5 min read

DevPilot dispatches issues to agents by writing a row to the dispatch_queue table, which the orchestrator picks up and hands to Claude Code, the only agent wired today. Adoption runs the opposite way: it finds sessions already running and puts them on the board without ever touching that queue.

Dispatch and Adoption Are Opposite Directions

When DevPilot dispatches an issue, it writes a row to the dispatch_queue table. The orchestrator polls that table, claims a row, and starts a Claude Code session against it — that's the only agent wired today. The row exists before the session does. Dispatch is DevPilot causing work to happen.

Adoption runs the other way. A Claude Code session is already running on the machine — started from a terminal, or left over from yesterday — and DevPilot did not start it. Adoption finds that session and gives it a place on the Linear board. No dispatch happened, so there is no dispatch_queue row to write. That's not an oversight; it's the point. Writing a dispatch_queue row for a session that already exists would mean the queue no longer describes what it claims to describe: work DevPilot caused.

The path is different too. Normal dispatch goes through the orchestrator's polling loop. Adoption is CLI-driven: a scan of the local session store, a match against what DevPilot already knows about, and a batch of writes for whatever it finds that it doesn't. The orchestrator is never called during adoption. It has nothing to claim.

What's Actually Been Proven End to End

The adoption path has been run against the real CLI, the real database, and real HTTP — none of these mocked. In one run, it adopted 6 live sessions. For each, it created a Linear issue — against a local GraphQL stub, not a live workspace, more on that below — and wrote a row with origin='adopted'. Six sessions in, six rows out.

Zero dispatch_queue rows were written. That's the number to check if you want to know whether adoption and dispatch share a code path: they don't, and the row count proves it rather than the code merely implying it.

A second run of the same adoption created nothing. Same sessions, same machine, same command — no new issues, no new rows. That's idempotency demonstrated, not asserted: running adoption twice is safe because the second run finds nothing left to adopt.

What's Proven Against Real Data

Separately, the scanner that adoption depends on has been run against this machine's actual shared session store, not a fixture. It read 670 sessions across 38 project directories in 634 ms on the reference machine. Those sessions spanned 12 repos under 5 owners.

That's a real number from a real disk, and it's worth being precise about what it proves. It proves the scanner can read a session store of that size, in that shape, in well under a second. It does not prove anything about dispatching issues to agents — scanning a session and dispatching work to one are different operations, and this section is only about the first.

What's Not Yet Proven

Every Linear call in the test suite — including the one behind the 6-session adoption run above — hits a local GraphQL stub that speaks the same protocol as linear.app, not linear.app itself. The documents sent in those calls are real: they're generated by the same route that would run in production, and that route's output has been verified independently of the stub. What hasn't happened is a live Linear workspace receiving one of these issues.

This matters because a stub can be wrong in ways a schema check won't catch — a field Linear silently ignores, a webhook that never fires, a rate limit the stub doesn't model. None of that has been ruled out yet. If you're deciding whether to route real Linear traffic through adoption today, read that as the open question, not a footnote.

docs/ROADMAP.md §0 states this gap in exactly these terms, and it's worth reading the roadmap's proof language as a checklist: each claim there is scoped to what was actually run, not what the feature is meant to eventually do.

Why DevPilot States Proof This Precisely

docs/ROADMAP.md §0 exists because this repo has a history of documentation asserting things the code did not do. That history is the reason the roadmap now separates three tiers explicitly: proven end to end against the live database over real HTTP, proven against real data, and not yet proven against Linear itself.

Those aren't interchangeable levels of confidence. "Proven end to end" means a real run happened and its output was counted — 6 sessions, 6 issues, 6 rows, zero dispatch_queue rows. "Proven against real data" means the input was real but the destination (Linear) was not — 670 sessions read, but no issue landed anywhere live. "Not yet proven" means exactly that: don't read the stub tests as evidence the live integration works, because they aren't testing the live integration.

Read the roadmap as you'd read a changelog with unit tests attached, not as a features list. Each line is a claim you could go and check.

How Dispatch and Adoption Fit Together on the Board

Most Claude Code sessions that get adopted predate DevPilot's involvement entirely — someone opened a terminal, started working, and only later pointed DevPilot at the machine. Adoption's job is to notice those sessions and put them on the Linear board as issues with origin='adopted', without ever writing to dispatch_queue, for the reason covered above: nothing was dispatched.

Once that issue exists on the board, though, dispatch and adoption converge. An adopted issue looks like any other issue to the rest of DevPilot — it can be picked up in a wave, it's subject to the same claim-once guarantees as dispatched work, and it counts toward how many agents you're running at once. The dispatch_queue row is what's missing from its history, not from its future.