Day 27: RAG Freshness: Git Webhooks vs Nightly Jobs

Day 27: RAG Freshness: Git Webhooks vs Nightly Jobs

When main moves every hour, a nightly-only indexer guarantees wrong answers by lunch. Freshness is an SLO, not a cron preference: define max staleness per corpus tier, then pick git webhooks versus batch reconcile accordingly. Day 27 wires the triggers, the generation markers, and the refuse-when-stale behavior that keeps citations honest.

⚡ TL;DR: Hot paths (SDK, auth, runbooks) reindex on push via webhook → queue → worker; cold wiki can stay nightly. Version the index with git_sha + built_at. Refuse or warn when answers would use chunks older than your SLA.

Write the freshness SLO first

Example contract: “P95 answers for /packages/sdk/** use chunks ≤ 15 minutes behind origin/main.” Without that sentence, teams argue about architecture forever.

Corpus Cadence Trigger
packages/sdk, services/*/src ≤15m push webhook
ADRs / wiki ≤24h nightly reconcile
PDFs / designs on upload S3 event
Generated API refs on release tag CI publish job

Publish the SLO next to your RAG runbook. Support engineers should see index_sha in the debug footer of every answer.

Webhook path that does not melt the monorepo

Validate signatures, enqueue changed paths only, and make consumers idempotent on (sha, path).

import hashlib, hmac, json

def handle_push(headers: dict, body: bytes, secret: str) -> dict:
    sig = headers.get("X-Hub-Signature-256", "")
    expect = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expect):
        raise PermissionError("bad_signature")
    event = json.loads(body)
    files = set()
    for c in event.get("commits", []):
        files.update(c.get("added", []))
        files.update(c.get("modified", []))
        for p in c.get("removed", []):
            files.add("DELETE:" + p)
    return {"sha": event["after"], "paths": sorted(files)}
reindex_all(repo)  # burns money and creates long windows of partial consistency

Deletes matter. Tombestone removed paths or your index will cite ghosts that fail Day 26 quote checks after the file is gone.

Nightly reconcile is mandatory backup

Webhooks drop. GitHub outages happen. Nightly jobs compare the git tree to the index, fix orphans, and rebuild after embedding-model upgrades. Treat nightly as reconcile, not the only writer.

INDEX_META = {"git_sha": sha, "built_at": iso_now, "embed_model": "amazon.titan-embed-text-v2:0"}

def answer_allowed(meta: dict, max_age_min: int) -> bool:
    return minutes_since(meta["built_at"]) <= max_age_min

Bedrock Knowledge Bases and multi-tenant notes

KB sync jobs are coarse. For SaaS, prefer S3 layouts you control (s3://corp-rag/{tenant}/...) plus metadata filters (Day 30), or self-managed OpenSearch/pgvector. Always stamp git_sha and tenant_id on documents. When freshness SLO burns, page data eng — not the model vendor.

Production checklist

  • [ ] Freshness SLO documented per corpus tier
  • [ ] Webhook signature verified; queue idempotent on (sha, path)
  • [ ] Deletes propagate (tombstones), not only upserts
  • [ ] Nightly reconcile compares tree vs index
  • [ ] Answers expose index git_sha in debug footer
  • [ ] Alert when P95 staleness exceeds SLO

Series navigation

← Day 26 · Day 28 →

Last updated September 11, 2026


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply