Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
When ai_control_enabled and mining interrupts or hashrate drops, the server composes same-agent fix plans (container restart, chain reorder, GPU swap, idle tune, RandomX restart) with Seer and oath ledger audit — no spread or lateral escalation.
163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package miningsurgery
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// SeerEvent is a read-only transcript row for the Seer page stream.
|
|
type SeerEvent struct {
|
|
Kind string `json:"kind"`
|
|
AgentID string `json:"agent_id"`
|
|
Payload json.RawMessage `json:"payload"`
|
|
Timestamp string `json:"timestamp"`
|
|
}
|
|
|
|
// Reporter callbacks for Seer + oath ledger hooks.
|
|
type Reporter struct {
|
|
OnSeer func(event SeerEvent)
|
|
OnOath func(agentID, actor, actionType, strain, whyHash, outcome string, payload map[string]interface{})
|
|
}
|
|
|
|
// Tracker queues per-agent mining self-surgery plans (same host only).
|
|
type Tracker struct {
|
|
mu sync.Mutex
|
|
pending map[string]Plan
|
|
lastPlan map[string]Plan
|
|
seerEvents []SeerEvent
|
|
reporter Reporter
|
|
}
|
|
|
|
func NewTracker() *Tracker {
|
|
return &Tracker{
|
|
pending: make(map[string]Plan),
|
|
lastPlan: make(map[string]Plan),
|
|
seerEvents: make([]SeerEvent, 0, 64),
|
|
}
|
|
}
|
|
|
|
func (t *Tracker) SetReporter(r Reporter) {
|
|
if t == nil {
|
|
return
|
|
}
|
|
t.mu.Lock()
|
|
t.reporter = r
|
|
t.mu.Unlock()
|
|
}
|
|
|
|
// QueuePlan stores a composed plan for the agent's next auth pass.
|
|
func (t *Tracker) QueuePlan(plan Plan) {
|
|
if t == nil || plan.AgentID == "" {
|
|
return
|
|
}
|
|
t.mu.Lock()
|
|
prev, seen := t.lastPlan[plan.AgentID]
|
|
if seen && plansEqual(prev, plan) {
|
|
t.mu.Unlock()
|
|
return
|
|
}
|
|
t.lastPlan[plan.AgentID] = plan
|
|
t.pending[plan.AgentID] = plan
|
|
reporter := t.reporter
|
|
t.mu.Unlock()
|
|
t.emit(plan, "pending", reporter)
|
|
}
|
|
|
|
// ConsumePlan returns and clears a pending plan for the agent's next auth handshake.
|
|
func (t *Tracker) ConsumePlan(agentID string) (Plan, bool) {
|
|
if t == nil || agentID == "" {
|
|
return Plan{}, false
|
|
}
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
plan, ok := t.pending[agentID]
|
|
if ok {
|
|
delete(t.pending, agentID)
|
|
}
|
|
return plan, ok
|
|
}
|
|
|
|
// RecordOutcome logs agent execution results to Seer + oath ledger.
|
|
func (t *Tracker) RecordOutcome(agentID string, outcome string, results []map[string]interface{}) {
|
|
if t == nil || agentID == "" {
|
|
return
|
|
}
|
|
t.mu.Lock()
|
|
plan, ok := t.lastPlan[agentID]
|
|
reporter := t.reporter
|
|
t.mu.Unlock()
|
|
if !ok {
|
|
plan = Plan{AgentID: agentID, Trigger: "self_surgery_report"}
|
|
}
|
|
payload := map[string]interface{}{
|
|
"agent_id": agentID,
|
|
"trigger": plan.Trigger,
|
|
"reason": plan.Reason,
|
|
"actions": plan.Actions,
|
|
"outcome": outcome,
|
|
"results": results,
|
|
}
|
|
raw, _ := json.Marshal(payload)
|
|
event := SeerEvent{
|
|
Kind: "mining_self_surgery",
|
|
AgentID: agentID,
|
|
Payload: raw,
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
t.mu.Lock()
|
|
t.seerEvents = append(t.seerEvents, event)
|
|
if len(t.seerEvents) > 500 {
|
|
t.seerEvents = t.seerEvents[len(t.seerEvents)-500:]
|
|
}
|
|
t.mu.Unlock()
|
|
if reporter.OnSeer != nil {
|
|
reporter.OnSeer(event)
|
|
}
|
|
if reporter.OnOath != nil {
|
|
reporter.OnOath(agentID, "ai_control:mining_surgery", "mining_self_surgery", "", hashWhy(plan), outcome, payload)
|
|
}
|
|
}
|
|
|
|
func (t *Tracker) emit(plan Plan, outcome string, reporter Reporter) {
|
|
raw, _ := json.Marshal(plan)
|
|
event := SeerEvent{
|
|
Kind: "mining_self_surgery",
|
|
AgentID: plan.AgentID,
|
|
Payload: raw,
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
t.mu.Lock()
|
|
t.seerEvents = append(t.seerEvents, event)
|
|
if len(t.seerEvents) > 500 {
|
|
t.seerEvents = t.seerEvents[len(t.seerEvents)-500:]
|
|
}
|
|
t.mu.Unlock()
|
|
if reporter.OnSeer != nil {
|
|
reporter.OnSeer(event)
|
|
}
|
|
if reporter.OnOath != nil {
|
|
payload := map[string]interface{}{
|
|
"agent_id": plan.AgentID,
|
|
"trigger": plan.Trigger,
|
|
"reason": plan.Reason,
|
|
"actions": plan.Actions,
|
|
}
|
|
reporter.OnOath(plan.AgentID, "ai_control:mining_surgery", "mining_self_surgery", "", hashWhy(plan), outcome, payload)
|
|
}
|
|
}
|
|
|
|
func plansEqual(a, b Plan) bool {
|
|
ab, _ := json.Marshal(a)
|
|
bb, _ := json.Marshal(b)
|
|
return string(ab) == string(bb)
|
|
}
|
|
|
|
func hashWhy(plan Plan) string {
|
|
raw, _ := json.Marshal(plan)
|
|
sum := sha256.Sum256(raw)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|