Skip to content

Fallback Pipeline

Maturity — Mature ⬤⬤⬤⬤⬤

Long-lived, battle-tested, and actively maintained. Depend on it freely. Rated by repository health, not version.

In a nutshell

When you say something and none of your assistant's regular skills know how to respond, the fallback pipeline is the safety net. It tries one last set of "catch-all" skills so the assistant still says something instead of going silent. Think of it as the help desk that gets your question only after everyone else has passed on it. It asks these backup skills in a set order until one of them handles the request. See the Converse Pipeline for what runs before this, or the Glossary for terms.

This is a flow stage: part of every standard pipeline rather than a matcher you choose between.

The rest of this page is for people deploying or customizing OVOS. If you only wanted to know what this stage does, you are done.

📐 Formal specification

The fallback plugin is specified by OVOS-FALLBACK-1 — Fallback Pipeline Plugin, built on OVOS-PIPELINE-1. See the spec index.

The Fallback Pipeline in OpenVoiceOS (OVOS) manages how fallback skills are queried when no primary skill handles a user's utterance. It coordinates multiple fallback handlers, so the system still attempts to respond even when regular intent matching fails.

How the spec frames it

A fallback skill declares no intent patterns. Instead it receives the raw utterance at query time and decides for itself whether it can respond (FALLBACK-1 §2). This is the right pattern for open-domain QA, LLM completions, and any coverage that cannot be modelled as a grammar.

The plugin builds an ordered handler pool from each skill's registered priority and the session preference session.fallback_handlers (FALLBACK-1 §4; current ovos-core orders purely by registered priority and does not yet consult session.fallback_handlers). It queries pool members one at a time via ovos.skills.fallback.ping / .pong, and returns a Match on the reserved intent_name fallback (PIPELINE-1 §7.3) targeting the first willing skill, dispatched on ovos.skills.fallback.{skill_id}.request. If the pool is exhausted it returns None, and the orchestrator emits ovos.intent.unmatched.

The high/medium/low stages below are one fallback plugin loaded at several pipeline positions, each restricted to a priority range (FALLBACK-1 §8.2).


Implementation

Module: ovos_core.intent_services.fallback_service.FallbackService Pipeline plugin ID: ovos-fallback-pipeline-plugin Stage names: ovos-fallback-pipeline-plugin-high, ovos-fallback-pipeline-plugin-medium, ovos-fallback-pipeline-plugin-low (deprecated aliases: fallback_high, fallback_medium, fallback_low)

FallbackService subclasses ConfidenceMatcherPipeline, so the single base ID auto-expands into the three match_high/match_medium/match_low matchers exposed as ovos-fallback-pipeline-plugin-high, -medium, and -low. It ships inside ovos-core:

[project.entry-points."opm.pipeline"]
ovos-fallback-pipeline-plugin = "ovos_core.intent_services.fallback_service:FallbackService"

Pipeline Stages

Pipeline ID Priority Range Description Use Case
ovos-fallback-pipeline-plugin-high 0 < p ≤ 5 High-priority fallback skills Critical fallback handlers
ovos-fallback-pipeline-plugin-medium 5 < p ≤ 90 Medium-priority fallback skills General fallback skills
ovos-fallback-pipeline-plugin-low 90 < p ≤ 101 Low-priority fallback skills Catch-all or chatbot fallback skills

Each matcher filters registered fallbacks with range.start < priority ≤ range.stop (exclusive start, inclusive stop). Lower priority numbers run first. A fallback that registers without a priority defaults to 101, placing it in the low tier. Priorities can be overridden by users via config.


How It Works

flowchart TD
    U["Utterance<br/>(all matchers failed)"] --> H["-high<br/>(0 < p ≤ 5)"]
    H -- no willing skill --> M["-medium<br/>(5 < p ≤ 90)"]
    M -- no willing skill --> L["-low<br/>(90 < p ≤ 101)"]
    H -- willing skill --> D["ovos.skills.fallback.<br/>{skill_id}.request"]
    M -- willing skill --> D
    L -- willing skill --> D
    L -- no willing skill --> N["ovos.intent.<br/>unmatched"]

