Deterministic freshness and drift linter for RAG knowledge bases: catch stale chunks, missing/future timestamps, age-threshold breaches, temporal clustering, and embed-model age before they degrade your retrieval.
Project description
staleness
Deterministic freshness and drift linter for RAG knowledge bases. One import, one call. Catches the silent decay bugs that turn a once-fresh corpus into a wrong-answer machine: chunks too old to be authoritative, missing or corrupted timestamps, future-dated bugs from clock skew, severe age skew, recent-window coverage gaps, and embedding-model-vs-corpus age mismatches.
pip install staleness # core (Python stdlib only)
pip install staleness[pdf] # adds PDF report support (fpdf2)
import staleness
from datetime import datetime, timezone
chunks = [
{"text": "...", "timestamp": "2024-01-15T00:00:00Z"},
{"text": "...", "timestamp": "2026-05-20T10:30:00Z"},
...
]
report = staleness.check_corpus(
chunks,
max_age_days=365,
embed_model_name="text-embedding-3-small",
now=datetime.now(timezone.utc),
)
print(report)
if not report.ok():
raise SystemExit("Refresh stale chunks before relying on this corpus.")
That's the whole API. staleness does not call any LLM, embed chunks, or hit a vector DB — it inspects timestamps and metadata. Pure Python stdlib. Sub-second on 100k chunks.
Why this exists
A RAG corpus that was fresh on launch day quietly rots. Six months in:
- A documentation chunk says "the API takes parameter X" — but parameter X was renamed in the last release.
- News retrieval returns last year's market summary because no one indexed this quarter.
- A chunk's timestamp is silently
None— the model treats stale and current content as equivalent. - Clock-skew bugs in the ingestion pipeline produce future-dated chunks that artificially rank well.
- Your embed model was trained in 2021. Your corpus is mostly 2025. Half your domain language doesn't embed well.
These are operational bugs, not algorithm bugs. No retriever fixes them. No reranker fixes them. They need to be caught at ingestion time, deterministically, before retrieval ever runs.
What it catches
| Code | Severity | What it catches |
|---|---|---|
ST001 |
critical / warning | Chunks with missing or unparsable timestamps (critical if ≥5% of corpus) |
ST002 |
critical / warning | Chunks older than max_age_days (critical if ≥25% of timestamped corpus) |
ST003 |
critical | Future-dated chunks (clock-skew / parser bugs) |
ST004 |
warning | Severe age skew — 80% of chunks fall inside <10% of the time span |
ST005 |
warning | Coverage gap in the recent window (recent_window_days) |
ST006 |
critical / warning | Embed model is much older than the corpus median date |
ST007 |
warning | Topic-localized staleness (one topic's median age is much older than overall) |
Each finding lists affected chunk indices and a concrete fix.
Demo
The repo ships examples/demo.py — a 30-chunk corpus with all the bugs baked in:
- 2 chunks with missing timestamps
- 8 chunks past the 365-day age threshold (one is 1500 days old)
- 1 future-dated chunk
- ~70% of timestamped chunks clustered inside a 30-day window (severe skew)
- 0 chunks in the most recent 60 days (coverage gap)
Run it:
cd examples
python demo.py
Expected: ST001/002/003 fire critical, ST004/005 fire warning, report.ok() == False.
Use it in CI
import staleness, sys
report = staleness.check_corpus(chunks, max_age_days=365)
sys.exit(0 if report.ok() else 1)
API reference
staleness.check_corpus(
chunks, # list[dict] each with a timestamp field
*,
timestamp_field="timestamp", # field name in each chunk
now=None, # defaults to datetime.now(timezone.utc)
max_age_days=365, # ST002 threshold
recent_window_days=30, # ST005 window
min_recent_pct=5.0, # ST005 threshold
embed_model_name=None, # enables ST006
topic_field=None, # enables ST007 if set (e.g. "topic")
) -> Report
Timestamps can be: datetime (naive treated as UTC), epoch seconds (int/float, ms auto-detected), or ISO 8601 strings.
Report:
report.ok()—Trueif no critical findings.report.findings,report.critical,report.warnings,report.infos— lists ofFinding.report.cleaned_chunks(chunks)— drops chunks flagged by any critical finding.print(report)— human-readable terminal summary.report.to_dict()— JSON-serializable.
Known embed-model cutoffs
staleness ships approximate training cutoffs for OpenAI text-embedding-3-{small,large,ada-002}, Cohere embed-english-v3.0, Voyage voyage-3, all-MiniLM-L6-v2, all-mpnet-base-v2, BAAI/bge-{small,base,large}-en-v1.5. Models not in the registry skip ST006 silently.
See also
- chaffer — sibling library: lints corpus for retrieval-quality bugs.
- redoubt — sibling library: scans corpus for prompt-injection signatures.
- corroborate — sibling library: deterministic answer-grounding after generation.
- dash-mlguard — same author, same form factor, but for ML training pipelines.
If you ship RAG to production, you probably want chaffer + redoubt + staleness + corroborate. Each takes a different swing at the corpus or its outputs.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file staleness-0.1.0.tar.gz.
File metadata
- Download URL: staleness-0.1.0.tar.gz
- Upload date:
- Size: 14.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b14f36bea33c5d92de5b942156e14f76f66e287456fb34a58cada5b8bcabb362
|
|
| MD5 |
b08407366d7907d6b5061c3c0d712c02
|
|
| BLAKE2b-256 |
634e4f4838acbfac57aa98b09c6070d8cc8cccb3f5e30439f098fde91e20632b
|
File details
Details for the file staleness-0.1.0-py3-none-any.whl.
File metadata
- Download URL: staleness-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5730623ca2f4e16f2a486afb84266493584e49a309c811fbbae906d3652d5e6c
|
|
| MD5 |
6c13127f394f06d36bd8ee525d27c089
|
|
| BLAKE2b-256 |
ca7e094a44411c8188edbb0eedca4692561c7d642cee6acd23baaf7288769679
|