Three Meetings to Change a Filter: London, Vienna, and the Rewrite That Landed
By moqtap team
Three Meetings to Change a Filter
The agenda for the 10 August interim contains a short list that tells you more about MoQT’s next six months than any draft diff. It is the in-flight PR queue for draft-20, in priority order:
- Location Filter rewrite #1809
- PUBLISH_STATE_NOTIFY #1820 — the agenda called it PUBLISH_NOTIFY
- Fill Fetch #1673
- SWITCH_FROM #1674 — flagged “hard”
Items 3 and 4 needed updating once the first two merged, so one PR gated the whole queue.
Three days after that interim, on 13 August, it merged. So did #1820, twenty-five minutes behind it. MoQT’s filter-type enum — Largest Object, Next Group Start, AbsoluteStart, AbsoluteRange — no longer exists in the editor’s copy. Four wire-visible filter shapes are now one length-prefixed parameter whose field count carries the meaning. It is the largest change to MoQT’s subscription grammar since draft-15 moved the filter out of the fixed SUBSCRIBE fields, and it landed with no announcement and no publication: draft-19, cut on 6 July, is still the current revision and still specifies the enum.
If you only read published drafts you would conclude nothing is happening to MoQT’s subscription model. The opposite is true — it has already been rewritten, and the argument that produced it has been running since June. This post follows it through the three meetings where it actually happened, then reads what came out the other end.
If you want the mechanics of subscriptions as they stand today, we covered them in How MoQT Subscriptions Work. This is the story of what replaces them.
London, 11–12 June: where the PR was commissioned
The working group met in person at County Hall in London for two days (interim-2026-moq-08, -10, -11). Filters took most of the first morning.
Mo Zanaty presented the adopted Object Range Filters design from PR #1518: a max_filter_ranges setup option defaulting to zero, so the whole feature is opt-in, and four filter types — Object ID, Subgroup, Priority, and Property. The interesting part is the set concept: filters within a set are ANDed, different sets are ORed, which lets a relay aggregate the filters of many downstream subscribers into one upstream subscription. That is the mechanism that makes filtering survive a relay tree instead of collapsing it.
Three things happened that morning that still shape the current PRs.
The Object ID filter nearly died. Mo proposed removing it, on the grounds that keyframe scrubbing by object ID 0 is not guaranteed once temporal scalability is in play. Victor Vasiliev pushed back: objects are the atomic unit of transmission, so filtering at the object ID level costs no more than filtering by property. Martin Duke, speaking as an individual, noted that using subgroups for keyframes conflicts with expressing dependencies. The filter stayed.
The Location Filter was commissioned. It had been in PR #1401 and was dropped from #1518. Mo argued for reinstating it specifically to replace the growing enum of subscription filter types. Cullen Jennings objected that SUBSCRIBE has historically ended on group boundaries for stream-termination reasons, and a location filter flattens that. Ian Swett’s answer was that the stream termination problem was already solved — we have FIN now, and the old reasons no longer apply. Victor supported the direction but wanted it in its own PR, with the interaction against Joining Fetch and standalone Fetch worked through properly.
The room agreed. The action item reads: Mo Zanaty — create location filter PR separate from #1518.
That PR is #1809. It merged nine weeks later to the day. Everything in the August queue descends from a decision made in a room in London on 11 June.
The Joining Fetch verdict
The second thing London settled was the fate of Joining Fetch, and it settled it with data.
Alan Frindell and Mo Zanaty brought a survey. The result is worth quoting precisely, because it is easy to misreport in either direction: nobody said Joining Fetch had failed functionally, but 8 of 12 respondents said they did not like it or that it was not working for them. The majority wanted more time. And the constraint the group accepted was strict — if it changes, Joining Fetch gets ripped out entirely rather than left in place beside a parallel mechanism.
What replaces it is fill fetch: a fetch-formatted unidirectional stream that the publisher opens in response to a subscribe or request-update covering a past range. One subscription, live objects on the subscribe path, backfill on a fill stream, no second request and no retransmitted track name.
The support was broad but not uniform. Suhas Nandakumar backed removing both fetch and joining fetch, arguing that standalone fetch has holes and inefficiencies of its own. Gwendal Simon worked the relay flow chart end to end and reported it holds. Will Law made the case for keeping standalone fetch as a fallback for other backfill needs — Joining Fetch, in their framing, was always a pipeline optimization rather than a primitive. Ali Begen dissented on priorities rather than design: it does not solve use cases that are not already solved, and finishing the existing draft matters more.
The group also listed what fill fetch loses against Joining Fetch, which is the kind of honesty that makes a design reviewable: you cannot set a different numeric subscriber priority on the subscribe than on the fetch, you cannot do “outside-in” group ordering, and there is no separate control message for fill-specific errors.
PR #1642 carried the original design. It has since been superseded by #1673, which defines the fill-stream semantics, removes the Joining variant of FETCH, and introduces FILL_PARAMETERS so the fill side can carry its own SUBSCRIBER_PRIORITY and GROUP_ORDER — a direct answer to the first item on that losses list.
That PR is also a small case study in how fast this area is moving. Its description still advertises renaming the filter types to AbsoluteStart / RangeFill and adding a RelativeStartFill. Its actual diff, updated on 20 August, contains none of those names. They stopped being necessary when the filter types themselves were deleted out from under it.
Cullen Jennings raised the objection that has not gone away: bursting the first half of the current group looks like a file transfer to QUIC’s congestion controller, and subgroup priority reordering can misorder objects in a way that delays the playout buffer. No formal consensus was reached on current-group delivery. It went back to focused conversations.
What the ABR measurements actually showed
The most useful hour in London was not a design discussion. Ali Begen and a collaborator presented experimental results comparing three ways for a subscriber to switch tracks — which is to say, three ways to do adaptive bitrate over MoQT.
| Method | Control messages | Downstream delay | Notes |
|---|---|---|---|
Toggle forward on two subscriptions | 3 | 1 RTT, ~0.5 s observed | Risks gaps or duplicate data |
| Joining Fetch | 3–5 | 1–2 RTT | Skipped a group in testing |
| Atomic switch message | 1 | 1 RTT | No gap, no redundant traffic |
With bandwidth unconstrained, the atomic switch message won on every axis: least delay, zero redundant traffic, one round trip.
Then they constrained it — a 3 Mbps pipe, downshifting from a 4 Mbps track — and all three methods degraded badly, with forward-toggling landing in the 5–7 second range. The switch message still won, but it did not rescue the scenario. Will Law’s summary is the line to remember: it is a physics problem, and you can only put 3 Mbps into a 3 Mbps pipe regardless of QUIC trickery.
This is the honest state of ABR over MoQT. The protocol work makes the switch cheap and clean. It does not make the downshift free, and any vendor promising otherwise is selling the round trip you saved while ignoring the buffer you still have to drain.
The relay-executed variant — Dynamic Track Switching — was the other half of the session. It uses active send-stream depth as a congestion signal rather than measuring bandwidth: upshift after N consecutive clean groups, downshift when depth grows, discard when the latency budget blows. Reported as simple but effective, and it tracked latency budgets reliably. Cullen Jennings flagged the implementation variance that makes results hard to compare — whether a relay queues data into the QUIC stack immediately, which makes removal hard, or holds it until the congestion window opens, which allows reprioritization. The draft deliberately does not mandate either.
Out of all this, SWITCH_FROM got its scope: a parameter on Subscribe or Request Update referencing the request ID of the track being switched from, with hard mode only. Hard mode sets forward=0 on the suspended subscription immediately. Soft mode — draining the old subscription to the end of the group before the new one starts — was deferred for edge-case reasoning, with Gwendal Simon and Cullen Jennings both interested in an intermediate mode that finishes the current group and stops. Its key property is that there is no race: both subscriptions must already exist before the switch references them. And it depends on fill fetch landing first, which is exactly why it sits at position 4 in the August queue.
Top Tracks: the extension that wants to be core
Running alongside the filter work is the Top Tracks Filter — renamed from “Track Filter” in London to reduce confusion, now PR #1830. It lets a subscriber ask for the N tracks in a namespace with the highest value of some property, with tracks moving through an Unknown → Newly Selected → Selected → Deselected → Evicted state machine.
The use cases presented go well past video: active speaker in a conference, security cameras ranked by activity, esports leaderboards by score, viral feeds by upvotes, top bids in market data — and, yes, highest quality within a bandwidth budget.
Cullen Jennings brought performance data, which is rarer than it should be in protocol arguments: under 2% CPU on the MonkX and MonkRS relays, under 300 bytes of extra state per subscriber, and 8,000 publishers across eight cores. Memory overhead was described as insignificant next to the QUIC stack itself. On DDoS, the design is asymmetric on purpose — high ingress, low egress — and relays keep the authority to cap namespace size and reject conflicting filters.
The objections are about architecture rather than cost. Ian Swett noted that top-N puts the relay in control of forward state, which has traditionally belonged to the subscriber; Will Law’s answer was that subscriber opt-in creates mutual understanding between the parties. Alan Frindell called it a sound feature that people will use, while holding out for a second independent implementation before standardization. Cullen argued the use case predates the working group charter and is critical for media vendors moving off SFUs onto MoQ relays.
On #1830 itself, Victor Vasiliev has raised the sharpest design objection: the mechanism does not let a relay pre-compute top-N at publication time without subscriber input, which is a scaling problem in the small-number-of-publishers-to-millions-of-subscribers shape that MoQ is supposed to be good at. That is unresolved.
Vienna: where it turned procedural
At IETF 126 the technical argument became a packaging argument, and then a process argument. We covered this in the IETF 126 recap: Cullen Jennings formally objected to separating the top-end filter and Sender-Side Track Switching into distinct drafts, citing a roughly even split in the room. Mike Bishop, as Area Director, clarified that chairs determine consensus and that dissenters may appeal through formal channels. A virtual interim was scheduled specifically to settle SSTS packaging.
It is worth being clear about what was and was not decided. The room did not resolve whether Top N Tracks and SSTS are two mechanisms or one — that question, raised by Ian Swett on the list in the “Top Tracks and SSTS (or ABR in general)” thread on 27 July, is still open. What Vienna established is who gets to decide and how, which is not nothing when a design has been running for three meetings.
What landed on 13 August
Back to that queue. Three days after the interim reviewed it, two of the four merged, both applied by Alan Frindell:
| PR | What it does | State |
|---|---|---|
| #1809 | Deletes the filter-type enum; unifies subscribe, fetch and fill under one length-prefixed Location Filter | Merged 13 August, 18:36 UTC. Opened 9 July, 25 commits |
| #1820 | Adds PUBLISH_STATE_NOTIFY (0x22), a unilateral publisher-to-subscriber notice that subscription state changed for a reason the subscriber did not request | Merged 13 August, 19:01 UTC |
| #1673 | Replaces Joining FETCH with fill fetch streams; supersedes #1642 | Open, diff updated 20 August. Now the head of the queue |
| #1674 | SWITCH_FROM, hard mode | Open, flagged “hard”; untouched since 10 August |
The filter that replaced four filters
This is what a Location Filter is now, in the editor’s copy:
LOCATION_FILTER Parameter { Parameter Type (vi64) = 0x21, Length (vi64), [StartGroup (vi64),] [StartObject (vi64),] [EndGroupDelta (vi64),] [EndObject (vi64),]}There is no filter type. Length determines how many of the four optional varints are present, and how many are present is the semantics:
- No fields at all — no filter. In
REQUEST_UPDATE, a zero length is how you remove one. StartGroupalone — a relative count backwards from the next group.0starts at the next group,1at the current group,Nat N−1 groups back.StartGroupandStartObject, both zero — start at the next object,{Largest Object.Group, Largest Object.Object + 1}.- Three or four fields — absolute, with
EndGroupDeltadelta-encoded fromStartGroup. OmitEndObjectand the filter takes the whole end group; omit both end fields on a subscription and it is open-ended.
Set that against draft-19, which is still the published revision and still carries Filter Type as a fixed field with four enumerated values. Largest Object (0x2) is now StartGroup and StartObject both zero. Next Group Start (0x1) is StartGroup alone, set to zero. AbsoluteStart (0x3) and AbsoluteRange (0x4) are the three- and four-field forms. Four wire-visible message shapes collapsed into one parameter whose length carries the discriminator.
That is what the PR title means by matching “the design of other filters.” The Range Filters that arrived in draft-19 — SUBGROUP_FILTER, OBJECTID_FILTER, PRIORITY_FILTER and the two property filters — already used length-prefixed optional fields. The Location Filter was the last one holding an enum, and now it is not.
The August interim spent time on the objection you would expect: an all-zero encoding meaning “the live edge” is not intuitive to read on the wire. Martin Duke and Cullen Jennings both landed on the view that avoiding separate filter types — and the overlapping error cases that come with them — justifies the compressed form. That is Cullen accepting the shape of a design he had objected to in London on entirely different grounds, which is what a working group looks like when it is working.
It merged over a standing objection
Worth recording precisely, because it is unusual enough to matter: #1809 merged with a CHANGES_REQUESTED review still open against it. Cullen Jennings filed it on 10 August, the day of the interim. It was never dismissed and never converted to an approval.
The substance is narrower than the review state makes it sound — it is a naming objection: “Start seems like a bad name. I think it more a count of Prior objects or groups that you add to current to get the StartGroup.” Read the merged text above and the point is hard to argue with. In the one-field form StartGroup is not a start at all; it is a count backwards. The name shipped anyway.
Victor Vasiliev approved at 18:29 UTC on 13 August. The merge came six minutes later.
The decisions that came with it
Four resolutions from the 10 August minutes shape how the filter behaves, and none of them show up in a diff:
- A relative start before anything has been published resolves to unfiltered. If a subscriber asks for N groups before the largest object and nothing has been published yet, the publisher treats the subscription as unfiltered rather than deferring resolution or erroring.
INVALID_RANGEis on the way out. All ranges are valid now; out-of-bounds requests clamp rather than fail. This one has not landed — it was agreed in the fill-fetch discussion, and #1673 is the PR that deletes the remaining uses. Until that merges,INVALID_RANGEis still sitting at error code 0x11 in the editor’s copy, and afterwards it applies to FETCH only.- A subscription whose range is entirely in the past stays open, to handle out-of-order publication and later
REQUEST_UPDATEs. - Changing subscriber priority does not retroactively reprioritize in-flight fill streams. It applies to the live subscription only.
And the interop target moved
The same minutes revise the release plan the whole queue was serving:
Draft 20 will be cut immediately prior to the editorial meeting and will consist of purely editorial changes (including alphabetical reordering of messages, as requested by Will Law). Draft 21 will capture the output of the editorial meeting. Draft 22 will be published as the next official interop target.
At Vienna the stated goal was “moving implementations to draft-20 for the Seattle meeting.” We reported that in the IETF 126 recap and again in MoQ News #1, and it was accurate when it was said. It is no longer the plan. Draft-20 is now a cut taken immediately before an in-person editors’ meeting — three to four weeks out from 10 August, so early September — carrying editorial work, including an alphabetical reordering of the message list. Draft-21 captures whatever that meeting produces. Draft-22 is the interop target, and no date has been attached to it.
Seattle is unchanged: five sessions across 14–15 October, on the calendar as interim-2026-moq-25 through -29. What changed is which revision anyone is expected to arrive running.
Still open
Three filter-adjacent issues were tabled at the August interim, all of which could still move the design: #1453 on a Send Rate parameter; #1352, asking whether SUBSCRIBE still needs a forward parameter once filters exist — Suhas Nandakumar’s demonstration is PR #1825, built on the location-filters branch, which pauses a subscription by declaring a start range greater than its end; and #1801, filed by Victor Vasiliev against draft-19, reconsidering the “OR” semantics in range filters.
That last one matters more than its issue number suggests. The OR-across-sets behaviour is what makes relay-side filter aggregation work. Reopening it after the Location Filter has already merged means the aggregation model, not the filter grammar, is now the unsettled half of this story.
What this means for implementers
The filter-type enum is gone — but only in the editor’s copy. If your codebase has a FilterType switch statement, it is correct against draft-19 and dead against main. That gap is now the most important thing to know about MoQT subscription code: the published revision and the working revision disagree about the wire format of every subscription, and they will keep disagreeing until draft-20 is cut. Expect four filter types to become four call sites that assemble one length-prefixed parameter.
If you implemented Joining Fetch, plan for its removal. Not deprecation — removal. The working group’s own constraint was that it gets ripped out entirely rather than coexisting with fill fetch. The functionality survives; the message does not. With #1809 and #1820 out of the way, #1673 is the head of the queue, so this is the next thing to land.
If you are doing client-side ABR, the switch is about to get cheaper and your buffer math will not change. One control message instead of three, one RTT instead of one to two, no skipped group. That is a real improvement worth waiting for — SWITCH_FROM is still open and still last in the queue, behind fill fetch. It is not a bandwidth solution, and the 3 Mbps measurements say so plainly.
If you run relays, watch #1801 and #1830. Filter aggregation across a relay tree and relay-side top-N are the two features that decide how much work a relay does on behalf of its subscribers, and both are unsettled.
Plan against 14 October, but stop planning against draft-20. The date still holds: the Seattle interim is 14–15 October, and IETF 127 is San Francisco in November, a month later — a distinction we got wrong ourselves in the IETF 126 recap and have since corrected. If you are scheduling implementation work off the plenary calendar, you are a month behind. What changed is the target. Draft-20 is an editorial cut, draft-21 is the editors’ output, and draft-22 is the revision the working group now calls its interop target — with no date attached to it. The next thing worth watching is the in-person editors’ meeting in early September, because draft-20 gets cut immediately before it.
We track this every two weeks in MoQ News. The next issue follows the 24 August interim — whose agenda, as of this writing, is still the placeholder it has been since 16 July.