Files
Open_Ultra_SAST/.kiro/specs/hunter-learning-loop/research.md
T
Marius CiepluchandClaude Fable 5.1 dc2da3d83a spec(hunter-learning-loop): gap analysis and design
Feature engineering is the centre: a feature table with switchable columns
is the contract between producers (source.facts_for, semantic facts, entry
points, diff hunks; lizard and Semgrep only after their evaluations), the
ablation runner and the hunter program. DSPy runs the program (ReAct over a
HuntSignature; GEPA and MIPROv2 propose), our rule accepts on held-out
folds. The pilot stays held out; the hook, its client and the core install
are untouched. Requirements approved by the maintainer ('4 first').

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-13 12:14:05 +02:00

9.9 KiB

Research and design decisions: hunter-learning-loop

Summary

  • Feature: hunter-learning-loop
  • Discovery scope: Extension of the learn harness and the valid benchmark; one new runtime dependency (DSPy).
  • Maintainer direction, 2026-10-13: "the core we need to evolve is feature engineering ... that is the deciding factor here"; "labels for me are a pandas task"; DSPy for the hunter program first, statistics libraries second, no OpenAI SDK on its own ("unless dspy pulls it in"; it does, through litellm); lizard against its alternatives is worth pursuing; labels and folds are a pandas and scikit-learn task; Semgrep OSS is a good idea. Each of those is a feature producer or a feature consumer, and the feature table decides between them.
  • Key findings:
    • The pilot (benchmarks/measurements/2026-10-12-valid-benchmark-pilot) measured the hunter at 5 to 6 of 12 and 54 false alarms per 100 pushes after the classifier; 250 of 343 "vulnerable" suspicions on ordinary pushes sit more than 10 lines from the diff. The hunter never sees the diff (01-flowcharts/U6-hunter-client.md).
    • The harness already has the pieces the loop needs: labels (learn/labels.py), repository folds and leak audit (learn/folds.py, learn/leaks.py), paired experiments with cluster bootstrap and McNemar (learn/experiments.py), the cached caller (learn/program.py), and the runner that joins hunter output to frozen changes (benchmarks/valid/hunter_arm.py). What it lacks is a feature table and a program runtime that can be optimised rather than hand-edited.
    • source.facts_for (P1, commit 062a7b7) is the first feature producer: language, enclosing function, span, callers, entry points, from the repository only.

Research log

DSPy as the program runtime

  • Context: requirements R3 to R6 need a signature with switchable inputs, demonstrations, instruction proposals, a scorer-driven compile, and metered, replayable model calls.
  • Sources: local install, dspy==3.4.1 into the research venv (uv pip install --dry-run dspy: 42 packages, among them litellm 1.105, openai 2.54, gepa 0.1.4, diskcache, json_repair, tenacity, pydantic); API introspection of the installed package (dspy.LM(model, cache=True, num_retries=3, ...), dspy.ReAct(signature, tools, max_iters=20), dspy.Predict, dspy.Signature with InputField/OutputField, dspy.Evaluate(devset, metric, num_threads), dspy.BootstrapFewShot, dspy.MIPROv2(metric, auto=...), dspy.GEPA(metric, auto, reflection_lm, candidate_selection_strategy="pareto"), dspy.track_usage(), dspy.Module.save(path)).
  • Findings: dspy.LM reaches DeepSeek and OpenRouter through litellm with an OpenAI-compatible base URL and the key from the environment; the project's model id deepseek-flash is not in litellm's price table, so prices are registered from our table (model/endpoint.py:55). DSPy caches responses on disk (diskcache) keyed by the request; a run's cache directory is settable, so a run can be replayed from its own directory. dspy.ReAct is the tool-calling loop the hunter already is (read_file, grep_repo, find_refs). GEPA is the optimiser the learning-harness brief described (per-instance Pareto archive, reflective edits); MIPROv2 is the instruction and demo search. Neither applies our acceptance rule; that stays outside DSPy (R6).
  • Implications: the hunter program is written as a DSPy module from the start; the classifier (learn/program.py) stays as is for this spec (R7.5). The hook's urllib client is untouched: the hook has no LLM tier yet, and the core install stays PyYAML-only; DSPy is a research extra.

The feature table (labels and features as a pandas task)

  • Context: R1, R3, R4 need one row per candidate location with named feature columns, a label, provenance, and the ability to switch columns off per ablation arm.
  • Findings: learn/features.py builds feature records as nested dicts and parts; learn/labels.py (979 lines) joins sources by hand; benchmarks/unseen/score.py joins records by id. All of it is group-by, merge and dedup work. pandas and scikit-learn (grouped folds, calibration, metrics) are what the maintainer expects here and are pulled by none of the current extras.
  • Implications: the loop's contract is a DataFrame (persisted as JSONL or parquet) with a fixed column schema; producers add columns, ablation arms drop columns, the program renders a row into its prompt. Existing dict-based builders are consumed, not rewritten, in this spec; migrating labels to pandas is part of the statistics item, not this one.

