Files
AetherForge/server/internal/db/ai_decisions.go
AetherForge 0002e5fd93
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add Calibrate AI Control UI and fleet LLM backend wiring.
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.
2026-06-07 02:14:28 -07:00

99 lines
2.6 KiB
Go

package db
import (
"database/sql"
"fmt"
"strings"
)
// AIDecisionRecord is one persisted fleet AI decision cycle.
type AIDecisionRecord struct {
ID int64 `json:"id"`
AgentID string `json:"agent_id"`
PromptHash string `json:"prompt_hash"`
Response string `json:"response"`
CommandsExecuted string `json:"commands_executed"`
Timestamp string `json:"ts"`
}
func (d *Database) ensureAIDecisionsTable() error {
_, err := d.Exec(`CREATE TABLE IF NOT EXISTS ai_decisions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id TEXT NOT NULL,
prompt_hash TEXT NOT NULL DEFAULT '',
response TEXT NOT NULL DEFAULT '',
commands_executed TEXT NOT NULL DEFAULT '',
ts DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)`)
if err != nil {
return err
}
_, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_ai_decisions_agent ON ai_decisions(agent_id)`)
_, _ = d.Exec(`CREATE INDEX IF NOT EXISTS idx_ai_decisions_ts ON ai_decisions(ts)`)
return nil
}
// InsertAIDecision logs one fleet AI decision cycle.
func (d *Database) InsertAIDecision(agentID, promptHash, response, commandsExecuted string) error {
if d == nil {
return nil
}
if err := d.ensureAIDecisionsTable(); err != nil {
return err
}
_, err := d.Exec(
`INSERT INTO ai_decisions (agent_id, prompt_hash, response, commands_executed) VALUES (?, ?, ?, ?)`,
agentID, promptHash, response, commandsExecuted,
)
return err
}
// ListAIDecisions returns the most recent decisions for an agent (or all agents when agentID empty).
func (d *Database) ListAIDecisions(agentID string, limit int) ([]AIDecisionRecord, error) {
if d == nil {
return nil, nil
}
if err := d.ensureAIDecisionsTable(); err != nil {
return nil, err
}
if limit <= 0 {
limit = 50
}
if limit > 500 {
limit = 500
}
var rows *sql.Rows
var err error
agentID = strings.TrimSpace(agentID)
if agentID != "" {
rows, err = d.Query(
`SELECT id, agent_id, prompt_hash, response, commands_executed, ts
FROM ai_decisions WHERE agent_id = ? ORDER BY id DESC LIMIT ?`,
agentID, limit,
)
} else {
rows, err = d.Query(
`SELECT id, agent_id, prompt_hash, response, commands_executed, ts
FROM ai_decisions ORDER BY id DESC LIMIT ?`,
limit,
)
}
if err != nil {
return nil, fmt.Errorf("list ai decisions: %w", err)
}
defer rows.Close()
out := make([]AIDecisionRecord, 0, limit)
for rows.Next() {
var rec AIDecisionRecord
var ts string
if err := rows.Scan(&rec.ID, &rec.AgentID, &rec.PromptHash, &rec.Response, &rec.CommandsExecuted, &ts); err != nil {
return nil, err
}
rec.Timestamp = ts
out = append(out, rec)
}
return out, rows.Err()
}