// Package memorycompiler implements the Memory v5 execution compiler runtime. // It is deliberately local and rule-driven: execution traces can update // strategy scores and compiler mutations, but the model never rewrites code. package memorycompiler import ( "bufio" "bytes" "context" "encoding/json" "errors" "fmt" "hash/fnv" "math" "math/rand" "os" "path/filepath" "sort" "strings" "sync" "time" "unicode" "reasonix/internal/fileutil" fileencoding "reasonix/internal/fileutil/encoding" "reasonix/internal/provider" ) const ( stateFile = "state.json" tracesFile = "traces.jsonl" learningTracesFile = "learning_traces.jsonl" debugTracesFile = "debug_traces.jsonl" debugTraceEnv = "REASONIX_MEMORY_COMPILER_DEBUG_TRACE" version = "v5.9" explorationRatePercent = 10 minExplorationRatePercent = 3 maxExplorationRatePercent = 12 mutationMinEvalTrials = 2 mutationAcceptThreshold = 0.60 mutationRegressionMargin = 0.05 mutationFeedbackCooldown = 30 * time.Minute strategyDecayK = 10.0 staleConfidenceThreshold = 0.2 compilerIROverheadSelfFeedback = "compiled IR overhead exceeded budget; reduce memory references before injection" planModeBlockedToolError = "blocked: plan mode is read-only" maxRuntimeTraceJSONLLines = 500 maxLearningTraceJSONLLines = 100 maxDebugTraceJSONLLines = 100 ) var runtimeLocks sync.Map // Runtime owns one project's Memory v5 state. type Runtime struct { dir string mu *sync.Mutex } // New returns a runtime backed by dir. A blank dir disables persistence and // returns nil so callers can keep the fast path simple. func New(dir string) *Runtime { if strings.TrimSpace(dir) == "" { return nil } dir = filepath.Clean(dir) return &Runtime{dir: dir, mu: runtimeLockForDir(dir)} } func runtimeLockForDir(dir string) *sync.Mutex { actual, _ := runtimeLocks.LoadOrStore(filepath.Clean(dir), &sync.Mutex{}) return actual.(*sync.Mutex) } // PlannerIR is the memory-compiled execution plan language embedded in the // cache-safe execution contract when there is useful learned state. type PlannerIR struct { Version string `json:"version"` Goal string `json:"goal"` SourceEvent string `json:"source_event"` RuntimeMode string `json:"runtime_mode"` Constraints []Constraint `json:"constraints"` StrategySelection *StrategyPick `json:"strategy_selection"` AvailableStrategies []StrategyRef `json:"available_strategies"` MemoryReferences []MemoryRef `json:"memory_references"` ExecutionSteps []Step `json:"execution_steps"` RiskNotes []string `json:"risk_notes"` } type Constraint struct { Type string `json:"type"` Text string `json:"text"` Source string `json:"source,omitempty"` } type StrategyRef struct { ID string `json:"id"` SuccessRate float64 `json:"success_rate"` Samples int `json:"samples"` Score float64 `json:"score,omitempty"` Reason string `json:"reason,omitempty"` } type StrategyPick struct { Selected string `json:"selected"` Reason string `json:"reason"` Score float64 `json:"score"` Mode string `json:"mode"` ExplorationRate float64 `json:"exploration_rate"` Rejected []RejectedStrategy `json:"rejected"` } type RejectedStrategy struct { ID string `json:"id"` Reason string `json:"reason"` Score float64 `json:"score"` } type MemoryRef struct { ID string `json:"id"` Content string `json:"content"` Quality string `json:"quality,omitempty"` Influence string `json:"influence,omitempty"` } type Step struct { ID string `json:"id"` Action string `json:"action"` } type ToolRecord struct { ID string `json:"id,omitempty"` Name string `json:"name"` Args string `json:"args,omitempty"` Output string `json:"output,omitempty"` Error string `json:"error,omitempty"` ReadOnly bool `json:"read_only"` Blocked bool `json:"blocked,omitempty"` DurationMs int64 `json:"duration_ms,omitempty"` Truncated bool `json:"truncated,omitempty"` } type ExecutionTrace struct { ID string `json:"id"` IRVersion string `json:"ir_version"` Goal string `json:"goal"` Steps []Step `json:"steps,omitempty"` Outcome string `json:"outcome"` Injected bool `json:"injected,omitempty"` EfficiencyScore float64 `json:"efficiency_score"` MemoryEffectiveness float64 `json:"memory_effectiveness"` StrategyUsed []string `json:"strategy_used,omitempty"` MemoryUsed []string `json:"memory_used,omitempty"` DecisionBranches []DecisionBranch `json:"decision_branches,omitempty"` CausalEdges []CausalEdge `json:"causal_edges,omitempty"` SemanticDrift []string `json:"semantic_drift,omitempty"` SemanticDriftHard []string `json:"semantic_drift_hard,omitempty"` SemanticDriftSoft []string `json:"semantic_drift_soft,omitempty"` SemanticShift []string `json:"semantic_shift,omitempty"` ControlMode string `json:"control_mode,omitempty"` ControlGain float64 `json:"control_gain,omitempty"` ControlSignals []string `json:"control_signals,omitempty"` EquilibriumTrace *EquilibriumTrace `json:"equilibrium_trace,omitempty"` Compression *CompressionReport `json:"compression,omitempty"` Cost CostMetrics `json:"cost,omitempty"` MutationEvaluations []MutationEvaluation `json:"mutation_evaluations,omitempty"` FailureReason string `json:"failure_reason,omitempty"` ToolResults []ToolRecord `json:"tool_results,omitempty"` StartedAt time.Time `json:"started_at"` CompletedAt time.Time `json:"completed_at"` } type DecisionBranch struct { Question string `json:"question"` Selected string `json:"selected"` Rejected []string `json:"rejected,omitempty"` SelectionReason string `json:"selection_reason,omitempty"` } type CausalEdge struct { From string `json:"from"` To string `json:"to"` Relation string `json:"relation"` } type CostMetrics struct { EstimatedInputTokens int `json:"estimated_input_tokens,omitempty"` EstimatedCompiledTokens int `json:"estimated_compiled_tokens,omitempty"` EstimatedIROverheadTokens int `json:"estimated_ir_overhead_tokens,omitempty"` LatencyMs int64 `json:"latency_ms,omitempty"` ToolCalls int `json:"tool_calls,omitempty"` ToolErrors int `json:"tool_errors,omitempty"` TruncatedToolResults int `json:"truncated_tool_results,omitempty"` } type EquilibriumTrace struct { State string `json:"state,omitempty"` ControlGraphEntropy float64 `json:"control_graph_entropy,omitempty"` SystemStabilityScore float64 `json:"system_stability_score,omitempty"` ConvergenceVelocity float64 `json:"convergence_velocity,omitempty"` OscillationIndex float64 `json:"oscillation_index,omitempty"` Actions []string `json:"actions,omitempty"` } type CompilerMutation struct { Target string `json:"target"` Change string `json:"change"` Reason string `json:"reason"` EvidenceTraceIDs []string `json:"evidence_trace_ids,omitempty"` Status string `json:"status,omitempty"` BaselineScore float64 `json:"baseline_score,omitempty"` EvaluationTraceIDs []string `json:"evaluation_trace_ids,omitempty"` EvaluationScore float64 `json:"evaluation_score,omitempty"` EvaluationReason string `json:"evaluation_reason,omitempty"` Applied bool `json:"applied"` CreatedAt time.Time `json:"created_at,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` } type MutationEvaluation struct { Target string `json:"target"` Change string `json:"change"` Reason string `json:"reason"` Decision string `json:"decision"` Score float64 `json:"score"` Baseline float64 `json:"baseline"` Trials int `json:"trials"` } type IRValidationResult struct { Findings []string HardFindings []string SoftFindings []string Reject bool } type ControlPolicy struct { Version string `json:"version"` Mode string `json:"mode"` Controller string `json:"controller"` ExplorationRatePercent int `json:"exploration_rate_percent"` Gain float64 `json:"gain"` ConsensusScore float64 `json:"consensus_score,omitempty"` Variance float64 `json:"variance,omitempty"` EquilibriumState string `json:"equilibrium_state,omitempty"` EquilibriumActions []string `json:"equilibrium_actions,omitempty"` ControlGraphEntropy float64 `json:"control_graph_entropy,omitempty"` SystemStabilityScore float64 `json:"system_stability_score,omitempty"` ConvergenceVelocity float64 `json:"convergence_velocity,omitempty"` OscillationIndex float64 `json:"oscillation_index,omitempty"` MutationCooldown time.Duration `json:"-"` MutationCooldownMs int64 `json:"mutation_cooldown_ms"` SemanticShift []string `json:"semantic_shift,omitempty"` Reasons []string `json:"reasons,omitempty"` } type TraceBundle struct { RuntimeTrace ExecutionTrace `json:"runtime_trace"` LearningTrace *LearningTrace `json:"learning_trace,omitempty"` DebugTrace *ExecutionTrace `json:"debug_trace,omitempty"` } type LearningTrace struct { ID string `json:"id"` IRVersion string `json:"ir_version"` Outcome string `json:"outcome"` Injected bool `json:"injected,omitempty"` QualityScore float64 `json:"quality_score"` StrategyUsed []string `json:"strategy_used,omitempty"` MemoryUsed []string `json:"memory_used,omitempty"` DecisionBranches []DecisionBranch `json:"decision_branches,omitempty"` CausalEdges []CausalEdge `json:"causal_edges,omitempty"` SemanticDrift []string `json:"semantic_drift,omitempty"` SemanticDriftHard []string `json:"semantic_drift_hard,omitempty"` SemanticDriftSoft []string `json:"semantic_drift_soft,omitempty"` SemanticShift []string `json:"semantic_shift,omitempty"` ControlMode string `json:"control_mode,omitempty"` ControlGain float64 `json:"control_gain,omitempty"` ControlSignals []string `json:"control_signals,omitempty"` EquilibriumTrace *EquilibriumTrace `json:"equilibrium_trace,omitempty"` Compression *CompressionReport `json:"compression,omitempty"` CausalFindings []string `json:"causal_findings,omitempty"` CompilerImprovements []string `json:"compiler_improvements,omitempty"` MutationEvaluations []MutationEvaluation `json:"mutation_evaluations,omitempty"` Cost CostMetrics `json:"cost,omitempty"` CreatedAt time.Time `json:"created_at"` } type DriftReport struct { TraceID string `json:"trace_id,omitempty"` OverusedStrategies []string `json:"overused_strategies,omitempty"` StaleMemoryNodes []string `json:"stale_memory_nodes,omitempty"` ConflictingFacts []string `json:"conflicting_facts,omitempty"` CreatedAt time.Time `json:"created_at"` } type MemoryQuality string const ( QualityHighSignal MemoryQuality = "HIGH_SIGNAL" QualityMediumSignal MemoryQuality = "MEDIUM_SIGNAL" QualityNoise MemoryQuality = "NOISE" QualityCorrupted MemoryQuality = "CORRUPTED" ) type MemoryNode struct { ID string `json:"id"` Type string `json:"type"` Content string `json:"content"` Timestamp time.Time `json:"timestamp"` Confidence float64 `json:"confidence"` Quality MemoryQuality `json:"quality"` Constraint *Constraint `json:"constraint,omitempty"` TruthLocked bool `json:"truth_locked,omitempty"` } type MemoryEdge struct { From string `json:"from"` To string `json:"to"` Relation string `json:"relation"` } type DecisionNode struct { ID string `json:"id"` Question string `json:"question"` SelectedOption string `json:"selected_option"` RejectedOptions []string `json:"rejected_options,omitempty"` Reasoning string `json:"reasoning"` Timestamp time.Time `json:"timestamp"` } type ExecutionState struct { GoalState string `json:"goal_state,omitempty"` CurrentPhase string `json:"current_phase,omitempty"` KnownFacts []string `json:"known_facts,omitempty"` ActiveConstraints []Constraint `json:"active_constraints,omitempty"` FailedStrategies []string `json:"failed_strategies,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` } type SystemLearning struct { TraceID string `json:"trace_id"` BadStrategies []string `json:"bad_strategies,omitempty"` GoodPatterns []string `json:"good_patterns,omitempty"` MemoryNoisePatterns []string `json:"memory_noise_patterns,omitempty"` CausalFindings []string `json:"causal_findings,omitempty"` CompilerImprovements []string `json:"compiler_improvements,omitempty"` CreatedAt time.Time `json:"created_at"` } type Strategy struct { ID string `json:"id"` Preconditions []string `json:"preconditions,omitempty"` ExecutionPlan []Step `json:"execution_plan,omitempty"` Successes int `json:"successes"` Failures int `json:"failures"` // InjectedSuccesses/InjectedFailures split the counters above by whether // the compiled contract was provider-visible that turn, so injected and // observe-only outcomes can be compared for real lift. InjectedSuccesses int `json:"injected_successes,omitempty"` InjectedFailures int `json:"injected_failures,omitempty"` LastUsedAt time.Time `json:"last_used_at,omitempty"` Description string `json:"description,omitempty"` } func (s Strategy) Samples() int { return s.Successes + s.Failures } func (s Strategy) SuccessRate() float64 { if s.Samples() == 0 { return 0 } return float64(s.Successes) / float64(s.Samples()) } type state struct { Nodes []MemoryNode `json:"nodes,omitempty"` Edges []MemoryEdge `json:"edges,omitempty"` Decisions []DecisionNode `json:"decisions,omitempty"` ExecutionState ExecutionState `json:"execution_state,omitempty"` Strategies []Strategy `json:"strategies,omitempty"` Mutations []CompilerMutation `json:"mutations,omitempty"` Learnings []SystemLearning `json:"learnings,omitempty"` DriftReports []DriftReport `json:"drift_reports,omitempty"` CompressionReports []CompressionReport `json:"compression_reports,omitempty"` NoisyRefs map[string]int `json:"noisy_refs,omitempty"` UpdatedAt time.Time `json:"updated_at,omitempty"` } // Turn records one top-level agent turn. type Turn struct { rt *Runtime ir PlannerIR trace ExecutionTrace strategy string citations []provider.MemoryCitation metrics TurnMetrics } // TurnMetrics is a content-free snapshot of Memory v5 participation for one // turn. It intentionally contains only counts and estimated token sizes so // desktop aggregate metrics can quantify memory usage without uploading memory // text, tool output, file paths, or prompts. type TurnMetrics struct { Injected bool UsefulIR bool CompiledTokens int IROverheadTokens int MemoryReferences int Constraints int RiskNotes int ExecutionSteps int TotalNodes int HighSignalNodes int ToolResultNodes int DecisionNodes int StrategyCount int LearningCount int } // StartTurn builds a cache-safe execution contract from prior learned state. It // returns an empty compiled input until the runtime has enough signal to // influence the next turn; when non-empty, callers should use the returned value // as the whole user turn instead of appending it as side context. func (r *Runtime) StartTurn(ctx context.Context, input string, _ []provider.Message) (string, *Turn) { if r == nil { return "", nil } // Classify the goal from the user's actual text, not the "Referenced context:" // preamble + file blocks the controller injects on @-references — otherwise // summarizeGoal and strategy matching key off file contents. SourceEvent (the // full input passed to buildIRWithPolicy) is kept intact on purpose: when the // compiled contract replaces the user turn, source_event is the model's only // view of the referenced files. goal := summarizeGoal(stripReferencedContext(input)) st := r.loadState() ir, policy := buildIRWithPolicy(goal, input, st) now := time.Now().UTC() id := traceID(now) t := &Turn{ rt: r, ir: ir, citations: memoryCitationsForIR(ir), metrics: turnMetricsForIR(ir, st), trace: ExecutionTrace{ ID: id, IRVersion: version, Goal: goal, Steps: ir.ExecutionSteps, MemoryUsed: memoryRefIDs(ir.MemoryReferences), DecisionBranches: decisionBranches(ir), StartedAt: now, SemanticShift: append([]string(nil), policy.SemanticShift...), ControlMode: policy.Mode, ControlGain: policy.Gain, ControlSignals: append([]string(nil), policy.Reasons...), EquilibriumTrace: equilibriumTraceForPolicy(policy), Cost: CostMetrics{ EstimatedInputTokens: estimateTokens(input), }, }, } if ir.StrategySelection != nil { t.strategy = ir.StrategySelection.Selected t.trace.StrategyUsed = []string{t.strategy} } t.trace.CausalEdges = causalEdgesForIR(t.trace.ID, ir) if !hasUsefulIR(ir) { return "", t } // Production hardening is an observability signal recorded on the trace; it // must not gate whether the cache-safe contract is injected. The contract is // plain input text (not a privileged action), and the execution that follows // is still bounded by tool permissions. Gating here previously made the whole // compiler fall silent once learned memory nodes reached their GC cap. compiled, err := compileExecutionContract(ir) if err != nil { return "", t } if err := ctx.Err(); err != nil { return "", t } t.trace.Cost.EstimatedCompiledTokens = estimateTokens(compiled) if t.trace.Cost.EstimatedCompiledTokens > t.trace.Cost.EstimatedInputTokens { t.trace.Cost.EstimatedIROverheadTokens = t.trace.Cost.EstimatedCompiledTokens - t.trace.Cost.EstimatedInputTokens } t.metrics.Injected = true t.metrics.CompiledTokens = t.trace.Cost.EstimatedCompiledTokens t.metrics.IROverheadTokens = t.trace.Cost.EstimatedIROverheadTokens return compiled, t } // MemoryCitations returns the local UI references that explain which memories // influenced this turn's compiled execution contract. func (t *Turn) MemoryCitations() []provider.MemoryCitation { if t == nil || !t.metrics.Injected || len(t.citations) == 0 { return nil } return append([]provider.MemoryCitation(nil), t.citations...) } // SuppressInjection keeps the turn open for trace writeback and learning while // marking the compiled contract as not used for this user turn. Agent-level // throttles call this when Memory v5 should observe the turn but must not replace // the user prompt or surface compiler citations. func (t *Turn) SuppressInjection() { if t == nil { return } t.citations = nil t.metrics.Injected = false t.metrics.CompiledTokens = 0 t.metrics.IROverheadTokens = 0 t.trace.Cost.EstimatedCompiledTokens = 0 t.trace.Cost.EstimatedIROverheadTokens = 0 t.trace.Steps = nil t.trace.MemoryUsed = nil t.trace.DecisionBranches = nil t.trace.CausalEdges = nil t.trace.StrategyUsed = nil t.strategy = "" } // Metrics returns a content-free Memory v5 usage snapshot for this turn. func (t *Turn) Metrics() TurnMetrics { if t == nil { return TurnMetrics{} } return t.metrics } func turnMetricsForIR(ir PlannerIR, st state) TurnMetrics { m := TurnMetrics{ UsefulIR: hasUsefulIR(ir), MemoryReferences: len(ir.MemoryReferences), Constraints: len(ir.Constraints), RiskNotes: len(ir.RiskNotes), ExecutionSteps: len(ir.ExecutionSteps), TotalNodes: len(st.Nodes), DecisionNodes: len(st.Decisions), StrategyCount: len(st.Strategies), LearningCount: len(st.Learnings), } for _, node := range st.Nodes { if node.Quality == QualityHighSignal { m.HighSignalNodes++ } if node.Type == "tool_result" { m.ToolResultNodes++ } } return m } func buildIR(goal, sourceEvent string, st state) PlannerIR { ir, _ := buildIRWithPolicy(goal, sourceEvent, st) return ir } func buildIRWithPolicy(goal, sourceEvent string, st state) (PlannerIR, ControlPolicy) { now := time.Now().UTC() st, drift := applyDriftControl(st, now, "") policy := controlPolicyForState(st, drift) ir := PlannerIR{ Version: version, Goal: goal, SourceEvent: sourceEvent, RuntimeMode: "control", } st.Strategies = ensureBuiltInStrategies(st.Strategies) rankedStrategies := rankStrategies(goal, st.Strategies) strategyPick := selectStrategy(goal, rankedStrategies, policy.ExplorationRatePercent) if strategyPick.Mode == "explore" { ir.RuntimeMode = "explore" } ir.StrategySelection = &strategyPick for _, c := range st.ExecutionState.ActiveConstraints { if isCompilerFeedbackNoise(c.Text) { continue } ir.Constraints = appendConstraint(ir.Constraints, c) } for _, failed := range st.ExecutionState.FailedStrategies { if strings.TrimSpace(failed) != "" { ir.RiskNotes = append(ir.RiskNotes, "avoid previously failed strategy "+failed) } } for _, noisy := range sortedNoisyRefs(st.NoisyRefs) { ref, count := noisy.ref, noisy.count if count < 2 || isCompilerFeedbackNoise(ref) { continue } ir.RiskNotes = append(ir.RiskNotes, "quarantined noisy memory pattern "+ref) } ir.RiskNotes = append(ir.RiskNotes, driftRiskNotes(drift)...) for _, node := range usableSubgraphNodes(st.Nodes, st.Edges, now) { if node.Constraint != nil && !isCompilerFeedbackNoise(node.Constraint.Text) { ir.Constraints = appendConstraint(ir.Constraints, *node.Constraint) } if (node.Quality == QualityHighSignal || node.Type == "tool_result") && !isCompilerFeedbackNoise(node.Content) { ir.MemoryReferences = append(ir.MemoryReferences, MemoryRef{ ID: node.ID, Content: node.Content, Quality: string(node.Quality), Influence: influenceForNode(node), }) if len(ir.MemoryReferences) >= 5 { break } } } for _, m := range st.Mutations { if !m.Applied { continue } if isCompilerFeedbackNoise(m.Reason) { continue } switch m.Change { case "decrease_k", "decrease_weight", "quarantine_pattern": ir.Constraints = appendConstraint(ir.Constraints, Constraint{Type: "avoid", Text: m.Reason, Source: m.Target}) case "increase_weight", "add_constraint": ir.Constraints = appendConstraint(ir.Constraints, Constraint{Type: "must_use", Text: m.Reason, Source: m.Target}) default: ir.Constraints = appendConstraint(ir.Constraints, Constraint{Type: "reference", Text: m.Reason, Source: m.Target}) } } for _, candidate := range rankedStrategies { s := candidate.strategy ref := StrategyRef{ID: s.ID, SuccessRate: s.SuccessRate(), Samples: s.Samples(), Score: candidate.score, Reason: candidate.reason} if lowSuccessStrategy(s) { if s.Samples() > 0 { ir.RiskNotes = append(ir.RiskNotes, "avoid low-success strategy "+s.ID) } continue } ir.AvailableStrategies = append(ir.AvailableStrategies, ref) if len(ir.AvailableStrategies) >= 3 { break } } if ir.StrategySelection != nil && ir.StrategySelection.Selected != "" { if plan := strategyPlan(st.Strategies, ir.StrategySelection.Selected); len(plan) > 0 { ir.ExecutionSteps = plan } } if len(ir.ExecutionSteps) == 0 && (len(ir.Constraints) > 0 || len(ir.MemoryReferences) > 0 || len(ir.RiskNotes) > 0) { if plan := strategyPlan(st.Strategies, bestStrategyID(goal, st.Strategies)); len(plan) > 0 { ir.ExecutionSteps = plan } } if len(ir.Constraints) > 0 || len(ir.AvailableStrategies) > 0 || len(ir.RiskNotes) > 0 { if len(ir.ExecutionSteps) == 0 { ir.ExecutionSteps = []Step{ {ID: "analyze", Action: "Inspect the current task and verify the relevant source of truth."}, {ID: "execute", Action: "Apply the highest-signal compatible strategy while respecting constraints."}, {ID: "validate", Action: "Validate the outcome with direct evidence before finalizing."}, } } } return canonicalizeIR(ir), policy } func hasUsefulIR(ir PlannerIR) bool { return len(ir.Constraints) > 0 || len(ir.MemoryReferences) > 0 || len(ir.RiskNotes) > 0 } // contractIR is the bounded, model-facing projection of PlannerIR that gets // serialized into the per-turn prompt contract. It is deliberately a separate // type from PlannerIR with omitempty everywhere so the injected JSON carries no // zero-value/null noise. The display layer (agent.memoryCompilerSourceEvent) // only needs planner_ir.source_event, which is preserved. type contractIR struct { Version string `json:"version,omitempty"` Goal string `json:"goal,omitempty"` SourceEvent string `json:"source_event"` RuntimeMode string `json:"runtime_mode,omitempty"` Constraints []Constraint `json:"constraints,omitempty"` StrategySelection *contractStrategy `json:"strategy_selection,omitempty"` MemoryReferences []MemoryRef `json:"memory_references,omitempty"` ExecutionSteps []Step `json:"execution_steps,omitempty"` RiskNotes []string `json:"risk_notes,omitempty"` } type contractStrategy struct { Selected string `json:"selected"` Reason string `json:"reason,omitempty"` Mode string `json:"mode,omitempty"` } func compileExecutionContract(ir PlannerIR) (string, error) { contract := struct { Type string `json:"type"` Instruction string `json:"instruction"` PlannerIR contractIR `json:"planner_ir"` }{ Type: "memory_v5_execution_contract", Instruction: "Execute source_event through planner_ir. Treat constraints, risk_notes, strategy_selection, and execution_steps as the controlling plan for this turn. Do not bypass contradictory or quarantined memory outside this IR.", PlannerIR: compactContractIR(canonicalizeIR(ir)), } body, err := json.Marshal(contract) if err != nil { return "", err } return "\n" + string(body) + "\n", nil } // compactContractIR projects the full canonical IR onto the model-facing // contractIR by dropping only the fields the model cannot act on. It does NOT // cap or re-truncate the actionable fields, so the constraints, memory // references, execution steps, risk notes, and selected strategy it injects are // byte-identical to what the previous full contract carried. The full IR is // still recorded on the execution trace for learning (writeTraceAndLearn keeps // the canonical IR); only the prompt copy is slimmed. // // Before this the contract re-serialized the entire planner IR every turn, // including a prose ir_explanation that just restated the constraints and // memory references, the ranked available_strategies candidate table, and each // strategy's rejected/score/exploration-rate control-loop state. On short user // turns that inflated the user message ~20-50x and grew unbounded as the // candidate table and explanation accreted. None of the dropped fields carry // guidance the kept fields don't already supply; the canonical IR's own limits // keep the kept fields bounded. func compactContractIR(ir PlannerIR) contractIR { out := contractIR{ Version: ir.Version, Goal: ir.Goal, SourceEvent: ir.SourceEvent, RuntimeMode: ir.RuntimeMode, Constraints: ir.Constraints, MemoryReferences: ir.MemoryReferences, ExecutionSteps: ir.ExecutionSteps, RiskNotes: ir.RiskNotes, } // Keep only the chosen strategy and its reason/mode; the rejected // candidates, numeric score, and exploration rate are internal control-loop // state the model cannot act on, and the ranked available_strategies table // is dropped entirely for the same reason. if ir.StrategySelection != nil { out.StrategySelection = &contractStrategy{ Selected: ir.StrategySelection.Selected, Reason: ir.StrategySelection.Reason, Mode: ir.StrategySelection.Mode, } } return out } func memoryCitationsForIR(ir PlannerIR) []provider.MemoryCitation { ir = canonicalizeIR(ir) out := []provider.MemoryCitation{} seen := map[string]bool{} add := func(c provider.MemoryCitation) { c.ID = strings.TrimSpace(c.ID) c.Source = strings.TrimSpace(c.Source) c.Note = summarizeText(c.Note, 180) c.Kind = strings.TrimSpace(c.Kind) if c.Source == "" { c.Source = "Memory v5" } key := c.Kind + "\x00" + c.ID + "\x00" + c.Source + "\x00" + c.Note if c.Note == "" || seen[key] || len(out) >= 5 { return } seen[key] = true out = append(out, c) } for _, ref := range ir.MemoryReferences { if ref.Influence == "evidence" { continue // tool_result nodes are internal graph state, not user-facing } note := ref.Content if ref.Influence != "" { note = ref.Influence + ": " + note } if ref.Quality != "" { note += " (" + ref.Quality + ")" } add(provider.MemoryCitation{ ID: ref.ID, Source: "Memory v5", Note: note, Kind: "compiler_reference", }) } for _, c := range ir.Constraints { note := c.Type + ": " + c.Text if c.Source != "" { note += " [" + c.Source + "]" } add(provider.MemoryCitation{ ID: c.Source, Source: "Memory v5", Note: note, Kind: "constraint", }) } for _, note := range ir.RiskNotes { add(provider.MemoryCitation{ Source: "Memory v5", Note: "risk: " + note, Kind: "risk_note", }) } return out } func selectedStrategy(ir PlannerIR) string { if ir.StrategySelection != nil && strings.TrimSpace(ir.StrategySelection.Selected) != "" { return strings.TrimSpace(ir.StrategySelection.Selected) } return "general" } func canonicalizeIR(ir PlannerIR) PlannerIR { ir.Version = strings.TrimSpace(ir.Version) if ir.Version == "" { ir.Version = version } ir.Goal = summarizeGoal(ir.Goal) ir.SourceEvent = strings.TrimSpace(ir.SourceEvent) ir.RuntimeMode = strings.TrimSpace(ir.RuntimeMode) if ir.RuntimeMode == "" { ir.RuntimeMode = "control" } ir.Constraints = canonicalConstraints(ir.Constraints) ir.AvailableStrategies = canonicalStrategyRefs(ir.AvailableStrategies) ir.MemoryReferences = canonicalMemoryRefs(ir.MemoryReferences) ir.ExecutionSteps = canonicalSteps(ir.ExecutionSteps) ir.RiskNotes = canonicalStrings(ir.RiskNotes) if ir.StrategySelection == nil { ir.StrategySelection = &StrategyPick{ Selected: "general", Reason: "default strategy", Mode: "control", ExplorationRate: float64(explorationRatePercent) / 100, Rejected: []RejectedStrategy{}, } } else { ir.StrategySelection.Selected = strings.TrimSpace(ir.StrategySelection.Selected) if ir.StrategySelection.Selected == "" { ir.StrategySelection.Selected = "general" } ir.StrategySelection.Reason = strings.TrimSpace(ir.StrategySelection.Reason) ir.StrategySelection.Score = roundScore(ir.StrategySelection.Score) ir.StrategySelection.Mode = strings.TrimSpace(ir.StrategySelection.Mode) if ir.StrategySelection.Mode == "" { ir.StrategySelection.Mode = "control" } ratePercent := int(math.Round(ir.StrategySelection.ExplorationRate * 100)) if ratePercent <= 0 { ratePercent = explorationRatePercent } ir.StrategySelection.ExplorationRate = float64(clampExplorationRatePercent(ratePercent)) / 100 ir.StrategySelection.Rejected = canonicalRejectedStrategies(ir.StrategySelection.Rejected) } if ir.Constraints == nil { ir.Constraints = []Constraint{} } if ir.AvailableStrategies == nil { ir.AvailableStrategies = []StrategyRef{} } if ir.MemoryReferences == nil { ir.MemoryReferences = []MemoryRef{} } if ir.ExecutionSteps == nil { ir.ExecutionSteps = []Step{} } if ir.RiskNotes == nil { ir.RiskNotes = []string{} } return ir } func canonicalConstraints(in []Constraint) []Constraint { out := make([]Constraint, 0, len(in)) for _, c := range in { c.Type = strings.TrimSpace(c.Type) c.Text = strings.TrimSpace(c.Text) c.Source = strings.TrimSpace(c.Source) if c.Type == "" || c.Text == "" { continue } out = append(out, c) } sort.Slice(out, func(i, j int) bool { if out[i].Type != out[j].Type { return out[i].Type < out[j].Type } if out[i].Source != out[j].Source { return out[i].Source < out[j].Source } return out[i].Text < out[j].Text }) return dedupeConstraints(out) } func dedupeConstraints(in []Constraint) []Constraint { seen := map[Constraint]bool{} out := make([]Constraint, 0, len(in)) for _, c := range in { if seen[c] { continue } seen[c] = true out = append(out, c) } return out } func canonicalStrategyRefs(in []StrategyRef) []StrategyRef { out := make([]StrategyRef, 0, len(in)) for _, s := range in { s.ID = strings.TrimSpace(s.ID) s.Reason = strings.TrimSpace(s.Reason) if s.ID == "" { continue } s.SuccessRate = roundScore(s.SuccessRate) s.Score = roundScore(s.Score) out = append(out, s) } sort.Slice(out, func(i, j int) bool { if out[i].Score == out[j].Score { return out[i].ID < out[j].ID } return out[i].Score > out[j].Score }) return out } func canonicalRejectedStrategies(in []RejectedStrategy) []RejectedStrategy { out := make([]RejectedStrategy, 0, len(in)) for _, r := range in { r.ID = strings.TrimSpace(r.ID) r.Reason = strings.TrimSpace(r.Reason) if r.ID == "" { continue } r.Score = roundScore(r.Score) out = append(out, r) } sort.Slice(out, func(i, j int) bool { if out[i].Score == out[j].Score { return out[i].ID < out[j].ID } return out[i].Score > out[j].Score }) return out } func canonicalMemoryRefs(in []MemoryRef) []MemoryRef { out := make([]MemoryRef, 0, len(in)) for _, ref := range in { ref.ID = strings.TrimSpace(ref.ID) ref.Content = strings.TrimSpace(ref.Content) ref.Quality = strings.TrimSpace(ref.Quality) ref.Influence = strings.TrimSpace(ref.Influence) if ref.ID == "" || ref.Content == "" { continue } out = append(out, ref) } sort.Slice(out, func(i, j int) bool { if out[i].Influence != out[j].Influence { return out[i].Influence < out[j].Influence } return out[i].ID < out[j].ID }) if len(out) > 5 { out = out[:5] } return out } func canonicalSteps(in []Step) []Step { out := make([]Step, 0, len(in)) for _, step := range in { step.ID = strings.TrimSpace(step.ID) step.Action = strings.TrimSpace(step.Action) if step.ID == "" || step.Action == "" { continue } out = append(out, step) } return out } func canonicalStrings(in []string) []string { out := dedupeStrings(in) sort.Strings(out) return out } func limitStrings(in []string, n int) []string { if n < 0 { n = 0 } if len(in) > n { return in[:n] } if in == nil { return []string{} } return in } func summarizeText(s string, maxRunes int) string { s = strings.Join(strings.Fields(strings.TrimSpace(s)), " ") if maxRunes <= 0 { return "" } if len([]rune(s)) <= maxRunes { return s } r := []rune(s) return string(r[:maxRunes]) + "..." } func roundScore(v float64) float64 { if v > -0.00005 && v < 0.00005 { return 0 } return math.Round(v*10000) / 10000 } func appendConstraint(existing []Constraint, next Constraint) []Constraint { next.Type = strings.TrimSpace(next.Type) next.Text = strings.TrimSpace(next.Text) if next.Type == "" || next.Text == "" { return existing } for _, c := range existing { if c.Type == next.Type && c.Text == next.Text && c.Source == next.Source { return existing } } return append(existing, next) } func usableNodes(nodes []MemoryNode, now time.Time) []MemoryNode { out := make([]MemoryNode, 0, len(nodes)) for _, node := range nodes { if node.Quality == QualityNoise || node.Quality == QualityCorrupted { continue } node.Confidence = decayedConfidence(node, now) if node.Confidence < 0.2 && !node.TruthLocked { continue } out = append(out, node) } sort.Slice(out, func(i, j int) bool { if out[i].Confidence == out[j].Confidence { if out[i].Timestamp.Equal(out[j].Timestamp) { return out[i].ID < out[j].ID } return out[i].Timestamp.After(out[j].Timestamp) } return out[i].Confidence > out[j].Confidence }) return out } func usableSubgraphNodes(nodes []MemoryNode, edges []MemoryEdge, now time.Time) []MemoryNode { usable := usableNodes(nodes, now) if len(usable) == 0 { return nil } byID := map[string]MemoryNode{} for _, node := range usable { byID[node.ID] = node } selected := map[string]bool{} frontier := make([]string, 0, 5) for _, node := range usable { selected[node.ID] = true frontier = append(frontier, node.ID) if len(frontier) >= 5 { break } } for len(frontier) > 0 && len(selected) < 12 { current := frontier[0] frontier = frontier[1:] for _, edge := range edges { if !traversableRelation(edge.Relation) { continue } next := "" switch { case edge.From == current: next = edge.To case edge.To == current: next = edge.From } if next == "" || selected[next] { continue } if _, ok := byID[next]; !ok { continue } selected[next] = true frontier = append(frontier, next) if len(selected) >= 12 { break } } } out := make([]MemoryNode, 0, len(selected)) for _, node := range usable { if selected[node.ID] { out = append(out, node) } } return out } func traversableRelation(relation string) bool { switch relation { case "supports", "depends_on", "derived_from", "causes": return true default: return false } } type noisyRefCount struct { ref string count int } func sortedNoisyRefs(noisy map[string]int) []noisyRefCount { out := make([]noisyRefCount, 0, len(noisy)) for ref, count := range noisy { out = append(out, noisyRefCount{ref: ref, count: count}) } sort.Slice(out, func(i, j int) bool { if out[i].count == out[j].count { return out[i].ref < out[j].ref } return out[i].count > out[j].count }) return out } func influenceForNode(node MemoryNode) string { if node.Constraint != nil { return node.Constraint.Type } switch node.Type { case "tool_result": return "evidence" case "decision": return "decision_history" default: return "reference" } } func decayedConfidence(node MemoryNode, now time.Time) float64 { if node.TruthLocked || node.Timestamp.IsZero() { return node.Confidence } days := now.Sub(node.Timestamp).Hours() / 24 if days <= 0 { return node.Confidence } factor := 1.0 for days >= 7 { factor *= 0.95 days -= 7 } return node.Confidence * factor } func strategyPlan(strategies []Strategy, id string) []Step { for _, s := range strategies { if s.ID == id { return append([]Step(nil), s.ExecutionPlan...) } } return nil } type scoredStrategy struct { strategy Strategy score float64 reason string } func rankStrategies(goal string, strategies []Strategy) []scoredStrategy { out := make([]scoredStrategy, 0, len(strategies)) for _, s := range strategies { score, reason := strategyScoreWithReason(goal, s) out = append(out, scoredStrategy{strategy: s, score: score, reason: reason}) } sort.Slice(out, func(i, j int) bool { if out[i].score == out[j].score { return out[i].strategy.ID < out[j].strategy.ID } return out[i].score > out[j].score }) return out } func selectStrategy(goal string, ranked []scoredStrategy, explorationRates ...int) StrategyPick { explorationRate := clampExplorationRatePercent(explorationRatePercent) if len(explorationRates) > 0 { explorationRate = clampExplorationRatePercent(explorationRates[0]) } pick := StrategyPick{ Selected: "general", Reason: "default strategy", Mode: "control", ExplorationRate: float64(explorationRate) / 100, Rejected: []RejectedStrategy{}, } eligible := make([]scoredStrategy, 0, len(ranked)) for _, candidate := range ranked { if !lowSuccessStrategy(candidate.strategy) { eligible = append(eligible, candidate) } } if len(eligible) > 0 { selected := eligible[0] if explore, candidate := explorationCandidate(goal, eligible, explorationRate); explore { selected = candidate pick.Mode = "explore" } pick.Selected = selected.strategy.ID pick.Reason = selected.reason pick.Score = selected.score if pick.Mode == "explore" { pick.Reason = "deterministic exploration buffer; " + pick.Reason } } for _, candidate := range ranked { if candidate.strategy.ID == pick.Selected { continue } reason := candidate.reason if lowSuccessStrategy(candidate.strategy) { reason = "rejected because prior success rate is below the risk threshold" } pick.Rejected = append(pick.Rejected, RejectedStrategy{ ID: candidate.strategy.ID, Reason: reason, Score: candidate.score, }) if len(pick.Rejected) >= 3 { break } } return pick } func explorationCandidate(goal string, eligible []scoredStrategy, explorationRate int) (bool, scoredStrategy) { explorationRate = clampExplorationRatePercent(explorationRate) if len(eligible) < 2 || explorationRate <= 0 { return false, scoredStrategy{} } h := fnv.New32a() _, _ = h.Write([]byte(strings.ToLower(strings.TrimSpace(goal)))) for _, candidate := range eligible { _, _ = h.Write([]byte{0}) _, _ = h.Write([]byte(candidate.strategy.ID)) _, _ = fmt.Fprintf(h, ":%d:%d", candidate.strategy.Successes, candidate.strategy.Failures) } if int(h.Sum32()%100) >= explorationRate { return false, scoredStrategy{} } candidates := append([]scoredStrategy(nil), eligible[1:]...) sort.Slice(candidates, func(i, j int) bool { if candidates[i].strategy.Samples() == candidates[j].strategy.Samples() { if candidates[i].score == candidates[j].score { return candidates[i].strategy.ID < candidates[j].strategy.ID } return candidates[i].score > candidates[j].score } return candidates[i].strategy.Samples() < candidates[j].strategy.Samples() }) return true, candidates[0] } func clampExplorationRatePercent(rate int) int { if rate < minExplorationRatePercent { return minExplorationRatePercent } if rate > maxExplorationRatePercent { return maxExplorationRatePercent } return rate } func equilibriumExplorationRatePercent(st state, drift DriftReport) int { return controlPolicyForState(st, drift).ExplorationRatePercent } // controlPolicyForState derives the per-turn control policy from a few legible // stability signals — sustained clean successes (stable), recent // failures/drift (unstable), and an alternating strategy history (oscillating). // // This replaces the former equilibrium/controlplane/controlsemantics packages // (~1600 LOC of oscillation/consensus/convergence/entropy math). Measured over // a varied multi-turn session, that whole apparatus reached the model-facing // contract through exactly one value — the exploration rate — and changed the // selected strategy on ~3% of turns versus a constant rate; it was an elaborate // "explore less when unstable" switch. The heuristic keeps that behavior and // populates the telemetry fields from the same signals so traces stay legible. func controlPolicyForState(st state, drift DriftReport) ControlPolicy { shift := semanticShiftSignals(st) policy := ControlPolicy{ Version: version, Mode: "balanced", Controller: "adaptive-heuristic", ExplorationRatePercent: explorationRatePercent, Gain: 1.0, EquilibriumState: "steady", ControlGraphEntropy: 0.7, SystemStabilityScore: 0.7, SemanticShift: shift, } switch { case equilibriumOscillating(st): // Strategies are thrashing: damp exploration to the floor and slow the // mutation-feedback loop so it can settle. policy.ExplorationRatePercent = minExplorationRatePercent policy.Gain = 0.5 policy.Mode = "stabilize" policy.EquilibriumState = "oscillating" policy.OscillationIndex = 0.8 policy.SystemStabilityScore = 0.3 policy.EquilibriumActions = []string{"oscillating strategy history damped exploration"} case len(shift) > 0: // IR execution is drifting from the planner IR (accumulated semantic // variation/drift): stabilize even when outcomes still look clean — this // takes priority over the stable-convergence branch below. policy.ExplorationRatePercent = minExplorationRatePercent policy.Gain = 0.6 policy.Mode = "stabilize" policy.EquilibriumState = "semantic_shift" policy.OscillationIndex = 0.5 policy.SystemStabilityScore = 0.4 policy.EquilibriumActions = []string{"accumulated semantic shift damped exploration"} case equilibriumUnstable(st, drift): // Recent failures or drift: stay conservative, explore less. policy.ExplorationRatePercent = minExplorationRatePercent policy.Gain = 0.7 policy.Mode = "dampen" policy.EquilibriumState = "unstable" policy.OscillationIndex = 0.4 policy.SystemStabilityScore = 0.4 policy.EquilibriumActions = []string{"recent instability reduced exploration"} case equilibriumStable(st, drift): // Sustained clean successes: widen exploration to keep learning. policy.ExplorationRatePercent = maxExplorationRatePercent policy.Gain = 1.15 policy.Mode = "explore" policy.EquilibriumState = "stable" policy.ConvergenceVelocity = 0.8 policy.SystemStabilityScore = 1.0 policy.EquilibriumActions = []string{"stable convergence widened exploration"} } policy.Reasons = append([]string(nil), policy.EquilibriumActions...) policy.ExplorationRatePercent = clampExplorationRatePercent(policy.ExplorationRatePercent) policy.Gain = roundScore(policy.Gain) policy.MutationCooldown = controlMutationCooldown(policy.Gain) policy.MutationCooldownMs = policy.MutationCooldown.Milliseconds() policy.EquilibriumActions = limitStrings(canonicalStrings(policy.EquilibriumActions), 6) policy.SemanticShift = limitStrings(canonicalStrings(policy.SemanticShift), 5) policy.Reasons = limitStrings(canonicalStrings(policy.Reasons), 5) return policy } func equilibriumTraceForPolicy(policy ControlPolicy) *EquilibriumTrace { return &EquilibriumTrace{ State: policy.EquilibriumState, ControlGraphEntropy: policy.ControlGraphEntropy, SystemStabilityScore: policy.SystemStabilityScore, ConvergenceVelocity: policy.ConvergenceVelocity, OscillationIndex: policy.OscillationIndex, Actions: append([]string(nil), policy.EquilibriumActions...), } } func controlMutationCooldown(gain float64) time.Duration { if gain <= 0 { gain = 1 } if gain < 0.35 { gain = 0.35 } if gain > 1.25 { gain = 1.25 } return time.Duration(float64(mutationFeedbackCooldown) / gain) } func semanticShiftSignals(st state) []string { recent := recentLearnings(st.Learnings, 6) softVariations := 0 hardDrifts := 0 failureMemoryFindings := 0 for _, learning := range recent { for _, finding := range learning.CausalFindings { lower := strings.ToLower(finding) if strings.Contains(lower, "semantic variation") { softVariations++ } if strings.Contains(lower, "semantic drift") { hardDrifts++ } if strings.Contains(lower, "memory ") && strings.Contains(lower, "failed outcome") { failureMemoryFindings++ } } } var signals []string if softVariations >= 3 { signals = append(signals, fmt.Sprintf("soft semantic variations accumulated across recent turns: %d", softVariations)) } if hardDrifts >= 2 { signals = append(signals, fmt.Sprintf("hard semantic drift repeated across recent turns: %d", hardDrifts)) } if failureMemoryFindings >= 3 { signals = append(signals, fmt.Sprintf("memory attribution repeatedly aligned with failed outcomes: %d", failureMemoryFindings)) } return limitStrings(canonicalStrings(signals), 5) } func equilibriumUnstable(st state, drift DriftReport) bool { if hasDrift(drift) { return true } for _, learning := range recentLearnings(st.Learnings, 5) { if len(learning.BadStrategies) > 0 || len(learning.MemoryNoisePatterns) > 0 || len(learning.CompilerImprovements) > 0 { return true } } return false } func equilibriumOscillating(st state) bool { seq := learningStrategySequence(recentLearnings(st.Learnings, 6)) if len(seq) < 4 { return false } unique := map[string]bool{} transitions := 0 for i, id := range seq { unique[id] = true if i > 0 && id != seq[i-1] { transitions++ } } return len(unique) >= 3 && transitions >= len(seq)-2 } func learningStrategySequence(learnings []SystemLearning) []string { out := make([]string, 0, len(learnings)) for _, learning := range learnings { id := firstNonEmpty(learning.GoodPatterns, "") if id == "" { id = firstNonEmpty(learning.BadStrategies, "") } id = strings.TrimSpace(id) if id != "" { out = append(out, id) } } return out } func equilibriumStable(st state, drift DriftReport) bool { if hasDrift(drift) { return false } recent := recentLearnings(st.Learnings, 5) if len(recent) < 3 { return false } for _, learning := range recent { if len(learning.GoodPatterns) == 0 || len(learning.BadStrategies) > 0 || len(learning.MemoryNoisePatterns) > 0 || len(learning.CompilerImprovements) > 0 { return false } } return true } func recentLearnings(in []SystemLearning, n int) []SystemLearning { if n <= 0 || len(in) == 0 { return nil } if len(in) <= n { return in } return in[len(in)-n:] } func bestStrategyID(goal string, strategies []Strategy) string { bestID := "general" bestScore := -1.0 for _, s := range strategies { score := strategyScore(goal, s) if score > bestScore { bestScore = score bestID = s.ID } } return bestID } func strategyScore(goal string, s Strategy) float64 { score, _ := strategyScoreWithReason(goal, s) return score } func strategyScoreWithReason(goal string, s Strategy) (float64, string) { score, reason := normalizedOutcomeScore(s) reasons := []string{reason} if bonus := strategyNoveltyBonus(s); bonus > 0 { score += bonus reasons = append(reasons, fmt.Sprintf("%.2f novelty bonus", bonus)) } if penalty := strategyUsagePenalty(s.Samples()); penalty > 0 { score -= penalty reasons = append(reasons, fmt.Sprintf("%.2f usage penalty", penalty)) } for _, p := range s.Preconditions { p = strings.ToLower(strings.TrimSpace(p)) if p != "" && strategyPreconditionMatches(goal, p) { score += 0.75 reasons = append(reasons, "matched precondition "+p) } } if s.ID == classifyStrategy(goal) { score += 0.5 reasons = append(reasons, "matched goal classifier") } if lowSuccessStrategy(s) { score -= 1.0 reasons = append(reasons, "low success history") } return score, strings.Join(reasons, "; ") } // strategyPreconditionMatches reports whether goal matches a strategy // precondition. Very short preconditions (<=2 runes, e.g. "ui") match only on // whole-token boundaries; a plain substring test let "ui" hit "pursuing", // "build", etc., which misrouted unrelated turns — including the synthetic // "Continue pursuing the active goal." message — to frontend-visual-verify. func strategyPreconditionMatches(goal, precondition string) bool { precondition = strings.ToLower(strings.TrimSpace(precondition)) if precondition == "" { return false } goal = strings.ToLower(goal) if len([]rune(precondition)) > 2 { return strings.Contains(goal, precondition) } for _, token := range strings.FieldsFunc(goal, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsDigit(r) }) { if token == precondition { return true } } return false } func normalizedOutcomeScore(s Strategy) (float64, string) { samples := s.Samples() if samples == 0 { return 0.5, "neutral prior" } return s.SuccessRate(), fmt.Sprintf("%.0f%% prior success after %d use(s)", s.SuccessRate()*100, samples) } func strategyNoveltyBonus(s Strategy) float64 { switch samples := s.Samples(); { case samples == 0: return 0.25 case samples < 3: return 0.15 default: return 0 } } func strategyUsagePenalty(samples int) float64 { if samples <= 0 { return 0 } return roundScore((1 - strategyUsageDecay(samples)) * 0.35) } func strategyUsageDecay(samples int) float64 { if samples <= 0 { return 1 } return math.Exp(-float64(samples) / strategyDecayK) } func lowSuccessStrategy(s Strategy) bool { return s.Failures >= 2 && s.SuccessRate() < 0.34 } func memoryRefIDs(refs []MemoryRef) []string { out := make([]string, 0, len(refs)) for _, ref := range refs { if strings.TrimSpace(ref.ID) != "" { out = append(out, ref.ID) } } return out } func decisionBranches(ir PlannerIR) []DecisionBranch { if ir.StrategySelection == nil || ir.StrategySelection.Selected == "" { return nil } rejected := make([]string, 0, len(ir.StrategySelection.Rejected)) for _, r := range ir.StrategySelection.Rejected { rejected = append(rejected, r.ID) } return []DecisionBranch{{ Question: "Which strategy should control this turn?", Selected: ir.StrategySelection.Selected, Rejected: rejected, SelectionReason: ir.StrategySelection.Reason, }} } func causalEdgesForIR(traceID string, ir PlannerIR) []CausalEdge { decisionID := "decision:" + traceID outcomeID := "outcome:" + traceID edges := make([]CausalEdge, 0, len(ir.MemoryReferences)+len(ir.Constraints)+1) for _, ref := range ir.MemoryReferences { edges = appendCausalEdge(edges, CausalEdge{From: ref.ID, To: decisionID, Relation: "influenced"}) } for _, c := range ir.Constraints { if c.Source != "" { edges = appendCausalEdge(edges, CausalEdge{From: c.Source, To: decisionID, Relation: "constrained"}) } } if ir.StrategySelection != nil && ir.StrategySelection.Selected != "" { edges = appendCausalEdge(edges, CausalEdge{From: decisionID, To: outcomeID, Relation: "selected_strategy:" + ir.StrategySelection.Selected}) } return edges } func validateIRExecution(ir PlannerIR, tr ExecutionTrace) IRValidationResult { ir = canonicalizeIR(ir) result := IRValidationResult{} addHard := func(finding string) { finding = strings.TrimSpace(finding) if finding == "" { return } result.HardFindings = append(result.HardFindings, finding) result.Reject = true } addSoft := func(finding string) { finding = strings.TrimSpace(finding) if finding == "" { return } result.SoftFindings = append(result.SoftFindings, finding) } if selected := selectedStrategy(ir); selected != "" && selected != "general" { if len(tr.StrategyUsed) == 0 || tr.StrategyUsed[0] != selected { addHard("selected strategy drift: IR=" + selected + " trace=" + firstNonEmpty(tr.StrategyUsed, "")) } } if !sameStepIDs(ir.ExecutionSteps, tr.Steps) { addSoft("execution steps varied from planner IR") } if !sameStringSet(memoryRefIDs(ir.MemoryReferences), tr.MemoryUsed) { addHard("memory references drifted from planner IR") } if len(ir.ExecutionSteps) > 0 && tr.Cost.ToolCalls > len(ir.ExecutionSteps)+3 && tr.Cost.ToolCalls >= 6 { addSoft(fmt.Sprintf("tool calls exceeded IR step budget: steps=%d tool_calls=%d", len(ir.ExecutionSteps), tr.Cost.ToolCalls)) } result.HardFindings = limitStrings(canonicalStrings(result.HardFindings), 5) result.SoftFindings = limitStrings(canonicalStrings(result.SoftFindings), 5) result.Findings = limitStrings(canonicalStrings(append(append([]string(nil), result.HardFindings...), result.SoftFindings...)), 5) return result } func sameStepIDs(a, b []Step) bool { if len(a) != len(b) { return false } for i := range a { if strings.TrimSpace(a[i].ID) != strings.TrimSpace(b[i].ID) { return false } } return true } func sameStringSet(a, b []string) bool { a = canonicalStrings(a) b = canonicalStrings(b) if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } func appendCausalEdge(edges []CausalEdge, next CausalEdge) []CausalEdge { if next.From == "" || next.To == "" || next.Relation == "" { return edges } for _, e := range edges { if e == next { return edges } } return append(edges, next) } func estimateTokens(s string) int { s = strings.TrimSpace(s) if s == "" { return 0 } // Cheap conservative estimate used only for local learning, not billing. return (len([]rune(s)) + 3) / 4 } func (t *Turn) RecordToolResults(records []ToolRecord) { if t == nil || len(records) == 0 { return } t.trace.ToolResults = append(t.trace.ToolResults, records...) } func (t *Turn) Finish(err error) { if t == nil || t.rt == nil { return } t.trace.CompletedAt = time.Now().UTC() t.trace.Injected = t.metrics.Injected t.trace.Outcome = outcomeFor(t.trace.ToolResults, err) if err != nil { t.trace.FailureReason = firstLine(err.Error()) } t.trace.Cost = finishCostMetrics(t.trace.Cost, t.trace.ToolResults, t.trace.StartedAt, t.trace.CompletedAt) if t.metrics.Injected { validation := validateIRExecution(t.ir, t.trace) t.trace.SemanticDrift = validation.Findings t.trace.SemanticDriftHard = validation.HardFindings t.trace.SemanticDriftSoft = validation.SoftFindings if validation.Reject && t.trace.Outcome == "success" { t.trace.Outcome = "partial_success" t.trace.FailureReason = "IR validation rejected inconsistent execution: " + strings.Join(validation.HardFindings, "; ") } } for i, rec := range t.trace.ToolResults { toolID := fmt.Sprintf("tool:%s:%d", t.trace.ID, i) relation := "supported_outcome" if strings.TrimSpace(rec.Error) != "" { relation = "weakened_outcome" } t.trace.CausalEdges = appendCausalEdge(t.trace.CausalEdges, CausalEdge{ From: toolID, To: "outcome:" + t.trace.ID, Relation: relation, }) } t.trace.EfficiencyScore = efficiencyScore(t.trace.ToolResults, t.trace.StartedAt, t.trace.CompletedAt) t.trace.MemoryEffectiveness = memoryEffectiveness(t.trace) t.rt.writeTraceAndLearn(t.trace, t.strategy) } func outcomeFor(records []ToolRecord, err error) string { if err != nil { // A user-cancelled turn says nothing about the strategy's quality; // keep it out of the success/failure counters entirely. if errors.Is(err, context.Canceled) { return "aborted" } return "failure" } // Verification-shaped commands (tests, vet, typecheck, lint) are a stronger // signal than ordinary tool errors: a final failing test run caps the turn // at partial_success, and a passing one confirms success unless a later // tool errored after it. if rec, idx, ok := lastVerificationToolRecord(records); ok { if strings.TrimSpace(rec.Error) != "" { return "partial_success" } if !anyToolErrorAfter(records, idx) { return "success" } } if len(records) == 0 { // A turn that finishes without error and without tool calls is a // successful plain-text answer, not a partial success. Returning // partial_success here made updateStrategy count every no-tool turn as a // strategy failure, poisoning scores once Memory v5 is on by default. return "success" } for i := len(records) - 1; i >= 0; i-- { if strings.TrimSpace(records[i].Name) == "" { continue } if isPlanModeBlockedToolRecord(records[i]) { continue } if strings.TrimSpace(records[i].Error) == "" { return "success" } return "partial_success" } return "success" } func isPlanModeBlockedToolRecord(rec ToolRecord) bool { if !rec.Blocked { return false } return strings.EqualFold(strings.TrimSpace(rec.Error), planModeBlockedToolError) } // verificationCommandMarkers identify shell commands whose exit status verifies // the turn's work (tests, vet, typecheck, lint, build). Matched as lowercase // substrings of the bash tool's raw argument JSON. var verificationCommandMarkers = []string{ "go test", "go vet", "go build", "gofmt -l", "golangci-lint", "npm test", "npm run test", "pnpm test", "pnpm run test", "yarn test", "vitest", "jest", "npx tsc", "tsc ", "typecheck", "type-check", "check:css", "eslint", "pytest", "ruff check", "cargo test", "cargo check", "cargo build", "mvn test", "gradle test", "make test", "ctest", "phpunit", "rspec", } func isVerificationToolRecord(rec ToolRecord) bool { return !rec.Blocked && IsVerificationToolCall(rec.Name, rec.Args) } // IsVerificationToolCall reports whether a persisted tool call is a shell // command whose exit status provides implementation evidence. It intentionally // returns only a boolean so diagnostic callers never need to expose arguments. func IsVerificationToolCall(name, args string) bool { if !strings.EqualFold(strings.TrimSpace(name), "bash") { return false } args = strings.ToLower(args) if args == "" { return false } for _, marker := range verificationCommandMarkers { if strings.Contains(args, marker) { return true } } return false } func lastVerificationToolRecord(records []ToolRecord) (ToolRecord, int, bool) { for i := len(records) - 1; i >= 0; i-- { if isVerificationToolRecord(records[i]) { return records[i], i, true } } return ToolRecord{}, -1, false } func anyToolErrorAfter(records []ToolRecord, idx int) bool { for i := idx + 1; i < len(records); i++ { if strings.TrimSpace(records[i].Name) == "" || isPlanModeBlockedToolRecord(records[i]) { continue } if strings.TrimSpace(records[i].Error) != "" { return true } } return false } func efficiencyScore(records []ToolRecord, start, end time.Time) float64 { if len(records) == 0 { return 0.5 } seconds := end.Sub(start).Seconds() if seconds <= 0 { return 1 } score := 1 / (1 + seconds/120) if score < 0 { return 0 } return score } func memoryEffectiveness(tr ExecutionTrace) float64 { if len(tr.MemoryUsed) == 0 && len(tr.StrategyUsed) == 0 && len(tr.Steps) == 0 { return 0 } switch tr.Outcome { case "success": return 1 case "partial_success": return 0.5 default: return 0 } } func finishCostMetrics(cost CostMetrics, records []ToolRecord, start, end time.Time) CostMetrics { cost.LatencyMs = end.Sub(start).Milliseconds() cost.ToolCalls = len(records) for _, rec := range records { if strings.TrimSpace(rec.Error) != "" { cost.ToolErrors++ } if rec.Truncated { cost.TruncatedToolResults++ } } return cost } func (r *Runtime) writeTraceAndLearn(tr ExecutionTrace, strategyID string) { r.mu.Lock() defer r.mu.Unlock() if err := os.MkdirAll(r.dir, 0o700); err != nil { return } st := r.loadStateLocked() st.Strategies = ensureBuiltInStrategies(st.Strategies) if strategyID == "" { strategyID = classifyStrategy(tr.Goal) } var evaluations []MutationEvaluation // Aborted turns carry no quality signal: they must not grade mutations // under evaluation or move strategy success/failure counters. if tr.Outcome != "aborted" { st.Mutations, evaluations = evaluateMutations(st.Mutations, tr) } tr.MutationEvaluations = evaluations now := time.Now().UTC() baseline := baselineScore(st, strategyID) if tr.Outcome != "aborted" { st.Strategies = updateStrategy(st.Strategies, strategyID, tr.Outcome, tr.Injected) } learning := analyzeTrace(tr, strategyID) if hasLearning(learning) { st.Learnings = appendLearning(st.Learnings, learning) } policy := controlPolicyForState(st, DriftReport{}) st.Nodes, st.Edges, st.Decisions = updateGraph(st.Nodes, st.Edges, st.Decisions, tr, learning) st.ExecutionState = updateExecutionState(st.ExecutionState, tr, learning) st.NoisyRefs = updateNoisyRefs(st.NoisyRefs, learning) st.Mutations = mergeMutationsWithPolicy(policy, st.Mutations, mutationsFromLearning(learning, baseline)...) st, drift := applyDriftControl(st, now, tr.ID) policy = controlPolicyForState(st, drift) tr.SemanticShift = append([]string(nil), policy.SemanticShift...) tr.ControlMode = policy.Mode tr.ControlGain = policy.Gain tr.ControlSignals = append([]string(nil), policy.Reasons...) tr.EquilibriumTrace = equilibriumTraceForPolicy(policy) if hasDrift(drift) { st.DriftReports = appendDriftReport(st.DriftReports, drift) } st, tr = applyCausalCompression(st, tr, learning, policy, now) st.UpdatedAt = now bundle := splitTrace(tr, learning, debugTraceEnabled()) _ = appendBoundedJSONL(filepath.Join(r.dir, tracesFile), bundle.RuntimeTrace, maxRuntimeTraceJSONLLines) if bundle.LearningTrace != nil { _ = appendBoundedJSONL(filepath.Join(r.dir, learningTracesFile), *bundle.LearningTrace, maxLearningTraceJSONLLines) } if bundle.DebugTrace != nil { _ = appendBoundedJSONL(filepath.Join(r.dir, debugTracesFile), *bundle.DebugTrace, maxDebugTraceJSONLLines) } _ = writeJSON(filepath.Join(r.dir, stateFile), st) } func splitTrace(tr ExecutionTrace, learning SystemLearning, includeDebug bool) TraceBundle { bundle := TraceBundle{RuntimeTrace: executionTraceProjection(tr)} if lt, ok := learningTraceFor(tr, learning); ok { bundle.LearningTrace = < } if includeDebug { debug := tr bundle.DebugTrace = &debug } return bundle } func executionTraceProjection(tr ExecutionTrace) ExecutionTrace { return ExecutionTrace{ ID: tr.ID, IRVersion: tr.IRVersion, Goal: tr.Goal, Steps: append([]Step(nil), tr.Steps...), Outcome: tr.Outcome, Injected: tr.Injected, EfficiencyScore: tr.EfficiencyScore, MemoryEffectiveness: tr.MemoryEffectiveness, StrategyUsed: append([]string(nil), tr.StrategyUsed...), MemoryUsed: append([]string(nil), tr.MemoryUsed...), SemanticDrift: append([]string(nil), tr.SemanticDrift...), SemanticDriftHard: append([]string(nil), tr.SemanticDriftHard...), SemanticDriftSoft: append([]string(nil), tr.SemanticDriftSoft...), ControlMode: tr.ControlMode, ControlGain: tr.ControlGain, EquilibriumTrace: cloneEquilibriumTrace(tr.EquilibriumTrace), Compression: cloneCompressionReport(tr.Compression), Cost: tr.Cost, FailureReason: tr.FailureReason, StartedAt: tr.StartedAt, CompletedAt: tr.CompletedAt, } } func learningTraceFor(tr ExecutionTrace, learning SystemLearning) (LearningTrace, bool) { if !hasLearning(learning) && len(tr.MutationEvaluations) == 0 { return LearningTrace{}, false } return LearningTrace{ ID: tr.ID, IRVersion: tr.IRVersion, Outcome: tr.Outcome, Injected: tr.Injected, QualityScore: traceQualityScore(tr), StrategyUsed: append([]string(nil), tr.StrategyUsed...), MemoryUsed: append([]string(nil), tr.MemoryUsed...), DecisionBranches: append([]DecisionBranch(nil), tr.DecisionBranches...), CausalEdges: compressCausalEdges(tr.CausalEdges, maxCompressedCausalAnchors).AnchorEdges, SemanticDrift: append([]string(nil), tr.SemanticDrift...), SemanticDriftHard: append([]string(nil), tr.SemanticDriftHard...), SemanticDriftSoft: append([]string(nil), tr.SemanticDriftSoft...), SemanticShift: append([]string(nil), tr.SemanticShift...), ControlMode: tr.ControlMode, ControlGain: tr.ControlGain, ControlSignals: append([]string(nil), tr.ControlSignals...), EquilibriumTrace: cloneEquilibriumTrace(tr.EquilibriumTrace), Compression: cloneCompressionReport(tr.Compression), CausalFindings: append([]string(nil), learning.CausalFindings...), CompilerImprovements: append([]string(nil), learning.CompilerImprovements...), MutationEvaluations: append([]MutationEvaluation(nil), tr.MutationEvaluations...), Cost: tr.Cost, CreatedAt: time.Now().UTC(), }, true } func cloneEquilibriumTrace(in *EquilibriumTrace) *EquilibriumTrace { if in == nil { return nil } out := *in out.Actions = append([]string(nil), in.Actions...) return &out } func debugTraceEnabled() bool { v := strings.ToLower(strings.TrimSpace(os.Getenv(debugTraceEnv))) return v == "1" || v == "true" || v == "yes" } func analyzeTrace(tr ExecutionTrace, strategyID string) SystemLearning { learning := SystemLearning{TraceID: tr.ID, CreatedAt: time.Now().UTC()} errorCounts := map[string]int{} for _, rec := range tr.ToolResults { if rec.Error != "" { errorCounts[rec.Name+"\x00"+rec.Error]++ } } for sig, n := range errorCounts { if n < 2 { continue } parts := strings.SplitN(sig, "\x00", 2) toolName := parts[0] errLine := firstLine(parts[1]) if isCompilerFeedbackNoise(errLine) { continue } learning.BadStrategies = append(learning.BadStrategies, strategyID) learning.MemoryNoisePatterns = append(learning.MemoryNoisePatterns, fmt.Sprintf("%s repeated error: %s", toolName, errLine)) learning.CompilerImprovements = append(learning.CompilerImprovements, fmt.Sprintf("avoid repeating %s after repeated error: %s", toolName, errLine)) } if tr.Outcome == "failure" { learning.BadStrategies = append(learning.BadStrategies, strategyID) learning.CompilerImprovements = append(learning.CompilerImprovements, "previous execution failed; require source-of-truth verification before acting") for _, memoryID := range tr.MemoryUsed { learning.CausalFindings = append(learning.CausalFindings, "memory "+memoryID+" participated in failed outcome") } } if tr.Outcome == "success" { learning.GoodPatterns = append(learning.GoodPatterns, strategyID) for _, memoryID := range tr.MemoryUsed { learning.CausalFindings = append(learning.CausalFindings, "memory "+memoryID+" supported successful outcome") } } hardDrift := tr.SemanticDriftHard softDrift := tr.SemanticDriftSoft if len(hardDrift) == 0 && len(softDrift) == 0 { hardDrift = tr.SemanticDrift } for _, finding := range hardDrift { learning.CausalFindings = append(learning.CausalFindings, "IR execution semantic drift: "+finding) learning.CompilerImprovements = append(learning.CompilerImprovements, "enforce IR execution contract: "+finding) } for _, finding := range softDrift { learning.CausalFindings = append(learning.CausalFindings, "IR execution semantic variation: "+finding) } if tr.Cost.ToolCalls > len(tr.Steps)+3 && tr.Cost.ToolCalls >= 6 { learning.CompilerImprovements = append(learning.CompilerImprovements, "tool call count exceeded plan shape; prefer tighter execution steps") } return dedupeLearning(learning) } func isCompilerFeedbackNoise(s string) bool { normalized := strings.Join(strings.Fields(s), " ") if normalized == compilerIROverheadSelfFeedback { return true } lower := strings.ToLower(normalized) if lower == planModeBlockedToolError { return true } return strings.Contains(lower, planModeBlockedToolError) && (strings.Contains(lower, "repeated error") || strings.Contains(lower, "avoid repeating")) } func mutationsFromLearning(learning SystemLearning, baseline float64) []CompilerMutation { var out []CompilerMutation now := time.Now().UTC() for _, reason := range learning.CompilerImprovements { target := "strategy_selector" change := "add_constraint" if strings.Contains(reason, "source-of-truth") { target = "ir_builder" } else if strings.Contains(reason, "IR execution") { target = "ir_builder" } else if strings.Contains(reason, "tool call count") { target = "strategy_selector" change = "decrease_k" } else if strings.Contains(reason, "IR overhead") { target = "memory_router" change = "decrease_k" } out = append(out, CompilerMutation{ Target: target, Change: change, Reason: reason, EvidenceTraceIDs: []string{learning.TraceID}, Status: "testing", BaselineScore: baseline, Applied: true, CreatedAt: now, UpdatedAt: now, }) } for _, pattern := range learning.MemoryNoisePatterns { out = append(out, CompilerMutation{ Target: "noise_filter", Change: "quarantine_pattern", Reason: pattern, EvidenceTraceIDs: []string{learning.TraceID}, Status: "testing", BaselineScore: baseline, Applied: true, CreatedAt: now, UpdatedAt: now, }) } return out } func evaluateMutations(existing []CompilerMutation, tr ExecutionTrace) ([]CompilerMutation, []MutationEvaluation) { if len(existing) == 0 { return existing, nil } now := time.Now().UTC() score := traceQualityScore(tr) evaluations := []MutationEvaluation{} for i := range existing { m := &existing[i] if !m.Applied || m.Status == "accepted" || m.Status == "rejected" { continue } if m.Status == "" { m.Status = "testing" } if containsString(m.EvaluationTraceIDs, tr.ID) || containsString(m.EvidenceTraceIDs, tr.ID) { continue } m.EvaluationTraceIDs = append(m.EvaluationTraceIDs, tr.ID) trials := len(m.EvaluationTraceIDs) m.EvaluationScore = averageEvaluationScore(m.EvaluationScore, trials-1, score) m.UpdatedAt = now decision := "testing" m.EvaluationReason = fmt.Sprintf("collecting mutation validation traces (%d/%d)", trials, mutationMinEvalTrials) if trials >= mutationMinEvalTrials { if m.EvaluationScore >= mutationAcceptThreshold && m.EvaluationScore+mutationRegressionMargin >= m.BaselineScore { decision = "accepted" m.Applied = true m.Status = "accepted" m.EvaluationReason = "validation traces met confidence threshold without regressing baseline" } else { decision = "rejected" m.Applied = false m.Status = "rejected" m.EvaluationReason = "validation traces failed confidence threshold or regressed baseline; mutation rolled back" } } evaluations = append(evaluations, MutationEvaluation{ Target: m.Target, Change: m.Change, Reason: m.Reason, Decision: decision, Score: m.EvaluationScore, Baseline: m.BaselineScore, Trials: trials, }) } return existing, evaluations } func averageEvaluationScore(previous float64, previousTrials int, next float64) float64 { if previousTrials <= 0 { return next } return (previous*float64(previousTrials) + next) / float64(previousTrials+1) } func traceQualityScore(tr ExecutionTrace) float64 { score := 0.0 switch tr.Outcome { case "success": score += 0.7 case "partial_success": score += 0.4 default: score += 0.1 } score += tr.EfficiencyScore * 0.2 score += tr.MemoryEffectiveness * 0.1 if tr.Cost.ToolCalls > 0 { score -= float64(tr.Cost.ToolErrors) / float64(tr.Cost.ToolCalls) * 0.2 } if score < 0 { return 0 } if score > 1 { return 1 } return score } func baselineScore(st state, strategyID string) float64 { for _, s := range st.Strategies { if s.ID == strategyID && s.Samples() > 0 { return 0.2 + s.SuccessRate()*0.6 } } return 0.5 } func mergeMutations(existing []CompilerMutation, next ...CompilerMutation) []CompilerMutation { return mergeMutationsWithPolicy(defaultControlPolicy(), existing, next...) } func mergeMutationsWithPolicy(policy ControlPolicy, existing []CompilerMutation, next ...CompilerMutation) []CompilerMutation { if policy.MutationCooldown <= 0 { policy.MutationCooldown = mutationFeedbackCooldown } seen := map[string]bool{} out := existing[:0] for _, m := range existing { key := m.Target + "\x00" + m.Change + "\x00" + m.Reason if seen[key] { continue } seen[key] = true out = append(out, m) } for _, m := range next { key := m.Target + "\x00" + m.Change + "\x00" + m.Reason if seen[key] || !validMutation(m) || mutationFeedbackInCooldown(out, m, policy.MutationCooldown) { continue } seen[key] = true out = append(out, m) } if len(out) > 50 { out = out[len(out)-50:] } return out } func defaultControlPolicy() ControlPolicy { policy := ControlPolicy{ Version: version, Mode: "balanced", Controller: "distributed-control-plane", ExplorationRatePercent: explorationRatePercent, Gain: 1.0, EquilibriumState: "stable", EquilibriumActions: []string{"maintain global equilibrium"}, ControlGraphEntropy: 1, SystemStabilityScore: 1, MutationCooldown: mutationFeedbackCooldown, MutationCooldownMs: mutationFeedbackCooldown.Milliseconds(), Reasons: []string{"balanced distributed control policy"}, } return policy } func mutationFeedbackInCooldown(existing []CompilerMutation, next CompilerMutation, cooldown time.Duration) bool { if next.CreatedAt.IsZero() { return false } if cooldown <= 0 { cooldown = mutationFeedbackCooldown } for _, m := range existing { if m.Target != next.Target || m.Change != next.Change { continue } if m.Status == "accepted" || m.Status == "rejected" { continue } ref := m.UpdatedAt if ref.IsZero() { ref = m.CreatedAt } if ref.IsZero() { continue } delta := next.CreatedAt.Sub(ref) if delta < 0 { delta = -delta } if delta < cooldown { return true } } return false } func hasLearning(l SystemLearning) bool { return len(l.BadStrategies) > 0 || len(l.GoodPatterns) > 0 || len(l.MemoryNoisePatterns) > 0 || len(l.CausalFindings) > 0 || len(l.CompilerImprovements) > 0 } func appendLearning(existing []SystemLearning, learning SystemLearning) []SystemLearning { for _, l := range existing { if l.TraceID == learning.TraceID { return existing } } existing = append(existing, learning) if len(existing) > 100 { existing = existing[len(existing)-100:] } return existing } func updateNoisyRefs(existing map[string]int, learning SystemLearning) map[string]int { if existing == nil { existing = map[string]int{} } for _, pattern := range learning.MemoryNoisePatterns { pattern = strings.TrimSpace(pattern) if pattern == "" { continue } existing[pattern]++ } return existing } func applyDriftControl(st state, now time.Time, traceID string) (state, DriftReport) { report := DriftReport{TraceID: traceID, CreatedAt: now} st.Strategies = ensureBuiltInStrategies(st.Strategies) for _, s := range st.Strategies { if s.Samples() >= 5 && strategyUsageDecay(s.Samples()) < 0.65 { report.OverusedStrategies = append(report.OverusedStrategies, s.ID) } } for i := range st.Nodes { node := &st.Nodes[i] if node.TruthLocked || node.Quality == QualityCorrupted { continue } decayed := decayedConfidence(*node, now) if decayed < staleConfidenceThreshold { node.Quality = QualityNoise report.StaleMemoryNodes = append(report.StaleMemoryNodes, node.ID) } } conflicts, edges := detectMemoryConflicts(st.Nodes) for _, edge := range edges { st.Edges = appendEdge(st.Edges, edge) } if len(st.Edges) > 600 { st.Edges = st.Edges[len(st.Edges)-600:] } report.ConflictingFacts = conflicts report.OverusedStrategies = limitStrings(canonicalStrings(report.OverusedStrategies), 10) report.StaleMemoryNodes = limitStrings(canonicalStrings(report.StaleMemoryNodes), 10) report.ConflictingFacts = limitStrings(canonicalStrings(report.ConflictingFacts), 10) return st, report } func detectMemoryConflicts(nodes []MemoryNode) ([]string, []MemoryEdge) { var conflicts []string var edges []MemoryEdge for i := 0; i < len(nodes); i++ { for j := i + 1; j < len(nodes); j++ { if !factsContradict(nodes[i], nodes[j]) { continue } conflicts = append(conflicts, nodes[i].ID+" contradicts "+nodes[j].ID) edges = appendEdge(edges, MemoryEdge{From: nodes[i].ID, To: nodes[j].ID, Relation: "contradicts"}) if len(conflicts) >= 25 { return conflicts, edges } } } return conflicts, edges } func factsContradict(a, b MemoryNode) bool { if a.ID == b.ID || a.Quality == QualityCorrupted || b.Quality == QualityCorrupted { return false } aSubject, aOK := toolResultPolarity(a.Content) bSubject, bOK := toolResultPolarity(b.Content) return aOK && bOK && aSubject.name == bSubject.name && aSubject.success != bSubject.success } type toolPolarity struct { name string success bool } func toolResultPolarity(content string) (toolPolarity, bool) { content = strings.TrimSpace(content) if strings.HasSuffix(content, " succeeded") { name := strings.TrimSpace(strings.TrimSuffix(content, " succeeded")) return toolPolarity{name: name, success: true}, name != "" } if name, _, ok := strings.Cut(content, " failed:"); ok { name = strings.TrimSpace(name) return toolPolarity{name: name, success: false}, name != "" } return toolPolarity{}, false } func appendDriftReport(existing []DriftReport, report DriftReport) []DriftReport { if !hasDrift(report) { return existing } existing = append(existing, report) if len(existing) > 30 { existing = existing[len(existing)-30:] } return existing } func hasDrift(report DriftReport) bool { return len(report.OverusedStrategies) > 0 || len(report.StaleMemoryNodes) > 0 || len(report.ConflictingFacts) > 0 } func driftRiskNotes(report DriftReport) []string { if !hasDrift(report) { return nil } var out []string for _, id := range report.OverusedStrategies { out = append(out, "drift control: reduce overused strategy "+id) } for _, id := range report.StaleMemoryNodes { out = append(out, "drift control: ignore stale memory "+id) } for _, conflict := range report.ConflictingFacts { out = append(out, "drift control: resolve memory conflict "+conflict) } return limitStrings(canonicalStrings(out), 6) } func dedupeLearning(l SystemLearning) SystemLearning { l.BadStrategies = dedupeStrings(l.BadStrategies) l.GoodPatterns = dedupeStrings(l.GoodPatterns) l.MemoryNoisePatterns = dedupeStrings(l.MemoryNoisePatterns) l.CausalFindings = dedupeStrings(l.CausalFindings) l.CompilerImprovements = dedupeStrings(l.CompilerImprovements) return l } func dedupeStrings(in []string) []string { seen := map[string]bool{} out := in[:0] for _, s := range in { s = strings.TrimSpace(s) if s == "" || seen[s] { continue } seen[s] = true out = append(out, s) } return out } func containsString(ss []string, target string) bool { for _, s := range ss { if s == target { return true } } return false } func updateGraph(nodes []MemoryNode, edges []MemoryEdge, decisions []DecisionNode, tr ExecutionTrace, learning SystemLearning) ([]MemoryNode, []MemoryEdge, []DecisionNode) { now := time.Now().UTC() traceNode := MemoryNode{ ID: "trace:" + tr.ID, Type: "state", Content: fmt.Sprintf("goal=%s outcome=%s", tr.Goal, tr.Outcome), Timestamp: now, Confidence: confidenceForOutcome(tr.Outcome), Quality: qualityForOutcome(tr.Outcome), TruthLocked: false, } nodes = upsertNode(nodes, traceNode) decision := DecisionNode{ ID: "decision:" + tr.ID, Question: "Which execution strategy should guide this turn?", SelectedOption: firstNonEmpty(tr.StrategyUsed, classifyStrategy(tr.Goal)), RejectedOptions: rejectedOptions(tr.DecisionBranches), Reasoning: "Selected by Memory v5 strategy registry from goal classification and prior outcomes.", Timestamp: now, } decisions = appendDecision(decisions, decision) nodes = upsertNode(nodes, MemoryNode{ ID: decision.ID, Type: "decision", Content: decision.SelectedOption + ": " + decision.Reasoning, Timestamp: now, Confidence: confidenceForOutcome(tr.Outcome), Quality: QualityMediumSignal, TruthLocked: false, }) edges = appendEdge(edges, MemoryEdge{From: decision.ID, To: traceNode.ID, Relation: "derived_from"}) for i, rec := range tr.ToolResults { id := fmt.Sprintf("tool:%s:%d", tr.ID, i) quality := QualityHighSignal constraint := (*Constraint)(nil) conf := 0.95 content := rec.Name + " succeeded" if rec.Error != "" { quality = QualityMediumSignal conf = 0.85 content = rec.Name + " failed: " + firstLine(rec.Error) constraint = &Constraint{Type: "avoid", Text: "Do not repeat " + rec.Name + " with the same failing condition: " + firstLine(rec.Error), Source: id} } nodes = upsertNode(nodes, MemoryNode{ ID: id, Type: "tool_result", Content: content, Timestamp: now, Confidence: conf, Quality: quality, Constraint: constraint, TruthLocked: true, }) edges = appendEdge(edges, MemoryEdge{From: id, To: traceNode.ID, Relation: "derived_from"}) } for _, causal := range tr.CausalEdges { relation := graphRelation(causal.Relation) if relation == "" { continue } to := causal.To if strings.HasPrefix(to, "outcome:") { to = traceNode.ID } edges = appendEdge(edges, MemoryEdge{From: causal.From, To: to, Relation: relation}) } for i, reason := range learning.CompilerImprovements { id := fmt.Sprintf("learning:%s:%d", tr.ID, i) nodes = upsertNode(nodes, MemoryNode{ ID: id, Type: "fact", Content: reason, Timestamp: now, Confidence: 0.75, Quality: QualityHighSignal, Constraint: &Constraint{Type: "reference", Text: reason, Source: id}, TruthLocked: false, }) edges = appendEdge(edges, MemoryEdge{From: id, To: traceNode.ID, Relation: "supports"}) } for i, pattern := range learning.MemoryNoisePatterns { id := fmt.Sprintf("noise:%s:%d", tr.ID, i) nodes = upsertNode(nodes, MemoryNode{ ID: id, Type: "state", Content: pattern, Timestamp: now, Confidence: 0.9, Quality: QualityCorrupted, Constraint: &Constraint{Type: "avoid", Text: pattern, Source: id}, TruthLocked: false, }) edges = appendEdge(edges, MemoryEdge{From: id, To: traceNode.ID, Relation: "contradicts"}) } nodes = retainMemoryNodes(nodes, maxMemoryGraphNodes) edges = retainMemoryEdges(edges, maxMemoryGraphEdges) if len(decisions) > 100 { decisions = decisions[len(decisions)-100:] } return nodes, edges, decisions } func rejectedOptions(branches []DecisionBranch) []string { for _, branch := range branches { if branch.Question == "Which strategy should control this turn?" { return append([]string(nil), branch.Rejected...) } } return nil } func graphRelation(relation string) string { switch { case relation == "influenced", relation == "supported_outcome": return "supports" case relation == "constrained": return "depends_on" case relation == "weakened_outcome": return "contradicts" case strings.HasPrefix(relation, "selected_strategy:"): return "causes" default: return "" } } func updateExecutionState(prev ExecutionState, tr ExecutionTrace, learning SystemLearning) ExecutionState { st := ExecutionState{ GoalState: tr.Goal, CurrentPhase: phaseForOutcome(tr.Outcome), KnownFacts: append([]string(nil), prev.KnownFacts...), ActiveConstraints: append([]Constraint(nil), prev.ActiveConstraints...), FailedStrategies: append([]string(nil), prev.FailedStrategies...), UpdatedAt: time.Now().UTC(), } if tr.Outcome == "success" { st.KnownFacts = append(st.KnownFacts, "strategy succeeded: "+strings.Join(tr.StrategyUsed, ",")) } else { st.FailedStrategies = append(st.FailedStrategies, learning.BadStrategies...) } for _, improvement := range learning.CompilerImprovements { st.ActiveConstraints = appendConstraint(st.ActiveConstraints, Constraint{Type: "reference", Text: improvement, Source: "learning:" + learning.TraceID}) } st.KnownFacts = lastNStrings(dedupeStrings(st.KnownFacts), 40) st.FailedStrategies = lastNStrings(dedupeStrings(st.FailedStrategies), 20) if len(st.ActiveConstraints) > 40 { st.ActiveConstraints = st.ActiveConstraints[len(st.ActiveConstraints)-40:] } return st } func upsertNode(nodes []MemoryNode, next MemoryNode) []MemoryNode { if next.ID == "" { return nodes } for i, node := range nodes { if node.ID != next.ID { continue } if node.TruthLocked { return nodes } nodes[i] = next return nodes } return append(nodes, next) } func appendDecision(decisions []DecisionNode, next DecisionNode) []DecisionNode { for _, d := range decisions { if d.ID == next.ID { return decisions } } return append(decisions, next) } func appendEdge(edges []MemoryEdge, next MemoryEdge) []MemoryEdge { if next.From == "" || next.To == "" || next.Relation == "" { return edges } for _, e := range edges { if e == next { return edges } } return append(edges, next) } func confidenceForOutcome(outcome string) float64 { switch outcome { case "success": return 0.9 case "partial_success": return 0.65 default: return 0.45 } } func qualityForOutcome(outcome string) MemoryQuality { switch outcome { case "success": return QualityHighSignal case "partial_success": return QualityMediumSignal default: return QualityNoise } } func phaseForOutcome(outcome string) string { switch outcome { case "success": return "validated" case "partial_success": return "needs_followup" default: return "failed" } } func firstNonEmpty(ss []string, fallback string) string { for _, s := range ss { if strings.TrimSpace(s) != "" { return s } } return fallback } func lastNStrings(ss []string, n int) []string { if len(ss) <= n { return ss } return ss[len(ss)-n:] } func validMutation(m CompilerMutation) bool { switch m.Target { case "memory_router", "scoring", "ir_builder", "strategy_selector", "noise_filter": default: return false } switch m.Change { case "increase_weight", "decrease_weight", "decrease_k", "increase_k", "change_decay", "add_constraint", "quarantine_pattern": return true default: return false } } func updateStrategy(strategies []Strategy, id, outcome string, injected bool) []Strategy { if outcome == "aborted" { return strategies } id = strings.TrimSpace(id) if id == "" { id = "general" } strategies = ensureBuiltInStrategies(strategies) for i := range strategies { if strategies[i].ID != id { continue } if outcome == "success" { strategies[i].Successes++ if injected { strategies[i].InjectedSuccesses++ } } else { strategies[i].Failures++ if injected { strategies[i].InjectedFailures++ } } strategies[i].LastUsedAt = time.Now().UTC() return strategies } s := Strategy{ID: id, LastUsedAt: time.Now().UTC()} if outcome == "success" { s.Successes = 1 if injected { s.InjectedSuccesses = 1 } } else { s.Failures = 1 if injected { s.InjectedFailures = 1 } } return append(strategies, s) } func ensureBuiltInStrategies(strategies []Strategy) []Strategy { byID := map[string]int{} for i, s := range strategies { byID[s.ID] = i } for _, builtin := range builtInStrategies() { if idx, ok := byID[builtin.ID]; ok { if strategies[idx].Description == "" { strategies[idx].Description = builtin.Description } if len(strategies[idx].ExecutionPlan) == 0 { strategies[idx].ExecutionPlan = append([]Step(nil), builtin.ExecutionPlan...) } if len(strategies[idx].Preconditions) == 0 { strategies[idx].Preconditions = append([]string(nil), builtin.Preconditions...) } continue } strategies = append(strategies, builtin) } return strategies } func builtInStrategies() []Strategy { return []Strategy{ { ID: "code-review", Description: "Inspect the real execution path, prioritize bugs and regressions, then verify with focused checks.", Preconditions: []string{"review", "pr", "diff"}, ExecutionPlan: []Step{ {ID: "review-diff", Action: "Inspect the real diff and touched code paths."}, {ID: "verify-behavior", Action: "Run or identify focused checks that cover the changed behavior."}, {ID: "report-findings", Action: "Report only actionable findings with file and line evidence."}, }, }, { ID: "bugfix-reproduce-first", Description: "Reproduce or localize the failing behavior before patching, then validate the repair.", Preconditions: []string{"bug", "fix", "error", "修复"}, ExecutionPlan: []Step{ {ID: "reproduce", Action: "Reproduce or trace the failure to a concrete source of truth."}, {ID: "patch", Action: "Patch the smallest boundary that owns the failing behavior."}, {ID: "validate", Action: "Run focused validation that would fail before the patch."}, }, }, { ID: "frontend-visual-verify", Description: "Validate frontend work with type checks and a rendered UI inspection when behavior is visual.", Preconditions: []string{"frontend", "ui", "desktop", "前端"}, ExecutionPlan: []Step{ {ID: "inspect-ui", Action: "Locate the relevant component, state, and i18n wiring."}, {ID: "implement-ui", Action: "Implement the control using existing design-system patterns."}, {ID: "verify-ui", Action: "Run type checks and inspect the rendered interaction when practical."}, }, }, { ID: "long-horizon-autoresearch", Description: "Use durable state, evidence, and pivots for long-running goals.", Preconditions: []string{"goal", "research", "持续"}, ExecutionPlan: []Step{ {ID: "load-state", Action: "Read the durable task state and previous directions."}, {ID: "evidence-chunk", Action: "Execute the smallest evidence-producing next chunk."}, {ID: "writeback", Action: "Persist trace, findings, and next constraints before reporting."}, }, }, { ID: "general", Description: "Default source-first execution strategy.", ExecutionPlan: []Step{ {ID: "inspect", Action: "Inspect current state before acting."}, {ID: "change", Action: "Make the smallest change that satisfies the task."}, {ID: "check", Action: "Run focused validation and summarize evidence."}, }, }, } } func classifyStrategy(goal string) string { lower := strings.ToLower(goal) switch { case strings.Contains(lower, "review") || strings.Contains(goal, "评审"): return "code-review" case strings.Contains(lower, "bug") || strings.Contains(lower, "fix") || strings.Contains(goal, "修复"): return "bugfix-reproduce-first" case strings.Contains(lower, "frontend") || strategyPreconditionMatches(goal, "ui") || strings.Contains(goal, "前端"): return "frontend-visual-verify" case strings.Contains(lower, "goal") || strings.Contains(lower, "research") || strings.Contains(goal, "持续"): return "long-horizon-autoresearch" default: return "general" } } func summarizeGoal(input string) string { input = strings.TrimSpace(input) input = strings.Join(strings.Fields(input), " ") if len([]rune(input)) > 180 { r := []rune(input) return string(r[:180]) + "..." } return input } // stripReferencedContext removes the "Referenced context:" preamble and the XML // reference blocks (///) the controller injects when // the user @-references files, returning the user's actual text. Used only for // goal classification (not SourceEvent). This duplicates // control.StripReferencedContextPrefix on purpose: memorycompiler cannot import // control because control imports the agent package that drives this runtime, so // the two must stay in sync by convention. func stripReferencedContext(content string) string { const preamble = "Referenced context:" s := strings.TrimSpace(content) if !strings.HasPrefix(s, preamble) { return content } s = strings.TrimSpace(s[len(preamble):]) for { s = strings.TrimSpace(s) if s == "" { return "" } if !strings.HasPrefix(s, "" closeIdx := strings.Index(s, closeTag) if closeIdx < 0 { break } s = strings.TrimSpace(s[closeIdx+len(closeTag):]) } return s } func traceID(t time.Time) string { return fmt.Sprintf("%s-%x", t.UTC().Format("20060102T150405.000000000"), rand.Int63()) } func firstLine(s string) string { s = strings.TrimSpace(s) if i := strings.IndexByte(s, '\n'); i >= 0 { s = s[:i] } return s } func (r *Runtime) loadState() state { r.mu.Lock() defer r.mu.Unlock() return r.loadStateLocked() } func (r *Runtime) loadStateLocked() state { var st state path := filepath.Join(r.dir, stateFile) b, err := fileencoding.ReadFileUTF8(path) if err != nil { return state{NoisyRefs: map[string]int{}} } if err := json.Unmarshal(b, &st); err != nil { _ = os.WriteFile(fmt.Sprintf("%s.corrupt-%d", path, time.Now().UnixNano()), b, 0o600) return state{NoisyRefs: map[string]int{}} } if st.NoisyRefs == nil { st.NoisyRefs = map[string]int{} } return st } func appendJSONL(path string, v any) error { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return err } f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600) if err != nil { return err } defer f.Close() _ = f.Chmod(0o600) w := bufio.NewWriter(f) if err := json.NewEncoder(w).Encode(v); err != nil { return err } return w.Flush() } func appendBoundedJSONL(path string, v any, maxLines int) error { if maxLines <= 0 { return appendJSONL(path, v) } if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return err } line, err := json.Marshal(v) if err != nil { return err } existing, err := fileencoding.ReadFileUTF8(path) if err != nil && !os.IsNotExist(err) { return err } var lines [][]byte if trimmed := bytes.TrimRight(existing, "\n"); len(trimmed) > 0 { lines = bytes.Split(trimmed, []byte("\n")) } lines = append(lines, line) if len(lines) > maxLines { lines = lines[len(lines)-maxLines:] } out := bytes.Join(lines, []byte("\n")) out = append(out, '\n') return fileutil.AtomicWriteFile(path, out, 0o600) } func writeJSON(path string, v any) error { b, err := json.MarshalIndent(v, "", " ") if err != nil { return err } b = append(b, '\n') return fileutil.AtomicWriteFile(path, b, 0o600) }