Class: Insika::Harvest::Gate
- Inherits:
-
Object
- Object
- Insika::Harvest::Gate
- Defined in:
- lib/insika/harvest/gate.rb
Overview
C7 — the eval half of the double gate (D7). Scores ONE candidate by RUNNING it — the Refinement::Gate mechanism, with the skill's apply:
1. clone the agent into `<agent>-harvest-<run8>`,
2. copy its instruction files, write the candidate skill into the
clone's AGENT-scoped SkillStore and enable it on the clone's
allowlist (the clone's catalog then serves it — the skill is gated
by being *usable*, not by prose),
3. replay the golden set against the clone over the ordinary public
surface, compare to the accepted baseline,
4. ANY regression disqualifies,
5. destroy the clone in an `ensure`.
Judges are MANDATORY in exactly the three refusal shapes the refined gate already encodes (no baseline / all-red baseline / judged baseline without a judge) — the P18 stamp. A skill that regresses ANY golden case is rejected — this gate is a veto, never a score to argue with.
Defined Under Namespace
Classes: Report
Constant Summary collapse
- DEFAULT_TOLERANCE =
0.05
Instance Method Summary collapse
- #clone_id_for(agent_id, run_id) ⇒ Object
-
#initialize(profiles:, agent_files:, goldens:, baselines:, skill_store:, skill_catalog:, transport_factory:, capabilities_factory: nil, judge_factory: nil, tolerance: DEFAULT_TOLERANCE) ⇒ Gate
constructor
skill_catalog: the graph's catalog (overlays the SkillStore) — the apply writes into the store then RELOADS it, so the clone's next dispatch serves the skill.
- #judged?(baseline) ⇒ Boolean
- #passing_cases(baseline) ⇒ Object
-
#score(agent_id:, skill:, run_id:) ⇒ Object
skill: the candidate's { name:, description:, body:, triggers: }.
Constructor Details
#initialize(profiles:, agent_files:, goldens:, baselines:, skill_store:, skill_catalog:, transport_factory:, capabilities_factory: nil, judge_factory: nil, tolerance: DEFAULT_TOLERANCE) ⇒ Gate
skill_catalog: the graph's catalog (overlays the SkillStore) — the apply writes into the store then RELOADS it, so the clone's next dispatch serves the skill. judge_factory: the settings panel (the same one the refinement gate receives).
41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/insika/harvest/gate.rb', line 41 def initialize(profiles:, agent_files:, goldens:, baselines:, skill_store:, skill_catalog:, transport_factory:, capabilities_factory: nil, judge_factory: nil, tolerance: DEFAULT_TOLERANCE) @profiles = profiles @agent_files = agent_files @goldens = goldens @baselines = baselines @skill_store = skill_store @skill_catalog = skill_catalog @transport_factory = transport_factory @capabilities_factory = capabilities_factory @judge_factory = judge_factory @tolerance = tolerance end |
Instance Method Details
#clone_id_for(agent_id, run_id) ⇒ Object
97 |
# File 'lib/insika/harvest/gate.rb', line 97 def clone_id_for(agent_id, run_id) = "#{agent_id}-harvest-#{run_id.to_s.delete('-')[0, 8]}" |
#judged?(baseline) ⇒ Boolean
103 104 105 |
# File 'lib/insika/harvest/gate.rb', line 103 def judged?(baseline) (baseline["cases"] || {}).any? { |_id, entry| entry.is_a?(Hash) && !entry["score"].nil? } end |
#passing_cases(baseline) ⇒ Object
99 100 101 |
# File 'lib/insika/harvest/gate.rb', line 99 def passing_cases(baseline) (baseline["cases"] || {}).count { |_id, entry| entry.is_a?(Hash) && entry["pass"] } end |
#score(agent_id:, skill:, run_id:) ⇒ Object
skill: the candidate's { name:, description:, body:, triggers: }. -> Report. Never raises for an ordinary refusal (no cases, no baseline, a replay that blew up).
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/insika/harvest/gate.rb', line 59 def score(agent_id:, skill:, run_id:) cases = @goldens.for_agent(agent_id) return refusal(skill, "the agent has no golden cases — nothing to gate against") if cases.empty? baseline = @baselines.get(agent_id) if baseline.nil? return refusal(skill, "no recorded baseline for '#{agent_id}' — " \ "run `insika evals:baseline import` or record one before gating") end if passing_cases(baseline).zero? return refusal(skill, "the recorded baseline for '#{agent_id}' has no PASSING case " \ "(#{baseline_size(baseline)} recorded, all failing) — nothing could " \ "regress, so every candidate would pass. Fix the agent or the cases, " \ "then re-record the baseline from a green run") end judge = @judge_factory&.call if judge.nil? && judged?(baseline) return refusal(skill, "the recorded baseline for '#{agent_id}' carries judge scores but no " \ "judge is configured — a rubric'd case with no verdict counts as a " \ "PASS, so every candidate would beat it. Configure the judge panel " \ "(Studio → Settings → Evals, or `settings[\"evals\"][\"judges\"]`) or " \ "re-record the baseline without one") end clone_id = clone_id_for(agent_id, run_id) begin build_clone(agent_id, clone_id, skill) ran = replay(cases, clone_id, judge) verdict(skill, ran, baseline, @tolerance) rescue StandardError => e refusal(skill, "gate failed to run: #{e.class}: #{e.}") ensure destroy_clone(clone_id) end end |