LemonRouter. RolloutGate
(lemon_router v0.1.0)
View Source
Measurable graduation gates for adaptive features.
Defines the quantitative thresholds that must be satisfied before a feature
flag can be promoted from opt-in to default-on. The gates are evaluated
against metrics snapshots — they do not read the database directly.
Covered features
:routing_feedback (M7-01)
History-aware model selection is safe to enable by default when all of:
- Sample size ≥ 50 — enough recorded runs to trust
aggregated statistics. Below this threshold the store returns
{:insufficient_data, _}and the router falls back to profile defaults. - Success delta ≥ +0.05 — the history-preferred model improves the observed success rate by at least 5 percentage points compared to the session-default model on the same task fingerprint family.
- Retry delta ≤ +0.05 — the retry rate has not increased (or has actively decreased) by more than the tolerance.
:skill_synthesis_drafts (M7-02)
Automated skill draft generation is safe to enable by default when all of:
- Candidate count ≥ 20 — enough memory documents evaluated to judge pipeline breadth and quality.
- Generation rate ≥ 0.6 — at least 60 % of qualified candidates produce a written draft (pipeline is not over-filtering or crashing silently).
- False-positive rate ≤ 0.1 — at most 10 % of generated drafts are blocked by the audit engine (content safety is working correctly and not blocking legitimate skill patterns).
Rollback procedure
If a feature is promoted to default-on and unexpected behaviour is observed:
Immediate kill-switch: set the flag to
"off"in~/.lemon/config.tomlor via environment variable:echo 'routing_feedback = "off"' >> ~/.lemon/config.toml # or export LEMON_FEATURE_ROUTING_FEEDBACK=offRestart the Lemon agent process (or run
mix lemon.setupto reload config in a live system).No data migration required — the routing feedback store and draft store are read-only from the feature gate's perspective. Disabling a feature stops new data from being written but does not delete existing records.
Re-evaluation: after rollback, investigate the feedback store via
LemonRouter.RoutingFeedbackReport.list_all/1or the draft store viamix lemon.skill draft list. Re-runevaluate_routing_feedback/1orevaluate_synthesis/1with fresh metrics before re-enabling.
Usage
metrics = %{
total_samples: 65,
success_rate: 0.72,
baseline_success_rate: 0.62,
retry_rate: 0.09,
baseline_retry_rate: 0.13
}
case LemonRouter.RolloutGate.evaluate_routing_feedback(metrics) do
{:ready, computed} ->
IO.puts("Gate passed — promote routing_feedback to default-on")
IO.inspect(computed)
{:not_ready, reasons, computed} ->
IO.puts("Gate blocked:\n" <> Enum.join(reasons, "\n"))
IO.inspect(computed)
end
Summary
Functions
Evaluate the rollout gate for the :routing_feedback feature.
Evaluate the :routing_feedback gate from raw store data.
Evaluate the rollout gate for the :skill_synthesis_drafts feature.
Evaluate the :skill_synthesis_drafts gate from a pipeline run result.
Maximum allowed retry-rate increase (fractional) for :routing_feedback to graduate.
Minimum recorded runs before :routing_feedback can graduate to default-on.
Minimum success-rate improvement (fractional) for :routing_feedback to graduate.
Maximum fraction of generated drafts that may be blocked by the audit engine.
Minimum candidate documents evaluated before :skill_synthesis_drafts can graduate.
Minimum fraction of candidates that must produce a written draft.
Types
@type computed_routing() :: %{ total_samples: non_neg_integer(), success_rate: float(), baseline_success_rate: float(), success_delta: float(), retry_delta: float() }
@type computed_synthesis() :: %{ total_candidates: non_neg_integer(), generated: non_neg_integer(), blocked_by_audit: non_neg_integer(), generation_rate: float(), false_positive_rate: float() }
@type gate_result(computed) :: {:ready, computed} | {:not_ready, [String.t()], computed}
@type routing_metrics() :: %{ total_samples: non_neg_integer(), success_rate: float(), baseline_success_rate: float(), retry_rate: float(), baseline_retry_rate: float() }
@type synthesis_metrics() :: %{ total_candidates: non_neg_integer(), generated: non_neg_integer(), blocked_by_audit: non_neg_integer() }
Functions
@spec evaluate_routing_feedback(routing_metrics()) :: gate_result(computed_routing())
Evaluate the rollout gate for the :routing_feedback feature.
Takes a metrics snapshot and checks all three graduation gates.
Parameters
metrics— map with keys::total_samples— total runs recorded in the feedback store:success_rate— observed success rate with history-preferred routing:baseline_success_rate— success rate without history-preferred routing:retry_rate— observed retry rate with history-preferred routing:baseline_retry_rate— retry rate without history-preferred routing
Returns
{:ready, computed} # all gates pass — safe to promote
{:not_ready, reasons, computed} # one or more gates failed
Evaluate the :routing_feedback gate from raw store data.
Unlike evaluate_routing_feedback/1, this variant accepts the output of
RoutingFeedbackStore.store_stats/0 and RoutingFeedbackStore.list_fingerprints/0
directly, using absolute success/failure rate thresholds instead of deltas.
Returns {:pass, notes} or {:fail, failures}.
@spec evaluate_synthesis(synthesis_metrics()) :: gate_result(computed_synthesis())
Evaluate the rollout gate for the :skill_synthesis_drafts feature.
Takes a metrics snapshot and checks all three graduation gates.
Parameters
metrics— map with keys::total_candidates— candidate documents evaluated by the pipeline:generated— drafts successfully written to the draft store:blocked_by_audit— generated drafts blocked by the audit engine
Returns
{:ready, computed} # all gates pass — safe to promote
{:not_ready, reasons, computed} # one or more gates failed
Evaluate the :skill_synthesis_drafts gate from a pipeline run result.
Unlike evaluate_synthesis/1, this variant accepts the structured output of
LemonSkills.Synthesis.Pipeline.run/3 (with generated as a list of keys and
skipped as a list of {key, reason} tuples) rather than pre-counted integers.
Returns {:pass, notes} or {:fail, failures}.
@spec routing_max_retry_delta_abs() :: float()
Maximum allowed retry-rate increase (fractional) for :routing_feedback to graduate.
@spec routing_min_samples() :: pos_integer()
Minimum recorded runs before :routing_feedback can graduate to default-on.
@spec routing_min_success_delta() :: float()
Minimum success-rate improvement (fractional) for :routing_feedback to graduate.
@spec synthesis_max_fp_rate() :: float()
Maximum fraction of generated drafts that may be blocked by the audit engine.
@spec synthesis_min_candidates() :: pos_integer()
Minimum candidate documents evaluated before :skill_synthesis_drafts can graduate.
@spec synthesis_min_generation_rate() :: float()
Minimum fraction of candidates that must produce a written draft.