<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anirudh Rajmohan</title>
    <description>The latest articles on DEV Community by Anirudh Rajmohan (@anirudh_rajmohan).</description>
    <link>https://dev.to/anirudh_rajmohan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4040634%2F65b0e576-b337-41c4-b193-2e50de7ddfe0.jpg</url>
      <title>DEV Community: Anirudh Rajmohan</title>
      <link>https://dev.to/anirudh_rajmohan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anirudh_rajmohan"/>
    <language>en</language>
    <item>
      <title>Your RAG Index Might Be Lying to You: Data Freshness Is the Missing Signal for AI Systems</title>
      <dc:creator>Anirudh Rajmohan</dc:creator>
      <pubDate>Wed, 29 Jul 2026 00:28:10 +0000</pubDate>
      <link>https://dev.to/anirudh_rajmohan/your-rag-index-might-be-lying-to-you-data-freshness-is-the-missing-signal-for-ai-systems-3ema</link>
      <guid>https://dev.to/anirudh_rajmohan/your-rag-index-might-be-lying-to-you-data-freshness-is-the-missing-signal-for-ai-systems-3ema</guid>
      <description>&lt;p&gt;A follow-up to &lt;a href="https://dev.to/anirudh_rajmohan/how-old-is-my-data-the-missing-opentelemetry-signal-4dj8"&gt;How Old Is My Data?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode that gets worse when a machine is reading the data
&lt;/h2&gt;

&lt;p&gt;In a classic dashboard, stale data is a human problem: someone looks at a number that's six hours old and makes a slightly worse decision. Annoying, rarely catastrophic.&lt;/p&gt;

&lt;p&gt;Now hand that same data to a retrieval-augmented-generation (RAG) pipeline, or to an autonomous agent. The stakes change. The system doesn't pause to sanity-check the timestamp — it acts. And when the data it acts on is stale, three things are true at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The answer is confidently wrong.&lt;/li&gt;
&lt;li&gt;There is no error to fire on — the query succeeded, the model responded, latency was normal.&lt;/li&gt;
&lt;li&gt;Every other signal on your dashboard is green.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the worst combination in observability: a real failure that is completely invisible to the signals we currently emit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where staleness hides in AI systems
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RAG: index vs. corpus.&lt;/strong&gt; Your vector index was built from a corpus at some point in time. The corpus keeps changing — documents get added, edited, retracted. If the re-embedding job stalls or falls behind, the index quietly drifts out of date. The retriever still returns plausible chunks; the model still writes a fluent answer. It's just answering from a version of reality that no longer exists. The quantity you care about is the age of the index relative to its source — not the age of either one alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature stores: online–offline skew.&lt;/strong&gt; The features your model trained on and the features it serves on are supposed to match. When the online store lags the offline pipeline, predictions degrade in a way that looks like model drift but is actually data staleness wearing a costume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents: stale shared state.&lt;/strong&gt; Multi-agent systems coordinate through shared memory, scratchpads, and context. An agent reasoning over state that another agent updated ten steps ago — but which never propagated — makes locally reasonable, globally wrong decisions. This isn't a new or exotic problem: it's exactly the regime that Age of Information theory was invented to analyze in real-time control and networked systems, where an actuator acting on a stale sensor reading is a known hazard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freshness is old; its absence from our telemetry is what's newly expensive
&lt;/h2&gt;

&lt;p&gt;None of this is a new metric. Freshness — the time since the most recent update was generated at its source — has a precise name in networked-systems research: the Age of Information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;age = now − event_time(freshest record)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a source stops producing, its &lt;code&gt;age&lt;/code&gt; climbs without bound while every other signal stays healthy. Data-observability vendors already compute this internally. &lt;code&gt;dbt source freshness&lt;/code&gt; computes it for dbt. Kafka-lag tools compute it for Kafka.&lt;/p&gt;

