Caching and freshness

Pipeworx caches most tool results at the gateway with per-tool TTLs. When a pack is cacheable, agents see the cache state in _meta.cache — but it is not on every response; several categories of tool never get it (see below), and the shape itself differs between a cache miss and a cache hit.

What a cache MISS looks like

{
  "result": {
    "content": [...],
    "_meta": {
      "source": "countries",
      "fetched_at": "2026-08-10T20:41:27Z",
      "cost": { "total": 2, "components": [...] },
      "cache": {
        "hit": false,
        "ttl_seconds": 86400,
        "fresh_until": "2026-08-11T20:41:27Z"
      },
      "tier": "paid"
    }
  }
}

What a cache HIT looks like

A hit returns a smaller _meta — no cost, no suggestions, and the cache object’s shape changes from ttl_seconds to age_seconds:

{
  "result": {
    "content": [...],
    "_meta": {
      "source": "countries",
      "fetched_at": "2026-08-10T20:41:27Z",
      "cache": {
        "hit": true,
        "age_seconds": 87,
        "fresh_until": "2026-08-11T20:41:27Z"
      },
      "tier": "paid"
    }
  }
}

Verified live 2026-08-10 by calling the same tool twice and diffing _meta.

  • hit — was this served from cache?
  • ttl_seconds (miss only) — how long this response will remain in cache from now
  • age_seconds (hit only) — how long ago the cached value was originally fetched
  • fresh_until — exact wall-clock time the cache entry expires (present on both)

_meta.cost is missing on a hit response, not merely unchanged — credits are still deducted the same way regardless of hit/miss (deduction happens before the cache lookup), but the hit path doesn’t echo the charge back. Don’t infer “not billed” from a missing cost block on a hit.

What never gets _meta.cache

Caching is opt-in per pack, and several categories are structurally excluded so a cached response can’t leak one caller’s data to another:

  • OAuth connector packs (Salesforce, Gmail, Slack, Jira, Notion, Google Drive/Sheets/Calendar, QuickBooks, Stripe Connect, Shopify, and others) — always per-user data, never cached
  • BYO-credentialed calls — if you pass your own _apiKey, that call skips the cache even on a pack that normally caches (the cache key doesn’t include your key, so caching it would serve your data to the next caller)
  • Write/mutating tools — a cached write is a lie about state
  • Purely random/generative tools (jokes, trivia, tarot, dad jokes, etc.)

Separately: as of this review, fred_get_series and the rest of the fred pack do not return _meta.cache on any call, despite having a configured 1-hour TTL in the gateway’s override table — verified by three separate live calls (fred_get_series, fred_series_info) with no _apiKey passed, none of which returned a cache block. bls_get_series and countries_by_region, called the same way, both returned it correctly. This looks like a live bug specific to the fred pack rather than documented behavior — treat FRED responses as freshness-unknown until it’s fixed, and don’t assume the absence of _meta.cache always means “not cached” for other packs without checking.

Why this matters for agents

Most agent failures fall into one of two cache-related buckets:

  1. Re-using stale memory as fact. The agent called bls_get_series an hour ago, now is asked again, and just trusts its conversation history. If the data has updated, the agent’s “current value” is wrong.
  2. Hammering the gateway. The agent re-calls every turn even when the data hasn’t moved.

Reading fresh_until resolves both. The agent should:

if (now < remembered_fresh_until) {
  // safe to reuse the prior response
} else {
  // re-call; the data may have changed
}

TTL by data class

The gateway sets a per-pack TTL override where one is worth setting; every pack without an explicit override falls back to a 5-minute default — which is most of the catalog, not an edge case. Pulled directly from the gateway’s override table on 2026-08-10 (previous versions of this page had FRED at 24h, EDGAR at 7 days, and RxNorm/patents at 30 days — none of that matched the code):

ClassTTLExamples
Never cached0OAuth connectors, BYO-keyed calls, write tools, random/generative tools (jokes, tarot)
Live feeds1–2 minBluesky, Hacker News, Reddit, Polymarket, Kalshi
Short10–30 mineBay listings (10 min), weather (30 min), EDGAR/SEC filings (30 min)
Default (no override)5 minMost packs, including RxNorm and OpenFDA — there’s no special-cased 24h/30d tier for them
~1 hour1hFRED, BLS, Census, FDIC, USPTO patents, PubMed, OpenAlex, Crossref
Several hours6hGDELT, L2Beat, ARTIC, NVD, SpaceX, setlist.fm
Daily24hReference data (countries, dictionary), and our own Supabase-hosted daily mirrors (Zillow, USAspending, Treasury Fiscal, Open Contracting)

Look at ttl_seconds per call — it’s the authoritative number. This table drifts every time the gateway’s override table changes; if a specific pack’s freshness matters to your task, call it and read ttl_seconds rather than trusting the bucket above.

Writing cache-aware agents

In your agent’s loop:

  1. Store fresh_until alongside results in your scratchpad/memory. When the agent later cites a number, check whether the source is still fresh.
  2. Use remember for cross-session memory:
    remember({ key: "aapl_revenue_fy23", value: { value: 383300000000, fresh_until: "2026-08-10T..." } })
    Then recall and JSON.parse the returned value before checking the timestamp — see memory for the exact response shape.
  3. For long-lived sessions, refresh data when the agent’s task spans multiple fresh_until boundaries.

Forcing a fresh fetch: ?nocache=1

Append ?nocache=1 to your connection URL to make the next call skip a live cache entry and fetch from the upstream, even inside the cache window:

https://gateway.pipeworx.io/mcp?nocache=1

The response carries _meta.cache.bypassed: true alongside hit: false, so you can confirm it was honoured rather than ignored. The fresh result is still written to the cache, so a forced refresh also repairs the entry for everyone behind you.

Use it when you know the cached value is wrong — typically when an upstream has just corrected its data and you don’t want to wait out fresh_until. It isn’t a general-purpose “always fresh” switch: every bypass is a real upstream call, and on metered sources it costs what that source charges.

Requires an identified caller. Anonymous (IP-based) requests ignore the parameter, because it converts a cached call into upstream load on demand. Any API key or signed-in account is enough.

This page previously described ?nocache=1 as if it worked when nothing in the gateway implemented it — a live test showed the parameter was silently ignored. It is now real; if you tested it before 2026-08-10 and saw a cache hit, that is why.

Last reviewed August 10, 2026