Skip to main content

What are NodeSets?

A NodeSet lets you group parts of your AI memory at the dataset level. You create them as a simple list of tags when adding data to Cognee:
node_set must be a list of non-empty strings, even for a single tag (["projectA"], not "projectA"). Any other shape raises InvalidNodeSetError before anything is stored. Content that incremental loading skips as already added is not checked, so it raises nothing. There are two exceptions: remember(content_type="skills") also accepts a single string, and a session write (with session_id) refuses an invalid value with ValueError instead (see Tagging a session with a node set).
Documents stored by earlier versions with a malformed node_set raise the same error when cognify reads them instead of being left untagged. Fix each one with update(), passing its data_id, its content, its dataset_id, and a list-shaped node_set. With the default incremental loading, re-adding identical content with add() or remember() is skipped as already added, so it leaves the stored value in place. These tags travel with your data into the knowledge graph, where they become first-class nodes connected with belongs_to_set edges — and you can later filter retrieval to only those subsets.

How they flow through Cognee

  • Remember:
    • NodeSets are attached as simple tags to datasets or documents
    • This happens when you first ingest data
    • The underlying graph-building step carries them into Documents, Chunks, and the entities extracted from those chunks — and, for a whole repository passed to add() or remember(), into every code graph node (see Code Graph)
    • They are materialized as real NodeSet nodes in the graph and connected with belongs_to_set edges
  • Recall:
    • NodeSets help define meaningful retrieval subsets
    • Use recall() with node_name and node_name_filter_operator to scope retrieval to specific node-set subsets
  • Improve:
    • The default improvement path runs memify-style enrichment
    • Related enrichment flows can create node sets such as coding_agent_rules or user_sessions_from_cache
  • Sessions:
    • A session write (QAEntry, TraceEntry, or plain text with session_id) can carry node_set. The first one pins it on the session. Later writes must repeat it or leave it out, and a different set raises SessionNodeSetConflictError
    • On the session path, an invalid value raises a validation error (ValueError, HTTP 400), not InvalidNodeSetError
    • When improve() bridges the session’s Q&A, agent traces and distilled lessons into the graph, it adds the pinned set to them, so node_name-scoped recall finds them

Entities shared across node sets

Entities are deduplicated by name. If documents tagged ["connA"] and ["connB"] in one dataset both mention the same entity, they produce a single entity node. Each write adds a belongs_to_set edge from that node to its document’s NodeSet node, and later writes never remove earlier edges. The entity ends up linked to both connA and connB, so a recall() scoped with node_name=["connA"] or node_name=["connB"] reaches it either way. node_name filtering follows these edges in the graph and checks the set names stored on the entity’s vector record. Scoping is by node, not by source document, so a node_name recall is not isolated from the other set. A node_name=["connA"] recall takes the nodes linked to connA and keeps every relationship between them, including one that only a connB document produced. If two entities both appear in connA, a fact connecting them that was extracted from connB can reach a GRAPH_COMPLETION answer scoped to connA. The set names are also stored as a belongs_to_set property, on the graph node and on the entity’s vector record. The LanceDB (default), PGVector, and Turso vector stores merge the vector record’s names on each write, and Neo4j merges the graph property. On the Ladybug (default), Postgres, and Turso graph stores, each write replaces the graph property, so it holds only the sets of the most recent write. To check membership in the graph, use the edges, not the property.

Why they matter

  • Provide a lightweight way to organize and tag your data
  • Enable graph-based filtering, traversal, and reporting
  • Ideal for creating project-, domain-, or user-defined subsets of your knowledge graph

What node_name scoping includes

When you pass node_name=["projectA"] to a graph search type such as GRAPH_COMPLETION, Cognee only scores vector hits from nodes tagged projectA. It then builds the memory fragment from three things: the projectA NodeSet node, every node directly linked to it (the documents, chunks, summaries, and entities tagged projectA), and every edge between those nodes. In practice:
  • An entity that appears only in documents from other sets is not tagged projectA, so it stays out.
  • An entity extracted from documents in several sets is one shared node linked to every one of those NodeSets, so it is in scope for each of them.
  • An edge between two in-scope entities is kept no matter which document it was extracted from. If a projectB document relates two entities that also appear in projectA, that relationship can show up in a projectA-scoped answer.
So node_name is a retrieval filter, not an isolation boundary. When sources must never feed each other’s answers, put them in separate datasets with multi-user mode on (the default for Ladybug and LanceDB). Organizing Your Data compares both layouts side by side.

See it in action

NodeSet Grouping is a short runnable guide that walks through all of this end to end: it remembers three passages under overlapping labels — ["AI", "FinTech"], ["AI"], and ["MedTech"] — renders the resulting graph so you can trace the belongs_to_set edges, and shows how to scope a later recall() to one group with node_name.

Remember

Where NodeSets are first attached

Improve

How enrichment flows add more NodeSet-based structure

Search

Use NodeSets as anchors in queries