From 87e87e1d6e5462219b5a81ea14fabf9f02dbd294 Mon Sep 17 00:00:00 2001 From: Caleb Gross Date: Sat, 21 Mar 2026 11:26:48 -0400 Subject: [PATCH] feat: make agent constants configurable via config.yaml Extract hardcoded magic numbers from dreaming, abstraction, metacognition, and episoding agents into config.yaml fields. All new fields have defaults matching previous hardcoded values so behavior is unchanged without config updates. New configurable values: - Startup delays for all four agents - Dreaming: dead memory window, insights budget, default confidence - Abstraction: grounding decay multipliers, confidence floor, defaults - Metacognition: reflection lookback, dead memory window - Episoding: startup lookback, default salience, polling interval Co-Authored-By: Claude Opus 4.6 (1M context) --- cmd/mnemonic/main.go | 39 +++++++++++--- internal/agent/abstraction/agent.go | 62 +++++++++++++++++----- internal/agent/dreaming/agent.go | 28 ++++++++-- internal/agent/episoding/agent.go | 18 ++++++- internal/agent/metacognition/agent.go | 31 ++++++++--- internal/config/config.go | 74 ++++++++++++++++++++++----- 6 files changed, 205 insertions(+), 47 deletions(-) diff --git a/cmd/mnemonic/main.go b/cmd/mnemonic/main.go index a9b0a69f..cee9f66a 100644 --- a/cmd/mnemonic/main.go +++ b/cmd/mnemonic/main.go @@ -1381,10 +1381,16 @@ func serveCommand(configPath string) { // --- Start episoding agent (groups raw events into episodes) --- var episodingAgent *episoding.EpisodingAgent if cfg.Episoding.Enabled { + pollingInterval := time.Duration(cfg.Episoding.PollingIntervalSec) * time.Second + if pollingInterval <= 0 { + pollingInterval = 10 * time.Second + } episodingCfg := episoding.EpisodingConfig{ EpisodeWindowSizeMin: cfg.Episoding.EpisodeWindowSizeMin, MinEventsPerEpisode: cfg.Episoding.MinEventsPerEpisode, - PollingInterval: 10 * time.Second, + PollingInterval: pollingInterval, + StartupLookback: cfg.Episoding.StartupLookback, + DefaultSalience: cfg.Episoding.DefaultSalience, } episodingAgent = episoding.NewEpisodingAgent(memStore, wrap("episoding"), log, episodingCfg) if err := episodingAgent.Start(rootCtx, bus); err != nil { @@ -1539,7 +1545,10 @@ func serveCommand(configPath string) { var metaAgent *metacognition.MetacognitionAgent if cfg.Metacognition.Enabled { metaAgent = metacognition.NewMetacognitionAgent(memStore, wrap("metacognition"), metacognition.MetacognitionConfig{ - Interval: cfg.Metacognition.Interval, + Interval: cfg.Metacognition.Interval, + StartupDelay: time.Duration(cfg.Metacognition.StartupDelaySec) * time.Second, + ReflectionLookback: cfg.Metacognition.ReflectionLookback, + DeadMemoryWindow: cfg.Metacognition.DeadMemoryWindow, }, log) if err := metaAgent.Start(rootCtx, bus); err != nil { @@ -1558,6 +1567,10 @@ func serveCommand(configPath string) { SalienceThreshold: cfg.Dreaming.SalienceThreshold, AssociationBoostFactor: cfg.Dreaming.AssociationBoostFactor, NoisePruneThreshold: cfg.Dreaming.NoisePruneThreshold, + StartupDelay: time.Duration(cfg.Dreaming.StartupDelaySec) * time.Second, + DeadMemoryWindow: cfg.Dreaming.DeadMemoryWindow, + InsightsBudget: cfg.Dreaming.InsightsBudget, + DefaultConfidence: cfg.Dreaming.DefaultConfidence, }, log) if err := dreamer.Start(rootCtx, bus); err != nil { @@ -1571,9 +1584,16 @@ func serveCommand(configPath string) { var abstractionAgent *abstraction.AbstractionAgent if cfg.Abstraction.Enabled { abstractionAgent = abstraction.NewAbstractionAgent(memStore, wrap("abstraction"), abstraction.AbstractionConfig{ - Interval: cfg.Abstraction.Interval, - MinStrength: cfg.Abstraction.MinStrength, - MaxLLMCalls: cfg.Abstraction.MaxLLMCalls, + Interval: cfg.Abstraction.Interval, + MinStrength: cfg.Abstraction.MinStrength, + MaxLLMCalls: cfg.Abstraction.MaxLLMCalls, + StartupDelay: time.Duration(cfg.Abstraction.StartupDelaySec) * time.Second, + DefaultConfidence: cfg.Abstraction.DefaultConfidence, + PatternAxiomConfidence: cfg.Abstraction.PatternAxiomConfidence, + ConfidenceModerateDecay: cfg.Abstraction.ConfidenceModerateDecay, + ConfidenceSignificantDecay: cfg.Abstraction.ConfidenceSignificantDecay, + ConfidenceSevereDecay: cfg.Abstraction.ConfidenceSevereDecay, + GroundingFloor: cfg.Abstraction.GroundingFloor, }, log) if err := abstractionAgent.Start(rootCtx, bus); err != nil { @@ -2502,7 +2522,7 @@ func formatDetailValue(val interface{}) string { // metaCycleCommand runs a single metacognition cycle and displays results. func metaCycleCommand(configPath string) { - _, db, llmProvider, log := initRuntime(configPath) + cfg, db, llmProvider, log := initRuntime(configPath) defer func() { _ = db.Close() }() ctx := context.Background() @@ -2510,7 +2530,9 @@ func metaCycleCommand(configPath string) { defer func() { _ = bus.Close() }() agent := metacognition.NewMetacognitionAgent(db, llmProvider, metacognition.MetacognitionConfig{ - Interval: 24 * time.Hour, // doesn't matter for RunOnce + Interval: 24 * time.Hour, // doesn't matter for RunOnce + ReflectionLookback: cfg.Metacognition.ReflectionLookback, + DeadMemoryWindow: cfg.Metacognition.DeadMemoryWindow, }, log) fmt.Println("Running metacognition cycle...") @@ -2567,6 +2589,9 @@ func dreamCycleCommand(configPath string) { SalienceThreshold: cfg.Dreaming.SalienceThreshold, AssociationBoostFactor: cfg.Dreaming.AssociationBoostFactor, NoisePruneThreshold: cfg.Dreaming.NoisePruneThreshold, + DeadMemoryWindow: cfg.Dreaming.DeadMemoryWindow, + InsightsBudget: cfg.Dreaming.InsightsBudget, + DefaultConfidence: cfg.Dreaming.DefaultConfidence, }, log) fmt.Println("Running dream cycle (memory replay)...") diff --git a/internal/agent/abstraction/agent.go b/internal/agent/abstraction/agent.go index 13362de7..6152dd9e 100644 --- a/internal/agent/abstraction/agent.go +++ b/internal/agent/abstraction/agent.go @@ -16,9 +16,16 @@ import ( ) type AbstractionConfig struct { - Interval time.Duration - MinStrength float32 - MaxLLMCalls int + Interval time.Duration + MinStrength float32 + MaxLLMCalls int + StartupDelay time.Duration + DefaultConfidence float32 + PatternAxiomConfidence float32 + ConfidenceModerateDecay float32 + ConfidenceSignificantDecay float32 + ConfidenceSevereDecay float32 + GroundingFloor float32 } type AbstractionAgent struct { @@ -94,8 +101,11 @@ func (aa *AbstractionAgent) RunOnce(ctx context.Context) (*CycleReport, error) { func (aa *AbstractionAgent) loop() { defer aa.wg.Done() - // 5-minute startup grace period (runs less frequently than other agents) - startupTimer := time.NewTimer(5 * time.Minute) + startupDelay := aa.config.StartupDelay + if startupDelay <= 0 { + startupDelay = 5 * time.Minute + } + startupTimer := time.NewTimer(startupDelay) defer startupTimer.Stop() ticker := time.NewTicker(aa.config.Interval) @@ -392,6 +402,24 @@ func (aa *AbstractionAgent) verifyGrounding(ctx context.Context, report *CycleRe continue } + // Load grounding multipliers from config with safe defaults + moderateDecay := aa.config.ConfidenceModerateDecay + if moderateDecay <= 0 { + moderateDecay = 0.9 + } + significantDecay := aa.config.ConfidenceSignificantDecay + if significantDecay <= 0 { + significantDecay = 0.7 + } + severeDecay := aa.config.ConfidenceSevereDecay + if severeDecay <= 0 { + severeDecay = 0.5 + } + groundingFloor := aa.config.GroundingFloor + if groundingFloor <= 0 { + groundingFloor = 0.5 + } + // Graduated grounding response switch { case groundingRatio >= 0.5: @@ -399,14 +427,14 @@ func (aa *AbstractionAgent) verifyGrounding(ctx context.Context, report *CycleRe continue case groundingRatio >= 0.3: // Moderate decay: reduce confidence slightly - abs.Confidence *= 0.9 + abs.Confidence *= moderateDecay case groundingRatio >= 0.1: // Significant decay: reduce confidence more - abs.Confidence *= 0.7 + abs.Confidence *= significantDecay report.AbstractionsDemoted++ default: - // Nearly all evidence gone: softened demotion (was 0.3, now 0.5) - abs.Confidence *= 0.5 + // Nearly all evidence gone + abs.Confidence *= severeDecay if abs.Confidence < 0.1 { abs.State = "fading" } @@ -414,8 +442,8 @@ func (aa *AbstractionAgent) verifyGrounding(ctx context.Context, report *CycleRe } // Enforce grace period floor for young abstractions - if isYoung && abs.Confidence < 0.5 { - abs.Confidence = 0.5 + if isYoung && abs.Confidence < groundingFloor { + abs.Confidence = groundingFloor } abs.UpdatedAt = time.Now() @@ -518,9 +546,13 @@ Set has_principle to false if: concepts = agentutil.DeduplicateConcepts(allConcepts) } + defaultConf := aa.config.DefaultConfidence + if defaultConf <= 0 { + defaultConf = 0.6 + } confidence := float32(result.Confidence) if confidence <= 0 || confidence > 1.0 { - confidence = 0.6 + confidence = defaultConf } return &store.Abstraction{ @@ -625,9 +657,13 @@ Set has_axiom to false if: concepts = agentutil.DeduplicateConcepts(allConcepts) } + axiomConf := aa.config.PatternAxiomConfidence + if axiomConf <= 0 { + axiomConf = 0.5 + } confidence := float32(result.Confidence) if confidence <= 0 || confidence > 1.0 { - confidence = 0.5 + confidence = axiomConf } return &store.Abstraction{ diff --git a/internal/agent/dreaming/agent.go b/internal/agent/dreaming/agent.go index 98ca301f..217ef2cb 100644 --- a/internal/agent/dreaming/agent.go +++ b/internal/agent/dreaming/agent.go @@ -22,6 +22,10 @@ type DreamingConfig struct { SalienceThreshold float32 AssociationBoostFactor float32 NoisePruneThreshold float32 + StartupDelay time.Duration + DeadMemoryWindow time.Duration + InsightsBudget int + DefaultConfidence float32 } type DreamingAgent struct { @@ -96,8 +100,11 @@ func (da *DreamingAgent) RunOnce(ctx context.Context) (*DreamReport, error) { func (da *DreamingAgent) loop() { defer da.wg.Done() - // 90-second startup grace period - startupTimer := time.NewTimer(90 * time.Second) + startupDelay := da.config.StartupDelay + if startupDelay <= 0 { + startupDelay = 90 * time.Second + } + startupTimer := time.NewTimer(startupDelay) defer startupTimer.Stop() ticker := time.NewTicker(da.config.Interval) @@ -313,7 +320,11 @@ func (da *DreamingAgent) crossPollinate(ctx context.Context, replayed []store.Me // noisePrune performs Phase 4: reduce salience of low-quality dead memories. func (da *DreamingAgent) noisePrune(ctx context.Context, report *DreamReport) error { - deadMemories, err := da.store.GetDeadMemories(ctx, time.Now().Add(-30*24*time.Hour)) + deadWindow := da.config.DeadMemoryWindow + if deadWindow <= 0 { + deadWindow = 30 * 24 * time.Hour + } + deadMemories, err := da.store.GetDeadMemories(ctx, time.Now().Add(-deadWindow)) if err != nil { return fmt.Errorf("failed to get dead memories: %w", err) } @@ -496,7 +507,10 @@ func (da *DreamingAgent) generateInsights(ctx context.Context, replayed []store. return nil } - insightsBudget := 2 + insightsBudget := da.config.InsightsBudget + if insightsBudget <= 0 { + insightsBudget = 2 + } for _, cluster := range clusters { if insightsBudget <= 0 { break @@ -641,9 +655,13 @@ Only share an insight if it's genuinely illuminating — something that makes yo concepts = agentutil.DeduplicateConcepts(allConcepts) } + defaultConf := da.config.DefaultConfidence + if defaultConf <= 0 { + defaultConf = 0.6 + } confidence := float32(result.Confidence) if confidence <= 0 || confidence > 1.0 { - confidence = 0.6 + confidence = defaultConf } abstraction := &store.Abstraction{ diff --git a/internal/agent/episoding/agent.go b/internal/agent/episoding/agent.go index b4414705..ec2ee549 100644 --- a/internal/agent/episoding/agent.go +++ b/internal/agent/episoding/agent.go @@ -21,6 +21,8 @@ type EpisodingConfig struct { EpisodeWindowSizeMin int // fixed window size in minutes (default 10) MinEventsPerEpisode int // minimum events to form an episode (default 2) PollingInterval time.Duration // how often to check for new events (default 10s) + StartupLookback time.Duration // how far back to look on startup (default 1h) + DefaultSalience float32 // fallback salience for synthesized episodes (default 0.5) } // DefaultEpisodingConfig returns sensible defaults. @@ -53,12 +55,16 @@ type EpisodingAgent struct { // NewEpisodingAgent creates a new episoding agent. func NewEpisodingAgent(s store.Store, llmProvider llm.Provider, log *slog.Logger, cfg EpisodingConfig) *EpisodingAgent { + lookback := cfg.StartupLookback + if lookback <= 0 { + lookback = 1 * time.Hour + } return &EpisodingAgent{ store: s, llmProvider: llmProvider, config: cfg, log: log, - lastProcessedTime: time.Now().Add(-1 * time.Hour), // look back 1 hour on start + lastProcessedTime: time.Now().Add(-lookback), assignedRawIDs: make(map[string]bool), } } @@ -67,6 +73,14 @@ func (ea *EpisodingAgent) Name() string { return "episoding" } +// defaultSalience returns the configured default salience, falling back to 0.5. +func (ea *EpisodingAgent) defaultSalience() float32 { + if ea.config.DefaultSalience > 0 { + return ea.config.DefaultSalience + } + return 0.5 +} + func (ea *EpisodingAgent) Start(ctx context.Context, bus events.Bus) error { ea.ctx, ea.cancel = context.WithCancel(ctx) ea.bus = bus @@ -412,7 +426,7 @@ Respond with ONLY a JSON object (no prose, no fences): ea.log.Warn("LLM episode synthesis failed, using fallback", "error", err) ep.Title = fmt.Sprintf("Session with %d events", len(ep.RawMemoryIDs)) ep.Summary = ep.Title - ep.Salience = 0.5 + ep.Salience = ea.defaultSalience() ep.Concepts = []string{} } else { // Parse LLM response diff --git a/internal/agent/metacognition/agent.go b/internal/agent/metacognition/agent.go index 64bdfe53..b45b90f6 100644 --- a/internal/agent/metacognition/agent.go +++ b/internal/agent/metacognition/agent.go @@ -14,7 +14,10 @@ import ( ) type MetacognitionConfig struct { - Interval time.Duration + Interval time.Duration + StartupDelay time.Duration + ReflectionLookback time.Duration + DeadMemoryWindow time.Duration } type MetacognitionAgent struct { @@ -84,7 +87,11 @@ func (ma *MetacognitionAgent) RunOnce(ctx context.Context) (*CycleReport, error) func (ma *MetacognitionAgent) loop() { defer ma.wg.Done() - startupTimer := time.NewTimer(60 * time.Second) + startupDelay := ma.config.StartupDelay + if startupDelay <= 0 { + startupDelay = 60 * time.Second + } + startupTimer := time.NewTimer(startupDelay) defer startupTimer.Stop() ticker := time.NewTicker(ma.config.Interval) @@ -116,8 +123,12 @@ func (ma *MetacognitionAgent) loop() { func (ma *MetacognitionAgent) runCycle(ctx context.Context) (*CycleReport, error) { startTime := time.Now() - // Cleanup: remove meta observations older than 7 days to prevent stale triggers - cutoff := time.Now().Add(-7 * 24 * time.Hour) + // Cleanup: remove meta observations older than reflection lookback to prevent stale triggers + lookback := ma.config.ReflectionLookback + if lookback <= 0 { + lookback = 7 * 24 * time.Hour + } + cutoff := time.Now().Add(-lookback) if deleted, err := ma.store.DeleteOldMetaObservations(ctx, cutoff); err != nil { ma.log.Warn("failed to cleanup old meta observations", "error", err) } else if deleted > 0 { @@ -277,7 +288,11 @@ func (ma *MetacognitionAgent) analyzeSourceDistribution(ctx context.Context) *st } func (ma *MetacognitionAgent) analyzeRecallEffectiveness(ctx context.Context) *store.MetaObservation { - deadMemories, err := ma.store.GetDeadMemories(ctx, time.Now().Add(-30*24*time.Hour)) + deadWindow := ma.config.DeadMemoryWindow + if deadWindow <= 0 { + deadWindow = 30 * 24 * time.Hour + } + deadMemories, err := ma.store.GetDeadMemories(ctx, time.Now().Add(-deadWindow)) if err != nil { ma.log.Error("failed to get dead memories", "error", err) return nil @@ -365,7 +380,11 @@ func (ma *MetacognitionAgent) checkConsolidationHealth(ctx context.Context) *sto // analyzeRetrievalFeedback reads actual retrieval feedback records and computes quality metrics. func (ma *MetacognitionAgent) analyzeRetrievalFeedback(ctx context.Context) *store.MetaObservation { - since := time.Now().Add(-7 * 24 * time.Hour) + feedbackLookback := ma.config.ReflectionLookback + if feedbackLookback <= 0 { + feedbackLookback = 7 * 24 * time.Hour + } + since := time.Now().Add(-feedbackLookback) feedbacks, err := ma.store.ListRecentRetrievalFeedback(ctx, since, 50) if err != nil { ma.log.Warn("failed to list recent retrieval feedback", "error", err) diff --git a/internal/config/config.go b/internal/config/config.go index 3fbb1e69..2c473d9b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -270,9 +270,14 @@ type RetrievalConfig struct { // MetacognitionConfig holds metacognition settings. type MetacognitionConfig struct { - Enabled bool `yaml:"enabled"` - IntervalRaw string `yaml:"interval"` - Interval time.Duration `yaml:"-"` + Enabled bool `yaml:"enabled"` + IntervalRaw string `yaml:"interval"` + Interval time.Duration `yaml:"-"` + StartupDelaySec int `yaml:"startup_delay_sec"` // seconds before first cycle (default: 60) + ReflectionLookbackRaw string `yaml:"reflection_lookback"` // how far back to analyze (default: "7d") + ReflectionLookback time.Duration `yaml:"-"` + DeadMemoryWindowRaw string `yaml:"dead_memory_window"` // age threshold for dead memory analysis (default: "30d") + DeadMemoryWindow time.Duration `yaml:"-"` } // DreamingConfig holds dreaming (memory replay) agent settings. @@ -284,13 +289,22 @@ type DreamingConfig struct { SalienceThreshold float32 `yaml:"salience_threshold"` AssociationBoostFactor float32 `yaml:"association_boost_factor"` NoisePruneThreshold float32 `yaml:"noise_prune_threshold"` + StartupDelaySec int `yaml:"startup_delay_sec"` // seconds before first cycle (default: 90) + DeadMemoryWindowRaw string `yaml:"dead_memory_window"` // age threshold for noise pruning (default: "30d") + DeadMemoryWindow time.Duration `yaml:"-"` + InsightsBudget int `yaml:"insights_budget"` // max insights per dream cycle (default: 2) + DefaultConfidence float32 `yaml:"default_confidence"` // fallback confidence for generated insights (default: 0.6) } // EpisodingConfig configures the episoding agent. type EpisodingConfig struct { - Enabled bool `yaml:"enabled"` - EpisodeWindowSizeMin int `yaml:"episode_window_size_min"` - MinEventsPerEpisode int `yaml:"min_events_per_episode"` + Enabled bool `yaml:"enabled"` + EpisodeWindowSizeMin int `yaml:"episode_window_size_min"` + MinEventsPerEpisode int `yaml:"min_events_per_episode"` + StartupLookbackRaw string `yaml:"startup_lookback"` // how far back to look on startup (default: "1h") + StartupLookback time.Duration `yaml:"-"` + DefaultSalience float32 `yaml:"default_salience"` // fallback salience for synthesized episodes (default: 0.5) + PollingIntervalSec int `yaml:"polling_interval_sec"` // seconds between episode checks (default: 10) } // AbstractionConfig configures the abstraction agent (hierarchical knowledge). @@ -300,6 +314,13 @@ type AbstractionConfig struct { Interval time.Duration `yaml:"-"` MinStrength float32 `yaml:"min_strength"` // minimum pattern strength to consider MaxLLMCalls int `yaml:"max_llm_calls"` // budget per cycle + StartupDelaySec int `yaml:"startup_delay_sec"` // seconds before first cycle (default: 300) + DefaultConfidence float32 `yaml:"default_confidence"` // fallback confidence for principles (default: 0.6) + PatternAxiomConfidence float32 `yaml:"pattern_axiom_confidence"` // fallback confidence for axioms (default: 0.5) + ConfidenceModerateDecay float32 `yaml:"confidence_moderate_decay"` // grounding multiplier for moderate decay (default: 0.9) + ConfidenceSignificantDecay float32 `yaml:"confidence_significant_decay"` // grounding multiplier for significant decay (default: 0.7) + ConfidenceSevereDecay float32 `yaml:"confidence_severe_decay"` // grounding multiplier for severe decay (default: 0.5) + GroundingFloor float32 `yaml:"grounding_floor"` // confidence floor for young abstractions (default: 0.5) } // OrchestratorConfig configures the autonomous orchestrator. @@ -612,9 +633,14 @@ func Default() *Config { }, }, Metacognition: MetacognitionConfig{ - Enabled: true, - IntervalRaw: "24h", - Interval: 24 * time.Hour, + Enabled: true, + IntervalRaw: "24h", + Interval: 24 * time.Hour, + StartupDelaySec: 60, + ReflectionLookbackRaw: "7d", + ReflectionLookback: 7 * 24 * time.Hour, + DeadMemoryWindowRaw: "30d", + DeadMemoryWindow: 30 * 24 * time.Hour, }, Dreaming: DreamingConfig{ Enabled: true, @@ -624,18 +650,34 @@ func Default() *Config { SalienceThreshold: 0.3, AssociationBoostFactor: 1.15, NoisePruneThreshold: 0.15, + StartupDelaySec: 90, + DeadMemoryWindowRaw: "30d", + DeadMemoryWindow: 30 * 24 * time.Hour, + InsightsBudget: 2, + DefaultConfidence: 0.6, }, Episoding: EpisodingConfig{ Enabled: true, EpisodeWindowSizeMin: 10, MinEventsPerEpisode: 2, + StartupLookbackRaw: "1h", + StartupLookback: 1 * time.Hour, + DefaultSalience: 0.5, + PollingIntervalSec: 10, }, Abstraction: AbstractionConfig{ - Enabled: true, - IntervalRaw: "6h", - Interval: 6 * time.Hour, - MinStrength: 0.4, - MaxLLMCalls: 5, + Enabled: true, + IntervalRaw: "6h", + Interval: 6 * time.Hour, + MinStrength: 0.4, + MaxLLMCalls: 5, + StartupDelaySec: 300, + DefaultConfidence: 0.6, + PatternAxiomConfidence: 0.5, + ConfidenceModerateDecay: 0.9, + ConfidenceSignificantDecay: 0.7, + ConfidenceSevereDecay: 0.5, + GroundingFloor: 0.5, }, Orchestrator: OrchestratorConfig{ Enabled: true, @@ -744,6 +786,10 @@ func (c *Config) process(configDir string) error { {c.Abstraction.IntervalRaw, &c.Abstraction.Interval, "abstraction.interval"}, {c.Orchestrator.SelfTestIntervalRaw, &c.Orchestrator.SelfTestInterval, "orchestrator.self_test_interval"}, {c.Orchestrator.MonitorIntervalRaw, &c.Orchestrator.MonitorInterval, "orchestrator.monitor_interval"}, + {c.Metacognition.ReflectionLookbackRaw, &c.Metacognition.ReflectionLookback, "metacognition.reflection_lookback"}, + {c.Metacognition.DeadMemoryWindowRaw, &c.Metacognition.DeadMemoryWindow, "metacognition.dead_memory_window"}, + {c.Dreaming.DeadMemoryWindowRaw, &c.Dreaming.DeadMemoryWindow, "dreaming.dead_memory_window"}, + {c.Episoding.StartupLookbackRaw, &c.Episoding.StartupLookback, "episoding.startup_lookback"}, } for _, d := range durations { if d.raw != "" {