&lt;p&gt;What's missing is a standard, portable way to emit it — one vocabulary that works whether the "source" is a Postgres table, a Kafka topic, a feature store, or a vector index, and that lands in any backend unchanged. That gap was tolerable when humans were the only consumers. It stops being tolerable when the consumer is a model that acts without a second look.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposal: &lt;code&gt;data.staleness.*&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I've been working on a small, vendor-neutral semantic convention for data staleness, built on OpenTelemetry, plus a working reference implementation. To be clear about status: this is an independent, community project — not an official OpenTelemetry project, and not endorsed by or affiliated with OpenTelemetry or the CNCF. The convention is a proposal at "Development" stability, under discussion with the Semantic Conventions SIG (&lt;a href="https://github.com/open-telemetry/semantic-conventions/issues/3909" rel="noopener noreferrer"&gt;issue #3909&lt;/a&gt;). Names may change.&lt;/p&gt;

&lt;p&gt;The shape is deliberately compact:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.age&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;now − event_time&lt;/code&gt; of the freshest record (the primary signal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.lag&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;processing_time − event_time&lt;/code&gt; of the most recent record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.last_update.timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unix time of the last successful update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.records.behind&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;positional backlog (e.g. Kafka consumer lag)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.sla.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;configured threshold + breach evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key insight for AI systems is the differential case: the age of an index relative to the corpus it was built from, or a replica relative to its primary. A single number ("the index is 40 minutes behind its source") is exactly the alert a RAG pipeline needs and rarely has.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design rule that matters most for AI: never fabricate a number
&lt;/h2&gt;

&lt;p&gt;One principle runs through the whole implementation: an unmeasurable freshness value must be visible, never faked. An empty table, a &lt;code&gt;NULL MAX()&lt;/code&gt;, a timeout, an empty partition — each surfaces as &lt;code&gt;data.staleness.probe.errors&lt;/code&gt; with a specific &lt;code&gt;error.type&lt;/code&gt;, not as a fabricated "0 seconds old."&lt;/p&gt;

&lt;p&gt;This matters doubly for AI. A freshness check that silently reports "fresh" when it actually failed is worse than no check at all — because it will reassure you precisely when your RAG index or agent state is most broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it against one AI workload
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Python SDK&lt;/strong&gt; — point a differential probe at your index and its source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;data-staleness-otel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;otel_staleness&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StalenessMonitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conventions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;otel_staleness.probes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SQLFreshnessProbe&lt;/span&gt;

&lt;span class="n"&gt;monitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StalenessMonitor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Freshest source document vs. freshest embedded doc in the index:
&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLFreshnessProbe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fetch_max_epoch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;latest_source_doc_ts&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;source_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kb_corpus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POSTGRESQL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sla_threshold_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Zero-code Collector&lt;/strong&gt; — scrape SQL, Kafka, Kinesis, files, HTTP, and schema registries from YAML alone, no application code, and emit the same  &lt;code&gt;data.staleness.*&lt;/code&gt; metrics into the same backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get involved
&lt;/h2&gt;

&lt;p&gt;If "how stale is the data my model is answering from?" is a question you've had to answer with a bespoke query and a fragile dashboard, I'd value your input:&lt;/p&gt;

&lt;p&gt;The convention proposal is &lt;a href="https://github.com/open-telemetry/semantic-conventions/issues/3909" rel="noopener noreferrer"&gt;open in the OpenTelemetry Semantic Conventions repo (#3909)&lt;/a&gt; — thumbs-up, comments, and concrete AI use cases genuinely help gauge demand.&lt;br&gt;
Code, spec, model, and validation: &lt;a href="https://github.com/anirudhrajreliability/otel-data-staleness" rel="noopener noreferrer"&gt;https://github.com/anirudhrajreliability/otel-data-staleness&lt;/a&gt; (Apache-2.0).&lt;br&gt;
Try it (&lt;code&gt;pip install data-staleness-otel&lt;/code&gt;) and tell me where it breaks.&lt;/p&gt;

&lt;p&gt;We standardized latency and errors because machines needed portable signals to reason about. AI systems reasoning over data need a portable signal for freshness for exactly the same reason. Let's give them one.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>observability</category>
      <category>opentelemetry</category>
    </item>
    <item>
      <title>How Old Is My Data? The Missing OpenTelemetry Signal</title>
      <dc:creator>Anirudh Rajmohan</dc:creator>
      <pubDate>Wed, 22 Jul 2026 21:14:54 +0000</pubDate>
      <link>https://dev.to/anirudh_rajmohan/how-old-is-my-data-the-missing-opentelemetry-signal-4dj8</link>
      <guid>https://dev.to/anirudh_rajmohan/how-old-is-my-data-the-missing-opentelemetry-signal-4dj8</guid>
      <description>&lt;p&gt;&lt;em&gt;A proposal — and a working reference implementation — for making data freshness a portable, first-class telemetry signal.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode nobody's dashboard catches
&lt;/h2&gt;

&lt;p&gt;Picture a data pipeline at 3 a.m. Every service is green. Latency is low, error rates are zero, throughput is normal. Every SLO is met.&lt;/p&gt;

&lt;p&gt;And the executive dashboard it feeds is quietly &lt;strong&gt;six hours out of date&lt;/strong&gt;, because one upstream producer stalled and nothing downstream noticed. There was no error to fire on. No latency spike. The data just… stopped being fresh.&lt;/p&gt;

&lt;p&gt;This is one of the most common and most invisible failure modes in modern data systems, and it has a precise name from networked-systems research: the &lt;strong&gt;Age of Information&lt;/strong&gt; (AoI) — how much time has elapsed since the freshest piece of data was generated at its source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;age = now − event_time(freshest record)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a source stops producing, its &lt;code&gt;age&lt;/code&gt; climbs without bound while every other signal stays perfectly healthy. That's exactly the condition freshness monitoring exists to catch — and it's exactly the signal our observability stacks don't have a standard way to express.&lt;/p&gt;

&lt;h2&gt;
  
  
  We standardized everything except freshness
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry has become the de facto standard for telemetry. It gives us portable conventions for latency, errors, throughput, database calls, and messaging. You can move a dashboard or an alert between backends and vendors because the &lt;em&gt;names&lt;/em&gt; are standardized.&lt;/p&gt;

&lt;p&gt;But there is &lt;strong&gt;no&lt;/strong&gt; OpenTelemetry semantic convention for data freshness. &lt;code&gt;db.*&lt;/code&gt; and &lt;code&gt;messaging.*&lt;/code&gt; describe &lt;em&gt;operations&lt;/em&gt; — how long a query took, how many messages were published — not how &lt;em&gt;old the data itself is&lt;/em&gt;. There isn't even a standardized consumer-lag metric.&lt;/p&gt;

&lt;p&gt;So today, every team reinvents freshness in a silo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data-observability vendors (Monte Carlo, Metaplane, Sifflet, …) each compute it internally, incompatibly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dbt source freshness&lt;/code&gt; computes it for dbt.&lt;/li&gt;
&lt;li&gt;Kafka-lag tools (Burrow) compute it for Kafka.&lt;/li&gt;
&lt;li&gt;Everyone else writes a bespoke &lt;code&gt;SELECT MAX(updated_at)&lt;/code&gt; and a Grafana panel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of it is comparable across systems or portable across backends. Freshness is a well-understood quantity trapped in a dozen proprietary shapes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposal: &lt;code&gt;data.staleness.*&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I've been working on a small, vendor-neutral semantic convention for data staleness / freshness, built on OpenTelemetry, plus a complete reference implementation. &lt;strong&gt;To be clear about status: this is an independent, community project — not an official OpenTelemetry project, and not endorsed by or affiliated with the OpenTelemetry project or the CNCF. The convention is a &lt;em&gt;proposal&lt;/em&gt;, at OpenTelemetry "Development" stability, currently under discussion with the Semantic Conventions SIG (&lt;a href="https://github.com/open-telemetry/semantic-conventions/issues/3909" rel="noopener noreferrer"&gt;issue #3909&lt;/a&gt;). It is not (yet) an accepted standard, and names may change.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The shape is deliberately compact — a handful of metrics grounded in AoI:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.age&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;now − event_time&lt;/code&gt; of the freshest record (the primary signal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.lag&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;processing_time − event_time&lt;/code&gt; of the most recent record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.last_update.timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unix time of the last successful update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.records.behind&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;positional backlog (e.g. Kafka consumer lag)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.staleness.sla.*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;configured threshold + breach evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;…with attributes like &lt;code&gt;data.source.system&lt;/code&gt;, &lt;code&gt;data.source.name&lt;/code&gt;, &lt;code&gt;data.staleness.method&lt;/code&gt;, and &lt;code&gt;data.staleness.partition&lt;/code&gt; — orthogonal to, and composable with, the existing &lt;code&gt;db.*&lt;/code&gt; and &lt;code&gt;messaging.*&lt;/code&gt; conventions.&lt;/p&gt;

&lt;p&gt;The key insight is &lt;code&gt;age&lt;/code&gt; vs &lt;code&gt;lag&lt;/code&gt;: if a source stops producing, the last record's &lt;code&gt;lag&lt;/code&gt; stays constant while &lt;code&gt;age&lt;/code&gt; keeps rising. That divergence is the alert you actually want.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design principle: never fabricate a number
&lt;/h2&gt;

&lt;p&gt;One rule runs through the whole implementation: &lt;strong&gt;an unmeasurable freshness value must be &lt;em&gt;visible&lt;/em&gt;, never faked.&lt;/strong&gt; An empty table, a &lt;code&gt;NULL MAX()&lt;/code&gt;, a timeout, an empty Kafka partition — each surfaces as &lt;code&gt;data.staleness.probe.errors&lt;/code&gt; with a specific &lt;code&gt;error.type&lt;/code&gt;, not as a fabricated "0 seconds old." A broken freshness check that silently reports "fresh" is worse than no check at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in two minutes
&lt;/h2&gt;

&lt;p&gt;There are two ways to adopt it — a Python SDK for code you own, and a zero-code Collector for infrastructure you don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python SDK&lt;/strong&gt; — you give it a probe that returns the freshest event time; it emits the standardized metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;data-staleness-otel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;otel_staleness&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StalenessMonitor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conventions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;otel_staleness.probes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SQLFreshnessProbe&lt;/span&gt;

&lt;span class="n"&gt;monitor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StalenessMonitor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SQLFreshnessProbe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fetch_max_epoch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;latest_updated_at&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c1"&gt;# Unix seconds or datetime
&lt;/span&gt;    &lt;span class="n"&gt;source_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POSTGRESQL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sla_threshold_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Zero-code Collector&lt;/strong&gt; — a custom OpenTelemetry Collector receiver scrapes SQL, Kafka, Kinesis, files, HTTP, schema registries, and DB-migration versions directly from YAML, with no application code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;datastaleness&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;collection_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sql&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
        &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
        &lt;span class="na"&gt;dsn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres://user:pass@db:5432/app?sslmode=disable"&lt;/span&gt;
        &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
        &lt;span class="na"&gt;timestamp_column&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;updated_at&lt;/span&gt;
        &lt;span class="na"&gt;sla_threshold_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both emit the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;data.staleness.*&lt;/code&gt; metrics into the same backend, so they interoperate.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's more than a spec
&lt;/h2&gt;

&lt;p&gt;Because a convention is only credible if it's real and checkable, the project ships:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A written spec plus a &lt;strong&gt;machine-readable Weaver model&lt;/strong&gt; that passes &lt;code&gt;weaver registry check&lt;/code&gt; — the same tool the OpenTelemetry SIG uses to validate and generate conventions.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;language-agnostic conformance suite&lt;/strong&gt; so the numbers aren't one library's opinion.&lt;/li&gt;
&lt;li&gt;A two-language &lt;strong&gt;reference implementation&lt;/strong&gt; (Python SDK + Go Collector receiver &amp;amp; processor), &lt;strong&gt;validated end-to-end on real backends&lt;/strong&gt; — a suite stands up real Postgres, Kafka, Redis, LocalStack Kinesis, and a schema registry and asserts the emitted metrics are &lt;em&gt;numerically correct&lt;/em&gt; (age equal to a known injected offset, exact consumer lag), and that a stopped source surfaces &lt;code&gt;probe.errors&lt;/code&gt; rather than a fabricated value.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why standardize instead of ship-another-library?
&lt;/h2&gt;

&lt;p&gt;Because the value is portability. If freshness has one vocabulary, then a freshness alert, a Grafana panel, or an SLO travels across Postgres, Kafka, dbt, a vector index, and a replica — and across Prometheus, an OTLP store, or any vendor backend — unchanged. This isn't a new metric; it's the &lt;strong&gt;consolidation&lt;/strong&gt; of a well-studied one (AoI, and what every data-observability tool already computes) into the framework that already standardizes latency and errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get involved
&lt;/h2&gt;

&lt;p&gt;If "how old is my data?" is a question you've had to answer with a bespoke query and a fragile dashboard, I'd love your input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The convention proposal is &lt;a href="https://github.com/open-telemetry/semantic-conventions/issues/3909" rel="noopener noreferrer"&gt;open in the OpenTelemetry Semantic Conventions repo (#3909)&lt;/a&gt;&lt;/strong&gt; — thumbs-up, comments, and use cases genuinely help gauge demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The code, spec, model, and validation live here:&lt;/strong&gt; &lt;a href="https://github.com/anirudhrajreliability/otel-data-staleness" rel="noopener noreferrer"&gt;https://github.com/anirudhrajreliability/otel-data-staleness&lt;/a&gt; (Apache-2.0).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try it&lt;/strong&gt; (&lt;code&gt;pip install data-staleness-otel&lt;/code&gt;), tell me where it breaks, and — if this is your problem space — consider co-owning the effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Freshness is a known quantity that, paradoxically, has no standard way to be &lt;em&gt;emitted&lt;/em&gt;. Let's fix that.&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>dataengineering</category>
      <category>python</category>
    </item>
  </channel>
</rss>