Diagram: an utterance that failed all other matchers is tried against high, then medium, then low priority fallback skills, dispatching to the first willing skill, or ending as ovos.intent.unmatched if none accept it.

  1. A fallback stage is hit in the pipeline (after all other matchers fail)

  2. FallbackService.match_high/medium/low() filters registered fallbacks to the stage's priority range

  3. It pings candidates via ovos.skills.fallback.ping (carrying the priority range and a fallback_request_id) and collects ovos.skills.fallback.pong acknowledgements (can_handle, echoing the same fallback_request_id) within ~0.5s. The request id lets the service ignore a pong that answers a stale or concurrent poll round, instead of the one it is waiting on.

  4. Candidates are sorted by priority ascending; the winning match dispatches to that skill via ovos.skills.fallback.{skill_id}.request

  5. First skill that handles the utterance wins — it is consumed

  6. If no fallback skill accepts the utterance, no fallback response is generated


Skill Integration

Skills integrate as fallbacks by:

  • Inheriting from FallbackSkill and decorating one or more handlers with @fallback_handler(priority=...) (lower number = higher priority; default 50)

  • Implementing can_answer(self, message) to declare upfront whether the skill is willing to try (utterances are in message.data["utterances"]) — this is mandatory, the method is abstract and a skill without it does not load

  • On startup ovos-workshop registers the skill's lowest handler priority with the service via ovos.skills.fallback.register

  • Returning True from a handler when it consumes the utterance, False to pass to the next fallback

from ovos_workshop.skills.fallback import FallbackSkill
from ovos_workshop.decorators import fallback_handler

class MyFallback(FallbackSkill):
    def can_answer(self, message) -> bool:
        return True   # always willing to try

    @fallback_handler(priority=50)
    def handle_fallback(self, message):
        self.speak("I don't know, but I tried.")
        return True   # consumed

This lets you customize fallback behavior for your skill ecosystem.


Configuration

{
  "skills": {
    "fallbacks": {
      "fallback_priorities": {
        "my-skill-id": 10
      },
      "fallback_mode": "accept_all",
      "fallback_whitelist": [],
      "fallback_blacklist": []
    }
  }
}
Config Key Description
fallback_priorities Override developer-defined priorities per skill ID
fallback_mode "accept_all", "whitelist", or "blacklist"
fallback_whitelist Skills allowed to act as fallbacks (when mode is whitelist)
fallback_blacklist Skills blocked from fallback (when mode is blacklist)

FallbackMode

Mode Description
accept_all Default — any registered skill can act as fallback
whitelist Only skills in fallback_whitelist can act as fallback
blacklist All skills can act as fallback except those in fallback_blacklist

Bus Events Handled

Event Handler
ovos.skills.fallback.register handle_register_fallback
ovos.skills.fallback.deregister handle_deregister_fallback
ovos.skills.fallback.ping Skill-side: _handle_fallback_ack replies with ovos.skills.fallback.pong
ovos.skills.fallback.pong Service-side: handle_ack collects a skill's can_handle reply
ovos.skills.fallback.{skill_id}.request Skill-side: _handle_fallback_request actually runs the winning skill's fallback handlers

Which skill a registration acts on

Both ovos.skills.fallback.register and ovos.skills.fallback.deregister act on the skill_id in the message payload. That field names the target: the skill whose fallback handler is added to or removed from the registry.

A message also carries context.skill_id, which names the source that emitted it. The two are the same whenever a skill registers itself, which is the ordinary case, and ovos-workshop emits the registration that way on startup. They are not required to match. The service indexes under the payload value, does not substitute the context value for it, and rejects nothing on the strength of a difference or of a missing context skill_id — a source that is not a skill at all, such as a provisioning tool, registers on a skill's behalf. A plugin logs both at DEBUG when they differ.

Removing another skill's fallback handler is a form of remote uninstall, so a deployment may block cross-skill messages as hardening. The shape of that policy is left to the deployment.


Notes

  • The pipeline itself does not define or enforce a default fallback response.

  • The separate ovos-skill-fallback-unknown skill implements the default "I don't understand" reply.

  • This design lets developers create custom fallback strategies or add fallback chatbot skills without changing the core pipeline.

  • Fallback skills should implement some dialog if they consume the utterance.


Security

As with converse, a badly designed or malicious skill can hijack the fallback skill loop. This is less serious than with converse, but the pipeline still provides protections:

  • You can configure what skills are allowed to use the fallback mechanism via fallback_mode.

  • fallback_priorities lets users adjust priorities when the default values don't fit the installed skill collection.


Source code: OpenVoiceOS/ovos-core.


Read next: Adapt Pipeline Related: Fallback Skill · OCP Pipeline · Converse Pipeline