Skip to content

Commit 6d87f1d

Browse files
JarbasAlclaudeqwencoder
authored
chore: docs tests and misc optimizations (#752)
* Add required project configuration and documentation files - pyproject.toml: Python project configuration (required for build) - AUDIT.md: Known issues and technical debt documentation - QUICK_FACTS.md: Machine-readable project reference - docs/: Architecture and feature documentation - .env: Environment configuration These files were merged but not committed. Required for CI builds to succeed. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * update * Performance optimizations: fix race conditions, reduce per-utterance overhead **Priority 1 — Race Conditions (correctness + perf)** - Add lock to _unload_plugin_skill to prevent concurrent dict mutation (skill_manager.py:585) - Snapshot plugin_skills dict inside lock for safe iteration in 4 methods: send_skill_list, deactivate_skill, activate_skill, deactivate_except Prevents RuntimeError: dictionary changed size during iteration - Replace busy-wait in _collect_fallback_skills with threading.Event signaling (fallback_service.py:122-125) — reduces CPU usage on utterances reaching fallback **Priority 2 — Per-Utterance Work (latency)** - Replace threading.Event().wait(1) with self._stop_event.wait(1) (skill_manager.py:462) — reuses event, correctly respects stop signal - Move migration_map dict and re.compile regex to module-level constants (service.py) — 15 utterances/second → rebuilding these on every pipeline stage - Guard create_daemon calls with config check before spawning thread (service.py:322, 352) — skip thread creation when open_data.intent_urls not configured **Priority 3 — Minor Overhead** - Change _logged_skill_warnings from list to set (O(1) lookup vs O(n)) (skill_manager.py:111) - Cache sorted plugins in all 3 transformer services (transformers.py) Invalidate cache on load_plugins() - Read blacklist once before plugin scan loop instead of per-skill (skill_manager.py:361) All 65 unit tests pass. Coverage maintained at 60% for ovos_core.skill_manager. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * Update docs: performance optimizations, race condition fixes, audit report - FAQ.md: Add comprehensive Performance section documenting all optimizations (thread-safe loading, event signaling, caching, etc.) - MAINTENANCE_REPORT.md: Add detailed entry for 2026-03-11 performance optimization work - AUDIT.md: Document fixed race conditions (plugin_skills dict, busy-wait, temporary events) - SUGGESTIONS.md: Add S-007 marking all performance improvements as ADDRESSED All changes cross-referenced with code locations and commit SHA. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * Fix documented TODOs: skill uninstall + minor clarifications **S-002: Implement skill uninstall (VALID FIX)** - Implement handle_uninstall_skill() to call pip_uninstall() for skill packages - Replace 'not implemented' error with actual uninstall logic - Convert skill_id to package name (dots → hyphens) - Validate skill parameter before attempting uninstall **Minor TODO clarifications** - Docker detection warning in launch_standalone() for container environments - Clarified voc_match() TODO: explain why StopService reimplements instead of using ovos_workshop (StopService is not a skill; voc_match is service-specific) **Test updates** - Updated test_handle_uninstall_skill to expect 'no packages to install' instead of 'not implemented' **S-006 (DEFERRED)** - Reverted external skills registry implementation - These TODOs are architectural limitations, not missing features - External skills run in separate processes and only communicate via messagebus - They cannot be listed, activated, or deactivated by ovos-core All 65 unit tests pass. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * Update docs: clarify S-002 implementation and S-006 architectural limitation - SUGGESTIONS.md: Mark S-002 as ADDRESSED with implementation details - SUGGESTIONS.md: Document S-006 as architectural limitation (external skills run in separate processes) - SUGGESTIONS.md: Explain correct pattern for external skills (self-advertise + respond to bus messages) - MAINTENANCE_REPORT.md: Add entry for S-002 implementation with rationale on S-006 - All references include commit SHAs and line numbers Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * update * fix: bus listener leaks, None log crash, and intent service startup timeout - converse_service: wrap bus.on/event.wait/bus.remove in try/finally so the skill.converse.pong listener is always removed even if handle_ack raises; add skill_id guard against malformed pong; change can_handle default True→False (non-responding skill should not converse) - stop_service: same try/finally + skill_id guard + can_handle default True→False for skill.stop.pong listener - service.py: fix LOG.info string concat crash when cancel_word is None (use f-string instead of + operator) - skill_manager: add configurable max_wait to wait_for_intent_service (default 300 s via skills.intent_service_timeout); raises descriptive RuntimeError with instructions instead of looping forever Note: sound config caching was not applied — Configuration() is a live object in OVOS that reflects runtime changes without restart. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: StopService inherits OVOSAbstractApplication for voc_match StopService now follows the same pattern as CommonQAService and OCPPipelineMatcher: double-inheriting ConfidenceMatcherPipeline and OVOSAbstractApplication so that vocabulary loading and voc_match/voc_list are provided by the shared ovos-workshop infrastructure instead of a hand-rolled reimplementation. Changes: - Add OVOSAbstractApplication to base classes; call both __init__s with skill_id="stop.openvoiceos" and resources_dir=dirname(__file__) - Remove load_resource_files(), _voc_cache dict, _get_closest_lang(), and the custom voc_match() override (~60 lines deleted) - Replace self._voc_cache[lang]['stop'] in match_low with self.voc_list() - Replace _get_closest_lang() guards in match_* with voc_list() emptiness check (voc_list returns [] for unknown langs — no crash, no None sentinel) - Rename all locale/*.intent files to *.voc so OVOSSkill resource loading finds them via the standard ResourceType("vocab", ".voc", ...) path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add unit tests for StopService — 96% coverage 27 tests covering: - _collect_stop_skills: no active skills, can_handle True/False, timeout cleanup (try/finally), exception cleanup, malformed pong guard (no skill_id), blacklisted skills excluded from ping - handle_stop_confirmation: error branch, response-mode abort_question, converse force_timeout, TTS stop when speaking, skill_id fallback from msg_type - match_high: no vocab → None, stop+no active skills → global stop, stop+active skills → skill stop, global_stop voc → global stop - match_medium: no voc → None, stop/global_stop voc delegates to match_low - match_low: empty voc_list → None, below threshold → None, active skill confidence boost, above threshold → skill stop - handle_global_stop / handle_skill_stop bus message forwarding - get_active_skills session delegation - shutdown removes both bus listeners Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(e2e): add end2end tests for StopService OVOSAbstractApplication refactor Fix existing stop e2e tests: - Add 'stop.openvoiceos.stop.response' to all ignore_messages lists in test_stop.py — StopService now subclasses OVOSAbstractApplication so it responds to the mycroft.stop broadcast like other pipeline-plugin skills (common_query, ocp, persona already filtered there) New test file test_stop_refactor.py — 5 tests across 4 classes: TestGlobalStopVocabulary (no skills loaded): - test_global_stop_voc_no_active_skills: 'stop everything' matches global_stop.voc and emits stop:global (regression: .voc rename works) - test_stop_voc_exact_still_works: bare 'stop' still matches stop.voc (regression: .voc rename did not break the stop vocabulary) TestGlobalStopVocWithActiveSkill (count skill loaded): - test_global_stop_voc_with_active_skill: 'stop everything now' emits stop:global even when a skill is in the active list — verifying that global_stop.voc takes priority over the stop:skill path TestStopSkillCanHandleFalse (count skill loaded): - test_stop_with_active_skill_ping_pong: full stop ping-pong sequence with a running skill — verifies stop.ping → skill.stop.pong(can_handle=True) → stop:skill → {skill}.stop → {skill}.stop.response chain TestStopServiceAsSkill (no skills loaded): - test_stop_service_emits_activate_and_stop_response: explicitly asserts that stop.openvoiceos.activate and stop.openvoiceos.stop.response appear in the message sequence, confirming StopService participates in the OVOSSkill stop lifecycle Also installed missing test dependencies: ovos-skill-count, ovos-skill-parrot, ovos-skill-hello-world, ovos-skill-fallback-unknown, ovos-padatious-pipeline-plugin (from local workspace), ovos-adapt-pipeline-plugin (from local workspace), ovos-utterance-plugin-cancel (from local workspace) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(S-003): strengthen validate_skill with GitHub API validation - Parse owner/repo from URL; call api.github.com/repos/{owner}/{repo}/contents/ - Reject repos that don't exist (HTTP 404) - Reject bare setup.py-only repos (legacy Mycroft packaging) - Fetch pyproject.toml/setup.cfg and reject if MycroftSkill or CommonPlaySkill found - Fail-open on network errors and unexpected API status codes (3 s timeout) - Fix 3 existing tests that assumed no network call (now mock requests.get/validate_skill) - Add 10 new unit tests covering all validation branches - Add test_converse_service.py (43 tests, 81% coverage) - Update FAQ.md and MAINTENANCE_REPORT.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: add unit tests for fallback_service, transformers, and intent service - test_fallback_service.py: 34 tests, 93% coverage - handle_register/deregister_fallback, _fallback_allowed (ACCEPT_ALL/BLACKLIST/WHITELIST), _collect_fallback_skills (ping-pong, timeouts, blacklisted sessions), _fallback_range, match_high/medium/low delegation, shutdown - test_transformers.py: 40 tests, 66% coverage - All three transformer services (Utterance/Metadata/Intent) - Plugin loading, priority ordering + caching, transform chaining, exception swallowing, context merging, session key stripping - test_intent_service_extended.py: 37 tests, raises service.py from 0% to 49% - _handle_transformers, disambiguate_lang, get_pipeline_matcher (migration map), get_pipeline, context handlers, send_cancel_event/send_complete_intent_failure, _emit_match_message, handle_utterance (cancel/no-match), handle_get_intent, shutdown Total: 247 unit tests passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(e2e): add intent pipeline routing end-to-end tests 4 tests covering basic pipeline routing with ovos-skill-count: - Padatious intent matched end-to-end (full handler lifecycle) - High-priority pipeline stage handles before lower-priority stages - Unrecognized utterance produces complete_intent_failure + error sound - Blacklisted skill falls through to complete_intent_failure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(locale): sync .voc files from translations/ (source of truth) Regenerate all stop.voc and global_stop.voc files directly from translations/{lang}/intents.json to ensure they stay in sync. Changes: - Preserve phrase order from translations (previously sorted alphabetically) - Add missing phrases that existed in translations but not locale - Remove phrases in locale that were not in translations - Normalize fa-IR -> fa-ir (locale dir is always lowercase) - nl-NL and nl-nl both exist in translations; nl-nl (canonical) wins - nl-be has no global_stop translation — global_stop.voc intentionally absent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix pyproject.toml * chore: Add ovoscope end-to-end tests with bus coverage report to CI - Created .github/workflows/ovoscope.yml using gh-automations@dev reusable workflow - Enables bus coverage tracking for behavioural test metrics - Posts 🔌 Skill Tests (ovoscope) and 🚌 Bus Coverage sections to PR comments - Requires Adapt and Padatious pipelines for comprehensive intent testing - Updated FAQ.md with CI/Testing section explaining bus coverage - Updated QUICK_FACTS.md with testing workflow reference - Updated MAINTENANCE_REPORT.md with session log Bus coverage complements code coverage by showing which bus message types are exercised during tests, helping identify gaps in skill interaction testing. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix pyproject.toml * fix * Delete .coverage * . * . * . * fix: thread names in bus coverage report * more workflows * coderabbit --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
1 parent 8ea3f6e commit 6d87f1d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+4804
-716
lines changed

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
with:
1515
system_deps: 'python3-dev swig libssl-dev portaudio19-dev libpulse-dev libfann-dev'
1616
install_extras: '.[mycroft,plugins,skills-essential,lgpl,test]'
17-
test_path: 'test/'
17+
test_path: 'test/unittests'
1818
coverage_source: 'ovos_core'
1919
deploy_pages: true
2020
gh_pages_branch: 'gh-pages'

.github/workflows/docs_check.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Docs Check
2+
3+
on:
4+
pull_request:
5+
branches: [dev]
6+
workflow_dispatch:
7+
8+
jobs:
9+
docs_check:
10+
uses: OpenVoiceOS/gh-automations/.github/workflows/docs-check.yml@dev
11+
secrets: inherit
12+
with:
13+
pr_comment: true

.github/workflows/locale_check.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Locale Build Check
2+
3+
on:
4+
pull_request:
5+
branches: [dev]
6+
workflow_dispatch:
7+
8+
jobs:
9+
locale_check:
10+
uses: OpenVoiceOS/gh-automations/.github/workflows/locale-check.yml@dev
11+
secrets: inherit
12+
with:
13+
locale_path: 'ovos_core/intent_services/locale'
14+
pr_comment: true

.github/workflows/ovoscope.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Ovoscope End-to-End Tests
2+
3+
on:
4+
push:
5+
branches: [dev]
6+
pull_request:
7+
branches: [dev]
8+
workflow_dispatch:
9+
10+
jobs:
11+
ovoscope:
12+
uses: OpenVoiceOS/gh-automations/.github/workflows/ovoscope.yml@dev
13+
secrets: inherit
14+
with:
15+
runner: "ubuntu-latest"
16+
python_version: "3.11"
17+
system_deps: "python3-dev swig libssl-dev portaudio19-dev libpulse-dev libfann-dev"
18+
install_extras: "test"
19+
test_path: "test/end2end/"
20+
require_adapt: true
21+
require_padatious: true
22+
bus_coverage: true
23+
bus_coverage_include: ""
24+
bus_coverage_exclude: "^Thread-|^intents$|^skills$|^__core__$"
25+
pr_comment: true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Release Preview
2+
3+
on:
4+
pull_request:
5+
branches: [dev]
6+
workflow_dispatch:
7+
8+
jobs:
9+
release_preview:
10+
uses: OpenVoiceOS/gh-automations/.github/workflows/release-preview.yml@dev
11+
secrets: inherit
12+
with:
13+
version_file: 'ovos_core/version.py'

.github/workflows/repo_health.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Repo Health
2+
3+
on:
4+
pull_request:
5+
branches: [dev]
6+
workflow_dispatch:
7+
8+
jobs:
9+
repo_health:
10+
uses: OpenVoiceOS/gh-automations/.github/workflows/repo-health.yml@dev
11+
secrets: inherit
12+
with:
13+
version_file: 'ovos_core/version.py'
14+
pr_comment: true
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Sync Translations
2+
3+
on:
4+
push:
5+
branches: [dev]
6+
paths:
7+
- 'ovos_core/intent_services/locale/**'
8+
workflow_dispatch:
9+
10+
jobs:
11+
sync_translations:
12+
if: github.actor == 'gitlocalize-app[bot]' || github.event_name == 'workflow_dispatch'
13+
uses: OpenVoiceOS/gh-automations/.github/workflows/sync-translations.yml@dev
14+
secrets: inherit
15+
with:
16+
branch: 'dev'
17+
locale_path: 'ovos_core/intent_services/locale'

.github/workflows/type_check.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
name: Type Check
2+
3+
on:
4+
pull_request:
5+
branches: [dev]
6+
workflow_dispatch:
7+
8+
jobs:
9+
type_check:
10+
uses: OpenVoiceOS/gh-automations/.github/workflows/type-check.yml@dev
11+
secrets: inherit
12+
with:
13+
python_version: "3.11"
14+
pr_comment: true

AUDIT.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@
2222
- **Missing return type hints** across `skill_manager.py` (all 35 methods), `skill_installer.py`, `transformers.py` — all added.
2323
- **Missing docstrings** on `SkillsStore` methods — added.
2424

25+
## Race Conditions (Fixed 2026-03-11)
26+
- **plugin_skills dict concurrent mutation** (skill_manager.py:585-603, 618-661) — `_unload_plugin_skill` and iteration methods were not guarded by `_plugin_skills_lock`, creating RuntimeError: dictionary changed size during iteration. FIXED: added lock guards and snapshot-before-iterate pattern.
27+
- **Busy-wait in fallback skill response collection** (fallback_service.py:122-125) — `_collect_fallback_skills` spun with `time.sleep(0.02)` on every utterance. FIXED: replaced with `threading.Event` signaling.
28+
- **Temporary Event object spam** (skill_manager.py:462) — `wait_for_intent_service` created one throwaway Event per 1-second retry. FIXED: reused `self._stop_event`.
29+
2530
## Known Open Issues (Tracked in SUGGESTIONS.md)
2631
- **S-001**: `_unload_on_network_disconnect/internet_disconnect/gui_disconnect` are stub methods — no implementation.
2732
- **S-002**: `handle_uninstall_skill` always returns "not implemented".

FAQ.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,70 @@
11

22
# FAQ - ovos-core
33

4+
## CI / Testing
5+
6+
### What end-to-end tests does ovos-core run?
7+
8+
ovos-core uses **ovoscope** for end-to-end skill testing. Tests live in `test/end2end/` and run via the `ovoscope.yml` GitHub Actions workflow.
9+
10+
The workflow:
11+
- Installs ovos-core with `[mycroft,plugins,skills-essential,lgpl,test]` extras
12+
- Runs all tests in `test/end2end/` using pytest
13+
- Tests Adapt, Padatious, fallback, converse, and stop pipeline behaviours
14+
- Posts a `🔌 Skill Tests (ovoscope)` section to PR comments
15+
- Generates a `🚌 Bus Coverage` report showing which bus messages were observed/asserted
16+
17+
See [ovoscope documentation](https://github.com/TigreGotico/ovoscope) for framework details.
18+
19+
### How do I run end-to-end tests locally?
20+
21+
```bash
22+
# Install ovos-core with test extras
23+
uv pip install -e .[test]
24+
25+
# Run end-to-end tests
26+
pytest test/end2end/ -v --timeout=60
27+
28+
# With bus coverage tracking
29+
pytest test/end2end/ -v --ovoscope-bus-cov --ovoscope-bus-cov-verbose
30+
```
31+
32+
### What is bus coverage?
33+
34+
Bus 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.
35+
36+
The bus coverage report shows:
37+
- **Listeners**: Which message handlers were triggered (and how many times)
38+
- **Emitters**: Which messages were emitted during tests
39+
- **Assertions**: Which emitted messages were explicitly asserted in test expectations
40+
41+
See `ovoscope/docs/ci-integration.md` for configuration details.
42+
43+
---
44+
45+
## How does `validate_skill` prevent installing incompatible skills?
46+
47+
`SkillsStore.validate_skill()` (`skill_installer.py:226`) performs lightweight GitHub API validation (no auth required for public repos):
48+
49+
1. URL must start with `https://github.com/`.
50+
2. The repository must exist (HTTP 200 from `api.github.com/repos/{owner}/{repo}/contents/`).
51+
3. The repo must contain `pyproject.toml` or `setup.cfg` — a bare `setup.py`-only repo is rejected as legacy packaging.
52+
4. `pyproject.toml`/`setup.cfg` must not reference `MycroftSkill` or `CommonPlaySkill` — those indicate an incompatible legacy skill.
53+
54+
If GitHub is unreachable (network error or non-404 API error), the method returns `True` (fail-open) so transient outages do not block installs.
55+
56+
---
57+
58+
## Why does IntentService time out waiting at startup?
59+
60+
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).
61+
62+
## Why does converse/stop skip a skill that doesn't respond to the ping?
63+
64+
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.
65+
66+
---
67+
468
## What is ovos-core?
569
`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.
670

@@ -100,3 +164,35 @@ Set `intents.multilingual_matching: true` in `mycroft.conf`. If the primary lang
100164
### What are utterance transformers?
101165
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`.
102166

167+
---
168+
169+
## Performance
170+
171+
### What performance optimizations are in place?
172+
`ovos-core` includes several built-in optimizations:
173+
174+
- **Thread-safe skill loading**`_plugin_skills_lock` prevents concurrent dict mutation during `_load_plugin_skill()` and `_unload_plugin_skill()` (skill_manager.py:585-603)
175+
- **Safe iteration snapshots**`send_skill_list()`, `deactivate_skill()`, `activate_skill()`, and `deactivate_except()` snapshot the plugin_skills dict inside the lock before iterating to prevent RuntimeError during concurrent modifications
176+
- **Event-based fallback signaling**`_collect_fallback_skills()` uses `threading.Event` instead of busy-wait (fallback_service.py:122-125), reducing CPU usage on utterances reaching fallback
177+
- **Reusable stop event**`wait_for_intent_service()` reuses `self._stop_event` instead of creating temporary Event objects (skill_manager.py:462)
178+
- **Pipeline matcher caching**`get_pipeline_matcher()` uses module-level constants for migration map and pre-compiled regex (service.py:39-63, 237-238)
179+
- **Deferred thread spawning**`create_daemon()` for metrics upload is guarded by config check; threads only spawn if `open_data.intent_urls` is configured (service.py:322, 352)
180+
- **Transformer plugin caching**`UtteranceTransformersService`, `MetadataTransformersService`, and `IntentTransformersService` cache sorted plugins; cache is invalidated on `load_plugins()` (transformers.py)
181+
- **Fast blacklist lookup**`_logged_skill_warnings` is a set (O(1) lookup) instead of list (skill_manager.py:111)
182+
- **Single blacklist read** — blacklist is read once before the plugin scan loop, not per-skill (skill_manager.py:363)
183+
184+
### How can I measure ovos-core performance?
185+
Use the `Stopwatch` utility from `ovos_utils.metrics` to profile hot paths. Example:
186+
```python
187+
from ovos_utils.metrics import Stopwatch
188+
with Stopwatch("intent_match") as s:
189+
match = self.intent_plugins.transform(match)
190+
LOG.info(f"Intent transform took {s.total} seconds")
191+
```
192+
193+
### Why does my IntentService seem slow on startup?
194+
Common causes:
195+
- **No internet** — pipeline plugins that require network (e.g., OpenWeatherMap) may timeout. Set their timeout or disable them.
196+
- **Many skills** — each skill loads sequentially by default. Enable deferred loading: `skills.use_deferred_loading: true`
197+
- **Slow utterance transformers** — check that plugins are not making network calls in the critical path. Consider disabling unused ones.
198+

0 commit comments

Comments
 (0)