Summary
Reviewer agent names are derived from the issue number alone, with no repo qualifier — so the same issue number in two different repos produces the same agent name, and the second dispatch collides with a name the broker still holds. The implementer does not have this problem: it is already repo-scoped.
This makes factory effectively single-repo per issue-number-space, which contradicts the multi-repo routing the config is built around (repos.names[], byLabel, byProject, keywordRules, repos.default).
Observed
Dispatching hoopsheet#26 after relayed#26 had been dispatched previously (2026-07-15):
$ factory dispatch /github/repos/AgentWorkforce__hoopsheet/issues/by-id/26.json
[factory-sdk] marked spawned agent terminal {"name":"ar-26-review","reason":"resume-already-exists"}
[factory] resume skipped: broker still holds agent name (relay#1116); not retrying {"issue":"26","name":"ar-26-review"}
[factory-sdk] suppressed duplicate agent exit {"count":1,"name":"ar-26-review"}
[factory] error {}
[factory] task injection target not registered yet; retrying {"to":"ar-26-review","attempt":1,"error":"Timed out waiting for delivery_injected for http_…"}
[factory] task injection target not registered yet; retrying {"to":"ar-26-review","attempt":2,…}
[factory] task injection target not registered yet; retrying {"to":"ar-26-review","attempt":3,…}
The dispatch wedges: the reviewer never registers, and factory retries task injection into an agent that will never exist.
Root cause
src/triage/heuristic.ts:376:
function agentBaseName(issue: LinearIssue): string {
const number = issue.key.match(/\d+/)?.[0] ?? sanitizeSlug(issue.key)
return `ar-${number}` // issue number only — no repo
}
For GitHub issues issue.key is the bare number, so hoopsheet#26 and relayed#26 both yield ar-26. The implementer name gets a repo suffix (slugFromRepo, heuristic.ts:381) → ar-26-impl-hoopsheet. The reviewer does not → bare ar-26-review. Hence implementers never collide and reviewers always do.
(Duplicate agentBaseName also exists at src/orchestrator/factory.ts:5697 — worth checking they stay in sync, or de-duplicating.)
Scale of the problem in one workspace
Every reviewer from this workspace's history is squatting on the global name space:
ar-12-impl-relayed | ar-12-review ← implementer repo-scoped, reviewer NOT
ar-13-impl-relayed | ar-13-review
ar-24-impl-relayed | ar-24-review
ar-25-impl-relayed | ar-25-review
…
Poisoned numbers currently registered (all offline/unknown, all still holding the name):
7, 9, 12, 13, 14, 15, 16, 19, 21, 23, 24, 25, 26, 27, 28, 29, 31, 33, 41, 43, 45
Consequence for a second repo in the same workspace: 14 of hoopsheet's 22 factory-ready issues cannot be dispatched (12, 13, 14, 15, 23, 24, 25, 26, 27, 28, 29, 31, 33, 41). Only 11, 22, 30, 34, 35, 36, 37, 38 are usable — purely by the accident of which numbers relayed happened to use.
Fix
Repo-qualify the reviewer exactly as the implementer already is:
ar-26-impl-hoopsheet (today)
ar-26-review-hoopsheet (proposed)
Points to consider:
Workaround
Release the held name before re-dispatching:
agent-relay node agent release ar-26-review # or: factory reap-orphans
Impact
Multi-repo is a headline capability (repos.byLabel / byProject / keywordRules / repos.default all exist to route across repos), but the reviewer name space is global and unqualified — so the second repo onboarded into a workspace silently loses most of its issue numbers, with a failure mode (task injection target not registered yet, retried forever) that gives no hint the cause is a name collision with a different repo.
Related: relay#1116 (broker holds agent name), #59 / #67 / #69 (already exists respawn handling), #81 (dispatch resolver ergonomics).
Summary
Reviewer agent names are derived from the issue number alone, with no repo qualifier — so the same issue number in two different repos produces the same agent name, and the second dispatch collides with a name the broker still holds. The implementer does not have this problem: it is already repo-scoped.
This makes factory effectively single-repo per issue-number-space, which contradicts the multi-repo routing the config is built around (
repos.names[],byLabel,byProject,keywordRules,repos.default).Observed
Dispatching
hoopsheet#26afterrelayed#26had been dispatched previously (2026-07-15):The dispatch wedges: the reviewer never registers, and factory retries task injection into an agent that will never exist.
Root cause
src/triage/heuristic.ts:376:For GitHub issues
issue.keyis the bare number, sohoopsheet#26andrelayed#26both yieldar-26. The implementer name gets a repo suffix (slugFromRepo,heuristic.ts:381) →ar-26-impl-hoopsheet. The reviewer does not → barear-26-review. Hence implementers never collide and reviewers always do.(Duplicate
agentBaseNamealso exists atsrc/orchestrator/factory.ts:5697— worth checking they stay in sync, or de-duplicating.)Scale of the problem in one workspace
Every reviewer from this workspace's history is squatting on the global name space:
Poisoned numbers currently registered (all
offline/unknown, all still holding the name):7, 9, 12, 13, 14, 15, 16, 19, 21, 23, 24, 25, 26, 27, 28, 29, 31, 33, 41, 43, 45Consequence for a second repo in the same workspace: 14 of
hoopsheet's 22factory-readyissues cannot be dispatched (12, 13, 14, 15, 23, 24, 25, 26, 27, 28, 29, 31, 33, 41). Only 11, 22, 30, 34, 35, 36, 37, 38 are usable — purely by the accident of which numbersrelayedhappened to use.Fix
Repo-qualify the reviewer exactly as the implementer already is:
Points to consider:
ar-${number}-*roles (e.g. the babysitter —agentBaseName(issue)+-babysit) have the same defect and should be qualified together.#babysitterSpawned/#slackThreadIds/ClarificationRecordkey off, and the reviewer prompt tells the implementer to DM${input.reviewerName}— keep those consistent.offlineagents should hold a name at all, or whether dispatch should reclaim a terminal agent's name (see relay#1116, referenced in the error). Even with repo-qualified names, re-running the same issue in the same repo will hit the same wall — which theresume-already-existshandling (One-shotfactory dispatchshuts down its self-spawned broker before the agent finishes (cold-start dispatch never completes end-to-end) #59, dispatch hangs: implementer resume respawn hits 500 "agent already exists", agent-exit tracker never resolves → never reaches human-review (0.1.16) #67/fix(factory): resolve reviewer hang so dispatch reaches human-review (fixes #67, supersedes #68) #69) is already fighting.Workaround
Release the held name before re-dispatching:
Impact
Multi-repo is a headline capability (
repos.byLabel/byProject/keywordRules/repos.defaultall exist to route across repos), but the reviewer name space is global and unqualified — so the second repo onboarded into a workspace silently loses most of its issue numbers, with a failure mode (task injection target not registered yet, retried forever) that gives no hint the cause is a name collision with a different repo.Related: relay#1116 (broker holds agent name), #59 / #67 / #69 (
already existsrespawn handling), #81 (dispatch resolver ergonomics).