Skip to main content
Michael Onofre
AI Evaluation7 min read

The data was correct. It was just from 1947.

How I traced a plausible-but-stale result through retrieval and caching, fixed the data contract, and made future failures easier to investigate.

Written by

Michael Onofre

Notes on practical AI systems, evaluation, and human review.

While manually testing Tradings.Guru, I started seeing results that felt wrong. The responses were structured correctly. The fields were populated. The underlying values were real. But they did not match what I expected from the market and economic data I already knew.

The problem was not that the system had invented a number. It had retrieved a legitimate number from the wrong point in time. A request intended to describe current conditions had started in 1947. The data was correct; its use was not.

1. The failure passed every shallow check

Nothing about the response looked like a conventional outage. The provider returned a successful HTTP status. The response parsed successfully. Expected fields existed. A basic health check could reasonably mark the request successful even though the selected observations were decades old.

The affected Federal Reserve Economic Data (FRED) tools combined a small result limit with the API's default ascending date order. That selected the earliest observations instead of the newest ones. Retail-sales data began in the 1990s; GDP and inflation series reached back to 1947. Those records belong in a historical analysis, but they are not suitable evidence for a current-state tool.

The dates were present, so a model might have noticed them. The workflow could not rely on a model to infer and enforce an unstated freshness rule, however. A hallucination is not the only path to a confident, misleading answer: a model can reason faithfully over context that the retrieval layer should have rejected first.

2. Freshness is part of the data contract

The fix could not be one universal cache duration. Tradings.Guru combined providers including Finnhub, Yahoo Finance, FRED, and LunarCrush across tools with very different time horizons. A stock quote, insider filing, social-sentiment signal, options calculation, and macroeconomic series should not all expire on the same schedule.

Freshness is relative to the decision. If a workflow labels a quote as live, even a short delay can matter. Insider filings and economic releases change more slowly. A sector-seasonality view may intentionally use years of history, but it still needs to state the observation window and distinguish historical patterns from current conditions.

Quotes and market movement
Use short freshness windows, validate the symbol and market session, and avoid presenting a prior close as a live price.
Options and event data
Tie the result to its expiration or event date. Max-pain, split, and catalyst data lose meaning when their time boundary is missing.
Insiders and social sentiment
Preserve the provider, collection window, and latest available record so a slower update cadence is not mistaken for a failed feed.
Macro and historical analysis
Request an explicit order and range, then label the latest observation date. Older records may be essential context without being current evidence.

3. Last-known-good memory creates resilience—and risk

The cache layer was designed to use Vercel KV, a key-value cache, when configured, with an in-memory fallback for local development. Each entry had a fresh period, a stale period, and what I called a last-known-good value—the most recent payload that passed the cache's structural success checks. When an upstream provider failed or returned an obvious error-shaped payload, the system could keep a tool available instead of replacing prior data with an error or empty panel.

That is useful resilience. It is also a trust problem when the age of the fallback is hidden. The cache could retain a cacheable payload well beyond its fresh window and return it with a successful HTTP status plus a STALE-FALLBACK header. At the time I reviewed it, the tool interface parsed the JSON body, discarded that header, and showed a static Live Data badge. A user could therefore see available data without seeing the difference between fresh, stale, and last-known-good.

I think of this as retrieval memory, but not literally as retrieval-augmented generation (RAG). There were no embeddings or vector search involved in the cache. The useful analogy is that both systems retrieve prior information for a later decision. In either case, memory needs provenance and time boundaries or old context can quietly become present-tense evidence.

4. Model routing does not repair bad context

In my broader local setup, separate from the specific FRED routes, I sent more demanding AI tasks through Vercel and OpenAI APIs while a local OpenClaw setup handled smaller jobs. That kind of routing can control cost, latency, and capability. It does not change the evidence requirement.

A larger model may explain stale data more convincingly; a smaller model may summarize it more cheaply. Either might notice an old timestamp, but model choice cannot repair a data contract the workflow never enforced. Freshness validation has to happen before the model path becomes relevant.

5. Fix the input before tuning the generation

The first repair was explicit: request the newest FRED observations with descending date order, use bounded result counts, replace a GDP-per-capita series that returned no usable observations, align the remaining series to the intended metrics, and transform each response into a predictable shape.

In subsequent hardening, similar rules were applied elsewhere: normalize ticker symbols, keep cache keys specific to the requested symbol, reject obvious error-shaped provider responses, and preserve a prior cacheable payload rather than overwriting it with a failed refresh. Fast-moving market data received shorter freshness budgets; filings, fundamentals, and macro data could use longer ones. Results carried provider and last-updated information where the tool supported it, and multiple quote providers could be tried when a primary source returned nothing.

Those changes did not make the system infallible. They made failure easier to identify and less likely to masquerade as success. That is the kind of improvement I look for in AI evaluation: not a promise that the model will always be right, but evidence that the surrounding workflow can recognize when its inputs are not trustworthy enough to proceed normally.

6. Verify manually, then turn the lesson into monitoring

I did not treat one corrected response as proof that the workflow was fixed. I manually compared familiar results with their original providers until the dates, ranges, and behavior were consistent. Domain familiarity helped expose the problem, but the validation still needed to be repeatable by someone who did not already know what the answer should look like.

Later, I added diagnostic logs and provider health probes, then established trigger conditions for future investigation. They made some failures easier to inspect, but an availability check was not the same as a freshness check: a provider could still return a successful response containing old data. A stronger next step was to alert on observation age and cache state, not only provider reachability.

I do not have a defensible percentage improvement or trading-performance claim to attach to this work, and the case study does not need one. The concrete outcome was narrower and more useful: the oldest-first selection was corrected in those FRED tools, obvious error-shaped refreshes no longer replaced prior cacheable data, and future reliability problems became easier to investigate.

  • What source produced this value?
  • What entity, symbol, or series does it describe?
  • When was the underlying observation recorded—not merely fetched?
  • How old may it be for this specific decision?
  • Is the response live, cached, stale, fallback, or unavailable?
  • What should the workflow do when any required answer is missing?