
Run Cognee file-based memory on one Tigris bucket and recover corrupted state by forking a baseline snapshot instead of rebuilding.
Why Agent Memory Breaks the First Time You Get It Wrong
Giving an agent memory also creates new ways for that memory to become unreliable. A bad document can enter the system, an improve() pass can alter relationships that were already correct, or a prune operation intended for testing can accidentally run on live data. Even a manual correction can introduce a mistake. These failures do not necessarily trigger an error, so the agent may keep answering questions normally while relying on information that is outdated, corrupted, or simply wrong internally.
Fixing this is harder than it sounds. Most agent-memory systems use three stores: a vector database for embeddings, a graph database for relationships, and a metadata store connecting everything. Each has its own backup process, with no shared recovery point. Restoring consistent memory means piecing together separate backups and hoping they match.
That's why teams often rebuild everything from scratch. But re-ingestion takes time, especially as memory grows. More importantly, it cannot undo wrong answers or actions the agent already took while relying on corrupted memory.
A better approach is to move recovery into the storage layer, and that's where Tigris comes in. If the agent's memory lives as files in one bucket, snapshots and forks let you restore a known-good point without stitching together separate backups. Derived state can then be rebuilt from the restored data. You still can't unsay the answers the agent already gave, but you can stop bad memory from producing new ones, much like reverting a bad commit instead of hand-editing every file it touched.
Why not just restore a backup?
If memory can go bad, the obvious answer is "back it up." The catch is what an agent's memory actually is: raw documents, a vector index, a knowledge graph, and a metadata store, four stores that only mean something when they agree. Back them up separately, and recovery becomes a reconciliation problem, matching four restores taken at four moments and hoping they line up. Re-ingesting from source avoids that but pays twice: it re-embeds everything, and it still can't retract the wrong answers the agent already gave.

Image by Author
Keeping all four in one bucket collapses the problem. A single snapshot freezes every layer at the same instant, and a fork rewinds them together, in one operation, as fast whether the memory is megabytes or gigabytes. The difference isn't just speed. It's that recovery becomes a property of where memory is stored, not a cleanup job you assemble after something breaks.
One Bucket, Four Layers, and a Rewind Button
What makes storage-level recovery possible is how Cognee stores its data. In file-based mode, Cognee doesn't need three separate managed services. Instead, it stores everything as files on disk, organized into four parts:
- The original ingested documents
- A LanceDB vector index
- A Kuzu knowledge graph
- A SQLite metadata database
With that directory stored in a Tigris bucket, all four parts of the agent's memory live together as objects in one place, which is the design the rest of this tutorial rests on: one bucket, four layers.

