Skip to main content

Module evolution

Module evolution 

Source
Expand description

Self-evolution governor — live daemon wiring (arXiv 2507.21046, the remaining daemon steps from docs/proposals/self-evolution-governor.md / docs/proposals/remaining-integration-work.md §3).

Three pieces live here:

  1. Signal populaters for the components memgine can’t see. The engine folds Memory / Skills / Context off its own graph (MemgineEngine::evolution_component_states); the daemon appends:
    • Harnessharness_component_from_events over the session event log via car_eventlog::harness_adapt::diagnose. Pressure = min(1, implicated / total_events) where implicated sums each recurring intervention’s evidence_count — the fraction of logged events implicated in a recurring failure pattern (one-offs are noise by the diagnosis’s own rule). Evidence = the number of events diagnosed over.
    • Toolstools_component_from_connectors over the live connector registry. Pressure = disconnected / total connectors. Evidence = the connector count — ConnectorStatus carries no per-connector call counters, so the population size is the honest evidence figure (min_evidence 1: even one broken connector is real).
  2. The real executor behind evolution.run and the cadence timer: Memory → consolidate() sized by maintenance::decide_maintenance, Skills → evolve_skills(failed_events, domain) over failure traces folded from the event log (failed_trace_events), Harness → the harness_evolution diagnose→gate→apply loop (HITL-gated; handler.rs owns that arm because it needs the session ApprovalLedger), Context → run_context_evolution, the context_evolution loop over the engine’s own conversation-layer saturation. That arm now has TWO authorization paths: an opt-in pre-activation grade (context_measure → two bench replays over the same split, one under the live MemgineConfig and one under it plus the patch, graded on task outcomes by EvolutionAgent::evaluate_context, promoting or rejecting with no human in the loop) and, for everything the grade did not run on or did not decide, the original diagnose→approve→apply→measure→revert human path with its post-apply margin check retained as defence in depth. Tools has no mechanism by decision and says so (TOOLS_OUT_OF_SCOPE_REASON) rather than erroring: a scope boundary reported as a failed step is how a working system reads as a broken one.
  3. The autonomous cadence timer (spawn_evolution_cadence): one background task over the daemon’s shared engine, opt-in via .car/config.toml evolution_interval_secs (absent/0 = off). Guarded by CycleGuard so a slow cycle is never overlapped by the next tick; each cycle’s outcome is appended as an EvolutionTriggered event to a dedicated journal (<journal_dir>/evolution.jsonl). The task dies with the daemon’s tokio runtime, like every other boot timer.

Structs§

ContextBackoff
Per-fingerprint exponential backoff for context mutations whose post-apply measurement falsified them (kernel review S5, applied to Context).
CycleGuard
Non-overlap guard for the cadence timer: a tick that arrives while the previous cycle is still running is skipped, never queued. RAII — dropping the token releases the guard even if the cycle errors.
CycleToken
Held while a cycle runs; releases the guard on drop.
HarnessMeasureRequest
What to replay. Serde-derived: it is the harness_measure param of evolution.run verbatim.
SkillsBackoff
Per-domain exponential backoff state for the cadence Skills arm.

Constants§

TOOLS_OUT_OF_SCOPE_REASON
The reason the Tools pillar records when it is planned. Verbatim in both call sites (evolution.run and the cadence) so an operator reading either report gets the same explanation, and so changing the boundary is one edit.

Traits§

HarnessMeasurer
What the daemon needs in order to grade a harness candidate ITSELF.

Functions§

