Merge pull request #471 from multica-ai/agent/j/959392dd

feat: support multiple agents running on same issue
This commit is contained in:
Bohan Jiang 2026-04-08 12:45:56 +08:00 committed by GitHub
commit 98af9f442c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 139 additions and 86 deletions

View file

@ -536,17 +536,22 @@ func (h *Handler) ListTaskMessages(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, resp)
}
// GetActiveTaskForIssue returns the currently running task for an issue, if any.
// GetActiveTaskForIssue returns all currently active tasks for an issue.
// Returns { tasks: [...] } array (may be empty).
func (h *Handler) GetActiveTaskForIssue(w http.ResponseWriter, r *http.Request) {
issueID := chi.URLParam(r, "id")
tasks, err := h.Queries.ListActiveTasksByIssue(r.Context(), parseUUID(issueID))
if err != nil || len(tasks) == 0 {
writeJSON(w, http.StatusOK, map[string]any{"task": nil})
return
if err != nil {
tasks = nil
}
writeJSON(w, http.StatusOK, map[string]any{"task": taskToResponse(tasks[0])})
resp := make([]AgentTaskResponse, len(tasks))
for i, t := range tasks {
resp[i] = taskToResponse(t)
}
writeJSON(w, http.StatusOK, map[string]any{"tasks": resp})
}
// CancelTask cancels a running or queued task by ID.

View file

@ -549,9 +549,12 @@ func (h *Handler) shouldEnqueueOnComment(ctx context.Context, issue db.Issue) bo
return false
}
// Coalescing queue: allow enqueue when a task is running (so the agent
// picks up new comments on the next cycle) but skip if a pending task
// already exists (natural dedup for rapid-fire comments).
hasPending, err := h.Queries.HasPendingTaskForIssue(ctx, issue.ID)
// picks up new comments on the next cycle) but skip if this agent already
// has a pending task (natural dedup for rapid-fire comments).
hasPending, err := h.Queries.HasPendingTaskForIssueAndAgent(ctx, db.HasPendingTaskForIssueAndAgentParams{
IssueID: issue.ID,
AgentID: issue.AssigneeID,
})
if err != nil || hasPending {
return false
}

View file

@ -109,6 +109,7 @@ WHERE id = (
AND NOT EXISTS (
SELECT 1 FROM agent_task_queue active
WHERE active.issue_id = atq.issue_id
AND active.agent_id = atq.agent_id
AND active.status IN ('dispatched', 'running')
)
ORDER BY atq.priority DESC, atq.created_at ASC
@ -118,10 +119,10 @@ WHERE id = (
RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id
`
// Claims the next queued task for an agent, enforcing per-issue serialization:
// a task is only claimable when no other task for the same issue is already
// dispatched or running. This guarantees serial execution within an issue
// while allowing parallel execution across different issues.
// Claims the next queued task for an agent, enforcing per-(issue, agent) serialization:
// a task is only claimable when no other task for the same issue AND same agent is
// already dispatched or running. This allows different agents to work on the same
// issue in parallel while preventing a single agent from running duplicate tasks.
func (q *Queries) ClaimAgentTask(ctx context.Context, agentID pgtype.UUID) (AgentTaskQueue, error) {
row := q.db.QueryRow(ctx, claimAgentTask, agentID)
var i AgentTaskQueue

View file

@ -75,10 +75,10 @@ SELECT * FROM agent_task_queue
WHERE id = $1;
-- name: ClaimAgentTask :one
-- Claims the next queued task for an agent, enforcing per-issue serialization:
-- a task is only claimable when no other task for the same issue is already
-- dispatched or running. This guarantees serial execution within an issue
-- while allowing parallel execution across different issues.
-- Claims the next queued task for an agent, enforcing per-(issue, agent) serialization:
-- a task is only claimable when no other task for the same issue AND same agent is
-- already dispatched or running. This allows different agents to work on the same
-- issue in parallel while preventing a single agent from running duplicate tasks.
UPDATE agent_task_queue
SET status = 'dispatched', dispatched_at = now()
WHERE id = (
@ -87,6 +87,7 @@ WHERE id = (
AND NOT EXISTS (
SELECT 1 FROM agent_task_queue active
WHERE active.issue_id = atq.issue_id
AND active.agent_id = atq.agent_id
AND active.status IN ('dispatched', 'running')
)
ORDER BY atq.priority DESC, atq.created_at ASC