ovos-core uses ovoscope for end-to-end skill testing. Tests live in test/end2end/ and run via the ovoscope.yml GitHub Actions workflow.
The workflow:
- Installs ovos-core with
[mycroft,plugins,skills-essential,lgpl,test]extras - Runs all tests in
test/end2end/using pytest - Tests Adapt, Padatious, fallback, converse, and stop pipeline behaviours
- Posts a
🔌 Skill Tests (ovoscope)section to PR comments - Generates a
🚌 Bus Coveragereport showing which bus messages were observed/asserted
See ovoscope documentation for framework details.
# Install ovos-core with test extras
uv pip install -e .[test]
# Run end-to-end tests
pytest test/end2end/ -v --timeout=60
# With bus coverage tracking
pytest test/end2end/ -v --ovoscope-bus-cov --ovoscope-bus-cov-verboseBus coverage tracks which bus message types your tests observe and assert against. Unlike code coverage, it measures behavioural coverage — whether your tests exercise the full range of bus interactions a skill produces.
The bus coverage report shows:
- Listeners: Which message handlers were triggered (and how many times)
- Emitters: Which messages were emitted during tests
- Assertions: Which emitted messages were explicitly asserted in test expectations
See ovoscope/docs/ci-integration.md for configuration details.
SkillsStore.validate_skill() (skill_installer.py:226) performs lightweight GitHub API validation (no auth required for public repos):
- URL must start with
https://github.com/. - The repository must exist (HTTP 200 from
api.github.com/repos/{owner}/{repo}/contents/). - The repo must contain
pyproject.tomlorsetup.cfg— a baresetup.py-only repo is rejected as legacy packaging. pyproject.toml/setup.cfgmust not referenceMycroftSkillorCommonPlaySkill— those indicate an incompatible legacy skill.
If GitHub is unreachable (network error or non-404 API error), the method returns True (fail-open) so transient outages do not block installs.
If wait_for_intent_service raises RuntimeError: IntentService did not become ready within 300 seconds, the IntentService process is either not running or not connected to the messagebus. The timeout is configurable via skills.intent_service_timeout in mycroft.conf (seconds, default 300).
Since 2026-03-12, _collect_converse_skills and _collect_stop_skills use can_handle default False. A skill that does not respond to the converse/stop ping within 0.5 s is excluded — it is not assumed to want to handle the utterance. This avoids stale listeners and unexpected behaviour when a skill process is unresponsive.
ovos-core is the central component of the OpenVoiceOS platform, responsible for skill management, intent parsing, and orchestration of the voice assistant's features. It is a fork of the original Mycroft AI core.
Run the full skill manager (with all subsystems):
ovos-coreAvailable flags: --disable-file-watcher, --disable-skill-api, --disable-intent-service, --disable-installer, --disable-event-scheduler.
ovos-intent-serviceThis starts only IntentService connected to the messagebus, without loading any skills. Useful for debugging pipeline issues.
ovos-skill-installerListens on ovos.skills.install, ovos.pip.install, etc. without loading skills.
Enable pip-based installation in mycroft.conf:
{"skills": {"installer": {"allow_pip": true}}}Then emit a bus message or use ovos-skill-installer. Skills are installed as Python packages via pip or uv (if available).
Add the skill's skill_id to the configuration:
{"skills": {"blacklisted_skills": ["skill-name.author"]}}The skill will be skipped during load_plugin_skills() in SkillManager.
This warning from SkillManager.__init__() means find_skill_plugins() returned no results. Either no OVOS skills are installed in the current Python environment, or skills are running in standalone mode (which is fine — the warning can be ignored in that case).
Skills are Python packages that register an entry point under the ovos.plugins.skill namespace in their pyproject.toml. ovos-plugin-manager discovers them via find_skill_plugins().
Yes. SkillManager runs a loop every 30 seconds calling _load_new_skills(), which picks up newly installed skills automatically. You can also trigger a reload by emitting mycroft.skills.train on the bus.
By default, all skills load unconditionally at startup via SkillManager.run() → _load_new_skills() (ovos_core/skill_manager.py). Runtime requirements (network_before_load, internet_before_load) are ignored by default.
To enable deferred loading (legacy behavior), set skills.use_deferred_loading: true in mycroft.conf. When enabled, skills with connectivity requirements are held until those conditions are met via bus events (mycroft.network.connected, mycroft.internet.connected, etc.).
Pipeline plugins implement the opm.pipeline entry point and provide intent matching strategies. Each plugin exposes a match() method (or match_high/medium/low for ConfidenceMatcherPipeline). They are loaded by OVOSPipelineFactory at startup.
Core pipeline plugins registered by ovos-core:
ovos-converse-pipeline-plugin— active skill conversation handlingovos-common-query-pipeline-plugin— CommonQuery skill routingovos-fallback-pipeline-plugin-{high,medium,low}— fallback skill tiersovos-stop-pipeline-plugin-{high,medium,low}— stop intent handling
Additional plugins (Adapt, Padatious, Padacioso, OCP, etc.) are installed separately.
Set intents.pipeline in mycroft.conf with an ordered list of pipeline plugin IDs:
{"intents": {"pipeline": [
"ovos-converse-pipeline-plugin",
"ovos-adapt-pipeline-plugin-high",
"ovos-padatious-pipeline-plugin-high",
"ovos-fallback-pipeline-plugin-high"
]}}Utterances are passed to each plugin in order until one matches.
Set intents.multilingual_matching: true in mycroft.conf. If the primary language fails to match, IntentService.handle_utterance() will retry all user-configured languages in get_valid_languages().
IntentService.disambiguate_lang() checks context keys in priority order:
stt_lang— language used by STT to transcriberequest_lang— language volunteered by the source (e.g., wake word detector)detected_lang— language set by an utterance transformer plugin- Default config language
Plugins under the opm.utterance_transformer entry point that pre-process utterances before intent matching. Configured under utterance_transformers in mycroft.conf. Loaded by UtteranceTransformersService in ovos_core/transformers.py.
ovos-core includes several built-in optimizations:
- Thread-safe skill loading —
_plugin_skills_lockprevents concurrent dict mutation during_load_plugin_skill()and_unload_plugin_skill()(skill_manager.py:585-603) - Safe iteration snapshots —
send_skill_list(),deactivate_skill(),activate_skill(), anddeactivate_except()snapshot the plugin_skills dict inside the lock before iterating to prevent RuntimeError during concurrent modifications - Event-based fallback signaling —
_collect_fallback_skills()usesthreading.Eventinstead of busy-wait (fallback_service.py:122-125), reducing CPU usage on utterances reaching fallback - Reusable stop event —
wait_for_intent_service()reusesself._stop_eventinstead of creating temporary Event objects (skill_manager.py:462) - Pipeline matcher caching —
get_pipeline_matcher()uses module-level constants for migration map and pre-compiled regex (service.py:39-63, 237-238) - Deferred thread spawning —
create_daemon()for metrics upload is guarded by config check; threads only spawn ifopen_data.intent_urlsis configured (service.py:322, 352) - Transformer plugin caching —
UtteranceTransformersService,MetadataTransformersService, andIntentTransformersServicecache sorted plugins; cache is invalidated onload_plugins()(transformers.py) - Fast blacklist lookup —
_logged_skill_warningsis a set (O(1) lookup) instead of list (skill_manager.py:111) - Single blacklist read — blacklist is read once before the plugin scan loop, not per-skill (skill_manager.py:363)
Use the Stopwatch utility from ovos_utils.metrics to profile hot paths. Example:
from ovos_utils.metrics import Stopwatch
with Stopwatch("intent_match") as s:
match = self.intent_plugins.transform(match)
LOG.info(f"Intent transform took {s.total} seconds")Common causes:
- No internet — pipeline plugins that require network (e.g., OpenWeatherMap) may timeout. Set their timeout or disable them.
- Many skills — each skill loads sequentially by default. Enable deferred loading:
skills.use_deferred_loading: true - Slow utterance transformers — check that plugins are not making network calls in the critical path. Consider disabling unused ones.