NEWVectors or files. Pick a path.Start →
    Safety and compliance
    Template v1.0 · updated 2026-09-03

    Video Moderation for Training Sets

    Pre-filtering at the ingest stage, before a single frame reaches training. Reduce raw footage to the subset you can defend training on: every excluded clip carries its reason, every uncertain one goes to a person, and the thresholds are yours.

    A cleared training set, filtered at ingest, with an audit trail for every clip that did not make it.

    Teams preparing video corpora for model training who have to prove what was excluded and why. The problem shows up at ingest, where the corpus is largest and the cost of getting it wrong compounds through every downstream run.

    82%
    of minors caught at a cutoff of 18
    on 650 labelled faces, 320 minors and 330 adults, single frame; 4.8% of adults sent to review
    100%
    recall is reachable, and the page says what it costs
    a cutoff of 30 catches every minor and sends 94.5% of adult footage to review; the whole curve is in the evaluation tab
    0.70
    face detector gate
    cleared six phantom minors the detector found in non-faces while keeping 85% of genuine faces
    39%
    of detections routed to review as unaged
    no usable face, under 24px, or detector confidence under 0.70. None of them is passed as adult.

    What it looks like

    Frame in, decisions fire, a verdict lands, a reviewer's call feeds back.

    Simulated walkthrough · illustrative frames, scripted decisions
    A close-up interview frame with one face in shotface 0.93 · est. 16 to 18 · borderlinereview
    faces -> age-estimation -> person-verdictsStill: Pexels / Ron Lachframe 1 of 4
    Decision path
    1. face detected 0.93
    2. grouped across 3 looks
    3. age estimate: borderline
    4. route: review
    within 6 years of the cutoff, and 3 looks
    Running tally
    0
    cleared
    1
    review
    0
    excluded
    Reviewer feedback
    When the frames are done, a reviewer's call on the borderline case feeds back into the thresholds.
    What goes in
    • source-video
      Your footage, read from your own object storage. Nothing is copied out.
    • brand-mark-refs
      Logo and mark variants you supply, one file per variant, with a hard-fail or contextual rule each.
    • person-refs
      Public-figure reference imagery you supply and hold the rights to. Several poses per person.
    Where the decisions fire
    Illustrative frames; boxes show the decision path, not live model output
    A close-up interview frame with one face in shotface 0.93 · est. 16 to 18 · borderlinereview
    faces -> age-estimation -> person-verdicts · A face is found, grouped with every other look at the same person across cuts, then aged. Borderline lands in review, never in the training set.Still: Pexels / Ron Lach
    A street scene with a lit storefront signbrand mark · match 0.88 · rule: contextualcleared
    keyframes -> brand-mark-match · A keyframe embedding is matched against your brand-mark reference index. The rule attached to that mark decides hard fail or contextual.Still: Pexels / Mathias Reding
    Two people sparring in a gymstaged 0.81 · real 0.19 · margin +0.62cleared
    keyframes -> violence-context · Violence is scored in context. Sparring, practice and play score staged; the real thing scores real. The margin between them is what you set.Still: Pexels / Franco Monsalvo
    A reporter beside a van carrying a BREAKING NEWS graphiconscreen_text · BREAKING NEWS · staticcleared
    onscreen-text · Words shown on screen and never spoken are read by the on-screen text pass, static overlays and scrolling bands alike, and land in their own searchable field. Nothing in the audio says BREAKING NEWS.Still: Pexels / cottonbro studio
    What comes out
    • segment-verdicts
      One row per segment: decision, the reason, and counts of people, minors, unaged and borderline faces.
    • review-queue
      Everything routed to a human, ranked, with the decision chain attached to each item.
    • cleared-set-export
      The segments you can train on, as a filterable set ready to hand to a training job.

    How the namespace is wired

    3 buckets, 8 collections, 2 clean views, 6 retrievers. The diagram generates the manifest below; they cannot drift apart.

    SourceBucketCollectionClean viewRetrieverClick a node to inspect it
    syncsyncsyncmultimodal_extractorimage_extractorface_identity_extractorface_identity_extractorimage_extractorscrolling_text_extractorage_estimatorcontext_scorersearchsearchtransformtransformpipeline inpipeline inpipeline inpipeline inpipeline outsearchsearchsearch

    Reward signals

    How reviewer decisions move the thresholds

    Thresholds at ingest drift as the corpus changes. The reviewers working the queue are the ones who see where a threshold is wrong first, so this template routes their decisions back into the model that set it.

    Reviewers do not only clear a queue. Every call they make is a labelled example, and the template is built so those labels move the thresholds rather than sit in a spreadsheet.
    Explicit signals

    A reviewer marks a verdict right or wrong and attaches what they saw. That is an annotation, written with POST /v1/annotations, versioned, and stored in the namespace's own mxp_document_annotations collection.

    Implicit signals

    What a reviewer opens, skips, or acts on is an interaction, written with POST /v1/retrievers/interactions into mxp_retriever_interactions. Nobody fills in a form for these.

    Where they land
    mxp_document_annotationsmxp_retriever_interactionsmxp_retriever_evaluations

    System collections in your namespace, on the same vector store as the rest of the template. They are yours to query.

    How the loop closes

    POST /v1/retrievers/{id}/evaluations/generate-from-interactions turns those signals into an evaluation set, so a threshold change is judged by precision and recall moving on held-out data rather than by whether signals were captured. moderation-search is configured with learned fusion in shadow mode: it learns from the signals and reports what it would have ranked, and you flip it on with a retriever PATCH when the numbers justify it.

    One file spins up the namespace. Generated from the diagram above. Also served at /templates/video-moderation.namespace.yaml.

    # video-moderation: 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.
    version: '1.0'
    metadata:
      name: video-moderation
      description: "Namespace template video-moderation. Generated from the flow diagram on mixpeek.com/templates/video-moderation."
    namespaces:
      - name: video-moderation
        description: "Everything below lives in this namespace."
        feature_extractors:
          - name: multimodal_extractor
            version: v2
          - name: image_extractor
            version: v1
          - name: face_identity_extractor
            version: v1
          - name: scrolling_text_extractor
            version: v1
    
    # 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.
    #   source-video: s3, continuous, your footage, in your account -> bucket source-video
    #   brand-marks: manual, one-shot, customer-supplied logo and mark variants -> bucket brand-mark-refs
    #   person-references: manual, one-shot, customer-supplied public-figure reference imagery -> bucket person-refs
    buckets:
      - name: source-video
        namespace: video-moderation
        description: "Fed by source-video (s3, continuous)."
        schema:
          properties:
            content:
              type: video
      - name: brand-mark-refs
        namespace: video-moderation
        description: "Fed by brand-marks (manual, one-shot)."
        schema:
          properties:
            content:
              type: image
      - name: person-refs
        namespace: video-moderation
        description: "Fed by person-references (manual, one-shot)."
        schema:
          properties:
            content:
              type: image
    collections:
      - name: video-segments
        namespace: video-moderation
        description: "multimodal_extractor@v2 over bucket source-video. Feeds faces, keyframes, onscreen-text, roll-up-by-segment."
        source:
          type: bucket
          bucket: source-video
        feature_extractor:
          name: multimodal_extractor
          version: v2
          input_mappings:
            video: content
          field_passthrough:
            - source_path: file_location
            - source_path: segment_id
            - source_path: start_time
            - source_path: end_time
        enabled: true
      - name: brand-mark-index
        namespace: video-moderation
        description: "image_extractor@v1 over bucket brand-mark-refs. Feeds brand-mark-match."
        source:
          type: bucket
          bucket: brand-mark-refs
        feature_extractor:
          name: image_extractor
          version: v1
          input_mappings:
            image: content
          field_passthrough:
            - source_path: mark_name
            - source_path: rule
        enabled: true
      - name: person-ref-index
        namespace: video-moderation
        description: "face_identity_extractor@v1 over bucket person-refs. Feeds public-figure-match."
        source:
          type: bucket
          bucket: person-refs
        feature_extractor:
          name: face_identity_extractor
          version: v1
          input_mappings:
            image: content
          field_passthrough:
            - source_path: person_name
            - source_path: rights_note
        enabled: true
      - name: faces
        namespace: video-moderation
        description: "face_identity_extractor@v1 over collection video-segments. Feeds age-estimation, person-verdicts."
        source:
          type: collection
          collection: video-segments
        feature_extractor:
          name: face_identity_extractor
          version: v1
          input_mappings:
            video: content
          field_passthrough:
            - source_path: segment_id
            - source_path: bbox
            - source_path: detection_score
            - source_path: quality_score
        enabled: true
      - name: keyframes
        namespace: video-moderation
        description: "image_extractor@v1 over collection video-segments. Feeds violence-context."
        source:
          type: collection
          collection: video-segments
        feature_extractor:
          name: image_extractor
          version: v1
          input_mappings:
            image: content
          field_passthrough:
            - source_path: segment_id
            - source_path: frame_ts
        enabled: true
      - name: onscreen-text
        namespace: video-moderation
        description: "scrolling_text_extractor@v1 over collection video-segments. Feeds roll-up-by-segment."
        source:
          type: collection
          collection: video-segments
        feature_extractor:
          name: scrolling_text_extractor
          version: v1
          input_mappings:
            video: content
          field_passthrough:
            - source_path: segment_id
        enabled: true
      - name: person-verdicts
        namespace: video-moderation
        description: "Derived from faces, age-estimation. One document per person_id. Writes back age_median, aged, n_looks. Declared as a passthrough collection because the manifest schema has no resource for a retriever-materialised collection."
        source:
          type: collection
          collection: faces
        feature_extractor:
          name: passthrough_extractor
          version: v1
          field_passthrough: []
        enabled: true
      - name: segment-verdicts
        namespace: video-moderation
        description: "Materialised by retriever roll-up-by-segment (group_by segment_id) over person-verdicts, violence-context, onscreen-text, video-segments. One document per segment_id. Writes back decision, reason, n_person, n_minor, n_unaged, n_borderline. Declared as a passthrough collection because the manifest schema has no resource for a retriever-materialised collection."
        source:
          type: collection
          collection: person-verdicts
        feature_extractor:
          name: passthrough_extractor
          version: v1
          field_passthrough: []
        enabled: true
    
    # Template-provided extractors. These collections run on age_estimator and context_scorer,
    # plugins that ship with this template rather than hosted extractors, so apply would fail on
    # a namespace that does not have them yet. Install the extractors, then move this block above.
    #   - name: age-estimation
    #     namespace: video-moderation
    #     description: "age_estimator@v1 over collection faces. Feeds person-verdicts. age_estimator is a template-provided extractor (a plugin that ships with this template, not a hosted one): install it, then apply this block."
    #     source:
    #       type: collection
    #       collection: faces
    #     feature_extractor:
    #       name: age_estimator
    #       version: v1
    #       input_mappings:
    #         face: content
    #       field_passthrough:
    #         - source_path: person_id
    #         - source_path: age_median
    #         - source_path: aged
    #         - source_path: n_looks
    #     enabled: true
    #   - name: violence-context
    #     namespace: video-moderation
    #     description: "context_scorer@v1 over collection keyframes. Feeds roll-up-by-segment. context_scorer is a template-provided extractor (a plugin that ships with this template, not a hosted one): install it, then apply this block."
    #     source:
    #       type: collection
    #       collection: keyframes
    #     feature_extractor:
    #       name: context_scorer
    #       version: v1
    #       input_mappings:
    #         image: content
    #       field_passthrough:
    #         - source_path: segment_id
    #         - source_path: staged_score
    #         - source_path: real_score
    #         - source_path: margin
    #     enabled: true
    retrievers:
      - name: brand-mark-match
        namespace: video-moderation
        description: "Searches brand-mark-index across 1 feature index."
        collections:
          - brand-mark-index
        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://image_extractor@v1/google_siglip_base_v1"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 5
              fusion: rrf
              final_top_k: 5
        tags:
          - template:video-moderation
      - name: public-figure-match
        namespace: video-moderation
        description: "Searches person-ref-index across 1 feature index."
        collections:
          - person-ref-index
        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://face_identity_extractor@v1/insightface__arcface"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 5
              fusion: rrf
              final_top_k: 5
        tags:
          - template:video-moderation
      - name: roll-up-by-segment
        namespace: video-moderation
        description: "Pipeline retriever over person-verdicts, onscreen-text, video-segments. Groups documents by segment_id. Materialises into segment-verdicts."
        collections:
          - person-verdicts
          - onscreen-text
          - video-segments
          # - violence-context  (add once context_scorer is installed and its block above is applied)
        input_schema:
          limit:
            type: text
            required: false
            description: "Optional cap on documents grouped per run"
        stages:
          - stage_name: group
            stage_id: group_by
            parameters:
              group_by_field: segment_id
        tags:
          - template:video-moderation
      - name: review-queue
        namespace: video-moderation
        description: "Searches segment-verdicts across 1 feature index."
        collections:
          - segment-verdicts
        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://multimodal_extractor@v2/gemini-embedding-2"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 100
              fusion: rrf
              final_top_k: 100
        tags:
          - template:video-moderation
      - name: moderation-search
        namespace: video-moderation
        description: "Searches segment-verdicts across 3 feature indexes with learned fusion."
        collections:
          - segment-verdicts
        input_schema:
          query:
            type: text
            required: true
            description: "What to look for; searched across every index below"
          session_id:
            type: text
            required: false
            description: "Session identifier; the learned fusion uses it as personal context"
        stages:
          - stage_name: search
            stage_id: feature_search
            parameters:
              searches:
                - feature_uri: "mixpeek://multimodal_extractor@v2/gemini-embedding-2"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 25
                - feature_uri: "mixpeek://multimodal_extractor@v2/multilingual_e5_large_instruct_v1"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 25
                - feature_uri: "mixpeek://multimodal_extractor@v2/multilingual_e5_large_instruct_ocr_v1"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 25
              fusion: learned
              final_top_k: 25
              # Shadow first: weights are learned and logged, static fusion is served, until the
              # signals justify a rollout. Flip rollout_pct through the retriever update path.
              learning_config:
                shadow_mode: true
                rollout_pct: 0.0
                context_features:
                  - INPUT.session_id
        tags:
          - template:video-moderation
      - name: cleared-set-export
        namespace: video-moderation
        description: "Searches segment-verdicts across 1 feature index."
        collections:
          - segment-verdicts
        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://multimodal_extractor@v2/gemini-embedding-2"
                  query:
                    input_mode: text
                    value: "{{INPUT.query}}"
                  top_k: 1000
              fusion: rrf
              final_top_k: 1000
        tags:
          - template:video-moderation