NEWVectors or files. Pick a path.Start →
    Media
    v1.0 · updated 2026-09-13

    UX session analysis

    Moderated sessions become a corpus you can question. The transcript is joined to what was on screen at that second, so one query reaches every session at once and returns the moment rather than the recording.

    Applies empty and reads Three to five sessions from ONE study. Connect your own data before querying.

    word-timed words joined to screen states
    7,257 / 319
    median scene, cut at screen transitions
    5s
    claim moments, setting changes, task boundaries
    112 / 22 / 9
    sessions where hand-off-wheel timing works
    2 of 6
    click to first answer
    6.3 s
    How these were measured
    • 7,257 / 319 word-timed words joined to screen states: Measured on the reference corpus of six sessions. The join is the product: a word without the screen it was spoken over is a transcript, and a screen state without the word is a screenshot.
    • 5s median scene, cut at screen transitions: Segmenting on the display changing rather than on a fixed interval is what makes a result land on a behaviour instead of in the middle of one.
    • 112 / 22 / 9 claim moments, setting changes, task boundaries: From the same six sessions: 112 moments where a participant made a claim, 22 setting changes with durations read off the display rather than from a knob sensor, and 9 task boundaries taken from the moderator narrating the protocol.
    • 2 of 6 sessions where hand-off-wheel timing works: 61.5% of frames on one rig and 15.9% on another, agreeing with 7 of 9 hand-labelled frames. It fails on the rest because the camera is framed on the display with the wheel out of shot. This is a rig property, not a model property.
    • 6.3 s click to first answer: from clicking apply to the first search that answers, timed on production on 6 November 2026 by the harness that runs nightly

    What’s inside

    Extractors
    multimodaltext
    Retrievers
    find-the-moment·feature_searchtask-roll-up·feature_search → group_byfrustration-monitor·feature_search

    See it work

    • where did people hesitate on the checkout step
      find-the-moment

      the word and the screen state at that second, in one result

    • what did everyone say about the new navigation
      task-roll-up

      grouped on participant_id, so no participant is missing from the answer

    • the moment someone says they are confused
      find-the-moment

      opens at the second it happened rather than at the recording

    Simulated walkthrough · illustrative frames, scripted decisions
    Two people at a wall-mounted touchscreen, one reaching toward the display while the other watchescleared
    session-scenes -> session-transcriptsStill: Pexels / Darlene Aldersonframe 1 of 1
    Detections on this frame
    Decision path
    1. session-scenes -> session-transcripts
    2. participant
    3. screen state at that second
    4. route: cleared
    cut at the screen transition, transcript joined to it
    Running tally
    1
    cleared
    0
    review
    0
    excluded
    What a person's call does here
    None. No saves, no flags, no observer marks are recorded by the template as shipped.
    Try a search
    Pick a search to see which retriever answers it and whether it works on a one-click deploy.
    InsessionsOutsession-scenessession-transcripts

    How it's wired

    Applies 1 buckets, 2 collections and 3 retrievers today

    SourceBucketCollectionRetrieverAlertNot applied yet
    SOURCENeeds your inputclinic-recordingss3 · continuousBUCKETsessionscontent: video · dedupCOLLECTIONDeploys readysession-scenesmultimodal_extractor@v1COLLECTIONDeploys readysession-transcriptstext_extractor@v1RETRIEVERDeploys readyfind-the-momentrrf · top 25RETRIEVERDeploys readytask-roll-uppipeline · group by participant…RETRIEVERDeploys readyfrustration-monitortop 5ALERTparticipant-frustration

    Drag nodes to rearrange. Drag the background to pan, ctrl or cmd plus scroll to zoom. Click a node for its settings.

    What it costs

    Usage cost per month
    $7,693
    $0.77 per file · rate card when this page was built

    Processing is priced the way a batch is charged: each bucket's minutes, images, pages or tokens times its rate card lines. The batch dry run in Studio gives the binding quote.

    Models

    Collections and their documents

    session-scenesfrom bucket sessions, by multimodal_extractor@v1

    Reads video from content

    FieldTypeRequired
    document_idstringset by Mixpeek
    collection_idstringset by Mixpeek
    study_idstringno
    participant_idstringno
    rig_idstringno
    study_task_idstringno
    file_locationas sentno
    segment_idas sentno
    start_timeas sentno
    end_timeas sentno
    session-transcriptsfrom collection session-scenes, by text_extractor@v1

    Reads text from transcription

    FieldTypeRequired
    document_idstringset by Mixpeek
    collection_idstringset by Mixpeek
    study_idas sentno
    participant_idas sentno
    study_task_idas sentno
    segment_idas sentno
    video_segment_urlas sentno
    start_timeas sentno
    end_timeas sentno

    Retrievers

    • find-the-moment
      feature_search over session-scenes, session-transcripts; input query
    • task-roll-up
      feature_search then group_by over session-transcripts, session-scenes; input query
    • frustration-monitor
      feature_search over session-transcripts; input query, document_id

    Spend limits

    Daily spend budget in the manifest
    0.1 USD a day; new batches pause once it is spent
    Acceptance run ceiling
    0.1 USD
    Model cost
    Unmeasured for 2 of 2 models

    Monthly cost at your volume is in the calculator. Served models start at zero replicas, so an idle namespace runs no model.

    Reward signals

    Nothing in this template writes signals back. A researcher marking a moment as the right one would be the obvious loop and it is not wired.

    How reviewer decisions move the thresholds
    Explicit signals

    None. No saves, no flags, no observer marks are recorded by the template as shipped.

    Implicit signals

    None from the application. Mixpeek records retriever executions server-side, which is telemetry about queries rather than feedback about results.

    Where they land
    mxp_retriever_executions
    How the loop closes

    The observers' notes are the ground truth this use case is missing: task start and stop, errors, and confidence per participant. That is an input to evaluation rather than a signal the template collects.

    Ask one question across every session and get every participant's answer, attributed. The query that defines this template is the roll-up: one task, every participant, with each result opening at the second it happened.

    • Sessions are cut at screen transitions, so a result is a moment with its screen state rather than a timestamp somebody has to scrub to.
    • The spoken word and the display are indexed together, which is what lets a claim be read beside the screen it was spoken over.
    • The roll-up groups on the participant field, so no participant is silently missing from an answer that summarises a task.
    • It runs where you put it, which matters when the footage cannot leave your environment.

    For: UX research teams running moderated usability sessions

    What goes in
    sessions
    One object per recording, with study_id, participant_id, rig_id and study_task_id as fields. Those four are what every roll-up groups and filters on, so they are the schema decision that matters.
    What comes out
    session-scenes
    Segment documents with a 1408-dimension multimodal embedding, timing, transcription, and the on-screen text read off that segment.
    session-transcripts
    The spoken side as a 1024-dimension text embedding, carrying the study, participant and task through so a filtered question stays filtered.

    One file spins up the namespace. Generated from the workflow above. Also served at /templates/ux-session-analysis.namespace.yaml.

    # ux-session-analysis: one manifest spins up the namespace.
    # Platform manifest schema (GET /v1/discovery/schema). Validate with POST /v1/manifest/validate,
    # apply with POST /v1/manifest/apply or the Deploy button. Wiring comes from the flow diagram:
    # edges are bucket -> collection sources, collection -> retriever scope, retriever -> view.
    # Applying a SECOND time, over a namespace this template already created: use
    # POST /v1/manifest/apply?mode=create_missing, which creates what is missing and leaves
    # what exists alone. The default, create_only, fails the WHOLE apply and rolls it back if
    # any resource already exists, so an upgrade looks like a dead end without this. Use
    # mode=upsert to also patch resources that exist but have drifted from this file.
    version: '1.0'
    metadata:
      name: ux-session-analysis
      description: "Namespace template ux-session-analysis. Generated from the flow diagram on mixpeek.com/templates/ux-session-analysis."
    namespaces:
      - name: ux-session-analysis
        description: "Everything below lives in this namespace."
        feature_extractors:
          - name: multimodal_extractor
            version: v1
          - name: text_extractor
            version: v1
        # Daily spend budget, in dollars of what you are charged (1,000 credits a dollar).
        # New batches pause once the namespace has spent this much today; queries are not capped.
        # Raise it later on the namespace: PATCH /v1/namespaces/<id> {"spend_budget": {"daily_usd": N}}.
        budget:
          daily_usd: 0.1
    
    # Data sources. A storage connection carries credentials, so it is created in Studio
    # (or POST /v1/organizations/storage-connections) and synced into the bucket named here.
    #   clinic-recordings: s3, continuous, moderated session recordings from your rig, in your account -> bucket sessions
    buckets:
      - name: sessions
        namespace: ux-session-analysis
        description: "Meant to be fed by clinic-recordings (s3, continuous). No source is connected yet: applying this manifest creates the bucket only. Connect the source to this bucket in Studio (Syncs) to start the feed."
        schema:
          properties:
            content:
              type: video
              required: true
            study_id:
              type: string
              required: true
            participant_id:
              type: string
              required: true
            study_task_id:
              type: string
            rig_id:
              type: string
    
    # Connect your own storage. Create the two secrets, uncomment, and apply again;
    # apply live-tests the connection, so it must have real credentials to succeed.
    # Until then the buckets above accept direct uploads.
    # storage_connections:
    #   - name: clinic-recordings-connection
    #     provider: s3
    #     description: "Read-only access to the s3 location holding moderated session recordings from your rig, in your account."
    #     config:
    #       region: us-east-1
    #       credentials:
    #         type: access_key
    #         access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    #         secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
    # bucket_syncs:
    #   - name: clinic-recordings-sync
    #     bucket: sessions
    #     connection: clinic-recordings-connection
    #     source_path: "sessions/"
    #     sync_mode: continuous
    #     polling_interval_seconds: 300
    #     skip_duplicates: true
    #     file_filters:
    #       include_patterns: ["*.mp4", "*.mov"]
    #     schema_mapping:
    #       mappings:
    #         content:
    #           target_type: blob
    #           source: {type: file}
    #           blob_type: video
    
    collections:
      - name: session-scenes
        namespace: ux-session-analysis
        description: "multimodal_extractor@v1 over bucket sessions. Feeds session-transcripts, find-the-moment, task-roll-up."
        source:
          type: bucket
          bucket: sessions
        feature_extractor:
          name: multimodal_extractor
          version: v1
          parameters:
            run_transcription: true
            run_ocr: true
          input_mappings:
            video: content
          field_passthrough:
            - source_path: study_id
            - source_path: participant_id
            - source_path: rig_id
            - source_path: study_task_id
            - source_path: file_location
            - source_path: segment_id
            - source_path: start_time
            - source_path: end_time
        enabled: true
      - name: session-transcripts
        namespace: ux-session-analysis
        description: "text_extractor@v1 over collection session-scenes. Feeds frustration-monitor, participant-frustration, find-the-moment, task-roll-up."
        source:
          type: collection
          collection: session-scenes
        feature_extractor:
          name: text_extractor
          version: v1
          input_mappings:
            text: transcription
          field_passthrough:
            - source_path: study_id
            - source_path: participant_id
            - source_path: study_task_id
            - source_path: segment_id
            - source_path: video_segment_url
            - source_path: start_time
            - source_path: end_time
        alert_applications:
          - alert: participant-frustration
            input_mappings:
              - input_key: query
                source: {source_type: constant, value: "user expresses frustration or gives up"}
              - input_key: document_id
                source: {source_type: document_field, path: _internal.document_id}
        enabled: true
    retrievers:
      - name: find-the-moment
        namespace: ux-session-analysis
        description: "Searches session-scenes, session-transcripts across 2 feature indexes with rrf fusion."
        collections:
          - session-scenes
          - session-transcripts
        input_schema:
          query:
            type: text
            required: true
            description: "What to look for; searched across every index below"
        stages:
          - stage_name: search
            stage_id: feature_search
            parameters:
              searches:
                - feature_uri: "mixpeek://text_extractor@v1"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 25
                - feature_uri: "mixpeek://multimodal_extractor@v1/vertex_multimodal_embedding"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 25
              fusion: rrf
              final_top_k: 25
        tags:
          - template:ux-session-analysis
      - name: task-roll-up
        namespace: ux-session-analysis
        description: "Searches session-transcripts, session-scenes across 1 feature index. Groups documents by participant_id."
        collections:
          - session-transcripts
          - session-scenes
        input_schema:
          query:
            type: text
            required: true
            description: "What to look for; searched across every index below"
        stages:
          - stage_name: search
            stage_id: feature_search
            parameters:
              searches:
                - feature_uri: "mixpeek://text_extractor@v1"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 200
              fusion: rrf
              final_top_k: 200
          - stage_name: group
            stage_id: group_by
            parameters:
              group_by_field: participant_id
        tags:
          - template:ux-session-analysis
      - name: frustration-monitor
        namespace: ux-session-analysis
        description: "The alert's check: does the transcript just written show a participant frustrated or giving up. Scoped to that one transcript. Materialises into participant-frustration."
        collections:
          - session-transcripts
        input_schema:
          query:
            type: text
            required: true
            description: "What to look for; searched across every index below"
          document_id:
            type: text
            required: true
            description: "The written document this run is scoped to, supplied by the alert attachment"
        stages:
          - stage_name: search
            stage_id: feature_search
            pre_filters:
              AND:
                - field: _internal.document_id
                  operator: eq
                  value: "{{INPUT.document_id}}"
            parameters:
              searches:
                - feature_uri: "mixpeek://text_extractor@v1"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 5
                  min_score: 0.82
              fusion: rrf
              final_top_k: 5
        tags:
          - template:ux-session-analysis
    alerts:
      - name: participant-frustration
        namespace: ux-session-analysis
        description: "Emails the org notifications address (Settings -> Notifications; unset means opted out) when a new session transcript shows a participant frustrated or giving up on a task."
        source: retriever
        retriever: frustration-monitor
        trigger_on: results
        notification_config:
          channels:
            - channel_type: email
          include_matches: true
          include_scores: true
        enabled: false
    
    # What a run of this template must show (plan 11.14). One realtime step only:
    # the sessions bucket requires metadata fields on every object, and a seed
    # object carries blobs alone, so no document can be seeded and the document,
    # query and alert steps the other templates run have nothing to read here.
    acceptance:
      max_cost_usd: 0.1
      steps:
        - name: query-embedding-served
          kind: realtime
          feature_uri: mixpeek://text_extractor@v1
          inputs: {text: "the participant could not find the checkout button"}
          # No dims: the step checks the length the index declares where it runs
          # (1024 under e5, 384 under the standalone's MiniLM).
          expect: {}

    Read from the manifest, so a part it ships commented out never shows as ready.

    session-scenesCollectionDeploys ready
    session-transcriptsCollectionDeploys ready
    find-the-momentRetrieverDeploys ready
    task-roll-upRetrieverDeploys ready
    frustration-monitorRetrieverDeploys ready
    group_byRetriever stageDeploys ready
    clinic-recordingsData sourceNeeds your input
    What each part needs
    session-scenes
    Deploys and processes documents as they arrive.
    session-transcripts
    Deploys and processes documents as they arrive.
    find-the-moment
    Deploys and answers queries once its collections hold documents.
    task-roll-up
    Deploys and answers queries once its collections hold documents.
    frustration-monitor
    Deploys and answers queries once its collections hold documents.
    group_by
    The one-click manifest includes this stage.
    clinic-recordings
    Create the s3 credentials as manifest secrets, then uncomment the sync. It ships commented out because apply tests a connection as it creates it.
    ComponentRoleSourceToday
    multimodal_extractor@v1Segments each session and produces transcription, on-screen text and a visual embedding per segmentThe @v1 pin is required rather than current: @v2 exposes gemini-embedding-2 and does not carry vertex_multimodal_embedding, so a version bump silently drops the 1408-dimension space.Built inDeploys ready
    text_extractor@v1Embeds the spoken transcript so a researcher's typed question reaches the wordsMultilingual E5 large instruct, 1024 dimensions, cosine.Built inDeploys ready
    group_byThe roll-up: one task, every participant, each attributedGrouping on participant_id is what makes 'summarise every user on this task' return every user rather than the loudest few.Built inDeploys ready
    Your own cloud

    The usual choice here. Clinic footage is participant video, and a policy that blocks external upload is the normal starting condition rather than an exception.

    Mixpeek cloud

    Apply the manifest and the namespace comes up with two collections and two retrievers.

    Permissions
    A key with namespace create and collection create. The storage connection is a separate grant and the manifest ships it commented out, because apply live-tests a connection and would roll the whole namespace back on a bad credential.
    Footprint
    Two vector indexes, 1408 and 1024 dimensions, over one segment document per screen transition. At a 5 second median, an hour-long session is on the order of several hundred segments.
    Reference fixtures

    None ship with this template. The reference engagement agreed its success criteria in advance and has not scored them, because scoring needs the observers' notes as ground truth.

    CategoryRule
    Scene segmentationCut on screen transitions. The criterion agreed with the reference customer is boundaries within 3 seconds of the moderator's own marks on at least 85% of tasks, which has not been run.
    Roll-up completenessOne task across N participants returns every participant and none missing. This is checkable without ground truth: compare the group count against the participant count.
    Time on taskOnly meaningful where the rig frames the wheel. Treat it as unavailable on any session whose camera does not, rather than as a low number.
    Throughput

    Unmeasured on this platform path. The criterion the reference customer set is a 60-minute session processed in under 30 minutes with the cost stated, and nobody has run it through to check.

    Does this read the participant's face for confusion or frustration?+

    No, and it is left out on evidence rather than as a limitation of the platform. On the reference corpus the face is about 53 pixels wide and usable on 1.9% of frames, and where it is large enough the reading tracks head pose: looking down at the display returns anger at high confidence, which is what a participant does for most of a session. Confusion is not a category in off-the-shelf models at all. A wrong reading with a plausible story attached is worse than no reading.

    Does it separate the moderator from the participant?+

    Not by diarisation. The reference pipeline attributes turns with a phrase heuristic that its own source calls a hint used for counting and never presented as ground truth. If attribution has to be reliable, that is a diarisation model to add rather than something this template already does.

    Can it tell me how long a task took?+

    Where the camera includes the wheel, yes: hands leaving and returning is the timer, and it agreed with 7 of 9 hand-labelled frames on the sessions where it works. On sessions framed only on the display it cannot, and that is a rig change with lead time rather than a tuning question.

    How accurate is the retrieval?+

    Unscored, and this page will not invent a number. The criterion agreed with the reference customer is the right moment in the top 10 for at least 80% of researcher queries, scored on sessions nobody tuned against. That needs their query set and their answers.

    Can it run where the footage has to stay?+

    Yes, and for this use case that is usually the deciding property. The same manifest applies against a customer-hosted deployment, so participant video never leaves the environment you nominate.