Skill Interaction Methods
September 2, 2026 · View on GitHub
OVOSSkill provides two high-level methods for collecting structured user input: ask_yesno for binary yes/no questions and ask_selection for multiple-choice prompts. Both are backed by pluggable agent engines that can be swapped per-skill or system-wide.
ask_yesno
OVOSSkill.ask_yesno(prompt: str, data: Optional[dict] = None) -> Optional[str]
Speaks prompt, waits for the user's response, and classifies it as "yes", "no", or the raw response string if neither matched.
Source: OVOSSkill.ask_yesno in ovos_workshop/skills/ovos.py
Parameters
| Parameter | Type | Description |
|---|---|---|
prompt | str | Dialog ID (looked up in locale/) or a literal string to speak. |
data | dict | None | Template variables for Mustache rendering of the dialog string. |
Return values
| Value | Meaning |
|---|---|
"yes" | User confirmed (e.g. "yeah", "sure", "of course") |
"no" | User declined (e.g. "nope", "nah", "definitely not") |
str | User spoke something that could not be classified: raw transcript returned |
None | No response received (timeout or user said nothing) |
Basic usage
class MySkill(OVOSSkill):
def handle_delete_intent(self, message):
if self.ask_yesno("confirm_delete") == "yes":
self._do_delete()
self.speak_dialog("deleted")
else:
self.speak_dialog("cancelled")
locale/en-us/confirm_delete.dialog:
Are you sure you want to delete this?
Do you really want to delete it?
With template data
answer = self.ask_yesno("confirm_action", data={"action": "restart the server"})
locale/en-us/confirm_action.dialog:
Are you sure you want to {{action}}?
Handling all return values
answer = self.ask_yesno("do_you_want_music")
if answer == "yes":
self.play_music()
elif answer == "no":
self.speak_dialog("okay_nevermind")
elif answer is None:
self.speak_dialog("no_response")
else:
# answer is the raw transcript: user said something unexpected
self.speak_dialog("did_not_understand")
How it works internally
get_response(dialog=prompt, data=data): speaks the prompt, records user reply._get_yesno_engine(): resolves the plugin name (settings → mycroft.conf →ovos-solver-yes-no-plugin), loads it once, and caches it. Falls back toHeuristicYesNoEngineif loading fails.OVOSSkill._get_yesno_engine:ovos_workshop/skills/ovos.py:1932engine.yes_or_no(question=prompt, response=resp, lang=self.lang)→True,False, orNone.True→"yes",False→"no",None/unmatched → raw response.OVOSSkill.ask_yesno:ovos_workshop/skills/ovos.py:1970
ask_selection
OVOSSkill.ask_selection(
options: List[str],
dialog: str = '',
data: Optional[dict] = None,
min_conf: float = 0.65,
numeric: bool = False,
num_retries: int = -1,
) -> Optional[str]
Speaks the options list to the user, optionally follows with a dialog prompt, then resolves the user's spoken response to one of the options.
Source: OVOSSkill.ask_selection in ovos_workshop/skills/ovos.py
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
options | List[str] | : | The predefined options to offer. |
dialog | str | '' | Dialog ID or literal string spoken after the options list. |
data | dict | None | None | Template variables for the dialog string. |
min_conf | float | 0.65 | Minimum fuzzy-match confidence for the default plugin. Passed to the OptionMatcherEngine config if no plugin-level config overrides it. |
numeric | bool | False | If True, speaks each option prefixed with its number ("one, pizza, two, pasta, and so on"). If False, speaks them as a joined list ("pizza, pasta, or salad?"). |
num_retries | int | -1 | How many times to re-prompt on no response. -1 means use the system default. |
Return values
| Value | Meaning |
|---|---|
str | One of the strings from options, exactly as provided. |
None | No match, no response, or plugin failure. |
Special cases handled before user interaction:
- Empty
options→Noneimmediately. - Single-element
options→ returns that element immediately (no prompt).
Basic usage
class MySkill(OVOSSkill):
def handle_transport_intent(self, message):
modes = ["bus", "train", "bicycle"]
choice = self.ask_selection(modes, dialog="which_transport")
if choice:
self.speak_dialog("you_chose", {"mode": choice})
locale/en-us/which_transport.dialog:
Which would you prefer?
How would you like to travel?
The skill speaks: "bus, train, or bicycle? Which would you prefer?"
The user can say:
"train": fuzzy-matched directly"the second one"/"number two"/"two": position matched"the last one": last-word matched"option 3": numeric matched (requiresovos-number-parser)
Numeric menu
choice = self.ask_selection(options, numeric=True)
Speaks each option as: "one, bus, two, train, three, bicycle". Useful when options are long or ambiguous. The user can then say "two" or "the second one".
Handling None
choice = self.ask_selection(options, dialog="which_one", num_retries=1)
if choice is None:
self.speak_dialog("could_not_understand")
return
How it works internally
- Validates
options(raisesValueErrorif not a list, returns immediately for 0 or 1 items). - Speaks options (as list or numbered menu based on
numeric). get_response(dialog=dialog, data=data, num_retries=num_retries): speaks optional follow-up dialog, records reply._get_selection_engine(): resolves the plugin name (settings → mycroft.conf →ovos-option-matcher-fuzzy-plugin), loads it once, and caches it. Falls back toFuzzyOptionMatcherPluginif loading fails. Setsengine.config["min_conf"] = min_confbefore calling.OVOSSkill._get_selection_engine:ovos_workshop/skills/ovos.py:1951engine.match_option(utterance=resp, options=options, lang=self.lang): resolves response to a slot.OVOSSkill.ask_selection:ovos_workshop/skills/ovos.py:1990- If the engine raises or returns
None,ask_selectionreturnsNone.
Plugin system
Both methods are backed by pluggable agent engines discovered and loaded via ovos-plugin-manager.
ask_yesno: YesNoEngine
Plugin type: YesNoEngine (opm.agents.yesno)
Config key: ask_yesno_plugin
Default plugin name looked up: ovos-solver-yes-no-plugin — no shipped opm.agents.yesno entry point
registers under that name, so this lookup always fails and the code falls back to the bundled
HeuristicYesNoEngine. The plugin this repo actually ships and depends on registers as
ovos-yes-no-plugin (see ovos_workshop/skills/ovos.py's _get_yesno_engine and pyproject.toml).
See the manual's Prompts page for details.
Hard fallback: HeuristicYesNoEngine from ovos-yes-no-plugin (used when the named plugin fails to load,
which in practice is always, given the mismatch above)
YesNoEngine plugins implement:
def yes_or_no(self, question: str, response: str, lang: Optional[str] = None) -> Optional[bool]:
... # True = yes, False = no, None = unclear
The question argument gives the plugin context about what was asked, enabling smarter inference (e.g. an LLM-backed plugin could use it to resolve ambiguous answers).
Available plugins:
| Plugin | Description |
|---|---|
ovos-yes-no-plugin | Ships HeuristicYesNoEngine, the plugin ovos-workshop actually depends on and falls back to |
ask_selection: OptionMatcherEngine
Plugin type: OptionMatcherEngine (opm.agents.option_matcher)
Config key: ask_selection_plugin
Default plugin: ovos-option-matcher-fuzzy-plugin (installed as a dependency of ovos-workshop)
OptionMatcherEngine plugins implement:
def match_option(self, utterance: str, options: List[str], lang: Optional[str] = None) -> Optional[str]:
... # returns one of options, or None
Available plugins:
| Plugin | Description |
|---|---|
ovos-option-matcher-fuzzy-plugin | Fuzzy + ordinal/cardinal vocab + numeric fallback. Default. |
Configuration
Global defaults: mycroft.conf
{
"skills": {
"ask_yesno_plugin": "ovos-solver-yes-no-plugin",
"ask_selection_plugin": "ovos-option-matcher-fuzzy-plugin"
}
}
The code's default lookup name for ask_yesno_plugin is ovos-solver-yes-no-plugin, but no registered
opm.agents.yesno plugin uses that name, so the lookup always falls back to the bundled
HeuristicYesNoEngine from ovos-yes-no-plugin. ask_selection_plugin defaults to
ovos-option-matcher-fuzzy-plugin and resolves correctly. Both packages are installed as runtime
dependencies of ovos-workshop.
Per-skill override: settings.json
Place in the skill's settings.json to override for that skill only:
{
"ask_yesno_plugin": "my-llm-yesno-plugin",
"ask_selection_plugin": "my-embedding-option-matcher"
}
Plugin config
Pass configuration to the plugin via the same settings block:
{
"ask_selection_plugin": "ovos-option-matcher-fuzzy-plugin",
"ask_selection_plugin_config": {
"min_conf": 0.80
}
}
Priority order
settings.json > mycroft.conf skills block > built-in default
Plugins are loaded lazily on first use and cached per plugin name for the lifetime of the skill instance.
Writing a custom YesNoEngine plugin
# my_yesno/__init__.py
from typing import Optional
from ovos_plugin_manager.templates.agents import YesNoEngine
class MyYesNoPlugin(YesNoEngine):
def yes_or_no(self, question: str, response: str,
lang: Optional[str] = None) -> Optional[bool]:
r = response.lower()
if "yes" in r or "sure" in r:
return True
if "no" in r or "never" in r:
return False
return None # unclear
# pyproject.toml
[project.entry-points."opm.agents.yesno"]
my-yesno-plugin = "my_yesno:MyYesNoPlugin"
Activate for a skill:
{ "ask_yesno_plugin": "my-yesno-plugin" }
Writing a custom OptionMatcherEngine plugin
# my_matcher/__init__.py
from typing import List, Optional
from ovos_plugin_manager.templates.agents import OptionMatcherEngine
class MyOptionMatcher(OptionMatcherEngine):
def match_option(self, utterance: str, options: List[str],
lang: Optional[str] = None) -> Optional[str]:
# example: embedding similarity via a local model
scores = self._embed_and_score(utterance, options)
best_idx = max(range(len(scores)), key=lambda i: scores[i])
if scores[best_idx] >= self.config.get("min_conf", 0.6):
return options[best_idx]
return None
# pyproject.toml
[project.entry-points."opm.agents.option_matcher"]
my-option-matcher-plugin = "my_matcher:MyOptionMatcher"
Activate system-wide:
{ "skills": { "ask_selection_plugin": "my-option-matcher-plugin" } }
Failure behaviour
| Scenario | ask_yesno result | ask_selection result |
|---|---|---|
| User says nothing (timeout) | None | None |
| User response unclassifiable | raw transcript string | None |
| Plugin fails to load | falls back to HeuristicYesNoEngine | falls back to FuzzyOptionMatcherPlugin |
| Plugin raises at runtime | falls back to HeuristicYesNoEngine | None |
| No plugin configured | ovos-solver-yes-no-plugin used | ovos-option-matcher-fuzzy-plugin used |
ask_selection is intentionally strict: any failure returns None rather than guessing. Always handle the None case in your skill.
See also
ovos-solver-yes-no-plugin: built-in yes/no classifierovos-option-matcher-fuzzy-plugin: default selection plugin, with full docsOVOSSkill.get_response: lower-level method used internally by both- OPM agent templates:
YesNoEngine,OptionMatcherEnginebase classes