From b00e0df89c739ab82071fc2244ef173474e79555 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Feb 2026 03:07:07 +0000 Subject: [PATCH] fix(agentci): resolve agents by Forgejo username, not config key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds FindByForgejoUser() to Spinner so dispatch matches issues assigned to Forgejo users (Virgil, Claude, Charon) even when the agent config key differs (e.g. Hypnos → forgejo_user: Claude). Searches config key first (direct match), then ForgejoUser field. Co-Authored-By: Claude Opus 4.6 --- pkg/agentci/clotho.go | 19 +++++++++++++++++++ pkg/jobrunner/handlers/dispatch.go | 10 +++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pkg/agentci/clotho.go b/pkg/agentci/clotho.go index f8b25b3..998d502 100644 --- a/pkg/agentci/clotho.go +++ b/pkg/agentci/clotho.go @@ -61,6 +61,25 @@ func (s *Spinner) GetVerifierModel(agentName string) string { return agent.VerifyModel } +// FindByForgejoUser resolves a Forgejo username to the agent config key and config. +// This decouples agent naming (mythological roles) from Forgejo identity. +func (s *Spinner) FindByForgejoUser(forgejoUser string) (string, AgentConfig, bool) { + if forgejoUser == "" { + return "", AgentConfig{}, false + } + // Direct match on config key first. + if agent, ok := s.Agents[forgejoUser]; ok { + return forgejoUser, agent, true + } + // Search by ForgejoUser field. + for name, agent := range s.Agents { + if agent.ForgejoUser != "" && agent.ForgejoUser == forgejoUser { + return name, agent, true + } + } + return "", AgentConfig{}, false +} + // Weave compares primary and verifier outputs. Returns true if they converge. // This is a placeholder for future semantic diff logic. func (s *Spinner) Weave(ctx context.Context, primaryOutput, signedOutput []byte) (bool, error) { diff --git a/pkg/jobrunner/handlers/dispatch.go b/pkg/jobrunner/handlers/dispatch.go index de2ae7d..f33a28e 100644 --- a/pkg/jobrunner/handlers/dispatch.go +++ b/pkg/jobrunner/handlers/dispatch.go @@ -68,12 +68,12 @@ func (h *DispatchHandler) Name() string { } // Match returns true for signals where a child issue needs coding (no PR yet) -// and the assignee is a known agent. +// and the assignee is a known agent (by config key or Forgejo username). func (h *DispatchHandler) Match(signal *jobrunner.PipelineSignal) bool { if !signal.NeedsCoding { return false } - _, ok := h.spinner.Agents[signal.Assignee] + _, _, ok := h.spinner.FindByForgejoUser(signal.Assignee) return ok } @@ -81,7 +81,7 @@ func (h *DispatchHandler) Match(signal *jobrunner.PipelineSignal) bool { func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) { start := time.Now() - agent, ok := h.spinner.Agents[signal.Assignee] + agentName, agent, ok := h.spinner.FindByForgejoUser(signal.Assignee) if !ok { return nil, fmt.Errorf("unknown agent: %s", signal.Assignee) } @@ -133,10 +133,10 @@ func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.Pipelin } // Clotho planning — determine execution mode. - runMode := h.spinner.DeterminePlan(signal, signal.Assignee) + runMode := h.spinner.DeterminePlan(signal, agentName) verifyModel := "" if runMode == agentci.ModeDual { - verifyModel = h.spinner.GetVerifierModel(signal.Assignee) + verifyModel = h.spinner.GetVerifierModel(agentName) } // Build ticket.