diff --git a/tutorials/signals-agentic-contexts/conclusion.md b/tutorials/signals-agentic-contexts/conclusion.md new file mode 100644 index 000000000..8ffd62ac3 --- /dev/null +++ b/tutorials/signals-agentic-contexts/conclusion.md @@ -0,0 +1,40 @@ +--- +position: 5 +title: "Next steps after building a Signals agentic context" +sidebar_label: "Conclusion" +description: "Review what you built with Signals agentic contexts, then combine them with attribute services or add them to a full agent framework." +keywords: ["agentic context", "signals", "ai agent", "attribute services", "next steps"] +date: "2026-08-04" +--- + +You've built an agentic context end to end. You defined which events to capture and which properties to keep, published it, and read the same session back as both JSON and a narrative. You then appended that narrative to a system prompt the way a customer-facing agent does per turn, and used the prompt to change how a model interpreted the activity without changing what the agentic context captured. + +Nothing in this tutorial was framework-specific, which is the point. Any agent that can read a string can use what you just built. + +## Combine it with attributes + +An agentic context gives an agent the raw recent activity. [Attributes](/docs/signals/attributes/) give it computed values over longer windows, such as a lifetime order count or a running cart total, which no amount of reading recent page views can tell it. + +Most production agents want both. Group your attributes into a [service](/docs/signals/applications/services/) so your application can [retrieve them in one call](/docs/signals/applications/retrieve-attributes/), then pass the attributes and the agentic context narrative to the model together: the aggregates say who this user is, and the agentic context says what they're doing in the moment. + +You can also let Signals start the conversation rather than waiting for the user, by triggering an [intervention](/docs/signals/interventions/) when behavior crosses a threshold you define. + +## Build it into an agent + +These tutorials take the same building block into real frameworks, each with a working application at the end: + +* [Build an AI agent with real-time user context using Signals and Vercel AI SDK](/tutorials/signals-ai-agent-context/introduction), for a Next.js and Vercel AI SDK agent +* [Build a real-time context-aware agent with Signals, Google ADK, and CopilotKit](/tutorials/signals-google-adk-agent/introduction), for a Google ADK agent embedded in React +* [Build a Signals-powered AI agent with AWS Bedrock AgentCore](/tutorials/signals-agentic-accelerator/intro), for Strands Agents with Bedrock AgentCore Memory + +You can also keep working conversationally: the [Snowplow Assistant](/docs/llms-support/console-agent/) in Console and the [Snowplow MCP server](/docs/llms-support/snowplow-mcp/) manage agentic contexts alongside the rest of your Signals configuration. + +## Tidy up + +If you built this only to try it out, unpublish the agentic context and then delete it, using the `⋮` menu on its details page in Console. + +Your assistant can do it for you: + +```text +Unpublish and delete my session_context agentic context. +``` diff --git a/tutorials/signals-agentic-contexts/define-agentic-context.md b/tutorials/signals-agentic-contexts/define-agentic-context.md new file mode 100644 index 000000000..732c49dd0 --- /dev/null +++ b/tutorials/signals-agentic-contexts/define-agentic-context.md @@ -0,0 +1,178 @@ +--- +position: 2 +title: "Define and publish a Signals agentic context" +sidebar_label: "Define the agentic context" +description: "Define a Signals agentic context that buffers page views for the current session, choose the properties to keep, and publish it using an AI assistant, Snowplow Console, or the Python SDK." +keywords: ["agentic context", "event log", "snowplow assistant", "snowplow mcp", "signals python sdk", "publish", "event selection"] +date: "2026-08-04" +--- + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + +You'll define an agentic context that keeps the last 50 page views of a session, for up to 30 minutes, and attaches a prompt telling an agent how to read them. Signals scopes the buffer to a single session using the `domain_sessionid` [attribute key](/docs/signals/concepts/#attribute-keys), so each session gets its own rolling record. + +Do this by asking an AI assistant, in Snowplow Console, or with the Signals Python SDK. All three produce the same thing, so pick whichever you prefer. + + + + +Use the [Snowplow Assistant](/docs/llms-support/console-agent/) in Console, which needs no configuration, or your own MCP-capable assistant connected to the [Snowplow MCP server](/docs/llms-support/snowplow-mcp/). + +Paste this prompt into the chat: + +```text +Create a Signals agentic context called session_context, keyed on domain_sessionid, +that keeps the last 50 page_view events for up to 30 minutes. Attach the atomic +properties event_name, page_urlpath, and page_title to the event, and use this as +the prompt: + +"You are a support assistant on a product website. The activity below is what the +user has just been doing. Use it to work out what they are trying to achieve, then +answer their question in that context. If the activity is too thin to tell, say so +rather than guessing." +``` + +The assistant creates the definition and can publish it for you. Review it under **Signals** > **Agentic contexts** in Console before you publish. + + + + +Go to **Signals** > **Agentic contexts** in Snowplow Console and create a new agentic context. On the **Create context** form, fill in the **Details** and **Prompt** sections: + +| Field | Value | +| ----- | ----- | +| Name | `session_context` | +| Primary owner | Your email address | +| Description | `Rolling record of the pages a user has viewed in their current session` | +| Prompt | The prompt text below | + +Use this as the prompt: + +```text +You are a support assistant on a product website. The activity below is what the +user has just been doing. Use it to work out what they are trying to achieve, +then answer their question in that context. If the activity is too thin to tell, +say so rather than guessing. +``` + +![The Create context form in Snowplow Console, with the Details section filled in with the name session_context and a description of the rolling record, and the Prompt section holding the support assistant instructions](./images/agentic-context-create-form.png) + +Under **Lookback Window**, set **Max events** to `50` and **Max age** to `30` minutes. Underneath the fields, Console shows the window it uses. + +Under **Events and Properties**, click **Add event** and choose `page_view` at version `1-0-0`. Then use **Add property** to attach `event_name`, `page_urlpath`, and `page_title` to it. + +![The Lookback Window section of the form set to 50 max events and 30 minutes max age, and the Events and Properties section below it showing page_view version 1-0-0 with the three attached properties event_name, page_urlpath, and page_title](./images/agentic-context-event-selection.png) + + + + +You'll need Python 3.9 or later. Install the SDK: + +```bash +pip install snowplow-signals +``` + +The SDK models an agentic context as an `EventLog`, so that's the class you import. Store your [connection credentials](/docs/signals/connection/) in the environment first, then define and publish the agentic context: + +```python +import os +from snowplow_signals import ( + Signals, + EventLog, + EventSelection, + EventLogEvent, + EventLogAtomicProperty, + domain_sessionid, +) + +sp_signals = Signals( + api_url=os.environ["SP_API_URL"], + api_key=os.environ["SP_API_KEY"], + api_key_id=os.environ["SP_API_KEY_ID"], + org_id=os.environ["SP_ORG_ID"], +) + +session_context = EventLog( + name="session_context", + description="Rolling record of the pages a user has viewed in their current session", + owner="you@example.com", + prompt=( + "You are a support assistant on a product website. " + "The activity below is what the user has just been doing. " + "Use it to work out what they are trying to achieve, then answer their " + "question in that context. If the activity is too thin to tell, say so " + "rather than guessing." + ), + attribute_key=domain_sessionid, + max_events=50, + max_age_seconds=1800, + events=[ + EventSelection( + event=EventLogEvent( + name="page_view", + vendor="com.snowplowanalytics.snowplow", + version="1-0-0", + ), + properties=[ + EventLogAtomicProperty(name="event_name"), + EventLogAtomicProperty(name="page_urlpath"), + EventLogAtomicProperty(name="page_title"), + ], + ) + ], +) + +sp_signals.publish([session_context]) +``` + +`SP_API_URL` is your Signals API URL, in the form `https://YOUR_ID.signals.snowplowanalytics.com`. + + + + +## Choose which properties to keep + +An agentic context doesn't store whole events. For each event you select, you list the properties to project, and only those reach the agent. Properties can come from the [atomic event](/docs/fundamentals/canonical-event/), the event's own schema, or its entities. + +Here you keep three atomic properties of each page view: + +| Property | In the narrative | +| -------- | ---------------- | +| `event_name` | Fills the `event` column | +| `page_urlpath` | Fills the `url` column | +| `page_title` | Appears in the `event_context` field | + +For the full range of selection options, including schema and entity properties, see [selecting events](/docs/signals/agentic-contexts/#selecting-events). + +## Publish it + +Publishing sends the definition to your Signals infrastructure and starts the buffering. Until you publish, Signals captures nothing. + + + + +Ask the assistant to publish it: + +```text +Publish my session_context agentic context. +``` + + + + +Click **Publish**. Console keeps your changes as a draft until you do. + + + + +The `publish()` call in the code above both registers the agentic context and sends it to your Signals infrastructure. + +To change an agentic context you've already published, edit it in Console and publish the draft, as you'll do later in this tutorial. + + + + +Publish first, then browse: an agentic context captures events from the moment it goes live. There's one live agentic context per name, and publishing replaces the one that was live. diff --git a/tutorials/signals-agentic-contexts/images/agentic-context-create-form.png b/tutorials/signals-agentic-contexts/images/agentic-context-create-form.png new file mode 100644 index 000000000..0d30ad8f9 Binary files /dev/null and b/tutorials/signals-agentic-contexts/images/agentic-context-create-form.png differ diff --git a/tutorials/signals-agentic-contexts/images/agentic-context-event-selection.png b/tutorials/signals-agentic-contexts/images/agentic-context-event-selection.png new file mode 100644 index 000000000..6899d5801 Binary files /dev/null and b/tutorials/signals-agentic-contexts/images/agentic-context-event-selection.png differ diff --git a/tutorials/signals-agentic-contexts/meta.json b/tutorials/signals-agentic-contexts/meta.json new file mode 100644 index 000000000..fe8313d6b --- /dev/null +++ b/tutorials/signals-agentic-contexts/meta.json @@ -0,0 +1,8 @@ +{ + "title": "Give an AI agent real-time session context with Signals", + "description": "Define an agentic context to capture a user's recent session activity, and retrieve it as a plain-language narrative for any agent framework.", + "label": "Signals implementation", + "useCase": "Customer-facing AI agents", + "technologies": ["AI assistants", "Python"], + "snowplowTech": ["Signals", "Console"] +} diff --git a/tutorials/signals-agentic-contexts/retrieve-the-context.md b/tutorials/signals-agentic-contexts/retrieve-the-context.md new file mode 100644 index 000000000..7fc8fd15f --- /dev/null +++ b/tutorials/signals-agentic-contexts/retrieve-the-context.md @@ -0,0 +1,151 @@ +--- +position: 3 +title: "Retrieve a Signals agentic context as JSON or a narrative" +sidebar_label: "Retrieve the context" +description: "Find the domain_sessionid for a live session, then read the agentic context back from Signals as structured JSON or as a plain-language narrative, in Python or Node.js." +keywords: ["agentic context", "get_agentic_context", "domain_sessionid", "narrative format", "signals node sdk"] +date: "2026-08-04" +--- + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + +Your agentic context is live, so browse a few pages on your tracked site to give it something to capture. Visit several different pages, and leave a few seconds between them, so the record has a shape you can recognize later. + +## Get the session identifier + +An agentic context is scoped to one session, so you read it for a single `domain_sessionid` value. There are two ways to get one. + +To find the identifier for the session you just generated, connect the [Snowplow Inspector](/docs/testing/snowplow-inspector/signals-integration/) browser extension to Signals, then browse your site with the extension open. Inspector builds its list of attribute keys from the events it observes, so the **Attributes** tab shows the `domain_sessionid` value for the session you're in. + +In application code, read it client-side with the JavaScript tracker's [`getDomainSessionId`](/docs/sources/web-trackers/cookies-and-local-storage/getting-cookie-values/#domain-session-id) method and send it to your back-end, because retrieval happens server-side: + +```javascript +const domainSessionId = sp.getDomainSessionId(); +``` + +## Read it as JSON + +In your own code, use `format="json"` when you want to work with the activity programmatically, for example to build your own prompt or apply your own logic. If you defined the agentic context in Console or with an assistant, install the SDK with `pip install snowplow-signals` and connect it using your [Signals connection credentials](/docs/signals/connection/): + +```python +import os +from snowplow_signals import Signals + +sp_signals = Signals( + api_url=os.environ["SP_API_URL"], + api_key=os.environ["SP_API_KEY"], + api_key_id=os.environ["SP_API_KEY_ID"], + org_id=os.environ["SP_ORG_ID"], +) +``` + +Then pass the name of your agentic context and the session identifier: + +```python +context = sp_signals.get_agentic_context( + name="session_context", + identifier="2f8b41d0-5c6e-4a1b-9f3a-7d21c4e8b905", +) +``` + +For a session that visited a product page, its reviews, the size guide, and then the returns policy, the response looks like this: + +```json +{ + "summary": "13 seconds on the current page. Session started 50 seconds ago. Based on last 50 recorded events for the last 1800 seconds.", + "attribute_key": "domain_sessionid", + "identifier": "2f8b41d0-5c6e-4a1b-9f3a-7d21c4e8b905", + "name": "session_context", + "version": 1, + "prompt": "You are a support assistant on a product website. The activity below is what the user has just been doing. Use it to work out what they are trying to achieve, then answer their question in that context. If the activity is too thin to tell, say so rather than guessing.", + "started_at_ms": 1785334905303, + "events": [ + { + "derived_tstamp": "2026-07-29T14:21:45.303Z", + "event_name": "page_view", + "page_title": "Trail running shoes | Example Shop", + "page_urlpath": "/products/trail-running-shoes" + }, + { + "derived_tstamp": "2026-07-29T14:21:57.373Z", + "event_name": "page_view", + "page_title": "Reviews: Trail running shoes | Example Shop", + "page_urlpath": "/products/trail-running-shoes/reviews" + }, + { + "derived_tstamp": "2026-07-29T14:22:09.497Z", + "event_name": "page_view", + "page_title": "Size guide | Example Shop", + "page_urlpath": "/size-guide" + }, + { + "derived_tstamp": "2026-07-29T14:22:21.632Z", + "event_name": "page_view", + "page_title": "Returns policy | Example Shop", + "page_urlpath": "/support/returns-policy" + } + ] +} +``` + +The events are ordered oldest to most recent, so the last entry is the page the user is on, and each one carries the properties you selected plus a `derived_tstamp` that Signals adds for you. The `prompt` you wrote travels with the data, so whatever reads this context also gets its instructions. + +## Read it as a narrative + +Use `format="narrative"` when you want to drop the activity straight into a model's context without writing any formatting logic yourself. Signals returns a single string: + +```python +narrative = sp_signals.get_agentic_context( + name="session_context", + identifier="2f8b41d0-5c6e-4a1b-9f3a-7d21c4e8b905", + format="narrative", +) + +print(narrative) +``` + +The same session comes back like this: + +```text +You are a support assistant on a product website. The activity below is what the user has just been doing. Use it to work out what they are trying to achieve, then answer their question in that context. If the activity is too thin to tell, say so rather than guessing. +[START CONTEXT] +13 seconds on the current page. Session started 50 seconds ago. Based on last 50 recorded events for the last 1800 seconds. +## Real-time user behaviour +Events are ordered from oldest to most recent. +seconds_since_start_of_session, event, url, event_context +0, page_view, /products/trail-running-shoes, {page_title: 'Trail running shoes | Example Shop'} +12, page_view, /products/trail-running-shoes/reviews, {page_title: 'Reviews: Trail running shoes | Example Shop'} +24, page_view, /size-guide, {page_title: 'Size guide | Example Shop'} +36, page_view, /support/returns-policy, {page_title: 'Returns policy | Example Shop'} +[END CONTEXT] +``` + +Your prompt comes first, then the activity wrapped in `[START CONTEXT]` and `[END CONTEXT]` markers so a model can tell your instructions from the data. The summary line tells the model how fresh the activity is, and the table gives one row per event with the seconds elapsed since the session started. The `page_title` you selected lands in `event_context`, because only `event_name` and `page_urlpath` have columns of their own. + +## Retrieve it from Node.js + +If your agent runs on Node.js, use the [Node.js SDK](/docs/signals/connection/#signals-nodejs-sdk) instead. Install it with `npm i @snowplow/signals-node`, then call `getAgenticContext` with the same arguments: + +```javascript +import { Signals } from "@snowplow/signals-node"; + +const signals = new Signals({ + baseUrl: process.env.SNOWPLOW_SIGNALS_BASE_URL, + apiKey: process.env.SNOWPLOW_SIGNALS_API_KEY, + apiKeyId: process.env.SNOWPLOW_SIGNALS_API_KEY_ID, + organizationId: process.env.SNOWPLOW_SIGNALS_ORG_ID, +}); + +const narrative = await signals.getAgenticContext({ + name: "session_context", + identifier: domainSessionId, + format: "narrative", +}); + +console.log(narrative); +``` + +Define agentic contexts in Console, with an assistant, or with the Python SDK, and read them back from either SDK. diff --git a/tutorials/signals-agentic-contexts/start.md b/tutorials/signals-agentic-contexts/start.md new file mode 100644 index 000000000..b90c6f2cb --- /dev/null +++ b/tutorials/signals-agentic-contexts/start.md @@ -0,0 +1,32 @@ +--- +position: 1 +title: "Give an AI agent real-time session context with Signals" +sidebar_label: "Introduction" +description: "Give a customer-facing agent the user's live session as a plain-language narrative: what a Signals agentic context is, how it differs from attributes, and how to build one." +keywords: ["agentic context", "signals", "ai agent context", "llm context", "session activity", "customer-facing agent"] +date: "2026-08-04" +--- + +A customer-facing agent — a support bot, a shopping assistant, an in-app copilot — answers blind. It may know your product catalog and your documentation, but not that this user has just read your size guide twice, so it asks for information the session already holds. + +An [agentic context](/docs/signals/agentic-contexts/) hands the agent that session as a plain-language narrative it can read directly. It's a live, rolling record of what a user has just been doing: you choose which events to capture and which of their properties to keep, attach a written prompt for the agent, and read the result back as JSON or as a narrative. + +This is a different shape of data from [Signals attributes](/docs/signals/attributes/). Attributes are values Signals computes for you, such as a count of product views or a running cart total. An agentic context hands over the recent activity itself, event by event, and lets the model do the interpreting. + +## What you'll build + +You'll define an agentic context that captures page views for the current session, publish it, and retrieve it for a real session. Then you'll paste the narrative into a chat LLM to see what a model makes of it, and append it to a system prompt the way a customer-facing agent does on every turn. + +There's no agent framework here and nothing to scaffold. Everything you learn transfers to whichever framework you build in later. + +## Prerequisites + +This tutorial assumes that you have: + +* A Snowplow account and pipeline +* [Signals enabled](/docs/signals/setup/) on your account +* [Page view tracking](/docs/sources/web-trackers/tracking-events/page-views/) on a website you can browse + +If you don't have a Snowplow pipeline yet, you can follow along with a [Snowplow free trial](https://snowplow.io/get-started/snowplow-free-trial). + +This tutorial takes less than 15 minutes. diff --git a/tutorials/signals-agentic-contexts/use-it-with-an-llm.md b/tutorials/signals-agentic-contexts/use-it-with-an-llm.md new file mode 100644 index 000000000..d2799d45b --- /dev/null +++ b/tutorials/signals-agentic-contexts/use-it-with-an-llm.md @@ -0,0 +1,97 @@ +--- +position: 4 +title: "Feed a Signals agentic context to your agent" +sidebar_label: "Feed it to your agent" +description: "Paste a Signals agentic context narrative into a chat LLM, then append it to your customer-facing agent's system prompt on every turn so its answers reflect the user's current session." +keywords: ["agentic context", "system prompt", "customer-facing agent", "llm context", "ai agent", "signals"] +date: "2026-08-04" +--- + +The narrative is a plain string, so any model can read it and any language can build it into a prompt. Try it by hand first, to see what a model infers from what you chose to capture, then put the same string into your agent. + +## See what a model makes of it + +Copy the whole narrative string from the previous page, paste it into any chat LLM, and add a question underneath: + +```text +[paste the narrative here] + +The user has just asked: "Do these run true to size?" +Answer them. +``` + +The model picks up that this user has already read the reviews and the size guide, so it answers about fit rather than pointing them back to either. Your prompt and the `[START CONTEXT]` markers do the rest of the work, so there's no format to explain. + +## Add the narrative to your system prompt + +Your agent does that same read on every turn, without anyone pasting anything. Its back-end already knows which session the conversation belongs to, so it retrieves the narrative for that `domain_sessionid` and appends it to the instructions it sends to the model. Your own instructions go first, and the narrative brings both the prompt you wrote and the activity itself: + +```python +BASE_INSTRUCTIONS = ( + "You are the support assistant for Example Shop. " + "Answer in two sentences or fewer, and link to a page on the site where it helps." +) + +def build_system_prompt(domain_session_id): + narrative = sp_signals.get_agentic_context( + name="session_context", + identifier=domain_session_id, + format="narrative", + ) + return f"{BASE_INSTRUCTIONS}\n\n{narrative}" +``` + +This uses the same `sp_signals` connection you built on the previous page, and `getAgenticContext` from the Node.js SDK slots in the same way. Call `build_system_prompt` at the start of every turn, then pass what it returns as the system prompt, or instructions, of your model call, alongside the conversation history and the user's new message. Whatever provider or framework you're on, that's the whole integration: one string, rebuilt per turn. + +## Browse more, then ask again + +Rebuilding per turn is what keeps the agent current, because the buffer is live. Keep browsing your site: add something to a cart, or move to a checkout page. Wait a few seconds, then retrieve the narrative again with the same session identifier. + +The two new page views appear at the end, with the seconds since the session started showing the gap: + +```text +You are a support assistant on a product website. The activity below is what the user has just been doing. Use it to work out what they are trying to achieve, then answer their question in that context. If the activity is too thin to tell, say so rather than guessing. +[START CONTEXT] +28 seconds on the current page. Session started 147 seconds ago. Based on last 50 recorded events for the last 1800 seconds. +## Real-time user behaviour +Events are ordered from oldest to most recent. +seconds_since_start_of_session, event, url, event_context +0, page_view, /products/trail-running-shoes, {page_title: 'Trail running shoes | Example Shop'} +12, page_view, /products/trail-running-shoes/reviews, {page_title: 'Reviews: Trail running shoes | Example Shop'} +24, page_view, /size-guide, {page_title: 'Size guide | Example Shop'} +36, page_view, /support/returns-policy, {page_title: 'Returns policy | Example Shop'} +109, page_view, /cart, {page_title: 'Your cart | Example Shop'} +119, page_view, /checkout, {page_title: 'Checkout | Example Shop'} +[END CONTEXT] +``` + +Paste this newer version into a fresh chat and ask the same question. The answer changes: this user has reached a checkout rather than idly browsing a product page. An agent that rebuilds its system prompt each turn follows that shift on its own. + +## Change the prompt, not the capture + +Your agent's own instructions live in your code, but the prompt that tells it how to read the activity lives in the agentic context, so you can reshape its behavior without a deploy. So far that prompt has framed the job as support. Change the framing and the same activity supports a different conclusion. Ask your assistant to replace the prompt and publish the change, or click **Edit** on your agentic context's details page in Console, replace the **Prompt** text with this, then publish the draft: + +```text +You are a conversion assistant on a product website. From the activity below, +identify the single biggest thing standing between this user and completing their +purchase, and suggest one concrete way to remove it. Say which part of the +activity tells you this. +``` + +Retrieve the narrative once more. Only the first line has changed: + +```text +You are a conversion assistant on a product website. From the activity below, identify the single biggest thing standing between this user and completing their purchase, and suggest one concrete way to remove it. Say which part of the activity tells you this. +``` + +The captured events are unchanged, because the prompt doesn't affect what's buffered, so you can refine your wording against a session that's still in flight. + +Ask a model the same question with this version and the emphasis moves. Where the support framing explained the returns policy, the conversion framing reads that same visit as hesitation about fit, and suggests addressing it at the checkout. + +## Take it into a framework + +The pattern above holds wherever your agent runs. These tutorials build it into a working application, with the provider calls filled in: + +* [Vercel AI SDK, in a Next.js application](/tutorials/signals-ai-agent-context/introduction) +* [Google ADK, with a React front-end](/tutorials/signals-google-adk-agent/introduction) +* [Strands Agents on AWS Bedrock AgentCore](/tutorials/signals-agentic-accelerator/intro)