Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
63f5cc5
week_8: Module C (The Librarian) — reconcile the queue mirror with Mo…
PRAteek-singHWY Aug 12, 2026
fb48443
week_8: Module C (The Librarian) — envelope sink, and the rule that g…
PRAteek-singHWY Aug 12, 2026
b5b2e21
week_8: Module C (The Librarian) — the consumed_at write-back
PRAteek-singHWY Aug 12, 2026
9e39c41
week_8: Module C (The Librarian) — wire the C.4 safety seam into deci…
PRAteek-singHWY Aug 12, 2026
fa27e08
week_8: Module C (The Librarian) — component factory for the orchestr…
PRAteek-singHWY Aug 12, 2026
d916c65
week_8: Module C (The Librarian) — live queue runner + CLI
PRAteek-singHWY Aug 12, 2026
344fd22
week_8: fail the eval harness when the C.0 boundary rejects everything
PRAteek-singHWY Aug 12, 2026
bf53f8c
week_8: address CodeRabbit on #1011 — naive consumed_at, honest run s…
PRAteek-singHWY Aug 12, 2026
ae1ce26
week_8b: Module C (The Librarian) — package docs, final metrics, and …
PRAteek-singHWY Aug 12, 2026
32a5f95
week_8b: fix the regression gate's dependency install
PRAteek-singHWY Aug 12, 2026
63faf0b
week_8b: address CodeRabbit on #1012 — checkout credentials, diagram …
PRAteek-singHWY Aug 12, 2026
058ace6
week_8: Module C (The Librarian) — decision_queue, the C -> D handoff
PRAteek-singHWY Aug 13, 2026
29cc50b
week_8: address CodeRabbit on #1011 — project the highest link confid…
PRAteek-singHWY Aug 13, 2026
0ff8729
week_8: address CodeRabbit on #1011 — poison rows, blank run id, cros…
PRAteek-singHWY Aug 14, 2026
69c44c3
week_8: address the remaining CodeRabbit findings on #1011
PRAteek-singHWY Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/librarian_regression.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Librarian Regression Gate
# Module C's accuracy gate. The unit suite proves the code runs; this proves the
# pipeline still *decides* correctly over the golden set.
#
# Deliberately hermetic: no DB, no API key, no model download. The semantic
# reports (C.1 recall, C.2 top-1, the C.3 ECE gate, C.4 decision accuracy) need
# live CRE vectors and are measured locally — seeding a candidate pool from
# golden text offline is the leakage the hub firewall exists to strip, so a CI
# job that "measured" them would be measuring nothing.
#
# What this gate does catch is the failure that actually bit us: a Module B
# schema change silently rejecting every row at the C.0 boundary, which used to
# leave the explicit gate with nothing to count and still exit 0.

on:
pull_request:
paths:
- 'application/utils/librarian/**'
- 'application/tests/librarian/**'
- 'scripts/evaluate_librarian.py'
- '.github/workflows/librarian_regression.yml'
push:
branches: [main]
paths:
- 'application/utils/librarian/**'
- 'application/tests/librarian/**'
- 'scripts/evaluate_librarian.py'

permissions:
contents: read

jobs:
regression:
name: Librarian Regression Gate
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
# This job only reads the tree; leaving the token in .git/config would
# expose it to anything the test run executes.
persist-credentials: false

- uses: actions/setup-python@v5
with:
python-version: '3.12.3'
Comment thread
PRAteek-singHWY marked this conversation as resolved.

- name: Install python dependencies
# Installed straight into the runner's interpreter rather than through
# `make install-python`: that target builds a venv (needing an apt
# `virtualenv` the other workflows install first) and then runs
# `playwright install`, neither of which a hermetic librarian run uses.
# The steps below call `python` directly, so a venv the job never
# activates would leave them running against a bare interpreter.
run: |
pip install --upgrade pip setuptools
pip install -r requirements-dev.txt

- name: Librarian unit suite
run: python -m unittest discover -s application/tests/librarian -p '*_test.py' -t .