failed_trace_events
Fold the failure events evolve_skills consumes from an event-log tail: ActionFailed / ActionRejected / PolicyViolation / ReplanExhausted become TraceEvents (kind = the event kind’s snake_case name, tool lifted from data.tool when the executor recorded one, reward 0.0). This is what the session event log genuinely carries — per-action failure records, not full state-before/after trajectories; those fields stay None rather than being fabricated.
harness_component_from_events
Fold the Harness component’s evolution signals from an event-log tail.
harness_component_from_metrics
Fold the Harness component’s evolution signals from a caller-supplied car_eventlog::harness_metrics::HarnessMetrics snapshot — the planning twin of harness_component_from_events for evolution.run callers that pass harness_baseline_metrics (their own held-out telemetry). Pressure = failed attempts over total attempts; evidence = total attempts. None when the metrics record no attempts (nothing observed).
maintenance_input_from_stats
Price the localized-vs-global maintenance decision off the live car_memgine::memsys::MemoryStats: dirty regions = the reconciliation backlog (outstanding_outdated + facts_superseded — the same backlog the Memory pressure signal counts), total regions = total_facts, unit cost per region on both paths, global structural gain = the supersede-churn share (a store reorganization’s upside is proportional to how much of the store has churned), valued at one region per full unit of gain times the store size. Deterministic, no fabricated constants beyond the unit costs.
measure_baseline
Measure the harness AS IT STANDS — the baseline half of the comparison.
measure_candidate
Measure a candidate: base with patch applied, replayed on the same split under the mutated config.
measure_context_baseline
Measure the context config AS IT STANDS — the baseline half of the pre-activation comparison.
measure_context_candidate
Measure a context candidate: base with patch applied, replayed on the same split under the mutated context config.
parse_context_measure_request
Read the context_measure param of evolution.run. Ok(None) = not requested; a malformed object is an error, never a silently defaulted request.
parse_harness_measure_request
Read the harness_measure param of evolution.run. Ok(None) = not requested (measuring is strictly opt-in); a malformed object is an error, never a silently defaulted request — a typo’d model id would otherwise spend a real benchmark replay on the wrong model.
run_context_evolution
The Context arm of the evolution executor — the pillar’s real mechanism (car_memgine::context_evolution), replacing the not_executable error that used to make a documented boundary read as a failing subsystem.
run_evolution_cadence_cycle
Run one unattended evolution cycle over the daemon’s shared engine: fold its live Memory / Skills / Context signals plus Tools from connector health, plan under the default policy, and dispatch EvolveNow components to the same mechanics as evolution.run (dry_run = false).
run_memory_evolution
The Memory arm of the evolution executor: size the pass with decide_maintenance (localized vs global — recorded, since consolidate() is the single live mechanism for both today) and run engine.consolidate(). dry_run skips the consolidate and reports what would run (applied == false).
run_skills_evolution
The Skills arm of the evolution executor: evolve_skills(failed_events, domain) for every domain domains_needing_evolution flags (success rate below 0.6 with ≥3 recorded outcomes — the engine’s own threshold). Errors with "no inference engine" when the session engine has no model — evolution is inference-backed and silently returning nothing would be a stub. failed_events is whatever failure trace the caller’s event source genuinely holds (possibly empty — the domain outcome stats, not the traces, are what elect a domain for evolution).
run_skills_evolution_backoff
The cadence-timer Skills arm: like run_skills_evolution but backoff-gated per domain (kernel review S5) and — being unattended, with no session — running over an empty failure-trace set (the engine’s own per-domain outcome stats are what elect a domain; see the cadence scope notes on run_evolution_cadence_cycle).
seed_evolution_interval
Read the opt-in cadence interval from the .car/ project’s config.toml (evolution_interval_secs), discovered from the same anchor as crate::seed_memgine_config: $CAR_PROJECT_DIR when set, else the process cwd. Absent, 0, or no project → None (off — the no-surprise default).
spawn_evolution_cadence
Spawn the autonomous cadence timer (opt-in via .car/config.toml evolution_interval_secs): ONE background task over the daemon’s shared engine that every interval_secs runs run_evolution_cadence_cycle and appends the outcome as an EvolutionTriggered event (data.source = "cadence") to <journal_dir>/evolution.jsonl — capped at [EVOLUTION_LOG_MAX_EVENTS] in memory, and no-op cycles (nothing planned, nothing run) are not appended (kernel review S4), so an idle daemon doesn’t mint an audit line per tick. A tick that lands while the previous cycle is still running is skipped (CycleGuard); each cycle runs in its own task so a panic is isolated to that tick. The task ends with the daemon’s tokio runtime. Returns None (and warns) when the daemon has no shared engine to evolve.
tools_component_from_connectors
Fold the Tools component’s evolution signals from connector health.