Image by Author
The diagram shows the full process. On the left, the Cognee agent uses remember() to store information and recall() to retrieve it. In the middle, the persistence layer contains the four stores, along with the snapshot and fork. On the right is the recovery process when memory goes bad.
Keeping all four stores in one bucket makes recovery simpler. A snapshot captures them at the same point in time, so you don't end up with an updated vector index and an outdated graph or have to match backups taken at different times.
A fork creates a separate, writable copy of that memory. You can test changes, investigate problems, or recover from mistakes without touching the original data.
Forks use copy-on-write, so Tigris shares the existing data rather than copying every file. It only stores new changes as the fork diverges. This makes forking fast enough to use mid-incident: the clean copy is ready after one command, without waiting for an entire dataset to be copied.
Provisioning Tigris and Picking a Support Knowledge Base
Start by creating a Tigris account. The free monthly allowance is more than enough for this demo. Once inside the console, create an access key pair. The keys start with tid_ and tsec_. Since Tigris is S3-compatible, the same pair works for both the CLI and Cognee.
Install the CLI and sign in:
npm install -g @tigrisdata/cli
tigris configure --access-key tid_YOUR_KEY --access-secret tsec_YOUR_SECRET
tigris whoami
Now create the bucket, and note the one flag that matters:
tigris buckets create <bucket> --enable-snapshots
Snapshots must be enabled when creating the bucket. You can't turn them on later, and forks depend on them. Since recovery relies on snapshots, make sure to include --enable-snapshots.
Next, install Cognee. The [aws] extra adds the S3 support needed to read and write data to Tigris:
pip install "cognee[aws]"
Cognee loads its configuration from a .env file when imported, so create it before importing the library. It contains your Tigris credentials, LLM and embedding settings, and the storage options that point Cognee to the bucket instead of local disk:
# Tigris credentials (boto3/s3fs read these automatically)
AWS_ACCESS_KEY_ID=tid_YOUR_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=tsec_YOUR_SECRET_ACCESS_KEY
AWS_REGION=auto
AWS_ENDPOINT_URL=https://t3.storage.dev
# LLM + embedding models (OpenAI by default)
LLM_API_KEY=sk-your-openai-api-key
LLM_MODEL=openai/gpt-4o-mini
EMBEDDING_MODEL=openai/text-embedding-3-small
# Store data on Tigris instead of local disk
STORAGE_BACKEND=s3
STORAGE_BUCKET_NAME=<bucket>
DATA_ROOT_DIRECTORY=s3://<bucket>/cognee/data
# SYSTEM_ROOT_DIRECTORY: leave unset to auto-derive a local path (Windows),
# or set s3://<bucket>/cognee/system for fully single-bucket mode (Linux/WSL)
Three settings are worth paying attention to. Without STORAGE_BACKEND=s3, Cognee writes to local disk even if the s3:// paths are set. Pin EMBEDDING_MODEL from the start, since switching models later can cause an embedding-dimension mismatch. Finally, SYSTEM_ROOT_DIRECTORY behaves differently across platforms. On Windows, the embedded graph engine needs a local path for derived databases (leave it unset and the project creates one), while raw data still goes to Tigris. On Linux or WSL, you can use s3://<bucket>/cognee/system to keep everything in one bucket. See the Tigris guide for Cognee for the full setup, including Postgres for multi-instance deployments.
For the knowledge base, use a small, hand-written support FAQ with roughly 10 to 15 entries covering password resets, billing, refunds, and syncing. This keeps ingestion costs low and lets you control exactly which fact gets corrupted later. Alternatively, use an excerpt from an open-source project's FAQ or build a knowledge base from the Tigris documentation or CLI reference.
Teaching the agent: ingesting docs into memory
With the bucket and .env ready, teaching the agent takes one call. Cognee's remember() chunks the text, extracts entities and relationships into a knowledge graph, creates embeddings in a vector index, and saves the metadata. The script in src/ingest.py is mostly a file read and a single await:
async def ingest():
await cognee.forget(everything=True) # start clean
text = FAQ_PATH.read_text(encoding="utf-8")
await cognee.remember(text, dataset_name=DATASET)
Here's the important part: those four artifacts aren't separate services. They're files. When stored in one Tigris bucket, the agent's raw data and derived indexes can be snapshotted or forked together. That's what makes recovery possible without restoring four separate backups. This is a Tigris feature, not a Cognee one.
The script reads data/support_faq.md, a small support FAQ containing a refund fact we'll corrupt later. Run scripts/01_ingest.py, and the logs show each stage of ingestion:
python scripts/01_ingest.py
Three .env settings connect Cognee to Tigris. STORAGE_BACKEND=s3 enables S3 storage, DATA_ROOT_DIRECTORY sends raw text to s3://<bucket>/cognee/data, and SYSTEM_ROOT_DIRECTORY holds the derived data: vectors, graph, and metadata. Since Tigris is S3-compatible, Cognee uses the standard boto3/s3fs path. No Tigris-specific adapter is needed.
There's one platform difference to keep in mind:
On Windows, the embedded graph engine can't write to s3://, so derived data stays on a local path while raw data goes to Tigris. On Linux or WSL, SYSTEM_ROOT_DIRECTORY can also point to s3://<bucket>/cognee/system, keeping everything in one bucket.
You can verify the setup after ingestion by listing the stored files directly from the CLI:
tigris objects list <bucket>
The FAQ comes back as a single object under cognee/data, sitting in your bucket rather than on your machine:

Raw data persisted
There it is under cognee/data: the agent's raw memory, stored in your bucket. On Windows, the key may contain backslashes (cognee/data\...\text_....txt), a detail that matters during recovery.
Now let's check what the agent learned. recall() searches the stored memory using vector similarity and graph traversal, grounding its answer in the FAQ rather than relying on the base model. The wrapper lives in src/ask.py and is called from scripts/02_ask.py:
python scripts/02_ask.py "What is your refund policy?"
The answer comes back as the policy you wrote, the full 30-day refund:

