# When and How VFFilter Applies Visibility Filtering Decisions After the Ranking Process

> Learn when and how VFFilter applies visibility filtering after ranking. Discover how it prunes posts flagged with Drop, Tombstone, or NotEvaluated visibility actions before further processing.

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

---

**VFFilter executes as the first post-selection filter in the Home-Mixer pipeline, running immediately after the `TopKScoreSelector` ranks candidates to prune posts flagged with `Drop`, `Tombstone`, or `NotEvaluated` visibility actions before downstream processing occurs.**

In the `xai-org/x-algorithm` repository, the Home-Mixer service orchestrates content ranking through a multi-stage pipeline where VFFilter serves as the critical enforcement gate for visibility decisions. After candidates undergo hydration, scoring, and selection, this filter inspects pre-computed visibility metadata to determine which posts survive to reach side-effect stages and user responses.

## When VFFilter Runs in the Pipeline

VFFilter operates during the **post-selection phase**, positioned immediately after the ranking process completes. According to the pipeline definition in [`home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs), the system executes filters in a strict sequence:

```rust
let post_selection_filters: Vec<Box<dyn Filter<ScoredPostsQuery, PostCandidate>>> = vec![
    Box::new(VFFilter),               // <─ applies visibility decisions
    Box::new(AncillaryVFFilter),
    Box::new(DedupConversationFilter),
];

```

*(source: [[`phoenix_candidate_pipeline.rs`](https://github.com/xai-org/x-algorithm/blob/main/phoenix_candidate_pipeline.rs)](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs#L36-L41))*

This positioning ensures VFFilter evaluates candidates **only after** the `TopKScoreSelector` has applied ranking scores and selected the top-K items. The filter chain executes sequentially, meaning VFFilter's removal decisions occur before ancillary visibility tweaks or conversation deduplication filters process the remaining candidates.

## How VFFilter Processes Ranked Candidates

When invoked, VFFilter receives the ranked `Vec<PostCandidate>` and inspects the optional `visibility_action` field populated earlier by the Visibility-Filtering service. The implementation performs a simple partition operation to separate kept from removed items.

### The Partition Logic

The core filtering mechanism resides in [`home-mixer/filters/vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs) and uses Rust's `Iterator::partition` method:

```rust
let (removed, kept) = candidates.into_iter()
    .partition(|c| c.visibility_action
        .as_ref()
        .is_some_and(should_drop_action));
FilterResult { kept, removed }

```

