AgentCI and the Clotho Protocol
Package: forge.lthn.ai/core/go-scm/agentci
Agent orchestration, configuration management, and security utilities for the AgentCI dispatch system. Implements the Clotho Protocol for dual-run verification of critical tasks.
Overview
AgentCI coordinates AI coding agents (Claude, Codex, Gemini) that work on Forgejo issues. The agentci package provides:
- Agent configuration -- YAML-based agent registry with per-agent model, runner, and security settings
- Clotho Protocol -- Dual-run verification where two models independently solve the same task and outputs are compared
- Security utilities -- Path sanitisation, shell escaping, SSH command hardening, token masking
Agent Configuration
Agents are stored in ~/.core/config.yaml under the agentci.agents key:
agentci:
agents:
charon:
host: build-server.example.com
queue_dir: /home/claude/ai-work/queue
forgejo_user: charon-bot
model: sonnet
runner: claude
security_level: high
dual_run: true
verify_model: gemini-1.5-pro
roles: [build, test]
active: true
darbs:
host: research-server.example.com
queue_dir: /home/claude/ai-work/queue
forgejo_user: darbs-bot
model: haiku
runner: claude
active: true
clotho:
strategy: clotho-verified
validation_threshold: 0.85
signing_key_path: /path/to/key
AgentConfig
type AgentConfig struct {
Host string // SSH hostname
QueueDir string // Remote queue directory (default: /home/claude/ai-work/queue)
ForgejoUser string // Forgejo username (decouples agent name from identity)
Model string // Primary AI model (default: "sonnet")
Runner string // Runner binary: claude, codex, gemini (default: "claude")
VerifyModel string // Secondary model for dual-run verification
SecurityLevel string // "low" or "high"
Roles []string // Agent capabilities
DualRun bool // Enable dual-run verification
Active bool // Whether the agent is active
}
Configuration Functions
// Load all agents from config (returns empty map if none configured)
agents, err := agentci.LoadAgents(cfg)
// Load only active agents
active, err := agentci.LoadActiveAgents(cfg)
// List all agents (active and inactive)
all, err := agentci.ListAgents(cfg)
// Save/remove individual agents
err := agentci.SaveAgent(cfg, "charon", agentConfig)
err := agentci.RemoveAgent(cfg, "charon")
Clotho Protocol
Named after one of the three Fates in Greek mythology who spins the thread of life, the Clotho Protocol determines the execution strategy for each dispatched task.
Run Modes
type RunMode string
const (
ModeStandard RunMode = "standard" // Single-agent execution
ModeDual RunMode = "dual" // Dual-run verification
)
ClothoConfig
type ClothoConfig struct {
Strategy string // "direct" or "clotho-verified"
ValidationThreshold float64 // Divergence limit 0.0-1.0 (default: 0.85)
SigningKeyPath string // Path to signing key for verification
}
Spinner (Orchestrator)
The Spinner is the Clotho orchestrator that determines the fate of each task:
// Load configuration
clothoCfg, err := agentci.LoadClothoConfig(cfg)
agents, err := agentci.LoadActiveAgents(cfg)
// Create the spinner
spinner := agentci.NewSpinner(clothoCfg, agents)
DeterminePlan
Decides whether a signal requires dual-run verification:
mode := spinner.DeterminePlan(signal, "charon")
// Returns ModeStandard or ModeDual
The decision logic:
- If the global strategy is not
"clotho-verified", always returnModeStandard - If the agent's
DualRunfield istrue, returnModeDual - Axiom 1 -- Protect critical repos: If the repo is named
"core"or contains"security", returnModeDual - Otherwise, return
ModeStandard
GetVerifierModel
Returns the secondary model for the verification run:
model := spinner.GetVerifierModel("charon")
// Returns agent's VerifyModel, or "gemini-1.5-pro" as default
FindByForgejoUser
Resolves a Forgejo username to the agent config key and config. This decouples mythological agent names (Charon, Darbs) from Forgejo identities:
name, config, ok := spinner.FindByForgejoUser("charon-bot")
// name = "charon", config = AgentConfig{...}, ok = true
Resolution order:
- Direct match on config key (e.g.,
agents["charon-bot"]) - Search by
ForgejoUserfield across all agents
Weave
Compares primary and verifier outputs to determine convergence. Currently uses simple equality; future versions will implement semantic diff:
converged, err := spinner.Weave(ctx, primaryOutput, signedOutput)
Security Utilities
Path Sanitisation
Prevents path traversal attacks by validating filenames against a safe character set:
safe, err := agentci.SanitizePath("ticket-core-go-42.json")
// safe = "ticket-core-go-42.json"
_, err := agentci.SanitizePath("../../etc/passwd")
// err: "invalid characters in path element: ../../etc/passwd"
Shell Argument Escaping
Wraps strings in single quotes for safe remote shell insertion:
escaped := agentci.EscapeShellArg("hello 'world'")
// escaped = "'hello '\\''world'\\'''"
Secure SSH Commands
Creates SSH commands with strict host key checking and batch mode:
cmd := agentci.SecureSSHCommand("agent.example.com", "ls /queue")
// Equivalent to: ssh -o StrictHostKeyChecking=yes -o BatchMode=yes -o ConnectTimeout=10 agent.example.com "ls /queue"
Token Masking
Masks tokens for safe logging output:
masked := agentci.MaskToken("abc123456789xyz")
// masked = "abc1****9xyz"
masked := agentci.MaskToken("short")
// masked = "*****"
Integration with Job Runner
The agentci package is used by the Job-Runner dispatch handler to:
- Resolve the assigned Forgejo user to an agent config via
FindByForgejoUser - Determine the execution mode via
DeterminePlan - Build a dispatch ticket with model/runner/verification settings
- Transfer the ticket securely via
SecureSSHCommandwithSanitizePathandEscapeShellArg
See the handlers.DispatchHandler in Job-Runner for the full workflow.
See Also
- Job-Runner -- Uses AgentCI for dispatch and completion handling
- Forge-Client -- Forgejo API used by dispatch handlers
- Home -- Package overview