Grounded answer
That grounded answer is the state everything after this returns to. So before you touch anything, freeze it. One Tigris command snapshots the bucket:
tigris snapshots take <bucket>
tigris snapshots list <bucket>
The snapshot lands with a version number. Write it down; that single identifier is your rewind button:

Recovery point
This is the foundation of the tutorial. One command captures the entire bucket at a point in time, including raw text, vectors, graph data, and metadata stored in Tigris. It returns a snapshot version number that can restore the memory later if it is corrupted or deleted. Keep that number handy because you will need it next.
Breaking it on purpose, then rolling it back
You now have a working agent and a clean snapshot in Tigris. Next, we'll corrupt its memory, see how its answers change, and recover using a fork.
Break it
One of the easiest ways to corrupt memory is to ingest a wrong fact. No invalid input or error, just a sentence that contradicts what's already stored. We can reproduce this with one remember() call in src/corrupt.py:
BAD_FACT = (
"Policy update: Nimbus Notes does not offer refunds under any "
"circumstances. All sales are final and no payment can be refunded."
)
await cognee.remember(BAD_FACT, dataset_name="support")
The key detail is that remember() doesn't overwrite the original refund policy. It ingests BAD_FACT as a new TextDocument, creates a DocumentChunk, adds its embedding to the vector index, and extracts its relationships into the graph. The wrong fact now sits beside the real policy as a separate source. Run scripts/03_corrupt.py to inject it and ask the same question again:
python scripts/03_corrupt.py
The answer has flipped to the opposite of the truth:

Silent corruption
Nothing in the output suggests a problem. The agent gives a confident, fluent answer, but the information is wrong. There's no error or warning, and the evidence lies in the stored memory. Cognee lets us visualize that structure, and comparing the clean and corrupted graphs makes the change clear:

The counts make the problem clear. Clean memory contains one document and one chunk, the FAQ. Corrupted memory contains two of each, including the injected “Policy update.” Both can appear in a refund query through vector similarity or graph traversal, and the retriever has no built-in way to know which one is correct. As a result, the model may answer using the wrong information.
Re-ingesting won't help if the bad fact is still in the source, since you'd simply embed it again. And even a clean rebuild can't undo answers already given. To restore memory to a state before the bad fact existed, you need an earlier recovery point. That's exactly what the Tigris snapshot provides.
Let the agent notice
Recovery shouldn't depend on someone noticing a wrong answer. Instead, the agent can check its memory before responding and decide whether to continue. In src/check.py, the agent recalls the answer, then asks the model to check whether the retrieved context is consistent or contains contradictions:
result = await check("What is your refund policy?")
# -> {"verdict": "degraded", "reason": "..."}
Against the corrupted memory, the verdict comes back degraded, and the reason names the contradiction directly:
python src/check.py
The check recalls the answer, then asks the model to audit whether its own memory holds together.

Self-detected contradiction
That verdict is the trigger. A strong verdict answers the user; a degraded one withholds the answer and calls recovery, with no human in the loop. It's the difference between memory that rots silently and memory that raises its hand, and it's what turns the snapshot from a manual backup into something the agent can reach for on its own.
Roll it back
Recovery starts by forking a clean bucket from the baseline snapshot. Tigris uses copy-on-write, so the fork shares the snapshot's existing data and only stores new changes. There's no need to copy the entire dataset, making it fast enough to use mid-incident rather than waiting for a lengthy restore.
Tigris supports snapshots, forks, and rebases through its CLI. To use them from Python, src/tigris.py provides a small subprocess wrapper around the tigris binary:
def fork_from_snapshot(fork_name, source_bucket, snapshot_version):
return _run([
"buckets", "create", fork_name,
"--fork-of", source_bucket,
"--source-snapshot", snapshot_version,
])
scripts/04_recover.py forks from the baseline snapshot, repoints the agent at the clean copy, rebuilds its derived memory from the recovered raw data, and asks the refund question a third time:
python scripts/04_recover.py <snapshot_version> <fork_bucket>
The agent answers the refund question a third time, and the correct policy is back:

