# How VFFilter Works After Candidate Selection in the Home-Mixer Pipeline

> Discover how VFFilter operates post candidate selection in the Home-Mixer Pipeline. Learn how it filters posts based on visibility actions and labels like Drop or Tombstone.

- Repository: [SpaceXAI Org/x-algorithm](https://github.com/xai-org/x-algorithm)
- Tags: deep-dive
- Published: 2026-09-12

---

**VFFilter partitions hydrated PostCandidate objects into kept and removed sets based on visibility actions, discarding any post marked with Drop, Tombstone, or NotEvaluated labels before final ranking.**

VFFilter is a critical post-selection filter in the `xai-org/x-algorithm` Home-Mixer pipeline that executes after candidates have been hydrated and scored. This visibility-based filter ensures that unsafe or non-displayable content never reaches user feeds by examining each candidate's `visibility_action` field. According to the source code in [`home-mixer/filters/vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs), the filter implements a strict partitioning logic that separates permissible content from content that must be suppressed.

## Pipeline Placement and Execution Order

In the `ReverseChronPostsPipeline`, **VFFilter** is registered within the `post_selection_filters` vector alongside `AuthorSocialgraphFilter` and `AncillaryVFFilter` at lines 79-84 of [`home-mixer/candidate_pipeline/reverse_chron_posts_pipeline.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/candidate_pipeline/reverse_chron_posts_pipeline.rs).

The filter executes only after the pipeline has completed all hydration stages. Once candidates have passed through source retrieval, hydrators, pre-selection filters, scorers, and the selector, the pipeline iterates through each filter in the `post_selection_filters` list to perform final pruning operations.

## Filtering Logic and Implementation

### The Filter Trait Implementation

`VFFilter` implements the generic `Filter<ScoredPostsQuery, PostCandidate>` trait. Its `filter` method receives the complete candidate list and applies a partitioning operation based on visibility status:

```rust
let (removed, kept): (Vec<_>, Vec<_>) = candidates
    .into_iter()
    .partition(|c| c.visibility_action.as_ref().is_some_and(should_drop_action));

```

This Rust pattern uses `partition` to split the input vector into two distinct collections in a single pass, ensuring O(n) time complexity where n is the candidate count.

### Visibility Action Evaluation

The helper function `should_drop_action` determines which visibility labels trigger removal. Located in [`home-mixer/filters/vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs) at lines 14-27, this function matches against the `Action` enum from the visibility-filtering service:

```rust
pub(crate) fn should_drop_action(action: &Action) -> bool {
    match action {
        Action::Allow | Action::Interstitial | Action::Avoid | Action::Downrank => false,
        Action::Drop(_) | Action::Tombstone | Action::NotEvaluated => true,
    }
}

```

**Drop-type actions**—specifically `Drop(_)`, `Tombstone`, and `NotEvaluated`—return `true` and cause immediate candidate removal. **Permissible actions**—`Allow`, `Interstitial`, `Avoid`, and `Downrank`—return `false`, allowing candidates to proceed to downstream processing.

## Filter Results and Pipeline Continuation

After partitioning, `VFFilter` returns a `FilterResult` struct containing both vectors. The pipeline discards the `removed` candidates and continues processing only the `kept` set for subsequent stages such as final ranking and serialization.

The internal structure of `FilterResult` ensures that the pipeline can trace which candidates were filtered and why, though the primary consumer-facing output contains only the retained candidates that passed all visibility checks.

## Testing and Validation

The implementation includes comprehensive unit tests in [`home-mixer/filters/vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs) at lines 30-55 that verify correct behavior for every `Action` variant. These tests confirm that only drop-type actions trigger removal while permissible states correctly preserve candidates.

```rust
// Manual use of VFFilter outside the pipeline (for testing)
let candidate = PostCandidate {
    visibility_action: Some(Action::Drop(DropReason {})),
    ..Default::default()
};
let filtered = VFFilter.filter(&ScoredPostsQuery::default(), vec![candidate]);
assert!(filtered.removed.len() == 1);
assert!(filtered.kept.is_empty());

```

## Integration Example

When constructing a pipeline instance, `VFFilter` is automatically included in the post-selection phase:

```rust
// Creating a pipeline (mock version) that includes VFFilter
let pipeline = ReverseChronPostsPipeline::mock().await;

// Run the pipeline – after hydrators finish, VFFilter is invoked:
let result = pipeline.run(&ScoredPostsQuery::default()).await;

// `result.candidates` now contains only those whose `visibility_action`
// is not a drop type, thanks to VFFilter.

```

## Summary

- **VFFilter** executes in the post-selection phase of the Home-Mixer pipeline after hydration and scoring are complete.
- The filter partitions candidates based on the `visibility_action` field, removing any post with `Drop`, `Tombstone`, or `NotEvaluated` labels.
- Implementation resides in [`home-mixer/filters/vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs) and implements the `Filter<ScoredPostsQuery, PostCandidate>` trait.
- The `should_drop_action` helper function defines which visibility states trigger removal versus retention.
- Filtered results return both kept and removed sets, though only the kept candidates proceed to feed serialization.

## Frequently Asked Questions

### What is the difference between VFFilter and AncillaryVFFilter?

**VFFilter is the primary visibility filter that removes content based on core visibility actions**, while `AncillaryVFFilter` appears later in the same `post_selection_filters` vector to handle additional visibility concerns. According to [`home-mixer/candidate_pipeline/reverse_chron_posts_pipeline.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/candidate_pipeline/reverse_chron_posts_pipeline.rs), both filters run sequentially after candidate selection, but `VFFilter` performs the initial hard filtering on the main visibility action before ancillary checks are applied.

### What visibility actions cause a candidate to be dropped versus retained?

**Candidates are dropped when their `visibility_action` is `Drop(_)`, `Tombstone`, or `NotEvaluated`** as defined in the `should_drop_action` function within [`vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/vf_filter.rs). **Candidates are retained when the action is `Allow`, `Interstitial`, `Avoid`, or `Downrank`**. Notably, `Downrank` and `Avoid` do not trigger removal; instead, they signal to other pipeline stages that the candidate should receive lower ranking priority while remaining in the feed.

### Where does VFFilter fit in the Home-Mixer pipeline sequence?

**VFFilter executes after the selector but before final ranking and serialization**. Specifically, it runs within the `post_selection_filters` stage of `ReverseChronPostsPipeline`, which occurs immediately after candidates have been hydrated, scored, and selected. This positioning ensures that visibility decisions apply to fully enriched candidate objects with complete metadata.

### How does VFFilter affect the final feed output?

**VFFilter guarantees that no candidate with a drop-type visibility label reaches the user-facing feed**. By partitioning candidates before the final ranking stage, the filter creates a hard boundary that removes unsafe, deleted, or unevaluated content entirely. The `kept` candidates proceed to downstream processing, while the `removed` vector is discarded, ensuring feed safety without affecting the ranking algorithm's operations on permissible content.