> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Session Node Sets

> Pin a node set on a session so each project's session memory stays apart in the graph and in recall

A minimal guide to session node sets: tag a session's first write with a `node_set`, and the session is pinned to it. Use this when several projects share one dataset and you want each project's session memory to be recalled only within that project.

## Before You Start

* Complete [Quickstart](/getting-started/quickstart) to understand basic operations
* Ensure you have [LLM Providers](/setup-configuration/llm-providers) configured (`LLM_API_KEY`)
* Read [Persistent Memory Across Sessions](/guides/persistent-memory-across-sessions) for how `session_id` works
* Read [NodeSet Grouping](/guides/nodeset-grouping) for how node sets tag memory and scope recall

## Code in Action

```python theme={null}
import asyncio

import cognee
from cognee import SearchType

DATASET = "session_node_sets_guide"
QUESTION = "Who maintains the release checklist?"


async def main():
    # Start clean (optional in your app)
    await cognee.forget(everything=True)

    # A dataset for the sessions to bridge into.
    await cognee.remember("The office closes at six.", dataset_name=DATASET, self_improvement=False)

    # Project A: the node_set on the first typed entry pins the session.
    await cognee.remember(
        cognee.QAEntry(
            question=QUESTION,
            answer="In project A the release checklist is maintained by Ingrid.",
            node_set=["project-a"],
        ),
        dataset_name=DATASET,
        session_id="session-project-a",
    )
    # An agent step in the same session: repeating the set is fine.
    await cognee.remember(
        cognee.TraceEntry(
            origin_function="find_checklist_owner",
            status="success",
            method_return_value={"owner": "Ingrid"},
            node_set=["project-a"],
        ),
        dataset_name=DATASET,
        session_id="session-project-a",
    )

    # Project B: plain text with the call-level kwarg pins the second session.
    stored = await cognee.remember(
        "In project B the release checklist is maintained by Teodor.",
        dataset_name=DATASET,
        session_id="session-project-b",
        node_set=["project-b"],
    )
    await stored  # let the background session bridge finish before improving

    # A session never spans two projects.
    try:
        await cognee.remember(
            cognee.QAEntry(question="q", answer="a", node_set=["project-b"]),
            dataset_name=DATASET,
            session_id="session-project-a",
        )
    except cognee.SessionNodeSetConflictError as error:
        print("Refused:", error)

    # Bridge both sessions into the graph; each keeps its pinned set.
    await cognee.improve(DATASET, session_ids=["session-project-a", "session-project-b"])

    for project in ("project-a", "project-b"):
        results = await cognee.recall(
            QUESTION,
            datasets=[DATASET],
            query_type=SearchType.GRAPH_COMPLETION,
            node_name=[project],
        )
        print(f"{project}:", results[0].text if results else "(nothing found)")

    await cognee.wait_for_background_tasks()


if __name__ == "__main__":
    asyncio.run(main())
```

## What Just Happened

### Step 1: Pin a Session With a Typed Entry

```python theme={null}
await cognee.remember(
    cognee.QAEntry(
        question=QUESTION,
        answer="In project A the release checklist is maintained by Ingrid.",
        node_set=["project-a"],
    ),
    dataset_name=DATASET,
    session_id="session-project-a",
)
```

The first write to `session-project-a` carries `node_set=["project-a"]`, which pins that set on the session. The later `TraceEntry` in the same session repeats the set, which is allowed; omitting it would be allowed too.

### Step 2: Pin a Session With the Call-Level Kwarg

```python theme={null}
stored = await cognee.remember(
    "In project B the release checklist is maintained by Teodor.",
    dataset_name=DATASET,
    session_id="session-project-b",
    node_set=["project-b"],
)
await stored  # let the background session bridge finish before improving
```

Plain text pins a session too: the `node_set` kwarg on `remember()` means the same thing as the `node_set` field on a `QAEntry` or `TraceEntry`. Awaiting the returned result lets the background session bridge finish before `improve()` runs.

### Step 3: See a Conflicting Set Refused

```python theme={null}
try:
    await cognee.remember(
        cognee.QAEntry(question="q", answer="a", node_set=["project-b"]),
        dataset_name=DATASET,
        session_id="session-project-a",
    )
except cognee.SessionNodeSetConflictError as error:
    print("Refused:", error)
```

Writing a different set to an already-pinned session raises `cognee.SessionNodeSetConflictError` (HTTP 409 over the API; check for the error name in the response, since the remember endpoints return 409 for other failures too). A session never spans two projects, so start a new session for a different one.

### Step 4: Bridge the Sessions and Recall Per Project

```python theme={null}
await cognee.improve(DATASET, session_ids=["session-project-a", "session-project-b"])

for project in ("project-a", "project-b"):
    results = await cognee.recall(
        QUESTION,
        datasets=[DATASET],
        query_type=SearchType.GRAPH_COMPLETION,
        node_name=[project],
    )
    print(f"{project}:", results[0].text if results else "(nothing found)")
```

`improve()` copies each session into the graph and keeps its pinned set on everything it bridges: Q\&A, agent traces, and distilled lessons. A recall scoped with `node_name` then sees only that project's memory, so the same question gets Ingrid for project A and Teodor for project B.

## Rules and Limits

<AccordionGroup>
  <Accordion title="What a valid node set looks like">
    The list must hold non-empty names; a bare string is refused, so pass `["project-a"]` for one name. It is stored sorted and deduplicated. An empty list pins nothing.
  </Accordion>

  <Accordion title="Running several worker processes">
    The pin is checked under an in-process lock, so the one-set-per-session guarantee holds within one worker process. With several workers, route a session's writes to one worker: two workers taking the same session's first write at the same moment can each pin a set, and the last write wins.
  </Accordion>
</AccordionGroup>

<Columns cols={2}>
  <Card title="NodeSet Grouping" icon="layers" href="/guides/nodeset-grouping">
    Tag graph memory with node sets and scope recall by them
  </Card>

  <Card title="Session Distillation" icon="graduation-cap" href="/guides/session-distillation">
    Learn more about bridging sessions into the graph with improve()
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.