Skip to content

Commit 0af0268

Browse files
authored
Merge pull request #365 from AppSprout-dev/fix/yield-audit-355
fix: remediate all 9 yield audit findings (issue #355)
2 parents 3b2a2d9 + 8771e7b commit 0af0268

36 files changed

Lines changed: 3870 additions & 3816 deletions

cmd/mnemonic/cycle.go

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"os/signal"
9+
"path/filepath"
10+
"strings"
11+
"time"
12+
13+
"github.com/appsprout-dev/mnemonic/internal/agent/dreaming"
14+
"github.com/appsprout-dev/mnemonic/internal/agent/encoding"
15+
"github.com/appsprout-dev/mnemonic/internal/agent/metacognition"
16+
"github.com/appsprout-dev/mnemonic/internal/agent/orchestrator"
17+
"github.com/appsprout-dev/mnemonic/internal/agent/retrieval"
18+
"github.com/appsprout-dev/mnemonic/internal/config"
19+
"github.com/appsprout-dev/mnemonic/internal/events"
20+
"github.com/appsprout-dev/mnemonic/internal/mcp"
21+
)
22+
23+
// metaCycleCommand runs a single metacognition cycle and displays results.
24+
func metaCycleCommand(configPath string) {
25+
cfg, db, llmProvider, log := initRuntime(configPath)
26+
defer func() { _ = db.Close() }()
27+
28+
ctx := context.Background()
29+
bus := events.NewInMemoryBus(100)
30+
defer func() { _ = bus.Close() }()
31+
32+
agent := metacognition.NewMetacognitionAgent(db, llmProvider, metacognition.MetacognitionConfig{
33+
Interval: 24 * time.Hour, // doesn't matter for RunOnce
34+
ReflectionLookback: cfg.Metacognition.ReflectionLookback,
35+
DeadMemoryWindow: cfg.Metacognition.DeadMemoryWindow,
36+
}, log)
37+
38+
fmt.Println("Running metacognition cycle...")
39+
40+
report, err := agent.RunOnce(ctx)
41+
if err != nil {
42+
fmt.Fprintf(os.Stderr, "Metacognition cycle failed: %v\n", err)
43+
os.Exit(1)
44+
}
45+
46+
fmt.Printf("%sMetacognition complete%s (%dms):\n", colorGreen, colorReset, report.Duration.Milliseconds())
47+
48+
if len(report.Observations) == 0 {
49+
fmt.Println(" No issues found — memory health looks good.")
50+
return
51+
}
52+
53+
fmt.Printf(" %d observation(s):\n\n", len(report.Observations))
54+
for _, obs := range report.Observations {
55+
severityColor := colorGray
56+
switch obs.Severity {
57+
case "warning":
58+
severityColor = colorYellow
59+
case "critical":
60+
severityColor = colorRed
61+
case "info":
62+
severityColor = colorCyan
63+
}
64+
65+
typeLabel := strings.ReplaceAll(obs.ObservationType, "_", " ")
66+
typeLabel = strings.ToUpper(typeLabel[:1]) + typeLabel[1:]
67+
68+
fmt.Printf(" %s[%s]%s %s\n", severityColor, strings.ToUpper(obs.Severity), colorReset, typeLabel)
69+
for key, val := range obs.Details {
70+
keyLabel := strings.ReplaceAll(key, "_", " ")
71+
fmt.Printf(" %s: %v\n", keyLabel, val)
72+
}
73+
fmt.Println()
74+
}
75+
}
76+
77+
// dreamCycleCommand runs a single dream cycle and displays results.
78+
func dreamCycleCommand(configPath string) {
79+
cfg, db, llmProvider, log := initRuntime(configPath)
80+
defer func() { _ = db.Close() }()
81+
82+
ctx := context.Background()
83+
bus := events.NewInMemoryBus(100)
84+
defer func() { _ = bus.Close() }()
85+
86+
agent := dreaming.NewDreamingAgent(db, llmProvider, dreaming.DreamingConfig{
87+
Interval: 3 * time.Hour, // doesn't matter for RunOnce
88+
BatchSize: cfg.Dreaming.BatchSize,
89+
SalienceThreshold: cfg.Dreaming.SalienceThreshold,
90+
AssociationBoostFactor: cfg.Dreaming.AssociationBoostFactor,
91+
NoisePruneThreshold: cfg.Dreaming.NoisePruneThreshold,
92+
DeadMemoryWindow: cfg.Dreaming.DeadMemoryWindow,
93+
InsightsBudget: cfg.Dreaming.InsightsBudget,
94+
DefaultConfidence: cfg.Dreaming.DefaultConfidence,
95+
}, log)
96+
97+
fmt.Println("Running dream cycle (memory replay)...")
98+
99+
report, err := agent.RunOnce(ctx)
100+
if err != nil {
101+
fmt.Fprintf(os.Stderr, "Dream cycle failed: %v\n", err)
102+
os.Exit(1)
103+
}
104+
105+
fmt.Printf("%sDream cycle complete%s (%dms):\n", colorGreen, colorReset, report.Duration.Milliseconds())
106+
fmt.Printf(" Memories replayed: %d\n", report.MemoriesReplayed)
107+
fmt.Printf(" Associations strengthened: %d\n", report.AssociationsStrengthened)
108+
fmt.Printf(" New associations created: %d\n", report.NewAssociationsCreated)
109+
fmt.Printf(" Noisy memories demoted: %d\n", report.NoisyMemoriesDemoted)
110+
}
111+
112+
// mcpCommand runs the MCP server on stdin/stdout for AI agent integration.
113+
func mcpCommand(configPath string) {
114+
cfg, db, llmProvider, log := initRuntime(configPath)
115+
defer func() { _ = db.Close() }()
116+
117+
ctx, cancel := context.WithCancel(context.Background())
118+
defer cancel()
119+
120+
bus := events.NewInMemoryBus(100)
121+
defer func() { _ = bus.Close() }()
122+
123+
// Create encoding agent so remembered memories get encoded.
124+
// Polling is disabled in MCP mode — each MCP process only encodes via events
125+
// for memories it creates. The daemon is the sole poller. This prevents N
126+
// MCP processes from independently encoding the same unprocessed raw memories.
127+
mcpEncodingCfg := buildEncodingConfig(cfg)
128+
mcpEncodingCfg.DisablePolling = true
129+
encoder := encoding.NewEncodingAgentWithConfig(db, llmProvider, log, mcpEncodingCfg)
130+
if err := encoder.Start(ctx, bus); err != nil {
131+
log.Error("failed to start encoding agent for MCP", "error", err)
132+
}
133+
defer func() { _ = encoder.Stop() }()
134+
135+
// Create retrieval agent for recall
136+
retriever := retrieval.NewRetrievalAgent(db, llmProvider, buildRetrievalConfig(cfg), log, bus)
137+
138+
mcpResolver := config.NewProjectResolver(cfg.Projects)
139+
daemonURL := fmt.Sprintf("http://%s:%d", cfg.API.Host, cfg.API.Port)
140+
memDefaults := mcp.MemoryDefaults{
141+
SalienceGeneral: cfg.MemoryDefaults.InitialSalienceGeneral,
142+
SalienceDecision: cfg.MemoryDefaults.InitialSalienceDecision,
143+
SalienceError: cfg.MemoryDefaults.InitialSalienceError,
144+
SalienceInsight: cfg.MemoryDefaults.InitialSalienceInsight,
145+
SalienceLearning: cfg.MemoryDefaults.InitialSalienceLearning,
146+
SalienceHandoff: cfg.MemoryDefaults.InitialSalienceHandoff,
147+
FeedbackStrengthDelta: cfg.MemoryDefaults.FeedbackStrengthDelta,
148+
FeedbackSalienceBoost: cfg.MemoryDefaults.FeedbackSalienceBoost,
149+
}
150+
server := mcp.NewMCPServer(db, retriever, bus, log, Version, cfg.Coaching.CoachingFile, cfg.Perception.Filesystem.ExcludePatterns, cfg.Perception.Filesystem.MaxContentBytes, mcpResolver, daemonURL, memDefaults)
151+
152+
// Handle signal for graceful shutdown
153+
sigChan := make(chan os.Signal, 1)
154+
signal.Notify(sigChan, shutdownSignals()...)
155+
go func() {
156+
<-sigChan
157+
cancel()
158+
}()
159+
160+
if err := server.Run(ctx); err != nil {
161+
fmt.Fprintf(os.Stderr, "MCP server error: %v\n", err)
162+
os.Exit(1)
163+
}
164+
}
165+
166+
// autopilotCommand shows what the system has been doing autonomously.
167+
func autopilotCommand(configPath string) {
168+
_, db, _, _ := initRuntime(configPath)
169+
defer func() { _ = db.Close() }()
170+
171+
ctx := context.Background()
172+
173+
// Read health report
174+
homeDir, _ := os.UserHomeDir()
175+
healthPath := filepath.Join(homeDir, ".mnemonic", "health.json")
176+
data, err := os.ReadFile(healthPath)
177+
178+
fmt.Println("=== Mnemonic Autopilot Report ===")
179+
fmt.Println()
180+
181+
if err == nil {
182+
var report orchestrator.HealthReport
183+
if json.Unmarshal(data, &report) == nil {
184+
fmt.Printf("Last report: %s\n", report.Timestamp.Format("2006-01-02 15:04:05"))
185+
fmt.Printf("Uptime: %s\n", report.Uptime)
186+
fmt.Printf("LLM available: %v\n", report.LLMAvailable)
187+
fmt.Printf("Store healthy: %v\n", report.StoreHealthy)
188+
fmt.Printf("Memories: %d\n", report.MemoryCount)
189+
fmt.Printf("Patterns: %d\n", report.PatternCount)
190+
fmt.Printf("Abstractions: %d\n", report.AbstractionCount)
191+
fmt.Printf("Last consolidation: %s\n", report.LastConsolidation)
192+
fmt.Printf("Autonomous actions: %d\n", report.AutonomousActions)
193+
194+
if len(report.Warnings) > 0 {
195+
fmt.Println()
196+
fmt.Println("Warnings:")
197+
for _, w := range report.Warnings {
198+
fmt.Printf(" - %s\n", w)
199+
}
200+
}
201+
}
202+
} else {
203+
fmt.Println("No health report found. Start the daemon to generate one.")
204+
}
205+
206+
// Show recent autonomous actions
207+
fmt.Println()
208+
fmt.Println("--- Recent Autonomous Actions ---")
209+
actions, err := db.ListMetaObservations(ctx, "autonomous_action", 10)
210+
if err == nil && len(actions) > 0 {
211+
for _, a := range actions {
212+
action := ""
213+
if act, ok := a.Details["action"].(string); ok {
214+
action = act
215+
}
216+
fmt.Printf(" [%s] %s (severity: %s)\n",
217+
a.CreatedAt.Format("2006-01-02 15:04"), action, a.Severity)
218+
}
219+
} else {
220+
fmt.Println(" No autonomous actions recorded yet.")
221+
}
222+
223+
// Show recent patterns discovered
224+
fmt.Println()
225+
fmt.Println("--- Discovered Patterns ---")
226+
patterns, err := db.ListPatterns(ctx, "", 5)
227+
if err == nil && len(patterns) > 0 {
228+
for _, p := range patterns {
229+
project := ""
230+
if p.Project != "" {
231+
project = fmt.Sprintf(" [%s]", p.Project)
232+
}
233+
fmt.Printf(" %s%s: %s (strength: %.2f, evidence: %d)\n",
234+
p.Title, project, p.Description, p.Strength, len(p.EvidenceIDs))
235+
}
236+
} else {
237+
fmt.Println(" No patterns discovered yet.")
238+
}
239+
240+
// Show abstractions
241+
fmt.Println()
242+
fmt.Println("--- Abstractions ---")
243+
hasAbstractions := false
244+
for _, level := range []int{2, 3} {
245+
abs, err := db.ListAbstractions(ctx, level, 5)
246+
if err == nil && len(abs) > 0 {
247+
hasAbstractions = true
248+
for _, a := range abs {
249+
levelLabel := "principle"
250+
if a.Level == 3 {
251+
levelLabel = "axiom"
252+
}
253+
fmt.Printf(" [%s] %s: %s (confidence: %.2f)\n",
254+
levelLabel, a.Title, a.Description, a.Confidence)
255+
}
256+
}
257+
}
258+
if !hasAbstractions {
259+
fmt.Println(" No abstractions generated yet.")
260+
}
261+
262+
fmt.Println()
263+
}

0 commit comments

Comments
 (0)