Skip to content
Back to skills

Cairn Relevance Grading

ASecurity

Read-side relevance grading (memory_deliveries, rg grades, engagement labels, reranker/student, buckets) and the write-side A/B experiment (arms, AB_ARM_VERSIONS, offline replay).

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 2, 2026
ai-agentspythonrustgonodegit

Works with

  • cli

Security analysis

A100/100

Scanned October 2, 2026

npx -y skills add jimovonz/cairn --skill cairn-relevance-grading --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cairn Relevance Grading?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cairn Relevance Grading
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jimovonz-cairn-relevance-grading/badge)](https://www.skillsdirectory.com/skills/jimovonz-cairn-relevance-grading)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cairn-relevance-grading
description: Read-side relevance grading (memory_deliveries, rg grades, engagement labels, reranker/student, buckets) and the write-side A/B experiment (arms, AB_ARM_VERSIONS, offline replay).
---

## Read-side relevance grading & write-side A/B (docs/spec-memory-relevance-grading.md)

**Ranking weights — check these before reasoning about ranking.** `config.SCORE_W_*` is the composite scorer's only input: similarity 0.50, keywords 0.15, scope 0.05, and **confidence 0.0 and recency 0.0 — both deliberately disabled** ("veracity is not a ranking signal"; "age is not a usefulness signal; obsolescence handled by supersession"). `confidence` is still computed, stored, updated by `cu` corroboration / `-!` contradiction, and surfaced as the `reliability` attribute — it simply carries no ranking weight. `_recency_decay()` and `RECENCY_HALF_LIFE_DAYS` likewise still compute, and nothing consumes them. Reading either code path without checking its weight gives the wrong answer about how retrieval ranks.

**Read side — what gets injected, graded, and measured.** Every injected memory is logged to `memory_deliveries` (ephemeral DB) keyed by a cleaned recent-context window (`cairn/relevance.py:build_context_window`), with ranking provenance (`reranker_model`, `score_components`, `layer`, `scope`). Two labels accumulate per delivery:
- **Agent-as-teacher grades** — the `rg` field in the `[cm]` block (`"id:grade"`, 0–3 + trailing `!` for hard-negative) is written back by `apply_relevance_grades`. (Behaviour already specified in the memory rules.)
- **Behavioural engagement** (the PRIMARY, non-circular label) — at Stop time `apply_engagement` mechanically detects whether the response actually USED each delivered memory (distinctive-term overlap minus prompt terms) → `engaged` / `engaged_score`. Agent `rg` supplements it.

A mechanical **bucket-4 prefilter** (`is_self_referential_meta`) drops self-referential meta-memories (gated by `RELEVANCE_PREFILTER_ENABLED`, **ON** since the 2026-07-02 review; corrections exempt). The cross-encoder reranker is device-aware (`config.resolve_reranker`): `BAAI/bge-reranker-base` (floor **0.015**, recalibrated 2026-07-07 from 3733 Opus `rg` labels — the earlier 0.10, calibrated 2026-07-02 from 9k `memory_deliveries`, dropped 56% of grade-3 load-bearing memories) on CUDA when `RERANKER_BGE_ENABLED` **and** the GPU has >= `RERANKER_MIN_VRAM_GB` (6 GB), else `cross-encoder/ms-marco-MiniLM-L-6-v2` (floor −3.0); the daemon owns the model and scores the recent-context window, so the hot hook path never imports torch. A **trained student** (`CROSS_ENCODER_STUDENT_PATH`, a local per-machine dir) overrides both when present — see "Phase 3 — trained cross-encoder student" below. Phases 1–3 are live; teacher-demotion (Phase 4) is still future work. **Intended automation for Phase 4**: reuse the `calibration_selfmod.py` / `ab_selfmod.py` self-mod cron pattern — label-volume-gated retrain, then a promotion gate before swapping the deployed model file. Note the deploy gate that actually shipped is **beat the incumbent**, not the spec's >~90% held-out agreement (`docs/spec-memory-relevance-grading.md` A.8), which is demoted to an `auto-promote-safe` flag.

**Write side — generation quality.** Each agent-written memory is stamped with `config.GENERATION_PROMPT_VERSION` (the live value is machine-managed — `ab_selfmod` rewrites it on every promotion, so read `config.GENERATION_PROMPT_VERSION` rather than trusting a number quoted here) in `memories.source_ref` (the provenance join key; precedence `entry["source_ref"] > call param > NULL`). The live generation rules carry a dual-altitude **transferability** lever (generalised principle + specific anchor) and an **in-session duplicate-suppression** rule. Memory keywords **union** (not overwrite) on dedup, so re-encounters enrich findability.

**Two A/B paths exist — do not conflate them:**
- **LIVE per-prompt A/B** (`config.AB_TEST_ENABLED`, ON) — the real experiment. Each user prompt is randomly assigned **arm A** (control = current live rules) or **arm B** (control + one speculative variable from `config.AB_B_INSTRUCTION`, injected that turn). The prompt hook records the arm in `hook_state`; the Stop hook stamps that turn's memories with the arm's `source_ref` version (`config.AB_ARM_VERSIONS` — also machine-managed: `ab_selfmod` promotes B into A and queues a new candidate, so read the symbol, not a number quoted here). Subagents excluded; arm-B injection is post-cache (no cache disturbance). Compare arms with `.venv/bin/python cairn/query.py --delivery-stats` (engagement/grade grouped by generation version + reranker). To stop it: `AB_TEST_ENABLED=False`. To change what B tests: edit `AB_B_INSTRUCTION`.
- **OFFLINE replay harness** (`cairn/ab_writeside.py`) — an analysis tool (not the live experiment, never run on the corpus yet): replays transcripts through prompt-A vs prompt-B → Opus 4.8 judge, BLIND + position-swapped + pairwise (findability / self-sufficiency / fitness), A/B unit = session cohort; metrics dedup rate / findability backtest / self-sufficiency cold-read. CLI: `.venv/bin/python cairn/ab_writeside.py replay|ab --limit N [--dry-run]`.

### Phase 3 — trained cross-encoder student (shipped 2026-07-07)

The read-side gate now has a trained student, not just instrumentation:

- **`cairn/train_reranker.py`** — PAIRWISE fine-tune of a pretrained cross-encoder
  (default `ms-marco-MiniLM`) on `training_data/relevance_silver.jsonl` via
  `MarginRankingLoss` over `(query,memory)` logits; grades induce within-query order.
  Held-out split is **by query** (`--min-gap 2` = train/eval on clear pairs only, since
  adjacent grades dilute agreement to chance). The deploy gate is **beat the incumbent**
  (`resolve_reranker()` scored on the SAME held-out pairs) by `--deploy-margin`, NOT an
  arbitrary 90% (that's demoted to an `auto-promote-safe` flag). Saves to
  `training_data/reranker-student/` only if it beats the incumbent.
- **`cairn/calibrate_bge_floor.py`** — calibrates a suppression floor from the rg labels
  (keep grade-3, drop grade-0). This surfaced and fixed a live bug: the bge floor `0.10`
  was dropping 56% of load-bearing memories (now `0.015`).
- **Deployment** — `config.resolve_reranker()` returns `CROSS_ENCODER_STUDENT_PATH`
  (a LOCAL dir, offline-safe under `HF_HUB_OFFLINE`) when it exists, overriding the
  pretrained base on any device. `training_data/` is gitignored, so the student is a
  **per-machine** artifact — other nodes fall back to ms-marco gracefully until they have
  their own. The student is PAIRWISE-trained (good ordering, compressed absolute scores),
  so its floor is OFF (`CROSS_ENCODER_STUDENT_FLOOR=-100`) — it re-ranks via the
  normalised blend without hard-suppressing; floor calibration is a follow-up.
- **Status** — the first student beats the incumbent decisively (66.0% vs 39.6% held-out
  pairwise agreement) and is live on this machine. It is NOT auto-promote-grade (<90%);
  the lever to improve is MORE LABELS (per-delivery pool + other cairn instances), not
  more epochs — 5 epochs (66.0%) did not beat 3 (67.0%): the student is data-limited.
- **Engagement weak labels** — `train_reranker.py --engagement` merges behavioural
  engagement observations (`memory_deliveries.engaged`) into the training pairs as
  0/3 pseudo-grades, on top of the agent `rg` labels. Merged AFTER `split_by_query`,
  so held-out stays pure agent-rg and the beat-the-incumbent deploy gate is never
  judged on weak labels. Tunable via `--engagement-max-pairs` / `--engagement-min-pos`.
  `ENGAGEMENT_MIN_POS_DEFAULT = 0.2`: the lexical overlap ratio runs median 0.110 /
  p90 0.262, so the obvious-looking 0.5 admits ~1% of positives and starves the pool.
  Note `engaged_score` is bimodal — semantic-second-chance rows store a cosine
  (>= 0.55 by construction), lexical rows a much smaller overlap ratio.
- **Semantic engagement backfill** — `.venv/bin/python cairn/backfill_semantic_engagement.py`
  (`--dry-run`, `--limit`, `--json`) retro-scores historical deliveries, recovering the
  response text from the session transcript since it is not stored on the delivery row.
  Idempotent via the `engaged_method` tag (`lexical` / `semantic` / `semantic-backfill`) —
  that column exists so the two measurement bases stay separable; **a rate computed across
  untagged and tagged rows is invalid**. First run (2026-07-25): 981 examined, 26 rescued
  (2.7%). The rescues look precise but low-recall — rescued rows average agent grade 2.17
  vs 0.84 for non-rescued (n=6, too small to act on). Thresholds are NOT yet calibrated
  against the grade labels; do that the way `calibrate_bge_floor.py` does, not by taste.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.