Skip to main content
Your assistant’s memory holds both your product documentation and your sales-call transcripts, and its answers have started blending the two — a question about API rate limits comes back quoting whatever a rep promised a customer last quarter. The memory is doing its job by linking related facts; the problem is that everything lives in one undifferentiated pile.

What You’ll Build

Four small files — two technical documents (an API reference and an architecture guide) and two sales-call transcripts (where a rep promises “unlimited requests” and “instant sync”) — are ingested three ways: everything in one dataset, one dataset with node-set tags, and two walled-off datasets with node sets inside each. The same questions run under every layout, and each recall prints as a comparable QUERY / SCOPE / ANSWER block, so you can see exactly what each layout changes: the unfiltered pile blends promise with spec, node sets scope a query to one group while still answering cross-domain questions, and datasets make leakage impossible. The run ends with the rule of thumb the demo exists to teach: node sets are tags, datasets are walls. The complete runnable demo — the script plus the four data files it reads — is examples/demos/organizing_your_data/ — this page walks through its key moments rather than reproducing it.

Features in Play

  • NodeSets — tags each source with soft, overlappable groups at write time; node_name scopes retrieval to a tag at read time
  • Datasets — the hard boundary with its own permissions and storage that keeps sales transcripts out of technical answers entirely
  • Remember — ingests the four bundled files under each layout’s labels
  • Recall — runs the same questions under every layout, scoped by datasets and node_name

What to Expect

The excerpts below are from a real run, trimmed, and follow the same seven stages as How It Works: that section explains what each stage does, this one shows the output each stage produces. Stages 2 and 5 are remember() calls that print nothing, so they appear together with the recall that first reveals their effect. Every recall prints as a QUERY / SCOPE / NOTE / ANSWER block, and each ANSWER line is prefixed with the dataset it came from, so the same question can be compared across scopes by eye. Every answer is a live LLM call, so the wording varies from run to run. Stage 1 — one pile, one retrieval pool. With no filter, the rate-limit question retrieves from the API reference and the sales calls alike. In this run the model resolved the conflict in favor of the documentation, but nothing in the query kept the rep’s promise out of the pool; the NOTE line names the competition to watch for.
Stages 2 and 3 — node sets scope the same question two ways. The tagging in Stage 2 prints nothing; its effect shows the moment Stage 3 scopes a recall to one tag. Both recalls run against one dataset and one graph. Scoped to tech_docs, the answer is the documented limit; scoped to sales_calls with the same call shape, it is what the rep said, with no mention of the spec.
Stage 4 — the unfiltered cross-domain question pays off. Because both groups share one graph, the deliberately unscoped recall lines up the sales promises against the API reference and cites both sources. This is the question a dataset wall would make impossible.
Stages 5 and 6 — datasets wall the domains off, and node sets still slice inside. The three remember() calls in Stage 5 print nothing; the [tech_docs] prefix on the first answer shows it came from that dataset alone, and Stage 6’s combined datasets plus node_name filter narrows the second to the Initech call only.
Stage 7 — the footgun: an unscoped recall spans both datasets. The same rate-limit question without datasets returns one ANSWER per readable dataset, [tech_docs] and [sales_calls], so the rep’s promise is back in the result. Separating data at write time is not enough; the query has to choose its scope. The run then closes with the TAKEAWAY banner.

Before You Start

  • Complete Quickstart to understand basic operations
  • Ensure you have LLM Providers configured — every answer is a live LLM call
  • Use Ladybug (the default) or Neo4j as your graph store — both support node-set filtering out of the box
  • Leave ENABLE_BACKEND_ACCESS_CONTROL at its default, or set it to true: dataset scoping in recall() only walls datasets off in multi-user mode, which the default Ladybug and LanceDB stack turns on automatically
  • Run it from a checkout of the cognee repo: the script reads its two markdown documents and two transcripts from the sibling data/ folder
  • The script calls cognee.forget(everything=True) before each of its three sections, so point it at a scratch instance rather than memory you want to keep — see Forget

How It Works

Stage 1: Ingest Everything into One Dataset

All four files land in the default dataset with no labels, and the rate-limit question retrieves from docs and sales calls alike. This is the layout that produces the client complaint the demo opens with — the documented 100-requests-per-minute limit competes with the rep’s “unlimited requests” promise in one retrieval pool.

Stage 2: Tag Every Source with Node Sets

Same dataset, same graph — but now every item carries node-set tags. Tags are soft and overlappable: the shared acme_api tag cuts across the docs/calls split, which no folder-style hierarchy could express.

Stage 3: Scope a Recall to One Tag

node_name restricts retrieval to the tagged subgraph, so the same question now answers from the documentation alone. The sales calls are still in the graph — they just don’t participate in this query.

Stage 4: Ask a Cross-Domain Question Unfiltered

This is the payoff of tags over walls: because docs and calls share one graph, entities from both link to each other, and an unfiltered recall can draw on both sides to surface where the promises and the spec disagree. Choose node sets when you want both scoped and cross-domain questions to work.

Stage 5: Wall Off Tech and Sales into Datasets

The third layout gives each domain its own dataset: graphs are built independently, permissions and storage are separate, and either side can be forgotten without touching the other. Node sets still work inside each dataset — here the sales dataset tags each call by account.

Stage 6: Combine Dataset and Node-Set Filters

The two mechanisms compose: datasets picks the wall, node_name picks the tag inside it, and the answer covers one account’s calls only.

Stage 7: Watch an Unscoped Recall Span Both Datasets

The demo closes on its one footgun: separating data at write time is not enough. recall() without datasets spans every dataset you can read, so the final answer draws on both sides again — queries must opt into the scope they want.

Run It

Choosing a Layout

Start with the simplest layout that answers your queries cleanly. One dataset with no labels is fine while the corpus covers a single domain for a single audience. Reach for node sets the moment distinct content types share entities you still want linked — scoped questions stay clean, and cross-domain questions keep working. Move a domain into its own dataset when it must never contaminate the other’s answers, or when it needs different access control or retention — and remember the two compose, so datasets can carry node sets inside them.

NodeSets

How node-set labels are written and how node_name filters retrieval by them.

Datasets

What a dataset isolates — documents, permissions, and processing.

NodeSet Grouping

The minimal single-feature guide to tagging memories with node sets.

Agentic Procurement Decisions

A sibling demo where node-set scoping powers an agent’s research phase.