Memory restored
The agent is back to the correct answer, and the bad document and its derived relationships are gone. Recovery happens in two steps. First, the fork restores the clean raw data from the snapshot without downloading or locating a separate backup. Then Cognee rebuilds the vector index and graph from that data:
On Linux or WSL, Cognee rebuilds directly from the fork's s3:// path. On Windows, it uses a local mirror of the recovered data because of the backslash-key issue mentioned earlier. Both restore the same clean state.
The important part is that you don't need to reconstruct the knowledge base or restore each store separately. The fork brings back the original raw data, and Cognee rebuilds the derived memory from it, much like checking out a known-good Git commit.
Let it heal itself
Manual recovery is the whole pattern in the open, one step at a time. In practice you'd want the agent to run it the moment its memory looks wrong, with nobody watching. That's just a matter of composing the pieces you already have: the degraded verdict becomes the trigger, and recovery follows automatically.
scripts/05_autoheal.py does exactly that. It audits the memory, and if the verdict is degraded, it forks the latest snapshot, repoints at the clean copy, rebuilds, and re-checks, all in one call:
python scripts/05_autoheal.py
With the memory already corrupted, one run detects the problem and recovers on its own:

Self-healing memory
The loop detects the contradiction, recovers from the last known-good snapshot, and confirms the memory is healthy again, with no human involved. The wrong answer never had to be caught by a person.
It doesn't over-correct, either. Run the same check against healthy memory, and it passes without touching anything, so recovery only fires when something is actually wrong:

No false alarms
This is the difference between memory that rots silently and memory that repairs itself the moment it drifts, but only when it needs to.
One note on the Python integration
Tigris's Cognee guide uses TypeScript for snapshots and forks, while the agent runs in Python. The tigris CLI bridges that gap. A small subprocess wrapper lets you manage snapshots and forks directly from the same Python code that runs the agent, whether you trigger recovery by hand or let the agent heal itself.
Memory as a Versioned Artifact
The recovery process above illustrates a different approach to managing agent memory. Most agents rely on a live database, where recovery typically involves restoring backups, running migrations, or repairing state. While these methods are effective, reversing a bad change is often difficult because databases are designed to preserve the latest state rather than return cleanly to an earlier one. Here, memory is stored as files in a single Tigris bucket, including the raw data, vector index, graph, and metadata. This changes the recovery model. Instead of treating memory as a live service, you can manage it more like code:
- A snapshot is a commit: a frozen version of memory.
- A fork is a branch: an independent, writable copy.
- Recovery is a checkout: returning to a known-good version.
That's why recovery was so simple. You weren't repairing a database; you were reverting to an earlier version of memory.
Object storage is what makes this possible. A live database can't hand you a free, instant copy of its whole state, because that state is tied to a running process. Files have no such limit: they just sit there, and with copy-on-write, snapshots and forks share the same underlying data until something changes, so branching costs nothing until it diverges.
You can use the same approach to isolate memory per customer, undo a bad ingest, or test a new Cognee embedding model on a fork before committing.
The takeaway is simple: when memory is versioned, a bad learning update becomes something you can revert, not an incident you have to repair.
Gotchas, what's next, and where to take it
A few limits are worth knowing before building on this.
The biggest is concurrency. Cognee's file-based storage uses last-writer-wins, which works for this single-agent demo but not for multiple instances writing to the same memory. For concurrent writers, use Cognee's Postgres-backed mode for metadata and graph storage, while keeping raw data and snapshots in Tigris. The recovery approach stays the same, though the derived stores need their own recovery process.
Two smaller details matter too. On Windows, the embedded graph engine needs a local path, while Linux or WSL can keep everything in one bucket. Also, deleted bucket names aren't immediately reusable. If a recovery fork fails because of this cooldown, choose a new name.
The demo fits within the Tigris free tier, with minimal storage and requests. Copy-on-write forks share existing data until changes are made. OpenAI usage adds a small cost for embeddings and answers.
You can take this further by giving each tenant its own memory fork or testing changes on a separate fork before applying them. The idea stays the same: snapshots preserve a known-good state, and forks let you experiment or recover without touching the original memory. Much like version control for code, but for the data your agent relies on.