Add Calibrate AI Control UI and fleet LLM backend wiring.
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
Operators toggle Logic gates vs AI Control on Settings, refresh local Ollama models, and save ai_endpoint settings via Calibrate PUT; server scheduler and agent snapshot/command paths support stateless 60s fleet decisions.
This commit is contained in:
@@ -81,6 +81,16 @@ type ServerSettings struct {
|
||||
TripleOnionPolicy TripleOnionSettings `json:"triple_onion_policy,omitempty"`
|
||||
// AdaptiveStrategyEnabled learns LOTL tier order from fleet outcomes (user machines only).
|
||||
AdaptiveStrategyEnabled bool `json:"adaptive_strategy_enabled"`
|
||||
// AIControlEnabled switches fleet control from adaptive tier learning to local LLM decisions.
|
||||
AIControlEnabled bool `json:"ai_control_enabled"`
|
||||
// AIEndpoint is the OpenAI-compatible base URL (e.g. Ollama /v1).
|
||||
AIEndpoint string `json:"ai_endpoint"`
|
||||
// AIModel is the LLM model name for fleet AI control (Calibrate).
|
||||
AIModel string `json:"ai_model"`
|
||||
// AINoContext forces stateless single-turn decisions (no conversation memory).
|
||||
AINoContext bool `json:"ai_no_context"`
|
||||
// AIDecisionIntervalSec is seconds between AI decision cycles per agent (default 60).
|
||||
AIDecisionIntervalSec int `json:"ai_decision_interval_sec"`
|
||||
}
|
||||
|
||||
// WebRTCMeshPolicySettings is Calibrate policy for WebRTC LAN seed spread.
|
||||
@@ -283,6 +293,11 @@ func DefaultConfig() *Config {
|
||||
},
|
||||
ServiceDeployAllowlist: defaultServiceDeployAllowlist(),
|
||||
AdaptiveStrategyEnabled: true,
|
||||
AIControlEnabled: false,
|
||||
AIEndpoint: "http://127.0.0.1:11434/v1",
|
||||
AIModel: "",
|
||||
AINoContext: true,
|
||||
AIDecisionIntervalSec: 60,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -321,6 +336,7 @@ func LoadConfig() *Config {
|
||||
var presentKeys map[string]json.RawMessage
|
||||
_ = json.Unmarshal(data, &presentKeys)
|
||||
mergeConfigExplicit(cfg, &fileCfg, presentKeys)
|
||||
hydrateLegacyAIConfig(cfg, data)
|
||||
if !strings.Contains(string(data), `"open_firewall_on_start"`) {
|
||||
cfg.Server.OpenFirewallOnStart = true
|
||||
}
|
||||
@@ -941,6 +957,30 @@ func mergeConfigExplicit(dst, src *Config, present map[string]json.RawMessage) {
|
||||
if in(srvKeys, "public_builds_latest_n") && src.Server.PublicBuildsLatestN != 0 {
|
||||
dst.Server.PublicBuildsLatestN = src.Server.PublicBuildsLatestN
|
||||
}
|
||||
if in(srvKeys, "adaptive_strategy_enabled") {
|
||||
dst.Server.AdaptiveStrategyEnabled = src.Server.AdaptiveStrategyEnabled
|
||||
}
|
||||
if in(srvKeys, "ai_control_enabled") {
|
||||
dst.Server.AIControlEnabled = src.Server.AIControlEnabled
|
||||
}
|
||||
if in(srvKeys, "ai_endpoint") {
|
||||
dst.Server.AIEndpoint = src.Server.AIEndpoint
|
||||
}
|
||||
if in(srvKeys, "ai_local_endpoint") && src.Server.AIEndpoint != "" {
|
||||
dst.Server.AIEndpoint = src.Server.AIEndpoint
|
||||
}
|
||||
if in(srvKeys, "ai_model") {
|
||||
dst.Server.AIModel = src.Server.AIModel
|
||||
}
|
||||
if in(srvKeys, "ai_no_context") {
|
||||
dst.Server.AINoContext = src.Server.AINoContext
|
||||
}
|
||||
if in(srvKeys, "ai_decision_interval_sec") && src.Server.AIDecisionIntervalSec > 0 {
|
||||
dst.Server.AIDecisionIntervalSec = src.Server.AIDecisionIntervalSec
|
||||
}
|
||||
if in(srvKeys, "ai_interval_sec") && src.Server.AIDecisionIntervalSec > 0 {
|
||||
dst.Server.AIDecisionIntervalSec = src.Server.AIDecisionIntervalSec
|
||||
}
|
||||
}
|
||||
|
||||
if has("tunnel_defaults") {
|
||||
@@ -969,6 +1009,36 @@ func mergeConfigExplicit(dst, src *Config, present map[string]json.RawMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
func hydrateLegacyAIConfig(cfg *Config, raw []byte) {
|
||||
if cfg == nil || len(raw) == 0 {
|
||||
return
|
||||
}
|
||||
var root map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &root); err != nil {
|
||||
return
|
||||
}
|
||||
srvRaw, ok := root["server"]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var srv map[string]json.RawMessage
|
||||
if err := json.Unmarshal(srvRaw, &srv); err != nil {
|
||||
return
|
||||
}
|
||||
if ep, ok := srv["ai_local_endpoint"]; ok && cfg.Server.AIEndpoint == "" {
|
||||
var s string
|
||||
if json.Unmarshal(ep, &s) == nil && strings.TrimSpace(s) != "" {
|
||||
cfg.Server.AIEndpoint = strings.TrimSpace(s)
|
||||
}
|
||||
}
|
||||
if iv, ok := srv["ai_interval_sec"]; ok && cfg.Server.AIDecisionIntervalSec == 0 {
|
||||
var n int
|
||||
if json.Unmarshal(iv, &n) == nil && n > 0 {
|
||||
cfg.Server.AIDecisionIntervalSec = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) Save() error {
|
||||
configPath := filepath.Join(c.DataDir, "config.json")
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
|
||||
Reference in New Issue
Block a user