When and How VFFilter Applies Visibility Filtering Decisions After the Ranking Process
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, the system executes filters in a strict sequence:
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/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 and uses Rust's Iterator::partition method:
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/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:
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/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:
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:
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/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:
- Hydration, Scoring, and Ranking — Phoenix, Ranking, and VMRanker components generate and score candidates.
- Top-K Selection —
TopKScoreSelectorselects the highest-scored candidates. - 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.
- Side Effects — Metrics, Kafka events, and cache updates execute only for surviving candidates.
- Response Generation — The final
keptcollection returns to the caller.
Summary
- VFFilter runs immediately after the
TopKScoreSelectorcompletes, serving as the first element in thepost_selection_filtersvector defined inhome-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs. - Source locations include the filter implementation in
home-mixer/filters/vf_filter.rsand the pipeline registration at lines 36-41 ofphoenix_candidate_pipeline.rs. - Partition logic splits candidates based on the
visibility_actionfield using theshould_drop_actionhelper function. - Removal triggers include
Drop(_),Tombstone, andNotEvaluatedactions, whileAllow,Interstitial,Avoid, andDownrankpermit 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 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 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, 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →