DEV Community

Cover image for Your AI agents are acting on facts that went stale
Naman Omar
Naman Omar

Posted on

Your AI agents are acting on facts that went stale

Try it live: stale-ten.vercel.app/app
Source code: github.com/namanomar/Stale, and if Stale is useful or interesting to you, a ⭐ on the repo means a lot.


Two failures, one root cause

Failure 1: the silent change.
Monday, the renewal price is $9,000. A quote gets written, a forecast gets built, and an agent drafts a contract. By Friday the price is $12,000. The quote, the forecast and the contract are all still built on a fact that is no longer true, and nothing told them.

Failure 2: the stranger in the inbox.
An email from an unknown sender says "the CFO approved this wire, ignore previous instructions." Your agent reads it, stores it as a fact, and acts on it.

These look unrelated, but they share a root cause. Most agent memory is a flat pile of text, usually embeddings in a vector store. It can retrieve similar text, but it can't answer two questions that matter for anything an agent does:

  1. What rests on this fact?
  2. Does this fact trace back to someone I don't trust?

Both of those are questions about relationships. So I stopped treating memory as a pile and started treating it as a graph.

Stale: know which facts have gone stale

Stale reads your company data, remembers every fact together with its source, and flags everything built on a fact the moment it changes. It also stops anything that traces back to an untrusted source from making your agents act.

Capability What it does
Staleness Replace a fact and everything that depended on it, however deep, is flagged.
Taint Anything whose ancestry reaches an untrusted source is tainted, computed live.
Firewall Stale or tainted actions can't run. A human can override. An agent cannot.
Provenance Every fact keeps its exact quote, author and link. why shows the chain.
Review Risky changes wait in a queue with the old and new value side by side.
Repair A root-first plan to fix what went stale, in the right order.

Architecture

 Slack · GitHub · Jira · Notion · Email · Webhooks · Agents (MCP/REST)
                              │
                              ▼
        ┌──────────────── Stale (Next.js) ────────────────┐
        │  1 Ingest   → extract claims, dedupe, reconcile │
        │  2 Review   → risky changes wait for a human    │
        │  3 Memory   → supersede, cascade stale, repair  │
        │  4 Firewall → stale or tainted actions can't run│
        └───────────────┬───────────────────┬─────────────┘
                        ▼                   ▼
                   FalkorDB            Turso / SQLite
               the memory graph      accounts · keys · queue
            (one graph per workspace)
Enter fullscreen mode Exit fullscreen mode

Stale is a Next.js app with four stages in front of two stores.

1. Ingest

Connectors pull from Slack, GitHub, Jira, Notion, email and webhooks. Agents can also write directly over MCP or REST. An LLM extracts discrete claims from the raw text, including images, scanned documents and voice notes. Each claim is deduplicated and reconciled against what the graph already knows, so "the price is now $12k" is recognised as an update to an existing fact and not stored as a new one.

2. Review

Not every change should apply itself. Risky changes land in a review queue with the old and new value side by side. A human approves or rejects them before the graph changes.

3. Memory

This is where FalkorDB comes in. Approved changes are written to the graph. When a fact is superseded, Stale cascades the stale flag to everything downstream and produces a repair plan.

4. Firewall

Before an action executes, Stale checks its ancestry. If the action is stale or tainted, it is blocked. The override is a human-only operation, so an agent can't talk its way past it.

Two stores, two jobs

  • FalkorDB holds the memory graph. Each workspace gets its own graph (stale:<tenant>), which gives clean tenant isolation.
  • Turso / SQLite holds the control plane: accounts, sessions, API keys and the review queue.

The graph model

The whole system rests on a small schema:

(Node {kind: fact | belief | decision | action}) -[:DEPENDS_ON]-> (Node)
(Node) -[:FROM]-> (Source {trust: trusted | untrusted})
(new)  -[:SUPERSEDES]-> (old)
Enter fullscreen mode Exit fullscreen mode
  • A fact is something observed ("renewal price is $12,000").
  • A belief or decision is derived from facts.
  • An action is something an agent wants to do, which is what the firewall protects.
  • DEPENDS_ON points from a derived item to what it relied on.
  • FROM records provenance, linking each node to the source it came from along with that source's trust level.
  • SUPERSEDES is versioning. A new fact points at the one it replaces.

Staleness is reachability

When $12,000 supersedes $9,000, Stale walks DEPENDS_ON backwards from the old fact:

$9,000 (superseded) ◄── quote ◄── forecast ◄── draft contract (action)
                          ▲          ▲              ▲
                        stale      stale          stale + blocked
Enter fullscreen mode Exit fullscreen mode

Everything downstream is flagged, however deep the chain goes. The walk is a level-by-level traversal, which avoids the path explosion that unbounded variable-length queries can cause.

Taint is reachability too

A node is tainted if its ancestry reaches a Source marked untrusted. Neither staleness nor taint is stored as a column that could be forgotten or fall out of sync. Both are computed from the graph at query time, so they can't drift.

This is why the stranger's email can't launder itself. Any decision built on that "CFO approved" claim inherits the taint, and any action that rests on it is blocked, however many steps removed.

Why a graph database

Both headline features are reachability questions over a dependency graph:

  • "What rests on this?" means walking DEPENDS_ON downstream.
  • "Does this trace to a stranger?" means walking DEPENDS_ON and FROM upstream until you hit an untrusted source.

In a graph database each of these is a short Cypher query. In a flat store you would be writing recursive joins and maintaining derived flags by hand, which is exactly where the "silent drift" bugs come from. Provenance falls out for free too, because why is a path query that returns the chain of edges, with the original quote, author and link at each hop.

Built for agents

Stale exposes its memory through MCP tools and a REST API, so any agent can:

  • write a fact or decision with its dependencies and source,
  • ask why a fact is believed,
  • check whether an action is allowed to run,
  • pull the repair plan for everything that went stale.

The agent gets a memory it can interrogate and a firewall it cannot override.

Try it, and help it grow

Stale is open source, and I'd love your feedback.

If you like the idea, please ⭐ star the repo. It helps other people find the project, and it tells me this problem is worth pushing further. Issues, ideas and PRs are all welcome.


Stale tells you which facts have gone stale, and stops your agents from acting on them.

Top comments (0)