Before calling on the planner, Metnos tries to recognise the request. If it has seen it before and already knows how to handle it, it reuses the ready-made path — the sequence of executors that carries the request through — and answers in half a second instead of ten. No language model to consult: memory is enough.
The Metnos planner is a local language model — the wise tier,
the most capable one. It reasons well, but it takes about ten seconds to choose
the first step of a turn: for many requests that wait is out of all proportion.
“What time is it?” doesn't call for a model, it calls for the
get_now tool. And “download this page and sum it up in two
lines”, when we ask it often, doesn't call for the planner but for a path
we already know: get_urls, then describe_entries.
What remains is to recognise a known request — and to judge when one that is merely similar sits close enough to be handled the same way. That is the job of the introvertive fast-path, on two layers: they catch different degrees of similarity, but reach the same result, the right path run without a second thought.
The two layers differ in how much of the path they recognise. L0 recognises all of it, arguments included: the same request as before, with the same concrete values. L1 recognises the path alone — the skeleton of the solution, stripped of its arguments — and so it holds for a whole family of related requests.
| Layer | What it recognises | How | Cost per reuse |
|---|---|---|---|
| L0 | Path + arguments. The same request already solved — identical or very close in meaning — with its concrete values (that name, that URL, that date). | Exact hash (0a) + BGE-M3 cosine (0b) | < 5 ms (hash) / < 150 ms (cosine) |
| L1 | The path alone. The generalised skeleton, without arguments, valid for a group of related requests the user has confirmed. | Lookup by intent and group | ~30 ms |
The order is fixed: L0 first; if it finds nothing, L1. If both miss, the request reaches the planner (the engine), as always.
fastpath.py)
The first layer lives in runtime/engine/fastpath.py and keeps the
plans it has already run in a SQLite database (fastpaths.sqlite).
The entries appear on their own: whenever a turn succeeds — a fresh plan
from the engine, the reuse of an L1, a promotion from cosine 0b — Metnos
notes down the canonical query, its hash, the BGE-M3 embedding (the numeric form
of the text, the one used to gauge closeness in meaning), the whole plan (the
skeleton, or framework) and the intent: the verb and the object of the
request. No approval involved: the chains are built from executors already
vetted and tested.
The search runs in two phases:
/admin/praxis console.undo_last_turn and
get_inputs produce no fastpath, because what they mean depends on
the turn under way.since_iso="2026-06-11") is not recorded: replaying it on
another day would open a time window already frozen in the past. Relative dates
(time_window="today") are kept, because each replay works
them out afresh.autopath.py)
The second layer lives in runtime/engine/autopath.py and thinks
differently: it doesn't repeat the same query but generalises to a
group of related intents — the one the user has approved with a
positive rating.
At each user “✓”, Metnos notes the turn's skeleton, its hash and the group of meaning it belongs to. A few confirmations — configurable, one by default — on the same skeleton and group are enough for the plan to become a reusable autopath: next time, an intent from the same group sets it going again without troubling the planner.
L0 fastpaths age and die by fixed rules, with no model in the loop. Each night
the task_state_reaper process applies three aging rules and four
death conditions.
| Rule | Criterion | Default | Env |
|---|---|---|---|
| Never reused | Created more than N days ago but never served a second time | 14 days | METNOS_FASTPATH_GRACE_DAYS |
| Stale | Last use more than N days ago | 30 days | METNOS_FASTPATH_STALE_DAYS |
| LRU cap | Total entries above the cap; least recently used are pruned | 500 | METNOS_FASTPATH_MAX |
| Code | Cause | Inheritance |
|---|---|---|
| C1 | A tool in the plan no longer exists in the catalogue (retired, renamed, archived). Replay would fail. | No |
| C2 provenance | The fastpath was promoted to a synthetic executor (see §9) and that executor is now in the catalogue. | Yes |
| C2 name | An executor named {verb}_{object} matching the intent exists, but no tool in the plan belongs to that family. The fastpath would shadow the executor. | Yes |
| C2 pre-filter | For multi-step plans: the deterministic routing pre-filter on the canonical query shows that a single executor now covers the intent (even under a different name). | Yes |
When a fastpath dies because an executor has superseded it (the C2 conditions),
its usage counts (n_uses) pass to the heir executor, along the same
aging machinery the executors use. The demand already gathered isn't thrown
away.
Recognising the request is half the work. The other half is drawing out its
concrete values: which paths, which URLs, which date, which threshold. A
rule-based extractor (args_extractor.py) handles this, again with
no model:
https://...), path (~/... or /..., with
the “home” shortcut becoming ~/), email, numbers, file
extensions (“PDF file” → *.pdf), dates (today,
yesterday, tomorrow, the day after — in Italian and English —
turned into ISO format), and time windows (“this week”, “last
24 hours”, “last 7 days”).
The fast-path is tuned with METNOS_* environment variables. For
values meant to last there is a TOML file
(~/.config/metnos/runtime.toml); the value written in the module is
the last net, when everything else is silent.
| Variable | Default | Meaning |
|---|---|---|
METNOS_FASTPATH_STALE_DAYS | 30 | Calendar days after which an unused entry is pruned |
METNOS_FASTPATH_GRACE_DAYS | 14 | Grace days for never-reused entries |
METNOS_FASTPATH_MAX | 500 | Maximum rows (LRU cap) |
| Variable | Default | Meaning |
|---|---|---|
METNOS_AUTOPATH_MIN_OBS | 1 | Minimum positive observations to promote an autopath |
METNOS_AUTOPATH_TTL_ANTI | 2592000 (30 d) | Anti-autopath duration in seconds |
METNOS_AUTOPATH_TTL_REPEAT | 3600 (1 h) | Short window for repeated ratings |
| Variable | Default | Meaning |
|---|---|---|
METNOS_FP_PROMOTE_MIN_CLUSTER | 3 | Minimum distinct fastpaths in the group |
METNOS_FP_PROMOTE_MIN_USES | 15 | Minimum cumulative usage |
METNOS_FP_PROMOTE_MIN_AGE_DAYS | 30 | Minimum group age |
METNOS_FP_PROMOTE_MAX_PER_NIGHT | 3 | Maximum new proposals per night |
METNOS_FASTPATH_AUTOPROMOTE | off | Enables Mode 2 auto-promotion (no human approval) |
When several recurring L0 fastpaths share the same plan structure (the skeleton
hash) and the same intent, each night the task_fastpath_promotion
process weighs them as candidates to become a synthetic executor in their own
right. What it weighs is the group, never the single instance: at least three
distinct fastpaths, fifteen uses in all, and thirty days of age. And only
multi-step shapes are promoted: single-step ones already have an executor, and
there the fastpath only saves the LLM call, not the plan.
/admin/changes, the
unified view of changes (ADR 0158). On a yes, a synt_pending/
marker starts the full synthesis — the five stages, the tests, the
signing, the installation.
At promotion time Metnos notes, in a table (promotions), which
fastpaths the new executor was born from: it records their identifiers and
fingerprints. This is what closes the loop. Once the executor is in the
catalogue, the fastpaths that produced it are no longer needed and get retired
(the “C2 provenance” death condition from §6). Thanks to that
note the retirement is exact: Metnos knows for certain which
fastpaths to remove, because the link between each fastpath and its heir is
written down, not guessed from the name.
© 2026 Roberto Brunialti · Metnos documentation