> ## 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.

# Live-Context Support Agent

> Build a support chat agent that combines a shopper's live on-site behavior from Snowplow Signals with what cognee remembers about them, and see how memory changes the answer

Snowplow Signals knows what a shopper is doing right now. Cognee remembers their previous orders and preferences. Together, they let a support agent read the shopper's live behavior, pull the relevant facts about them from memory, and give advice that fits both the moment and the person.

## What You'll Build

Two shoppers' order histories, the store's returns policy, and its footwear fit notes go into cognee memory, each customer in their own node set and the store's documents in a shared one. Then two support scenes play out for one shopper, Maya. Live attributes in the shape Snowplow Signals returns go into the query text. Every chat message is answered twice with the same `recall()`: once over the store's documents only ("Signals only") and once with Maya's memory added ("Signals + cognee"). After the first scene, the chat outcome and a narrative of her on-site behavior are remembered too. Two weeks later, that memory changes the size the agent recommends.

The complete runnable script is
[`examples/demos/agentic/live_context_support_agent.py`](https://github.com/topoteretes/cognee/blob/dev/examples/demos/agentic/live_context_support_agent.py) —
this page walks through its key moments rather than reproducing it.

## Features in Play

* [NodeSets](https://docs.cognee.ai/core-concepts/further-concepts/node-sets.md): keep each customer's memory in its own node set next to a shared `store` node set, and choose per recall whose memory is in scope
* [Remember](https://docs.cognee.ai/core-concepts/main-operations/remember.md): stores the order histories and store documents up front, then the chat outcome and behavior narrative after scene 1
* [Recall](https://docs.cognee.ai/core-concepts/main-operations/recall.md): answers each chat message with `node_name` scoping, a custom `system_prompt` for the agent, and a fresh `session_id` per answer
* [Forget](https://docs.cognee.ai/core-concepts/main-operations/forget.md): deletes only the demo's own dataset before each run, so reruns start clean

## How It Looks

In scene 1 (Oct 26), Maya bought Talusworks Ridgeline trail shoes in the Autumn Sale in her usual EU 39, and they are too small. The script prints four Signals snapshots as she browses. When `returns_page_views_10m` reaches 3, the trigger fires and the agent opens the chat before she has typed anything. She asks to return the shoes. Sale items are final sale, but size exchanges are free, and the fit notes say the Ridgeline runs a size small. With her memory in scope, the expected answer is a free exchange for EU 40.

In scene 2 (Nov 9), she is looking at Northpeak Summit Hiker shoes and asks which size to take. Signals only knows which product she is viewing. Cognee remembers that she wears 39 and needed a 40 only because the Ridgeline runs small, and the fit notes say the Summit Hiker runs true to size. The expected "Signals + cognee" answer is to keep her usual EU 39. The answers are live LLM calls, so their wording changes from run to run.

## Before You Start

* Complete [Quickstart](https://docs.cognee.ai/getting-started/quickstart.md) to understand basic operations
* Configure [LLM Providers](https://docs.cognee.ai/setup-configuration/llm-providers.md). With the bundled samples, `LLM_API_KEY` is the only setting the script needs
* Use a [graph store](https://docs.cognee.ai/setup-configuration/graph-stores.md) that supports node-set filtering, such as Ladybug (the default), Neo4j, Postgres, or Turso. Customers are kept apart with node sets
* Run it from a checkout of the cognee repo. It reads its inputs from the sibling `live_context_support_agent_data/` folder: order histories for two customers, the returns policy, fit notes, the scene 1 chat outcome, a sample behavior narrative, and `signals_snapshots.json`. It also loads your `.env` with `load_dotenv()` and sets `LOG_LEVEL=ERROR` unless you set it yourself
* The script deletes and rebuilds only its own dataset, `live_context_support_agent_demo`, with `cognee.forget(dataset=...)`. It leaves your other datasets alone. See [Forget](https://docs.cognee.ai/core-concepts/main-operations/forget.md)
* Optional: to read real behavior instead of the samples, see [Connect Real Snowplow Signals](#connect-real-snowplow-signals) below

## How It Works

### Stage 1: Separate Customers by Node Set

```python theme={null}
    # Each customer's documents go in their own node set; the store's documents in a shared one.
    await cognee.remember(MAYA_ORDERS, dataset_name=DATASET, node_set=[CUSTOMER_ID])
    await cognee.remember(LEO_ORDERS, dataset_name=DATASET, node_set=["leo-21bd"])
    await cognee.remember(
        f"{RETURNS_POLICY}\n\n{FIT_NOTES}", dataset_name=DATASET, node_set=[STORE_NODE_SET]
    )
```

All three documents go into one dataset, but each customer's orders are tagged with that customer's id and the policy and fit notes with `store`. A recall can then include the store's documents for everyone and add one customer's history only when answering that customer. Node sets are a retrieval filter, not an isolation boundary: both customers bought the Ridgeline, so that shared entity, and relationships Leo's orders created between in-scope entities, can still reach Maya's answers. For customer data that must never mix, use separate [datasets](https://docs.cognee.ai/core-concepts/further-concepts/datasets.md), as [NodeSets](https://docs.cognee.ai/core-concepts/further-concepts/node-sets.md#what-node_name-scoping-includes) explains.

### Stage 2: Open the Chat When the Trigger Fires

```python theme={null}
    for step, sample in enumerate(SNAPSHOTS["scene_1"], start=1):
        live = get_live_context(CUSTOMER_ID, sample)
        print(f"[signals] snapshot {step}: {json.dumps(live)}")
        if wants_help_with_return(live):
            print("[signals] trigger fired: returns_page_views_10m >= 3, the agent opens the chat")
            opener = await cognee.recall(
                query_text=(
                    f"Today is Oct 26. Customer {CUSTOMER_ID} is on our site. {describe(live)}\n"
                    "They have not written anything yet. Open the chat proactively about what "
                    "they seem to need, in two sentences."
                ),
                datasets=[DATASET],
                node_name=[STORE_NODE_SET, CUSTOMER_ID],
                system_prompt=AGENT_PROMPT,
                session_id=f"support-{uuid4().hex[:8]}",
            )
            print(f"\nAgent (unprompted): {opener[0].text}")
            break
```

Scene 1 steps through Maya's snapshots as she browses. `get_live_context()` returns each one, from the bundled samples or from real Signals (see [Connect Real Snowplow Signals](#connect-real-snowplow-signals)). `wants_help_with_return()` is the trigger rule: it fires once `returns_page_views_10m` reaches 3. In production, this rule would be a Signals intervention on the site. When it fires, the agent opens the chat before Maya has written anything. The query holds only her live attributes and an instruction to open the chat, and `node_name` includes both the store's node set and hers. Live behavior decides when the agent speaks, and memory decides what it says.

### Stage 3: Answer the Same Message With and Without Memory

```python theme={null}
    query = (
        f"Today is {today}. Customer {CUSTOMER_ID} is on our site. {describe(live)}\n"
        f'They wrote: "{message}"'
    )
    # The same recall twice: without memory of the customer (only the store's documents), then
    # with it. A fresh session_id keeps answers cached in the default session out of the context.
    for label, node_sets in [
        ("Signals only (live behavior + the store's documents)", [STORE_NODE_SET]),
        (
            "Signals + cognee (live behavior + the store's documents + Maya's memory)",
            [STORE_NODE_SET, CUSTOMER_ID],
        ),
    ]:
        result = await cognee.recall(
            query_text=query,
            datasets=[DATASET],
            node_name=node_sets,
            system_prompt=AGENT_PROMPT,
            session_id=f"support-{uuid4().hex[:8]}",
        )
        print(f"\n--- {label} ---\n{result[0].text}")
```

Live behavior reaches the agent as query text. `describe()` turns the attributes into a `Live behavior from Snowplow Signals:` block of JSON, and the query adds today's date and the customer's message. Cognee doesn't store any of it. The two answers use the same query, prompt, and dataset. The only difference is `node_name`. Each answer gets a fresh `session_id`, so earlier answers cached in the default session can't leak into the comparison.

### Stage 4: Remember What Happened

```python theme={null}
    await cognee.remember(
        [CHAT_OUTCOME, get_behavior_narrative(CUSTOMER_ID)],
        dataset_name=DATASET,
        node_set=[CUSTOMER_ID],
    )
```

After scene 1, the chat outcome (exchanged EU 39 for EU 40 because the Ridgeline runs small) and a narrative of the session's behavior are stored in Maya's node set. The narrative has the shape returned by Signals' `get_agentic_context(..., format="narrative")`. This is the memory that changes the scene 2 answer. Live attributes from two weeks ago are gone by then, but what they led to is now in her history.

### Stage 5: Ask Again Two Weeks Later

```python theme={null}
    live = get_live_context(CUSTOMER_ID, SNAPSHOTS["scene_2"])
    print(f"[signals] snapshot: {json.dumps(live)}")
    await compare_answers("Which size should I get in these?", live, "Nov 9")
```

Scene 2 runs the same comparison as Stage 3 on a new snapshot, in which Maya is viewing the Northpeak Summit Hiker. Only the "Signals + cognee" answer sees Maya's memory: her orders in EU 39, and the chat outcome from Stage 4 that says she needed a 40 only because the Ridgeline runs small. The fit notes say the Summit Hiker runs true to size, so it can tell her to keep her usual EU 39. The "Signals only" answer has the same fit notes but doesn't know her size.

## Run It

```bash theme={null}
uv run python examples/demos/agentic/live_context_support_agent.py
```

The script prints `Resetting the demo dataset and building memory...`, then a `=== Scene 1 ... ===` header, one `[signals] snapshot N:` line per snapshot, the `[signals] trigger fired` line, and the agent's unprompted opener. Each customer message is followed by two labeled answers, `--- Signals only ... ---` and `--- Signals + cognee ... ---`. After `Remembering the chat outcome and Maya's on-site behavior...`, scene 2 prints its snapshot and the two answers to the sizing question. A successful run recommends an exchange to EU 40 in scene 1 and staying with EU 39 in scene 2 in the "Signals + cognee" answers.

## Connect Real Snowplow Signals

By default the scenes read the bundled samples. `get_live_context()` switches to the `snowplow-signals` SDK when `SNOWPLOW_SIGNALS_API_URL` is set in your `.env` (the script loads it with `load_dotenv()`) or in the environment:

```python theme={null}
def get_live_context(customer_id: str, sample: dict) -> dict:
    """Return the shopper's live attributes from Snowplow Signals, or the sample snapshot."""
    if not os.getenv("SNOWPLOW_SIGNALS_API_URL"):
        return sample
    return signals_client().get_service_attributes(
        name=os.environ["SNOWPLOW_SIGNALS_SERVICE"],
        attribute_key=os.getenv("SNOWPLOW_SIGNALS_ATTRIBUTE_KEY", "domain_userid"),
        identifier=os.getenv("SNOWPLOW_SIGNALS_IDENTIFIER", customer_id),
    )
```

Either way, the agent receives the same four attributes: `last_product_viewed`, `last_order_status_viewed`, `size_guide_views_10m`, and `returns_page_views_10m`. The real path also needs these variables, set the same way:

| Variable | Used for |
| - | - |
| `SNOWPLOW_SIGNALS_API_URL` | Turns on the real Signals path |
| `SNOWPLOW_SIGNALS_API_KEY`, `SNOWPLOW_SIGNALS_API_KEY_ID`, `SNOWPLOW_SIGNALS_ORG_ID` | Credentials for the `Signals` client |
| `SNOWPLOW_SIGNALS_SERVICE` | Service name passed to `get_service_attributes` |
| `SNOWPLOW_SIGNALS_ATTRIBUTE_KEY` | Optional. Attribute key to look up, default `domain_userid` |
| `SNOWPLOW_SIGNALS_IDENTIFIER` | Optional. Visitor identifier, defaults to the demo's customer id |
| `SNOWPLOW_SIGNALS_EVENT_LOG` | Optional. When set, the behavior narrative remembered after scene 1 comes from `get_agentic_context(name=..., format="narrative")` instead of the sample file |

The SDK isn't a cognee dependency, so add it for the run:

```bash theme={null}
uv run --with snowplow-signals python examples/demos/agentic/live_context_support_agent.py
```

> [!NOTE]
>
> The real Signals path needs your own Signals deployment and credentials. The bundled samples don't exercise it. The scene messages and dates stay hardcoded, so adapt them to your visitor's data.

> **[NodeSets](https://docs.cognee.ai/core-concepts/further-concepts/node-sets.md)**
>
> How node-set labels are written and how `node_name` filters retrieval by them.

> **[Recall](https://docs.cognee.ai/core-concepts/main-operations/recall.md)**
>
> The retrieval call behind every agent answer, including `system_prompt` and `session_id`.

> **[Agentic Procurement Decisions](https://docs.cognee.ai/examples/agentic-procurement.md)**
>
> Another agent that scopes each recall to one node-set category.


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