- name: Golden-set decision gate
# Non-zero on: a failed explicit-slice gate (C.0.5 must be 100%), or a
# C.0 boundary that rejected the whole dataset.
run: |
python scripts/evaluate_librarian.py \
--dataset application/tests/librarian/fixtures/golden_dataset.json
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ standards_cache.sqlite
!AGENTS.md
!docs/faq.md
!docs/Mid_eval_blog_gsoc2026/module_B_mideval_blog.md
!application/utils/librarian/README.md
!docs/gsoc_2026_module_c/*.md

### Dev DBDumps
*.sql
Expand Down
90 changes: 85 additions & 5 deletions application/cmd/cre_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1032,11 +1032,23 @@ def run(args: argparse.Namespace) -> None: # pragma: no cover
if args.upstream_sync:
download_graph_from_upstream(args.cache_file)
if args.run_librarian or args.librarian_dry_run:
run_librarian(
cache_file=args.cache_file,
dry_run=args.librarian_dry_run or not args.run_librarian,
source_jsonl=args.librarian_source,
)
# --run_id selects the live path: drain Module B's knowledge_queue for
# that run. Without it, the fixture walk-through stays the default so
# the pre-W8 command keeps behaving the way it always has.
run_id = (getattr(args, "run_id", "") or "").strip()
if run_id:
run_librarian_live(
cache_file=args.cache_file,
pipeline_run_id=run_id,
dry_run=args.librarian_dry_run,
envelopes_out=args.librarian_envelopes_out,
)
else:
run_librarian(
cache_file=args.cache_file,
dry_run=args.librarian_dry_run or not args.run_librarian,
source_jsonl=args.librarian_source,
)


def ai_client_init(database: db.Node_collection):
Expand Down Expand Up @@ -1238,6 +1250,74 @@ def run_librarian(
)


def run_librarian_live(
cache_file: str,
pipeline_run_id: str,
dry_run: bool = False,
envelopes_out: Optional[str] = None,
) -> None:
"""Module C entrypoint against Module B's live queue (W8).

The counterpart to ``--run_noise_filter``: where ``run_librarian`` walks a
JSONL fixture and logs shortlists, this drains the ``knowledge_queue`` rows
Module B wrote for ``pipeline_run_id`` through C.0->C.4 and stamps
``consumed_at`` on the ones it finished. Prints the ``RunSummary`` as JSON
on stdout so the OIE orchestrator can read it, exactly as Module B does.

Still writes no links: the graph/review writers are W8b. What a real run
does write is the envelopes (as JSONL, to ``envelopes_out``) and one column
on B's queue. Those two go together — retiring a row whose envelope was
discarded would lose the chunk — so a non-dry run requires an output path.
``dry_run`` writes neither.

Ops note: unchanged from ``run_librarian`` — opt-in CLI only, not on the
Procfile, not wired into the web app or the worker. It calls the paid
embedding API, so a deployment never triggers it on its own.
"""
from datetime import datetime, timezone

from application.utils.librarian.config_loader import load_config
from application.utils.librarian.envelope_sink import (
DbEnvelopeSink,
JsonlEnvelopeSink,
NullEnvelopeSink,
TeeEnvelopeSink,
)
from application.utils.librarian.factory import build_components
from application.utils.librarian.queue_runner import run_librarian_queue

cfg = load_config()
database = db_connect(path=cache_file)
components = build_components(database, config=cfg)

# `decision_queue` is where Module D reads from, so a real run writes there
# by default — the same handoff B makes to C through `knowledge_queue`.
# `--librarian_envelopes_out` additionally mirrors the batch to JSONL, which
# is useful for eyeballing a run but is not the contract.
if dry_run:
sink = JsonlEnvelopeSink(envelopes_out) if envelopes_out else NullEnvelopeSink()
elif envelopes_out:
sink = TeeEnvelopeSink(
DbEnvelopeSink(database.session, pipeline_run_id),
JsonlEnvelopeSink(envelopes_out),
)
else:
sink = DbEnvelopeSink(database.session, pipeline_run_id)

# The CLI boundary is the one place a clock read belongs; everything below
# takes `at` as an argument so a run stays reproducible.
summary = run_librarian_queue(
database.session,
pipeline_run_id,
components,
cfg,
at=datetime.now(timezone.utc),
sink=sink,
dry_run=dry_run,
)
print(summary.to_json())


def regenerate_embeddings(db_url: str) -> None:
"""Wipe all embedding rows, then rebuild (CRE + every node type) like ``--generate_embeddings``."""
from application.prompt_client import prompt_client as prompt_client
Expand Down
53 changes: 53 additions & 0 deletions application/database/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,59 @@ class KnowledgeQueueItem(BaseModel): # type: ignore
)


class DecisionQueueItem(BaseModel): # type: ignore
"""Module C's output queue: decided chunks for Module D.

The C -> D counterpart of ``knowledge_queue``, and deliberately the same
shape of handoff: C inserts one row per decided chunk, Module D reads the
rows it cares about and sets ``consumed_at``. Nothing is deleted, so the
table doubles as the audit trail of what C decided and what D did with it.

Both outcomes land here, separated by ``status`` — exactly as B puts
KNOWLEDGE and UNCERTAIN in one queue and lets its readers filter:

``linked`` an auto-link C is confident in (RFC ``LinkProposal``);
the graph writer is the consumer.
``review_required`` routed to a human (RFC ``ReviewItem``), carrying the
``reason_code`` that explains why; Module D's HITL
review is the consumer.

``envelope`` holds the whole RFC document — the retrieval audit included —
so a decision can be re-read and explained long after the run. The columns
beside it are for querying and filtering, not a second source of truth; they
are projected from the same envelope.
"""

__tablename__ = "decision_queue"
id = sqla.Column(sqla.String, primary_key=True, default=generate_uuid)
# provenance, carried verbatim from Module A through B
chunk_id = sqla.Column(sqla.String, nullable=False)
artifact_id = sqla.Column(sqla.String, nullable=False)
pipeline_run_id = sqla.Column(sqla.String, nullable=False)
schema_version = sqla.Column(sqla.String, nullable=False)
# C's verdict
status = sqla.Column(sqla.String, nullable=False) # linked | review_required
reason_code = sqla.Column(sqla.String, nullable=True) # review rows only
review_id = sqla.Column(sqla.String, nullable=True) # review rows only
confidence = sqla.Column(sqla.Float, nullable=True) # top-1 calibrated
# the full RFC LinkProposal / ReviewItem
envelope = sqla.Column(
sqla.JSON().with_variant(JSONB, "postgresql"), nullable=False
)
created_at = sqla.Column(
sqla.DateTime, nullable=False, server_default=sqla.func.now()
)
consumed_at = sqla.Column(sqla.DateTime, nullable=True)
__table_args__ = (
sqla.Index("ix_decision_queue_unconsumed", "consumed_at"),
sqla.Index("ix_decision_queue_run_status", "pipeline_run_id", "status"),
# One decision per chunk per run: replaying a run must not double-write.
sqla.UniqueConstraint(
"chunk_id", "pipeline_run_id", name="uq_decision_chunk_run"
),
)


def create_import_run(source: str, version: Optional[str] = None) -> ImportRun:
"""Create and persist an import run record. Returns the new ImportRun."""
from datetime import datetime, timezone
Expand Down
16 changes: 16 additions & 0 deletions application/tests/librarian/config_loader_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def test_defaults_when_env_unset(self):
self.assertEqual(cfg.top_k_retrieval, 20)
self.assertEqual(cfg.top_k_rerank, 5)
self.assertEqual(cfg.link_threshold, 0.8)
# 1.0 is the identity transform: an honestly *uncalibrated* softmax,
# rather than a temperature nobody fitted.
self.assertEqual(cfg.temperature, 1.0)
self.assertEqual(cfg.batch_size, 32)
self.assertEqual(cfg.ece_target, 0.10)
self.assertEqual(cfg.conformal_alpha, 0.10)
Expand All @@ -34,6 +37,7 @@ class TestConfigLoaderOverrides(unittest.TestCase):
"CRE_LIBRARIAN_TOP_K_RETRIEVAL": "50",
"CRE_LIBRARIAN_TOP_K_RERANK": "10",
"CRE_LIBRARIAN_LINK_THRESHOLD": "0.7",
"CRE_LIBRARIAN_TEMPERATURE": "1.208",
"CRE_LIBRARIAN_BATCH_SIZE": "64",
"CRE_LIBRARIAN_ECE_TARGET": "0.05",
"CRE_LIBRARIAN_CONFORMAL_ALPHA": "0.20",
Expand All @@ -47,6 +51,7 @@ def test_env_overrides_apply(self):
self.assertEqual(cfg.top_k_retrieval, 50)
self.assertEqual(cfg.top_k_rerank, 10)
self.assertAlmostEqual(cfg.link_threshold, 0.7)
self.assertAlmostEqual(cfg.temperature, 1.208)
self.assertEqual(cfg.batch_size, 64)
self.assertAlmostEqual(cfg.ece_target, 0.05)
self.assertAlmostEqual(cfg.conformal_alpha, 0.20)
Expand All @@ -65,6 +70,17 @@ def test_link_threshold_above_one_raises(self):
with self.assertRaises(ValueError):
load_config()

def test_non_positive_temperature_raises(self):
"""T divides the logits, so zero or negative is undefined, not merely a
bad setting — the same guard TemperatureScaler applies."""
for value in ("0", "-1.5", "nan"):
with self.subTest(value=value):
with mock.patch.dict(
os.environ, {"CRE_LIBRARIAN_TEMPERATURE": value}, clear=True
):
with self.assertRaises(ValueError):
load_config()

def test_negative_top_k_retrieval_raises(self):
with mock.patch.dict(
os.environ, {"CRE_LIBRARIAN_TOP_K_RETRIEVAL": "-1"}, clear=True
Expand Down
Loading
Loading