Ah, that familiar chatbot experience: like an over-exuberant shop assistant sliding up from the bottom-right corner four seconds after you land. "Hi! How can I help?"
It has no idea who you are, what you've looked at, or why you're here. You've spent five minutes comparing two pricing tiers and browsing a few products. You're unsure whether the higher-priced option is worth it. You've rage-clicked the dead-linked "expand benefits" three times looking for the detail that would help you decide.
The chatbot knows none of this. So by the time you finally start typing, your willingness to buy has already dropped. And until that moment, the agent has been sitting in the dark.
That blindness is the thing my brother James and I have spent the last few months fixing, and today we're open-sourcing the result: Contextune, a client-side library that captures what a user is actually doing and hands it to your agent as structured context. MIT-licensed, three-line installation, and entirely client-side: nothing leaves the browser.
Where this comes from
I've worked with user data for most of my career: building data products for fine-grained analytics use-cases, setting up a company's first data warehouse, and learning repeatedly that the hard part was not collecting the data, but getting it into the hands of someone at the exact moment it mattered. Most recently at Snowplow I built a real-time profile store and a context engine on top of their customer data infrastructure. So I've spent a long time on one question: how do you capture what people do and turn it into something an application can actually use? That question has got a lot more interesting in the agentic era.
The problem isn't the model, it's that the agent is blind.
Coding agents are genuinely game-changing, and vertical agents for support and back-office work are getting real traction. But customer-facing agents still mostly suck. Companies bolt a chatbot onto a legacy UI, it pops up at a random moment, it doesn't really do anything, and most teams are quietly terrified of putting it live. I think the primary reason is that nobody has nailed the effective interaction pattern for concierge-style, customer-facing agents.
At Next in Vegas this year, I talked to engineers at a couple of frontier labs to check I wasn't imagining this, and it's the same story everywhere. Their agents don't know what users are doing on the site. You give the agent your transactional data and your APIs, you ship it, and engagement stays low because the agent keeps fumbling the user's intent. It's answering in a vacuum.
So there's a clear gap here. The model isn't the bottleneck anymore, the context is, and an agent that can read the last 90 seconds of someone's session will run rings around a smarter model that can't.
Analytics already solved this, by accident
The old analytics world cracked this exact problem years ago. Google Analytics, Adobe, Segment, Snowplow (my old shop) all built the same thing: tracking for clicks, scrolls, hovers, and the rest. It went to marketing attribution and product dashboards. But that same structured behavioural trail is precisely what an agent needs to understand what someone was doing before they opened the chat. The data has existed for a decade. It's just been pointed at the wrong consumer.
You don't need the heavy machinery
Here's the realisation that made this fun to build: you don't need a pipeline, a service, and a warehouse sitting behind it. Most of what the agent needs to personalise its response is generated by the user, right there in the browser, by what they do on the page. So Contextune runs entirely client-side. The library computes a set of behavioural signals: a handful of static attributes plus a rolling event log. Optionally it can also push "triggers" that notify your agent to respond when a particular event occurs. Even better, it taps into your existing tracking setup (at launch we support Google Analytics, Segment and Mixpanel), so there's nothing to re-instrument.

That decision is also why the licensing is simple. Contextune is MIT-licensed and free. It runs inside your own app, so the behavioural data never leaves the user's browser.
What it actually looks like
The integration is three lines. Install it, call init() once, and read a snapshot at the moment your agent is invoked.
pnpm add @contextune/sdk
import { Contextune } from '@contextune/sdk';
// Tap the analytics you already run, and watch for rage clicks + scroll depth.
Contextune.init({ source: 'ga4', tracking: { rageClicks: true, scroll: true } });
// later, at the point your agent is invoked:
const snapshot = Contextune.getSnapshot({ format: 'toon' });
// inject `snapshot` into the agent's system prompt or first message.
getSnapshot({ format: 'toon' }) hands back a compact, token-optimised view of what the user has been up to. TOON (Token-Oriented Object Notation) flattens the payload into far fewer tokens than JSON, which matters when you're paying for every one in the prompt. Trimmed down, a checkout session might look like this:
marketing_params:
utm_source: google
utm_medium: cpc
utm_campaign: summer-jackets
referrer: https://www.google.com/
landing_page: https://shop.example.com/?utm_source=google
device_info:
viewport_w: 1440
viewport_h: 900
language: en-GB
device_type: desktop
event_log[3]{name,elapsed_s,properties}:
page_view,0,"{""url"":""/checkout""}"
add_payment_info,43,
rage_click,55,"{""target"":""button#submit"",""count"":3}"
Three rage-clicks on the submit button after a 12-second stall, still stuck on checkout. Drop that into the prompt and the agent stops guessing.
Does it actually help?

Fair question, and we built a benchmark set to find out rather than assert it. It holds the model fixed and changes only what the model can see. We wrote 16 real-world scenarios across three different agents (shopper, support, customer-facing data agent): a shopper rage-clicking Add to Bag, a SQL query erroring twice on the same line, an export that timed out twice, and so on. Then we had the same agent answer each one three ways: with the full behavioural snapshot, with only the current page (what a screenshot gives you), and blind.
With the snapshot, the agent solved 15 of 16. The version that could already see the current page solved 5, and the blind agent solved just 1. Behaviour roughly tripled the tasks solved, same model both times, the only difference being whether it could see what the user had been doing. It's a small synthetic set on one model, so read the gap rather than the absolute number. The full method, the scenarios, and every transcript is coming in a public repo soon.
Where this is going
Right now Contextune is a great fit for concierge and copilot agents, where you have an existing UI and the user is generating behaviour alongside the conversation. I think it gets more useful as we move toward generative-UI agents that hand back widgets, buttons, and whole interfaces to act on. The moment the user can do things inside the conversation, you need a structured way to capture what they did and feed it straight back. There's been almost no protocol for talking from the user back to the agent, and that gap is only going to matter more.
It's deliberately simple today, and fully client-side. We may add a hosted layer down the line for teams that want it, but the SDK stays free. For now we just want to put it into the world, in the hope it's useful and saves you some of the pain we hit building agents for our own customers.
Giving the agent eyes
The way we describe it: your agent already has ears. It can hear what the user types the instant they type it. But it's been blind to everything that came before, and Contextune gives it eyes.
In practice that achieves two things. A concierge, personalised opening message: when the user opens the agent, it lands with something relevant instead of "How can I help?". And better timing: you can hold that first message back until the user actually gets stuck, or has been idle for a while, instead of firing it off at a random four-second mark.
Try it today. It's open source.
View it on GitHub · contextune.com · docs.contextune.com

>_ Written By
John Reid