1 AgentCI-and-Clotho
Virgil edited this page 2026-02-19 17:01:37 +00:00

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:

  1. If the global strategy is not "clotho-verified", always return ModeStandard
  2. If the agent's DualRun field is true, return ModeDual
  3. Axiom 1 -- Protect critical repos: If the repo is named "core" or contains "security", return ModeDual
  4. 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:

  1. Direct match on config key (e.g., agents["charon-bot"])
  2. Search by ForgejoUser field 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:

  1. Resolve the assigned Forgejo user to an agent config via FindByForgejoUser
  2. Determine the execution mode via DeterminePlan
  3. Build a dispatch ticket with model/runner/verification settings
  4. Transfer the ticket securely via SecureSSHCommand with SanitizePath and EscapeShellArg

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