Agents: label-aware instance selection (match_labels on dispatch) #50
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#50
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
User story
As a maintainer, I want to route security-sensitive PRs to a specific reviewer instance (e.g.
reviewer-securityon opus 4.7) and leave everything else on the default reviewer (sonnet 4.6), by configuringmatch_labelsper instance and letting the dispatcher pick the best-matching instance, so that model cost tracks PR risk.Context
A2's pool scheduler picks round-robin. A3 adds a label-aware preference on top: when the dispatcher knows the triggering issue/PR's labels, it first tries to match an instance whose
match_labelsintersect the observed labels, and falls back to A2's round-robin when no instance matches.The initial driver: a
breakdownskill (A7) that emitssecuritylabels on specs dealing with auth / crypto / secrets — and a reviewer instance configured withmatch_labels: ["security"]+model: claude-opus-4-7that automatically picks those up. But A3 must work with any label source (human-added labels, CI-added labels, etc.).Acceptance criteria
Label-aware selection
dispatchByType(type, request, opts?): Promise<string>to acceptopts.labels?: string[].match_labelsintersectopts.labels. If multiple, prefer the most specific (largest intersection); tie-break by A2's "idle first, then least-loaded".match_labelslist (the "default" pool). A2's round-robin within that subset."[webhook] no match_labels catch-all for <type>; falling back to any instance".opts.labelsundefined / empty → behaves as A2 (no label-aware step).Label fetching
forgejo-api.ts. Use the PR body'sCloses #Npattern or the first label-bearing issue fromlist_pull_request_issuesif Forgejo exposes it — whichever is simpler to implement, document the choice.payload.issue.labels).Config surface
match_labelscolumn is a JSON array (A1 already added it). Dashboard (A6) will edit it; for this story, test via direct SQLite insert.match_labels: []ornullare the "catch-all" for their type. An instance withmatch_labels: ["security"]opts out of catch-all duty.Logging
[webhook] dispatch <type> -> <instance> (match: [security] ∩ [security,audit]; idle=2, loaded=0)or... (fallback: no match; round-robin catch-all; idle=1).Tests
dispatchByTypewith labels:match_labels→ that instance picked.match_labels, default pool non-empty → catch-all round-robin.Out of scope
securityweight 10,frontendweight 1) — v1 uses intersection size only.match_labels(see A6).References
Dependencies
main(after A2 lands).