Adds the Clotho dual-run verification system and hardens the entire agent dispatch pipeline against command injection, token exposure, and SSH MitM attacks. Breaks the agentci→handlers circular dependency. Security: - SanitizePath (regex whitelist + filepath.Base) for all dispatch inputs - EscapeShellArg for shell argument safety - SecureSSHCommand (StrictHostKeyChecking=yes, BatchMode=yes) - ForgeToken removed from ticket JSON, transferred via .env with 0600 - ssh-keyscan on agent add populates known_hosts before first connection Clotho: - Spinner orchestrator determines Standard vs Dual execution mode - Config-driven via ClothoConfig (strategy, validation_threshold) - Agent runner supports claude/codex/gemini backends with dual-run - Divergence detection compares thread outputs via git diff API: - LoadActiveAgents() returns map[string]AgentConfig (no handlers import) - LoadClothoConfig() reads clotho section from config - Forge helpers: AssignIssue, EnsureLabel, AddIssueLabels 32 tests pass (19 agentci + 13 dispatch). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
68 lines
1.8 KiB
Go
68 lines
1.8 KiB
Go
package agentci
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/host-uk/core/pkg/jobrunner"
|
|
)
|
|
|
|
// RunMode determines the execution strategy for a dispatched task.
|
|
type RunMode string
|
|
|
|
const (
|
|
ModeStandard RunMode = "standard"
|
|
ModeDual RunMode = "dual" // The Clotho Protocol — dual-run verification
|
|
)
|
|
|
|
// Spinner is the Clotho orchestrator that determines the fate of each task.
|
|
type Spinner struct {
|
|
Config ClothoConfig
|
|
Agents map[string]AgentConfig
|
|
}
|
|
|
|
// NewSpinner creates a new Clotho orchestrator.
|
|
func NewSpinner(cfg ClothoConfig, agents map[string]AgentConfig) *Spinner {
|
|
return &Spinner{
|
|
Config: cfg,
|
|
Agents: agents,
|
|
}
|
|
}
|
|
|
|
// DeterminePlan decides if a signal requires dual-run verification based on
|
|
// the global strategy, agent configuration, and repository criticality.
|
|
func (s *Spinner) DeterminePlan(signal *jobrunner.PipelineSignal, agentName string) RunMode {
|
|
if s.Config.Strategy != "clotho-verified" {
|
|
return ModeStandard
|
|
}
|
|
|
|
agent, ok := s.Agents[agentName]
|
|
if !ok {
|
|
return ModeStandard
|
|
}
|
|
if agent.DualRun {
|
|
return ModeDual
|
|
}
|
|
|
|
// Protect critical repos with dual-run (Axiom 1).
|
|
if signal.RepoName == "core" || strings.Contains(signal.RepoName, "security") {
|
|
return ModeDual
|
|
}
|
|
|
|
return ModeStandard
|
|
}
|
|
|
|
// GetVerifierModel returns the model for the secondary "signed" verification run.
|
|
func (s *Spinner) GetVerifierModel(agentName string) string {
|
|
agent, ok := s.Agents[agentName]
|
|
if !ok || agent.VerifyModel == "" {
|
|
return "gemini-1.5-pro"
|
|
}
|
|
return agent.VerifyModel
|
|
}
|
|
|
|
// 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) {
|
|
return string(primaryOutput) == string(signedOutput), nil
|
|
}
|