Files
gadfly/cmd/gadfly/delegate.go
T
Steve Dudenhoeffer 4b8f9aa39b
Build & push image / build-and-push (push) Successful in 33s
feat: dynamic auto specialist selection + worker-tier delegation
Two Phase-2 swarm upgrades:

- auto.go: GADFLY_SPECIALISTS=auto routes the review — a selector model
  (GADFLY_SELECTOR_MODEL, else the review model) reads the changed files + PR
  description and picks the smallest relevant lens set from the catalog, and may
  propose ad-hoc lenses for gaps (e.g. migrations). Structured output via
  majordomo.Generate[T]; capped + de-duped; falls back to the default suite.
- delegate.go: GADFLY_WORKER_MODEL adds a delegate_investigation tool so the
  reviewer offloads mechanical legwork (trace callers, gather usages) to a cheap
  worker sub-agent that returns an evidence-cited digest — the top model reasons
  over summaries, not raw file dumps. Workers get an fs-only toolbox (no
  sub-delegation). Unset = off.

resolveSpecialists now also returns the registry + an auto flag. Docs (README
Specialists + config table, CLAUDE.md, main.go header) + tests updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:35:59 -04:00

66 lines
2.6 KiB
Go

package main
import (
"context"
"fmt"
"strings"
"gitea.stevedudenhoeffer.com/steve/majordomo/agent"
llm "gitea.stevedudenhoeffer.com/steve/majordomo/llm"
)
// defaultWorkerMaxSteps bounds a delegated worker run.
const defaultWorkerMaxSteps = 8
// workerSystemPrompt drives a delegated investigation sub-agent. It does the
// grunt work (grep/read) and returns a tight, evidence-cited digest so the
// top reviewer reasons over a summary instead of raw file dumps.
const workerSystemPrompt = "You are a fast code investigator. Use the read-only tools " +
"(read_file, grep, find_files, list_dir, get_diff) to answer the SPECIFIC question you are " +
"given about this repository — nothing more. Be precise and concise: report concrete findings " +
"with `path:line` evidence, quote only the few lines that matter, and state plainly when " +
"something is absent. Do NOT speculate or pad. When you have the answer, stop and report it."
type delegateArgs struct {
Task string `json:"task" description:"A specific investigation to carry out by reading/grepping the repo, e.g. 'find every caller of parseAmount and list which ones don't check the error'. Ask for a concrete answer, not an opinion."`
}
// delegateTool lets the reviewer offload mechanical investigation to a (cheaper)
// worker model. Present only when GADFLY_WORKER_MODEL is configured.
func (r *repoFS) delegateTool() llm.Tool {
return llm.DefineTool[delegateArgs](
"delegate_investigation",
"Delegate focused legwork to a fast worker agent that reads/greps this repo and reports back "+
"a concise, evidence-cited digest. Use it to offload mechanical investigation (tracing all "+
"callers, gathering every usage of a symbol, checking a pattern across many files) so you can "+
"reason over the summary instead of pulling raw files into your own context.",
func(ctx context.Context, args delegateArgs) (any, error) {
task := strings.TrimSpace(args.Task)
if task == "" {
return nil, fmt.Errorf("task is required")
}
box, err := r.workerToolbox()
if err != nil {
return nil, err
}
w := agent.New(r.worker, workerSystemPrompt,
agent.WithToolbox(box),
agent.WithMaxSteps(envInt("GADFLY_WORKER_MAX_STEPS", defaultWorkerMaxSteps)),
agent.WithToolErrorLimits(3, 3),
)
res, runErr := w.Run(ctx, task)
out := ""
if res != nil {
out = strings.TrimSpace(res.Output)
}
if out == "" {
if runErr != nil {
return nil, fmt.Errorf("worker investigation failed: %w", runErr)
}
return "worker returned no findings", nil
}
return out, nil
},
)
}