Agents: pool scheduler — dispatch by type across multiple instances #49
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#49
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 the webhook to dispatch by type instead of by hardcoded agent name, picking an idle instance from the type's pool (or queuing on the type's aggregate queue), so that a long-running
devtask stops blocking every otherdevdispatch.Context
Today
handleIssueAssigneddoesgetWorker(assignee.login)— one worker per name. With A1 landed, a type can have N instances. The scheduler must:Session resume (
<type>:<repo>:<issue>key per A1) means any pool member can pick up a resumed dispatch — no sticky routing needed.Acceptance criteria
Worker registry
getWorker(name)stays (one worker per instance).getWorkersByType(type): Worker[]that returns all registered workers whose instancetypematches.resolveAgent()(A1) and creates oneWorkerper SQLite row at startup.Pool selection
dispatchByType(type, request): Promise<string>in the worker layer:candidates = getWorkersByType(type). If empty: return a clear error (no instance for type).worker.current === null && worker.queue.length === 0). If multiple, pick round-robin (keep a per-type cursor).queue.length). Tie-break by round-robin cursor.worker.enqueue(request)and return the task id.Webhook rewiring
src/webhook-handlers.tsswap fromgetWorker(<name>)todispatchByType(<type>, request):handleIssueAssigned— assignee login is the Forgejo user (e.g.dev), which is now the type. Use it directly.handleReviewRequested,handleChangesRequested,handleApproved,handleStatusEventdispatchFixCi— the target type is known from the dispatch context ("reviewer","boss", etc.). Replace hardcoded lookup.dispatchMerge(from #42) usesdispatchByType("boss", ...).Queueing semantics
Workerkeeps its own FIFO queue (as today). The pool is just "pick which worker to enqueue on." No shared queue between workers.dev-alpha's queue sticks there — it's not re-balanced ifdev-bravolater becomes idle. Re-balancing is a non-goal.Tests
dispatchByType— idle single, idle multiple (round-robin), all busy (least-loaded), none registered (error).dev-alpha+dev-bravoand confirminghandleIssueAssignedwithassignee.login === "dev"can land on either.Out of scope
(type, repo, issue)) — covered by the<type>:<repo>:<issue>session key.References
src/webhook-handlers.ts— search forgetWorker(.Workerclass:src/worker.ts.Dependencies
main(after A1 lands)./cancelendpoint: accept anagentparam instead of cancelling the first busy worker #87