pub async fn run_context_evolution(
engine: &Arc<Mutex<MemgineEngine>>,
state: &Arc<ServerState>,
dry_run: bool,
pending: &Mutex<Vec<Value>>,
backoff: Option<(&Mutex<ContextBackoff>, u64)>,
measure: Option<(&dyn HarnessMeasurer, &HarnessMeasureRequest)>,
) -> Result<EvolutionOutcome, String>Expand description
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.
The shape mirrors the Harness arm in handler.rs: diagnose from live
signals, fingerprint each mutation, resolve it against the daemon’s SHARED
durable approval ledger (approve on one connection, apply on another,
survives a restart), and report a per-mutation detail object plus a summary.
Authorization resolves most-binding-first, over two paths. A durable operator decision always wins; only a mutation nobody has decided on reaches the pre-activation gate; only a mutation the gate could not run on (or could not decide) reaches the human gate:
Rejectedin the ledger →rejected_by_operator. An operator’s “no” is the most binding thing in the system and is never re-litigated by a measurement.Approvedin the ledger → the human-approved path below, unchanged: backoff check, dry-run, then the one-lock baseline-compact / apply / re-compact / revert-unless-positive margin measurement.- No prior decision and a grade is runnable — a measurer and a
context_measurerequest were handed in, this is not adry_run, and the mutation carries a patch thatcar_memgine::context_evolution::requires_human_approvalsays is gradeable → the pre-activation gate. Read the livecar_memgine::MemgineConfigoff the engine, replay the split twice (baseline under the live config, candidate under the live config plus the patch — seemeasure_context_baseline/measure_context_candidate), and hand both documents tocar_memgine::harness_evolution::EvolutionAgent::evaluate_context, which is literally the same gate, with the same guards, that grades a harness mutation.Promoteapplies for real (applied,governance: "promoted");Rejectapplies nothing and reportsrejected_by_gate;NeedsApproval/Incomparablefall through topending_approvalcarrying the gate’s own reason; a replay error reportsmeasurement_failedand applies nothing; and aPromotewhose measured base moved before the apply reportsconfig_moved_during_measurementand also applies nothing. - Anything else →
pending_approval, with a reason naming the precondition that was missing ([context_pending_reason]).
Three properties of that resolution are load-bearing:
rejected_by_gatedoes NOT fall through to the human gate. It is a verdict, not an absence of one: the daemon measured this exact change on task outcomes and it came back a regression. Listing it for approval would invite an operator to approve a change the gate had just measured as worse — and because the ledger is daemon-wide and keyed on the change, that approval would then stand on every engine, permanently, over the top of a real measurement. An operator who disagrees can still approve the fingerprint directly throughpermission.approve; what must not happen is the daemon soliciting it.- A
measurement_failedmutation is not falsified. The measurement was.ContextBackoff::note_falsifiedis deliberately NOT called on that path: backing a mutation off because the bench errored would punish the change for an infrastructure failure and delay the retry that would have graded it honestly. - A promotion is re-checked against the live config before it applies.
The engine lock is DROPPED across the two replays, so the config the grade
was measured under can move before the apply — another session’s
evolution.runover the same engine, the human-approved path, a cadence tick. Under the same lock hold that would apply the patch, the fields acar_memgine::ContextConfigPatchcan reach are compared against the config the baseline ran under (context_patch_base_moved). If they moved, the step reportsconfig_moved_during_measurementcarrying both values and applies NOTHING: the verdict was computed against a base that no longer exists, and the inverse patchapply_context_patchhands back would describe the CURRENT value rather than the measured one, so even the rollback the contract promises would restore the wrong config. Likemeasurement_failed, this records NO backoff — the measurement was invalidated, not the change — and the correct recovery is a later cycle re-diagnosing and re-measuring against the new base.
The post-apply margin measurement is retained, unchanged, on the human-approved path (2) — defence in depth, and the only automatic check on a change an operator authorized without asking for a grade:
- On that path the measurement happens AFTER the apply, and it measures
the MARGIN.
Under ONE lock acquisition on the engine — otherwise another task’s ingest
would be credited or blamed — the arm compacts under the unchanged
conversation_keep_recentfirst (conversation_tokens_baseline), then applies the patch, compacts again, and re-reads (conversation_tokens_after). Comparing against that baseline rather than against the uncompacted layer is the load-bearing part: the uncompacted comparison would credit the mutation with every token compaction was going to save anyway, and on a change an operator authorized without asking for a grade this is the ONLY automatic check on it. If the margin is not positive, the contract predicted something that did not happen, so the inverse patch goes back on inside the same lock hold and the step reportsrolled_back(orrollback_failed, its own status, when even that does not take) and counts nothing as applied. Note what a rollback does and does not restore: the config knob goes back, the summarization performed while measuring does not — compaction replaces turns with summaries and keeps them in the layer, which is what the engine’s own heuristic does at this saturation anyway. backoffisSomeonly on the unattended cadence. A falsified mutation restores the knob, so the next tick re-diagnoses it, re-matches the same standing approval and repeats the whole apply-measure-revert round under the engine lock — forever.ContextBackoffis that brake, keyed per fingerprint, mirroringSkillsBackoff(kernel review S5). A session-drivenevolution.runpassesNone: a person asking for the check now should get it now.measureisSomeonly when the caller suppliedcontext_measureAND this build has an in-process measurer installed. The unattended cadence passesNoneon purpose: a timer that started spending benchmark replays because someone setevolution_interval_secswould turn an opt-in cost into a background one.dry_runis honoured here rather than by the caller so the pending reason can say which precondition was missing — a dry run that reports “you did not ask for a grade” would be lying.