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

# User Signals & Journeys

> Connect what your users do to the AI traces behind it

An AI trace (input, output, reasoning) only tells you what the model did. It doesn't tell you whether that was actually useful: did the user accept it, edit it, cancel it, escalate it to a human? That's what a **signal** is: an event you send us about user or product behavior.

A **journey** is the thread that ties a user's signals to the AI traces that happened around them, even across multiple calls or agents. Every signal and every trace carries the same `journey_id`, so DataFramer can line them up into one timeline.

## The pieces

<CardGroup cols={3}>
  <Card title="Browser SDK" icon="browser" href="/docs/user-signals/browser-sdk">
    `@dataframer/signals`: runs in your frontend, sends `track`/`identify`/`group` events, and generates the journey id.
  </Card>

  <Card title="Server Instrumentation" icon="server" href="/docs/user-signals/server-instrumentation">
    `dataframer-journey`: runs in your backend, picks up the journey id, and stamps it onto your Langfuse traces automatically.
  </Card>

  <Card title="Connect AI Observability" icon="plug" href="/docs/user-signals/connect-langfuse-langsmith">
    Already using an observability tool? Connect it and DataFramer pulls the traces in directly. Langfuse and LangSmith are supported today.
  </Card>
</CardGroup>

## How it fits together

<div className="not-prose my-8 flex flex-col sm:flex-row items-stretch gap-3">
  <div className="flex-[3] flex flex-col gap-3">
    <div className="flex-1 rounded-2xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/[0.03] p-4 flex flex-col items-center text-center gap-2">
      <span className="font-mono text-[11px] tracking-widest uppercase text-gray-500 dark:text-gray-400">Browser</span>
      <span className="font-mono text-xs rounded-full px-3 py-1.5 bg-gray-900 text-white dark:bg-white dark:text-gray-900">@dataframer/signals</span>
      <p className="text-sm text-gray-600 dark:text-gray-400 m-0">Sends <code>track</code>/<code>identify</code>/<code>group</code> events, tagged with <code>journey\_id</code>, straight to DataFramer</p>
    </div>

    <div className="flex-1 flex flex-col sm:flex-row items-stretch gap-2">
      <div className="flex-1 rounded-2xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/[0.03] p-4 flex flex-col items-center text-center gap-2">
        <span className="font-mono text-[11px] tracking-widest uppercase text-gray-500 dark:text-gray-400">Your Backend</span>
        <span className="font-mono text-xs rounded-full px-3 py-1.5 bg-gray-900 text-white dark:bg-white dark:text-gray-900">dataframer-journey</span>
        <p className="text-sm text-gray-600 dark:text-gray-400 m-0">Stamps <code>journey\_id</code> onto every trace</p>
      </div>

      <div className="hidden sm:flex items-center justify-center text-green-600 dark:text-green-400 font-mono text-lg px-1">→</div>
      <div className="flex sm:hidden items-center justify-center text-green-600 dark:text-green-400 font-mono text-lg py-0.5">↓</div>

      <div className="flex-1 rounded-2xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/[0.03] p-4 flex flex-col items-center text-center gap-2">
        <span className="font-mono text-[11px] tracking-widest uppercase text-gray-500 dark:text-gray-400">Your Observability Tool</span>
        <span className="font-mono text-xs rounded-full px-3 py-1.5 bg-gray-900 text-white dark:bg-white dark:text-gray-900">Langfuse / LangSmith</span>
        <p className="text-sm text-gray-600 dark:text-gray-400 m-0">Holds the tagged traces</p>
      </div>
    </div>
  </div>

  <div className="hidden sm:flex items-center justify-center text-green-600 dark:text-green-400 font-mono text-lg px-1">→</div>
  <div className="flex sm:hidden items-center justify-center text-green-600 dark:text-green-400 font-mono text-lg py-0.5">↓</div>

  <div className="flex-[2] rounded-2xl border border-green-600/30 dark:border-green-400/25 bg-green-600/[0.05] dark:bg-green-400/[0.06] p-5 flex flex-col items-center justify-center text-center gap-2">
    <span className="font-mono text-[11px] tracking-widest uppercase text-green-700 dark:text-green-400">DataFramer</span>

    <div className="flex flex-col gap-1.5 items-center">
      <span className="font-mono text-xs rounded-full px-3 py-1.5 bg-green-600 text-white">Signal · journey\_id</span>
      <span className="font-mono text-xs rounded-full px-3 py-1.5 bg-green-600 text-white">Trace · journey\_id</span>
    </div>

    <p className="text-sm text-gray-600 dark:text-gray-400 m-0">Both land with the same <code>journey\_id</code> and get lined up into one timeline</p>
  </div>
</div>

You only need the browser SDK if you want to capture user/product events. You only need the server library if you want your AI traces auto-tagged with the journey id. If your traces already live in an observability tool (Langfuse or LangSmith today), you don't need either: just [connect it](/docs/user-signals/connect-langfuse-langsmith) and DataFramer pulls them in. Most setups combine the SDK with one of the other two.

<Note>
  This feature is early; reach out at [info@dataframer.ai](mailto:info@dataframer.ai) if you hit anything unexpected.
</Note>
