Skip to main content

window

Function window 

Source
pub fn window<T>(items: &mut Vec<T>, offset: usize, limit: usize)
Expand description

Cut an already-ordered, already-materialised list down to the window a caller asked for: skip offset items from the front, then keep at most limit.

This is the one place that decides what limit and offset mean for the graph’s list lenses — debt_density, config_secrets and coupling here, and the /nodes and /hotspots endpoints in the roteiro binary. It exists because the parameter previously had two implementations: three lenses truncated here and treated 0 as “no limit”, while two HTTP handlers used Iterator::take and so returned nothing for 0 — the same parameter name with opposite meanings, and nothing that could make the disagreement visible (issue #375). A sixth list lens should call this rather than write a third. One did write a third — see Episodic recall below — and the warning is left standing because the next one will too.

The contract:

  • limit == 0 means unlimited — every item that survives offset is kept. This is the reading the CLI already documents (roteiro config-secrets --help: “0 shows every secret-named key”), so no published promise is withdrawn by making it universal; and it is the safer of the two, because a caller who passes an unset variable then gets more data than they meant to ask for rather than an empty page that reads like a truthful “nothing found”.
  • offset applies first, and limit to what remains. So offset = 20, limit = 0 is “skip the first 20, then every remaining item”, not “skip 20, then nothing”. An offset past the end yields an empty window rather than panicking — a page beyond the last one is empty, not an error.
  • A caller’s reported total must be taken before this runs: every surface reports the pre-windowing population, so a cut page still says what it was cut from.

Ordering is the caller’s job; this only removes, and only from the ends, so a deterministically-ordered input yields a deterministic window.

§The search channels call this too, and the unit there is per channel

search and the generated/memory channels behind search_channels used to keep a limit == 0 => no hits guard of their own — a third reading of one parameter name, in the same crate as the two #375 reconciled (issue #393). They now window here like every other lens, so limit has one definition and not a second implementation of it, which is exactly how the first two drifted.

What differs is the unit, not the rule: a search limit bounds each channel independently, so 0 is “every match, in every channel that was asked for”, not “every match overall”. That is a bounded request rather than “dump the graph”: a channel’s ranking only orders a set the query has already filtered — every token must appear in a hit — and a query with no tokens returns nothing at any limit, 0 included. Measured on this repository at 6,685 nodes: an unbounded one-token search returned ~2.7k hits in 0.24 s and a two-token one returned 3, against a full-population scan that every limit pays anyway, so unlimited costs no more than the default does.

The MCP tools are the one surface that cannot ask for it, deliberately: they clamp limit into 1..=25 and advertise "minimum": 1, because their results are spent against a model’s context window and 0 would be the one value that escaped the ceiling those clamps exist to impose. That is a surface declining to offer a value, not a second meaning for it — a model that sends 0 anyway gets the smallest page, never the silent empty answer this issue is about. The reasoning is restated where each clamp lives, in rto_render::mcp::GraphServer::search and the served-chat search arm in the roteiro binary; if this rule changes, those two must change with it.

§Episodic recall — the third implementation this doc predicted (issue #447)

crate::Store::recall_memory ranked, then called Vec::truncate directly, so limit = 0 emptied the result: recall --limit 0 returned nothing on a store where five other surfaces returned everything, and the JSON said "live": 8000 beside "results": [] — not a lie, and no help at all. It calls this now. Two details are worth keeping:

  • None and Some(0) had to collapse onto one meaning, not two. RecallOptions::limit is an Option, so “unlimited” was already sayable twice; the fix maps None to 0 rather than adding a branch, because two spellings of one request are how the first divergence started.
  • memory list cuts in SQL and so cannot call this. LIMIT 0 in SQL means the opposite of what this function means, so memory::records omits the clause entirely for 0. That is the contract translated, and it is the only place in the crate where the rule is re-expressed rather than called — worth knowing if it ever has to change.