Feature producers and the tools under evaluation

  • Context: the annotation model (R3) lists the facts; the maintainer wants the producers chosen by what gives the best features.
  • Candidates: source.facts_for (ours; P1), the semantic overlay facts (roles, def-use, obligations; semantic/), mapping.analyze_entry_points, lizard (function spans, nesting, cyclomatic complexity; pure Python), universal-ctags (spans across many languages; a binary), Semgrep OSS or Opengrep (pattern and taint findings as features; also a candidate to replace the quick-rule engine, a separate decision).
  • Status: a measured comparison of lizard against our two engines on the pair corpus is running (~/ousast-results/fossil/span-eval/REPORT.md); a licensing and capability brief for Semgrep CE and Opengrep is being written (~/ousast-results/fossil/semgrep-brief.md). Both land as producers behind the same contract; the ablation decides whether their columns stay.
  • Research needed: Semgrep registry rule licence terms for an open-source tool.

Training data apart from the pilot

  • Context: R2. The pilot (252 changes, 12 positives) is held out.
  • Findings: benchmarks/valid/catalog.py and verify.py produce fix-site positives from OSV advisories in about 12 accepted per 32 advisories; the used-set guard (benchmarks/unseen/*) excludes training-only sources. learn/folds.py groups by repository; learn/leaks.py audits per-signal separation.
  • Implications: about 100 advisories give about 40 positives; negatives at 20 per positive from the same repositories; folds by repository; the pilot is never read by the loop.

Architecture pattern evaluation

Option Description Strengths Risks Notes
A. Extend learn/program.py with a hunter signature one harness, our caller and cache no new dependency the compile loop has no train-vs-holdout rule, no tool loop, no optimiser beyond demos+instructions; everything the maintainer asked for (statistical mutation of prompts) is to build rejected
B. New hunt/ package on DSPy, feature table as the contract, harness reused for labels, folds, leaks, paired analysis the optimisers, the tool loop, caching and usage come with the library; our code is the feature producers, the table, the acceptance rule, the scoring one heavy research dependency; two caches (DSPy's and Caller's) until the classifier migrates selected
C. Rewrite the classifier onto DSPy too one runtime large rewrite of a parked line later, if B earns it

Design decisions

Decision: DSPy runs the hunter program; the acceptance rule stays ours

  • Alternatives: A above; a hand-written optimiser.
  • Selected: dspy.ReAct over a HuntSignature whose inputs are the rendered feature row plus the changed hunks; dspy.GEPA or MIPROv2 propose candidates; dspy.Evaluate scores them on compile folds with our metric; the candidate is accepted only by our rule on held-out folds (R6), implemented outside DSPy.
  • Trade-offs: DSPy's cache is request-keyed, not content-addressed; a run pins its cache directory and copies it with the run record. Cost comes from dspy.track_usage() with our prices registered in litellm.

Decision: the feature table is the contract between producers, ablation and the program

  • Selected: one row per (change, path, line) candidate with columns feature.<producer>.<name>, label, provenance.*, fold; producers are functions (root, change, path, line) -> dict; an ablation arm is a column mask recorded in the manifest; the program sees only the unmasked columns, rendered in a fixed order.
  • Trade-offs: a table per run is larger than nested records; it is also what pandas, scipy and the maintainer read.

Decision: producers are a fixed list, switched by name, no registry

  • source.facts_for, semantic facts (roles, def-use, obligations), mapping entry points, diff hunks; lizard and Semgrep/Opengrep join the list only after their evaluation reports.

Decision: objective and metric

  • Search stage: recall over verified fix sites (a suspicion within the scorer's tolerance of the fix site with the right family) and suspicions per ordinary push; the DSPy metric returns the per-change score and, for GEPA, a text feedback naming the missed site or the off-diff suspicions. No threshold is tuned (R4.7); the curve is reported (R4.8).

Risks and mitigations

  • DSPy or litellm upgrades change behaviour: pin dspy==3.4.*, record versions in every run artifact.
  • A producer reads a label by accident: producers take the repository and a location only; a test feeds a repository with a planted advisory id and asserts no feature carries it (R3.7).
  • Train-on-test: the pilot path is denied to the loop by construction (the runner refuses the pilot manifest digest), and every artifact lists the data digests it read (R2.4).
  • Cost: budgets per run (R9.1); DSPy cache on; --replay-only reruns at no cost.

References

  • DSPy 3.4.1 (installed; https://dspy.ai), GEPA (https://github.com/gepa-ai/gepa), litellm pricing registration.
  • PATHFINDER-2026-10-13/01-flowcharts/U6-hunter-client.md, U7-learn-memory-tasks.md; 02-duplication-report.md D1, D4.
  • benchmarks/measurements/2026-10-12-valid-benchmark-pilot/NOTES.md (numbers and the filter sweep).