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 nowage_seconds(hit only) — how long ago the cached value was originally fetchedfresh_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:
- Re-using stale memory as fact. The agent called
bls_get_seriesan hour ago, now is asked again, and just trusts its conversation history. If the data has updated, the agent’s “current value” is wrong. - 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):
| Class | TTL | Examples |
|---|---|---|
| Never cached | 0 | OAuth connectors, BYO-keyed calls, write tools, random/generative tools (jokes, tarot) |
| Live feeds | 1–2 min | Bluesky, Hacker News, Reddit, Polymarket, Kalshi |
| Short | 10–30 min | eBay listings (10 min), weather (30 min), EDGAR/SEC filings (30 min) |
| Default (no override) | 5 min | Most packs, including RxNorm and OpenFDA — there’s no special-cased 24h/30d tier for them |
| ~1 hour | 1h | FRED, BLS, Census, FDIC, USPTO patents, PubMed, OpenAlex, Crossref |
| Several hours | 6h | GDELT, L2Beat, ARTIC, NVD, SpaceX, setlist.fm |
| Daily | 24h | Reference 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:
- Store fresh_until alongside results in your scratchpad/memory. When the agent later cites a number, check whether the source is still fresh.
- Use
rememberfor cross-session memory:
Thenremember({ key: "aapl_revenue_fy23", value: { value: 383300000000, fresh_until: "2026-08-10T..." } })recallandJSON.parsethe returnedvaluebefore checking the timestamp — see memory for the exact response shape. - For long-lived sessions, refresh data when the agent’s task spans multiple
fresh_untilboundaries.
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=1as 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.