ADR-0004: Keyword and global routing, and how results are ranked¶
Status: Accepted
Context¶
One input box has to serve very different intents: launch an app, do a sum, search the web, find a file. Running every plugin for every keystroke gives noisy results and wastes time; making the user pick a mode first is slow.
Decision¶
The engine (crates/sevak-core/src/engine.rs, described at the top of the file
and in How Sevak works)
routes in three steps:
- Keyword route.
<keyword> <text>goes only to the plugin or plugins that own the keyword, with no fallback. Symbol keywords such as>need no space. - Global route. Otherwise every plugin that answers un-keyworded input is queried. Results from a plugin that has its own keyword but also answers globally (files) are down-weighted so dedicated sources (apps) stay on top.
- Fallback. If nothing answered, the configured fallback (web search by default) receives the whole input.
Ranking is a fuzzy score from nucleo-matcher, fixed bands for answers that
are not fuzzy matches (EXACT_ANSWER, KEYWORD, FALLBACK in model.rs),
and a usage boost that halves every 72 hours. The boost can reorder comparable
matches but cannot lift a weak match over a strong one. Ties break by title
and then id, so the order is deterministic.
A keyword that two plugins answer is allowed, not rejected; Sevak logs it once
(KeywordOwners::log_shared) and the workflow builder reports clashes.
Consequences¶
- Predictable: the same input and the same usage give the same order.
- Per-keystroke cost stays bounded because a keyword route touches few plugins; a query slower than 16 ms is logged with the slowest plugin.
- Learning from usage means
usage.jsonis a small behavioural record. Plugins whose results contain private text opt out (Plugin::tracks_usage), and the search history is a setting. - A script plugin or workflow can claim a keyword that a built-in plugin also answers. Both then answer; nothing is silently replaced.