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:
- 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:- Harness ←
harness_component_from_eventsover the session event log viacar_eventlog::harness_adapt::diagnose. Pressure =min(1, implicated / total_events)whereimplicatedsums each recurring intervention’sevidence_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. - Tools ←
tools_component_from_connectorsover the live connector registry. Pressure = disconnected / total connectors. Evidence = the connector count —ConnectorStatuscarries no per-connector call counters, so the population size is the honest evidence figure (min_evidence 1: even one broken connector is real).
- Harness ←
- The real executor behind
evolution.runand the cadence timer: Memory →consolidate()sized bymaintenance::decide_maintenance, Skills →evolve_skills(failed_events, domain)over failure traces folded from the event log (failed_trace_events), Harness → theharness_evolutiondiagnose→gate→apply loop (HITL-gated; handler.rs owns that arm because it needs the sessionApprovalLedger), Context →run_context_evolution, thecontext_evolutionloop 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 liveMemgineConfigand one under it plus the patch, graded on task outcomes byEvolutionAgent::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. - The autonomous cadence timer (
spawn_evolution_cadence): one background task over the daemon’s shared engine, opt-in via.car/config.tomlevolution_interval_secs(absent/0 = off). Guarded byCycleGuardso a slow cycle is never overlapped by the next tick; each cycle’s outcome is appended as anEvolutionTriggeredevent to a dedicated journal (<journal_dir>/evolution.jsonl). The task dies with the daemon’s tokio runtime, like every other boot timer.
Structs§
- Context
Backoff - Per-fingerprint exponential backoff for context mutations whose post-apply measurement falsified them (kernel review S5, applied to Context).
- Cycle
Guard - 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.
- Cycle
Token - Held while a cycle runs; releases the guard on drop.
- Harness
Measure Request - What to replay. Serde-derived: it is the
harness_measureparam ofevolution.runverbatim. - Skills
Backoff - 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.runand the cadence) so an operator reading either report gets the same explanation, and so changing the boundary is one edit.
Traits§
- Harness
Measurer - What the daemon needs in order to grade a harness candidate ITSELF.
Functions§
- failed_
trace_ events - Fold the failure events
evolve_skillsconsumes from an event-log tail:ActionFailed/ActionRejected/PolicyViolation/ReplanExhaustedbecomeTraceEvents (kind = the event kind’s snake_case name, tool lifted fromdata.toolwhen 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 stayNonerather 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::HarnessMetricssnapshot — the planning twin ofharness_component_from_eventsforevolution.runcallers that passharness_baseline_metrics(their own held-out telemetry). Pressure = failed attempts over total attempts; evidence = total attempts.Nonewhen 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:
basewithpatchapplied, 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:
basewithpatchapplied, replayed on the same split under the mutated context config. - parse_
context_ measure_ request - Read the
context_measureparam ofevolution.run.Ok(None)= not requested; a malformed object is an error, never a silently defaulted request. - parse_
harness_ measure_ request - Read the
harness_measureparam ofevolution.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 thenot_executableerror 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
EvolveNowcomponents to the same mechanics asevolution.run(dry_run = false). - run_
memory_ evolution - The Memory arm of the evolution executor: size the pass with
decide_maintenance(localized vs global — recorded, sinceconsolidate()is the single live mechanism for both today) and runengine.consolidate().dry_runskips 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 domaindomains_needing_evolutionflags (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_eventsis 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_evolutionbut 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 onrun_evolution_cadence_cycle). - seed_
evolution_ interval - Read the opt-in cadence interval from the
.car/project’sconfig.toml(evolution_interval_secs), discovered from the same anchor ascrate::seed_memgine_config:$CAR_PROJECT_DIRwhen 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.tomlevolution_interval_secs): ONE background task over the daemon’s shared engine that everyinterval_secsrunsrun_evolution_cadence_cycleand appends the outcome as anEvolutionTriggeredevent (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. ReturnsNone(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.