custom events
Log your own structured events without setting up Snowplow, Segment, GA, or GTM. AgentContext.track(name, properties?) lands the event in event_log with the same sanitisation as every other source.
The event lives in the snapshot in the user's browser, and you decide if and when it goes to your server. There's no collector behind it and nothing for us to host.
1 / basic usage
import { AgentContext } from '@contextune/sdk';
AgentContext.init();
// Anywhere in your app:
AgentContext.track('filter-dropdown:applied', { facet: 'size', value: 'M' });
AgentContext.track('checkout-form:submitted', { cart_value: 129 });
// No props is fine.
AgentContext.track('searched');track() is on both the static AgentContext façade and the instance returned by init(). Use whichever shape is convenient.
2 / react / framework integration
Wire track()into the same hooks you use for any other client-side event handler. There's no wrapper or provider to add.
'use client';
import { useEffect } from 'react';
import { AgentContext } from '@contextune/sdk';
export function FilterDropdown({ facet, value }: { facet: string; value: string }) {
useEffect(() => {
AgentContext.track('filter-dropdown:applied', { facet, value });
}, [facet, value]);
return null;
}3 / coexistence with source plugins
track() and an external event source (Segment / GA4 / GTM) can run side by side. The source plugin owns its own ingress path — Segment events come in through analytics.on('track'), GA4 / GTM through dataLayer.push. Custom track() calls land directly in the engine and skip whatever source you chose.
Both ingresses end up in the same event_log, in chronological order.
// Custom events coexist with source plugins. If you tap Segment,
// Segment events flow through analytics.on('track') as normal;
// your AgentContext.track() calls go straight to the engine.
// Same event_log, different ingress.
import { AgentContext } from '@contextune/sdk';
AgentContext.init({ eventSource: 'segment' });
// segment fires this through analytics.on('track')
analytics.track('Product Viewed', { product_id: '12345' });
// you fire this directly into the SDK engine
AgentContext.track('hero-cta:clicked', { variant: 'A' });
// both land in snapshot.event_log
const snapshot = AgentContext.getSnapshot();DOM-derived behavioural primitives (scroll, idle, rage clicks, current focus) run regardless — they're wired off native browser events, not the source plugin.
4 / naming events
Use noun-context:verb — concrete enough that you can search for it later, and easy for an agent reading the snapshot to interpret. The model will see the event names verbatim; clarity beats brevity.
// Use a verb plus a context. "Applied a filter" beats "filter."
AgentContext.track('filter-dropdown:applied', { facet: 'size' });
AgentContext.track('checkout-form:submitted', { cart_value: 129 });
AgentContext.track('hero-cta:clicked', { variant: 'A' });
// avoid generic verbs by themselves
// AgentContext.track('clicked'); <- which click?
// AgentContext.track('input'); <- which input?
// AgentContext.track('viewed'); <- viewed what?Event names are sanitised at the boundary (sanitiseForLLMstrips chat-template markers, control characters, and length-caps to 256 chars). You can't accidentally smuggle </system> into your event log.
5 / what the sanitiser does to your properties
Properties are recursively sanitised before they land in the snapshot. Three things happen:
- Sensitive keys dropped. Keys matching
password,email,credit_card,phone,dob,address,postcode,auth,authorization,bearer,cookie, or matching the regex/password|secret|token|api[_-]?key/iare removed. - URL-shaped values redacted. Keys called
url,href,uri,referrer,refererhave their values routed throughredactUrl— sensitive query params become[REDACTED], numeric path IDs become[id]. - Depth bounded at 4. Anything deeper than four levels of nesting is dropped. Limits work without bounding the load you might hand the model.
// track() is safe to call at any time. Pre-init and post-destroy
// calls are silent no-ops — you can wire it up without worrying
// about lifecycle. Properties are recursively sanitised; sensitive
// keys are dropped, URL-shaped values redacted, depth bounded at 4.
AgentContext.track('login-form:submitted', {
email: 'leaked@example.com', // dropped: matches sensitive key
remember_me: true, // kept
next_url: 'https://app/?token=secret', // redacted: query stripped
});The full sanitisation rules are documented in Security.
6 / when to use track() vs a source plugin
- You don't have analytics yet. Skip the source plugin. Call
track()directly at the points that matter. You get a clean event log with no third-party SDK on the page. - You already run Segment / GA4 / GTM. Set
eventSourceininit()and your existing events flow through automatically. Addtrack()calls only for the events your analytics provider doesn't already capture. - You want full control. Disable the source plugin and emit only the events you care about with
track(). You get a clean log with no framework noise and no third-party SDK to vet.