*(source: [[`vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/vf_filter.rs)](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs#L14-L20))*

### Drop Conditions in `should_drop_action`

The `should_drop_action` helper function defines the specific visibility actions that trigger removal:

```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,
    }
}

```

*(source: [[`vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/vf_filter.rs)](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs#L22-L26))*

**Kept candidates** carry actions such as `Allow`, `Interstitial`, `Avoid`, or `Downrank`, allowing them to continue through the pipeline. **Removed candidates** possess `Drop(_)`, `Tombstone`, or `NotEvaluated` actions, causing immediate exclusion from the `kept` vector and placement into the `removed` collection.

## Implementation Examples

### Pipeline Integration

The following example demonstrates how the Home-Mixer pipeline applies VFFilter after ranking completion:

```rust
let pipeline = PhoenixCandidatePipeline::mock().await;

// Pipeline executes: Hydration → Scoring → Ranking
let ranked_candidates = pipeline.execute(query).await;

// Post-selection filtering phase begins
let post_selection_filters = pipeline.post_selection_filters();
let mut filtered = FilterResult { kept: ranked_candidates, removed: vec![] };

for filter in post_selection_filters {
    let result = filter.filter(&query, filtered.kept);
    filtered = result;
    // After first iteration: VFFilter has removed Drop/Tombstone/NotEvaluated
}

// filtered.kept now contains only candidates passing all visibility filters

```

### Direct Filter Usage

For unit testing or custom implementations, you can invoke VFFilter directly against candidate vectors:

```rust
use home_mixer::filters::vf_filter::VFFilter;
use home_mixer::models::candidate::PostCandidate;
use home_mixer::models::query::ScoredPostsQuery;
use xai_visibility_filtering::models::Action;

let candidate = PostCandidate {
    visibility_action: Some(Action::Drop(Default::default())),
    ..Default::default()
};

let result = VFFilter.filter(&ScoredPostsQuery::default(), vec![candidate]);

assert_eq!(result.removed.len(), 1); // Dropped due to visibility action
assert!(result.kept.is_empty());

```

*(source: test in [[`vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/vf_filter.rs)](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs#L34-L55))*

## Execution Flow Summary

The complete lifecycle of a candidate through the visibility filtering stage follows this sequence:

1. **Hydration, Scoring, and Ranking** — Phoenix, Ranking, and VMRanker components generate and score candidates.
2. **Top-K Selection** — `TopKScoreSelector` selects the highest-scored candidates.
3. **Post-Selection Filters** — The pipeline executes filters in order:
   - **VFFilter** removes candidates with drop-worthy visibility actions.
   - **AncillaryVFFilter** applies secondary visibility adjustments.
   - **DedupConversationFilter** handles conversation deduplication.
4. **Side Effects** — Metrics, Kafka events, and cache updates execute only for surviving candidates.
5. **Response Generation** — The final `kept` collection returns to the caller.

## Summary

- **VFFilter runs immediately after** the `TopKScoreSelector` completes, serving as the first element in the `post_selection_filters` vector defined in [`home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs).
- **Source locations** include the filter implementation in [`home-mixer/filters/vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs) and the pipeline registration at lines 36-41 of [`phoenix_candidate_pipeline.rs`](https://github.com/xai-org/x-algorithm/blob/main/phoenix_candidate_pipeline.rs).
- **Partition logic** splits candidates based on the `visibility_action` field using the `should_drop_action` helper function.
- **Removal triggers** include `Drop(_)`, `Tombstone`, and `NotEvaluated` actions, while `Allow`, `Interstitial`, `Avoid`, and `Downrank` permit passage.
- **Execution order** guarantees that dropped candidates exit the pipeline before reaching `AncillaryVFFilter`, deduplication logic, or side-effect stages.

## Frequently Asked Questions

### At what exact stage does VFFilter execute relative to the ranking process?

VFFilter executes **after** ranking completes and **after** the `TopKScoreSelector` selects the final candidate set. It runs as the first filter in the post-selection phase, ensuring visibility decisions apply to the definitive ranked list before any downstream processing occurs. This positioning prevents computational waste on candidates that will never reach the user.

### Which visibility actions cause VFFilter to remove a candidate?

The `should_drop_action` function in [`vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/vf_filter.rs) returns `true` — triggering removal — for three specific actions: `Action::Drop(_)`, `Action::Tombstone`, and `Action::NotEvaluated`. Conversely, the filter permits candidates carrying `Action::Allow`, `Action::Interstitial`, `Action::Avoid`, or `Action::Downrank` to continue through the pipeline to subsequent filters and side-effect stages.

### Where is VFFilter configured within the Home-Mixer architecture?

You can find the filter registration in [`home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs) at lines 36-41, where it appears as the first element in the `post_selection_filters` vector. The actual filtering logic resides in [`home-mixer/filters/vf_filter.rs`](https://github.com/xai-org/x-algorithm/blob/main/home-mixer/filters/vf_filter.rs), which implements the `Filter<ScoredPostsQuery, PostCandidate>` trait and defines the `should_drop_action` helper.

### Can candidates removed by VFFilter be recovered later in the pipeline?

No. Once VFFilter partitions the candidate list, removed items populate the `removed` field of the `FilterResult` struct and exit the pipeline immediately. They do not proceed to `AncillaryVFFilter`, `DedupConversationFilter`, or side-effect stages such as metrics emission or Kafka logging. This hard stop ensures strict enforcement of visibility decisions without risk of downstream resurrection.