Fallback Pipeline Plugin Specification
August 13, 2026 · View on GitHub
Spec ID: OVOS-FALLBACK-1 · Version: 2 · Status: Draft
This specification defines the fallback pipeline plugin — a pipeline plugin that handles utterances no earlier stage claimed. It maintains a registry of fallback skills, constructs an ordered handler pool from that registry and the session's preferences, queries skills in pool order to find a willing handler, and dispatches to the first one that claims the utterance.
It builds on four companion specifications:
- the Utterance Lifecycle and Pipeline Specification
(OVOS-PIPELINE-1) — the pipeline-plugin contract, the
Matchshape, dispatch, the handler-lifecycle trio, the pipeline composition model, and the reserved-intent-name registry; - the Bus Message Specification (OVOS-MSG-1) — the envelope, routing keys, session carrier, and derivations every Message defined here travels in;
- the Session Specification (OVOS-SESSION-1) — the session field registry and the omission rule;
- the Intent and Entity Registration Bus Contract (OVOS-INTENT-4) — the session-scoped registration model.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, MAY, and RECOMMENDED are used as in RFC 2119.
1. Scope
This specification defines:
- the fallback plugin role (§2) — a pipeline plugin that delegates to registered fallback skills;
- skill registration (§3) — how a skill declares itself as a fallback handler with a default ordering priority;
- session fields (§4) — the one session-resident field that controls pool ordering, and the reuse of the OVOS-PIPELINE-1 denylists for access control;
- pool construction (§5) — how the ordered handler pool is derived from registration, session preference, and policy;
- the match contract (§6) — the sequential per-skill query, selection algorithm, and Match shape;
- the dispatch and handler contract (§7) — what the selected skill receives;
- pipeline positioning (§8) — where fallback stages sit and how multiple stages interleave with other plugins;
- bus surface (§9);
- conformance (§10).
It does not define:
- what a fallback skill does internally — whether it queries a language model, returns a canned response, or calls an external service is the skill's business;
- per-stage range boundaries — which priority numbers belong to which stage is deployment configuration; §3.3 provides non-normative guidance on the recommended tiers;
- query timeout values — these are deployment-tunable; §6.1 fixes only a recommended default ceiling.
2. The fallback plugin role
A fallback pipeline plugin is a pipeline plugin (PIPELINE-1 §3) that:
- occupies one or more positions in
session.pipeline(§8); - maintains a registry of skills that have declared themselves fallback handlers, each with a default ordering priority (§3);
- at each match call, constructs an ordered handler pool from the registry, session preference, and policy (§5);
- queries pool members in order until a willing skill is found (§6);
- returns a
Matchdelegating to that skill, orNoneif the pool is exhausted (§6).
The fallback plugin does not handle utterances itself.
The defining property of a fallback skill is that it evaluates the utterance using its own internal logic rather than relying on an intent model registered with the pipeline. Regular skills declare their coverage through INTENT-4 registrations — vocabulary, templates, or entity patterns — and a pipeline plugin selects them by matching those patterns against the utterance. A fallback skill declares no intent patterns; instead it receives the raw utterance at query time (§6.1) and decides for itself whether it can respond. This is the appropriate pattern when an utterance domain cannot be reliably modelled as a grammar or template: open-domain question answering, natural-language database queries, language-model completions, and any skill whose coverage is determined programmatically at runtime.
This mechanism is analogous to OVOS-CONVERSE-1's per-skill converse poll, where active handlers evaluate the current utterance themselves before the plugin dispatches to one of them.
3. Skill registration
3.1 Register
A skill that wishes to receive fallback dispatches emits:
ovos.fallback.register
| Field | Type | Required | Meaning |
|---|---|---|---|
skill_id | string | yes | The skill's identity. MUST equal context.skill_id of this Message. |
priority | integer | yes | Default ordering hint. Lower values sort earlier when no session preference overrides. |
The plugin adds the skill to its registry. Re-registration with
the same skill_id replaces the prior entry. The plugin MUST NOT
index a registration where the payload skill_id differs from
context.skill_id.
3.2 Deregister
ovos.fallback.deregister
| Field | Type | Required | Meaning |
|---|---|---|---|
skill_id | string | yes | The skill's identity. MUST equal context.skill_id of this Message. |
Removes the skill from the registry. Unknown skill_id is a no-op.
Deregistration is scoped exactly like registration. The plugin
MUST key the removal by context.session.session_id of the
deregistration Message — never by a session_id carried in
Message.data — per OVOS-INTENT-4 §11.3. A deregistration
arriving under the default session removes the "default"-scoped
entry only; it does not remove entries registered under a specific
session_id.
The plugin MUST NOT honour a deregistration whose payload
skill_id differs from context.skill_id: without this check any
skill can evict any other skill from the registry. A mismatch is
malformed — log at WARN with both identifiers and the topic, and do
not act on it.
3.3 Priority guidance
Priority values have no normative meaning beyond ordering — lower values appear earlier in the pool. This section provides non-normative guidance for skill authors and deployers.
The recommended convention divides the space into three tiers:
| Range | Tier | Typical skills |
|---|---|---|
| 0–49 | High confidence | Skills that search structured, bounded knowledge sources — local databases, domain-specific knowledge bases, FAQ indexes, entity lookups. These know quickly whether they have a relevant answer and are expected to be accurate when they claim the utterance. |
| 50–74 | Medium confidence | Skills that perform broader retrieval — web search, general knowledge queries — where answer quality depends on the query. They should handle the utterance if the topic is in scope but may decline when it is not. |
| 75–100 | Low confidence | General-purpose language-model chatbots and catch-all handlers that will attempt any utterance without domain restriction. These run last; they provide a response when nothing else can, not as a first choice. |
This three-tier mapping corresponds directly to the
fallback_high / fallback_medium / fallback_low multi-stage
pipeline example in §8.2. A deployment using a different band
convention maps registrations onto these tiers by band
membership (which confidence class a number belongs to), not by
numeric rescale — the numbers label the classes; they are not a
continuous scale.
A skill author uncertain which tier applies is better off registering at a higher number than a lower one. Pre-empting a more precise handler with a low-confidence catch-all degrades response quality silently.
The catch-all handler. The low-confidence tier is where a
deployment's catch-all handler belongs — the skill that answers
"I don't know how to answer that" when nothing else will. The
normative rule for that skill is §8.1; this section only says where
in the number space it sits. Note that a large priority number does
not by itself make a skill last: priority is an unbounded integer,
so any number can be undercut by a larger one. A catch-all is last
because of placement — it is the last entry of
session.fallback_handlers, or the only registration inside the
last stage's priority range (§8.2) — not because of the number it
chose.
3.4 Session-scoped registration
Registration is session-scoped per OVOS-INTENT-4 §11.1: the
plugin keys each entry by context.session.session_id of the
registration Message. Skills registered under "default" are
available to all sessions, because every session inherits the
"default" scope (OVOS-INTENT-4 §11.2). Skills registered
under a specific session_id extend the pool for that session
only.
4. Session fields
This specification claims one optional session field per OVOS-SESSION-1 §2.2.
| Field | Wire type | Owner |
|---|---|---|
fallback_handlers | array of string | §4 (this spec) |
session.fallback_handlers is an ordered list of skill_ids
expressing a session-level preference for the fallback handler
order. When present, this list is the primary ordering input to
pool construction (§5). When absent, registered priority
determines order.
The list MAY be partial. Skills not listed but registered and available are appended after the listed skills, sorted by registered priority ascending.
Per-skill access control uses the existing
session.blacklisted_skills field (OVOS-PIPELINE-1 §5.3) — no
separate fallback-specific denylist is needed. To block all
fallback handling for a session, add the fallback stage(s) to
session.blacklisted_pipelines, or omit them from
session.pipeline.
5. Pool construction
On each match call the plugin constructs the effective handler
pool:
-
Preference. If
session.fallback_handlersis present and non-empty, use it as the leading order. Append any registered skills not in the list, sorted by registered priority ascending. If absent, sort all registered skills by registered priority ascending. Two skills registered at the same priority MUST be ordered deterministically — the same registry and the same session always produce the same pool order. Ascendingskill_idis the RECOMMENDED tie-break. Registration arrival order is not an acceptable tie-break: it makes the selected handler depend on process start order. -
Stage range. If this plugin instance is configured with a priority range (§8.2), retain only skills whose registered priority falls within that range. Both bounds are inclusive: a range
[50, 74]retains priority 50 and priority 74.Ranges across the loaded stages SHOULD partition the priority space: a skill in no range is never queried by any stage.
The range filter runs after step 1's preference order. A skill named in
session.fallback_handlersis still dropped by a stage whose range excludes it — the preference list orders the pool, it does not admit a skill into a stage. -
Availability. Retain only skills present in the registry for the current
session_id(including"default"registrations per §3.4). -
Policy. Remove any
skill_idpresent insession.blacklisted_skills(OVOS-PIPELINE-1 §5.3).
The result is the ordered effective pool. An empty pool causes the
plugin to return None immediately. No later stage adds what an
earlier stage removed.
6. Match contract
6.1 The willingness contest
When the effective pool is non-empty the plugin broadcasts one ping for the round:
ovos.fallback.ping
derived from the inbound utterance Message via reply
(OVOS-MSG-1 §5.2). Every registered fallback skill evaluates
in parallel and answers with reply of the ping — claim or
explicit decline. One Message asks the whole pool; the round's
latency is one collection window, not a sum of per-skill waits.
Payload:
| Field | Type | Required | Meaning |
|---|---|---|---|
utterances | array of string | yes | The candidate utterance list the skill should evaluate. |
lang | string | yes | The resolved BCP-47 language tag. |
The skill uses these to run its own evaluation logic and decide whether it can produce a meaningful response. This is the point at which the fallback skill parses the utterance — it may query a knowledge base, run a classifier, call an LLM, or apply any other internal logic. The reply carries only the decision.
Each skill replies with:
ovos.fallback.pong
derived from the ping through the reply derivation (OVOS-MSG-1
§5.2), so that the pong reaches the plugin wherever the skill
runs. A skill SHOULD answer even when declining
(can_handle: false): explicit declines are what let the window
close early instead of waiting out the ceiling.
| Field | Type | Required | Meaning |
|---|---|---|---|
skill_id | string | yes | The responding skill's identity. |
can_handle | bool | yes | Whether this skill is willing to handle the current utterance. |
utterance | string | yes | Echo of the utterance evaluated — the first element of the ping's utterances. |
Round correlation. The round is the lifecycle: the plugin keys
poll state by session_id from context.session and by
context.utterance_id (OVOS-PIPELINE-1 §9.1.1), which the ping carries by
reply derivation and the pong carries back the same way — no
skill-side action, no field of this specification's own. A pong
whose utterance_id does not equal the round's MUST be
discarded: an answer that cannot prove which question it answers
never decides a round. This is the same correlation rule every poll
protocol uses (OVOS-COMMON-QUERY-1 §6.4).
The utterance field remains REQUIRED as the evaluated candidate —
it tells the skill what it judged (the first element of
utterances, §6.1), while utterance_id tells the plugin which
round the judgment belongs to.
The boolean's field name is protocol-specific: this spec and
OVOS-STOP-1 use can_handle, OVOS-CONVERSE-1's poll uses result,
and OVOS-COMMON-QUERY-1 uses can_answer. Each name is normative
only within its own protocol.
The plugin collects pongs inside one bounded window per round.
Without a bound, one unresponsive skill stalls the entire fallback
stage — and with it the utterance — forever. A skill silent at
window close, or whose pong is malformed (missing or non-boolean
can_handle, mismatched utterance_id), MUST be treated as
can_handle: false; this is uniform with the silence rules of
OVOS-CONVERSE-1 (§4.2 there) and OVOS-STOP-1 (§4.2 there).
The window SHOULD close early when a decision is already forced, and selection order makes two early closes safe:
- every skill in the effective pool has answered — nothing more can arrive that the round would wait for;
- the highest-priority skill still unanswered ranks below the best claimant so far — no later pong can outrank the claim already held.
The poll is the decision. Unlike OVOS-COMMON-QUERY-1, whose ping only filters plausible answerers before a separate answer round, the fallback ping is the whole contest: the pong is the claim, and there is no second round in which a slow evaluator can catch up. A skill that cannot answer the ping in time is not delayed, it is skipped. This follows OVOS-CONVERSE-1 §4.2, where the poll is likewise the decision point.
Ceiling calibration. The RECOMMENDED default ceiling is 0.5 s, matching OVOS-CONVERSE-1 §4.2 and OVOS-STOP-1 §4.1. That default suits a local evaluation — a lookup, a classifier, a vocabulary test. It is not a budget for a model-backed skill, and a deployment running one MUST raise that stage's ceiling above the stage's real evaluation latency. Left at the default, such a skill is silently a non-responder.
Stage collection ceiling. A sequential poll costs up to
pool_size × ceiling; the broadcast form costs one window whatever
the pool size. Either way that total is the stage's collection
ceiling, and a deployment MUST set the OVOS-PIPELINE-1 §4.4
match bound for this stage at or above it. A shorter bound kills the
stage mid-poll on every utterance, silently.
Selection is pool-ordered, never arrival-ordered. Pongs arrive in whatever order skills answer; the plugin selects the first willing skill in pool order (§6.2). A fast low-priority claim never beats a slow high-priority one inside the window.
Bus-exchange exception. The willingness contest is a documented exception to PIPELINE-1 §4.4's low-latency guidance, justified because fallback stage(s) are positioned after all other intent-matching stages (§8): no further stages are blocked during the window, and the early-close rules (§6.1) end it as soon as the outcome is decided. OVOS-CONVERSE-1 §4.2's contest is the same pattern.
6.2 Selection
The plugin selects the first skill in pool order whose
can_handle reply is true. If the pool is exhausted with no
willing skill the plugin returns None, and the pipeline emits
ovos.intent.unmatched (PIPELINE-1 §9.3).
6.3 Match shape
| Field | Value |
|---|---|
skill_id | The selected skill's skill_id. |
intent_name | "fallback" — reserved per PIPELINE-1 §7.3. |
lang | The resolved BCP-47 language tag. |
utterance | The first element of the input candidate list (PIPELINE-1 §4.1 fallback rule). |
slots | Empty. |
updated_session | Present if the plugin mutates session state. |
The skill_id targets the selected skill, not the fallback
plugin's own pipeline_id.
7. Dispatch and handler contract
The orchestrator dispatches <skill_id>:fallback per PIPELINE-1
§7, firing the standard handler-lifecycle trio. The dispatch
payload is the standard shape: lang, utterance, slots.
The selected skill's handler:
- MAY emit
ovos.utterance.speak(PIPELINE-1 §9.6); - MAY act silently;
- MUST complete within the handler lifecycle (PIPELINE-1 §8).
No fallback-specific response protocol is required beyond
subscribing to <own_skill_id>:fallback.
Activation. fallback is a reserved intent_name, but it is not
one of the reserved names whose session.active_handlers push is
suppressed — the per-row rule is in the OVOS-PIPELINE-1 §7.3
registry, and the fallback row does push. A fallback dispatch is a
fresh activation, not the continuation of an already-active skill,
so the dispatch pushes the selected skill_id onto
session.active_handlers per OVOS-PIPELINE-1 §7.1. This is what
makes a running fallback skill stoppable through OVOS-STOP-1, and
what lets a conversational fallback — a language-model chatbot, for
instance — take the next utterance through OVOS-CONVERSE-1 without
a second registration.
8. Pipeline positioning
8.1 General placement
Fallback stage(s) SHOULD be placed after all deterministic
intent-matching stages. Only the low-confidence, catch-all fallback
stage SHOULD additionally follow the persona stage (if present):
it is the last-resort tier (§3.3), and a persona claims everything
that reaches it (OVOS-PERSONA-1 §7.2), so anything placed behind
persona is unreachable while a persona is active — the catch-all
belongs there regardless. Higher-confidence fallback stages
(fallback_high, fallback_medium) MAY precede the persona
stage, interleaved with intent-matching stages by priority range, per
§8.2. Utterances that reach a fallback stage were not claimed by any
earlier stage.
Default response skill. A voice assistant should always produce
an answer rather than fail silently. Every deployment SHOULD
include a catch-all fallback skill that unconditionally returns
can_handle: true and responds with a graceful "I don't know how
to answer that" message. That skill SHOULD be reachable only
after every other fallback handler has declined — as the last entry
of session.fallback_handlers when that list is set, and inside
the range of the last fallback stage when stages are ranged (§8.2).
§3.3 places it in the low-confidence tier. Without it, an utterance
that no skill can handle produces ovos.intent.unmatched
(PIPELINE-1 §9.3) and no user-facing response.
8.2 Multiple stages and priority interleaving
A deployment MAY reach the fallback role from several
session.pipeline entries — separate plugin instances, each with
its own single pipeline_id, or several entries referencing one
instance's range configurations. Either way an entry is a matcher
reference and the actor identity is the plugin's one pipeline_id
(OVOS-PIPELINE-1 §3). Each is configured with a priority range — a [min, max] integer
interval — that restricts which registered skills it considers
(§5 step 2). This allows fallback skills to be interleaved with
other pipeline stages.
Example:
session.pipeline: [
"stop_high",
"converse",
"intent_high",
"fallback_high", ← priority range [0, 49]
"intent_medium",
"fallback_medium", ← priority range [50, 74]
"common_query",
"persona",
"fallback_low" ← priority range [75, 100]
]
A skill registered at priority: 10 is queried by fallback_high
before intent_medium runs. A skill registered at priority: 80 is
queried by fallback_low only after intent_medium,
fallback_medium, common_query, and persona have all declined.
fallback_high and fallback_medium interleave with the
intent-matching stages ahead of persona; only fallback_low, the
catch-all tier, follows it — per §8.1 and OVOS-PERSONA-1 §10, which
places persona after common_query and states the same ordering
from its side. A single-stage deployment sets no range restriction.
The configured ranges are inclusive at both bounds and SHOULD
partition the priority space (§5 step 2). Because priority is an
unbounded integer, the last stage's range SHOULD be open above
— [75, ∞) rather than [75, 100] — so that a skill registering
above the convention is still queried somewhere.
Within any stage, pool construction and session ordering (§5) work identically regardless of how many stages are present.
9. Bus surface
| Topic | Form | Direction | Purpose |
|---|---|---|---|
ovos.fallback.register | broadcast | skill → fallback | Register as a fallback handler (§3.1). |
ovos.fallback.deregister | broadcast | skill → fallback | Deregister (§3.2). |
ovos.fallback.ping | broadcast | fallback → all fallback skills | One willingness query per round (§6.1). |
ovos.fallback.pong | shared reply topic | skill → fallback | Claim or explicit decline, via reply (§6.1). |
<skill_id>:fallback | dispatch | orchestrator → skill | Dispatch to the selected skill (§7). |
10. Conformance
A fallback pipeline plugin MUST:
- expose a
match(utterances, lang, session) → Match | Noneoperation per PIPELINE-1 §4; - maintain a session-scoped registry of skills with integer priorities per §3.4;
- subscribe to
ovos.fallback.registerandovos.fallback.deregister(§3); - reject any registration where payload
skill_id≠context.skill_id(§3.1), and any deregistration with the same mismatch (§3.2); - key registration and deregistration by
context.session.session_id, never by asession_idinMessage.data(§3.2, §3.4); - construct the effective handler pool per §5 on each match call, ordering equal priorities deterministically (§5 step 1);
- broadcast one
ovos.fallback.pingper round and collectovos.fallback.pongwithin a bounded window (§6.1); - treat a skill silent at window close, or a malformed pong, as
can_handle: false(§6.1); - discard a pong whose
context.utterance_idor session does not match the round (§6.1); - select the highest-ranked claimant in pool order (§6.2);
- return a
Matchwithintent_name: "fallback"targeting the selected skill (§6.3); - return
Nonewhen no skill in the pool is willing (§6.2).
A fallback pipeline plugin SHOULD:
- use the recommended 0.5 s default poll ceiling unless the deployer configures otherwise (§6.1);
- when configured with a priority range, apply it as §5 step 2, and log at WARN a registered skill that falls in no range (§5);
- close the collection window early on all-answered, or when no unanswered skill can outrank the best claimant (§6.1);
A fallback pipeline plugin MAY:
- mutate session state via
Match.updated_session(§6.3).
A skill registered as a fallback handler MUST:
- emit
ovos.fallback.registerwith itsskill_idandprioritybefore receiving fallback dispatches (§3.1); - subscribe to
ovos.fallback.pingand answer — claim or explicit decline — withovos.fallback.pong, derived through the reply derivation and echoing the evaluatedutterance(§6.1); - subscribe to
<own_skill_id>:fallbackto receive dispatches (§7).
A deployment SHOULD:
- position fallback stage(s) after deterministic intent-matching
and persona stages in
session.pipeline(§8.1); - include a catch-all fallback skill, reachable only after every other fallback handler has declined (§8.1);
- configure priority ranges that partition the priority space, with the last stage open above (§8.2).
A deployment MUST:
- set each fallback stage's match-timeout bound at or above that stage's collection ceiling (§6.1, OVOS-PIPELINE-1 §4.4);
- raise the poll ceiling of a model-backed fallback stage above that stage's evaluation latency (§6.1).
See also
- OVOS-PIPELINE-1 — pipeline-plugin contract, Match shape, dispatch, reserved intent-name registry (§7.3).
- OVOS-MSG-1 — envelope, derivations, and routing keys.
- OVOS-SESSION-1 — session field registry and the omission rule.
- OVOS-INTENT-4 — session-scoped registration model (§11).
- OVOS-CONVERSE-1 — the dotted-addressed per-skill query pattern this specification follows.
- OVOS-PERSONA-1 — the persona stage that precedes fallback.