Skip to content

fix: tie the trained wait to the trainer's own state - #189

Merged
JarbasAl merged 1 commit into
devfrom
fix/trained-wait-on-engine-state
Sep 9, 2026
Merged

JarbasAl merged 1 commit into
devfrom
fix/trained-wait-on-engine-state

Conversation

@JarbasAl

@JarbasAl JarbasAl commented Sep 8, 2026

Copy link
Copy Markdown
Member

🤖 Auto-generated by Claude Opus 5 (claude-opus-5) via Claude Code — NOT human-reviewed. Verify before acting.

This is the one durable fix for the trained wait, and it supersedes #178: it carries #178's _DEFAULT_TRAINED_TIMEOUT = 180.0 block verbatim, so #178 can close unmerged rather than the two competing.

get_minicroft decided whether to wait from two things it had happened to observe on the bus: the registrations seen so far, and whether anything subscribed to mycroft.skills.train. Both are the wrong instrument, and they fail in opposite directions.

A boot that checked before the registrations landed saw an empty set, skipped the wait, and ran the suite against a padatious container that was still compiling — every utterance came back ovos.intent.unmatched. That skip was traceless: its only log line sat inside if registered and not has_trainer, which cannot fire when registered is empty. In the other direction the timeout was a wall clock on the whole training pass, so a skill with many locales on a loaded runner was declared stuck while padatious was still working, and the RuntimeError came out of setUpClass and took the whole class with it.

Training-requested is now a fact, not an observation. load_plugin_skills always emits mycroft.skills.train after loading, so it records that it did. Nothing has to be seen on the bus for the wait to be entered.

Outstanding work is read from the trainers. A cleared finished_training_event says a pass is running; needs_compile on a container says work exists but the background worker has not picked it up yet — that is the window train() opens by design, since it never trains on the calling thread. A container the plugin has given up on is excluded, or the wait would never end. Where a plugin offers wait_until_trained, it is asked directly; it blocks on the container state the bus event only reports afterwards.

Both guards survive. A bus subscriber that owns no training state still promises to report, and #179's loud raise still holds it to that. A load where no skill registered an intent still returns at READY.

Every skip says why, at INFO. Four distinct reasons, each named: opted out, nothing loaded, nothing trains, nothing registered.

The empty boot pays nothing. A fresh padatious container reports needs_compile before anything is registered in it, so an earlier revision of this branch waited on a compile nobody had asked for: 4.08s for a boot with no skills at all, against 0.59s on dev, and ovoscope's own suite at 326s against 113s. The wait is now skipped whenever no skill registered an intent, whatever a container reports, and the same measurements are 0.58s and 117s.

docs/minicroft.md understated the ceiling. It asked for a 300-second pytest-timeout, below the wait's own worst case of OVOSCOPE_TRAINED_MAX plus OVOSCOPE_TRAINED_TIMEOUT, which is 780 seconds at the shipped defaults. A framework timeout below that kills setup mid-wait and reads as a boot failure, which is the misdiagnosis that section exists to prevent. It now names the sum and asks for 900.

before after
skip leaves a log line only in one branch always
timeout bounds the whole training pass silence from an idle trainer, OVOSCOPE_TRAINED_MAX overall
trainer discovered by bus subscription training state it owns, bus subscription as well

Tests: 49 pass in test_minicroft.py, including new cases for a dirty container before the pass starts, a container that only dirties after the wait is entered, a given-up container, and trainer discovery without any bus subscription. The full suite is 584 passed with 12 failures in test_listener_stream.py, test_golden.py and test_audit_round1.py — the same 12 fail on untouched dev in the same environment, so they are inherited, not introduced.

Two existing assertions changed, and it is worth saying why rather than burying it. test_no_intents_registered_skips_wait asserted _trained_times == [] and test_trained_event_lets_quiet_window_elapse_and_return asserted exactly one event. Both were really asserting that the boot returned before a trainer reported, which is a race and not a contract — a trainer with empty containers may report a pass anyway. They now assert the contract: the first that the boot does not spend the bound waiting, timed rather than counted; the second that a report was recorded and that no trainer is left holding work.

Not yet demonstrated here: the acceptance runs. ovos-skill-count#69's merged tree across five consecutive runs, and pokepedia fr-FR under load, both need this published as an alpha first, since the skills install ovoscope from PyPI. I will run them against the alpha and post the counts.

@github-actions github-actions Bot added the fix label Sep 8, 2026
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Beep boop! Here's the latest status of your PR checks. 🤖

I've aggregated the results of the automated checks for this PR below.

📋 Repo Health

I've checked the repo's reflexes (aka build speed). ⚡

✅ All required files present.

Latest Version: 1.8.1a1

ovoscope/version.py — Version file
README.md — README
LICENSE — License file
pyproject.toml — pyproject.toml
⚠️ setup.py — setup.py
CHANGELOG.md — Changelog
ovoscope/version.py has valid version block markers

🏷️ Release Preview

The draft for the big day is ready for review. 📝

Current: 1.8.1a1Next: 1.8.2a1

Signal Value
Label fix
PR title fix: tie the trained wait to the trainer's own state
Bump build

✅ PR title follows conventional commit format.


🚀 Release Channel Compatibility

Predicted next version: 1.8.2a1

Channel Status Note Current Constraint
Stable Not in channel -
Testing Too new (must be <1.0.0) ovoscope>=0.7.2,<1.0.0
Alpha Compatible ovoscope>=1.6.15a1

🔒 Security (pip-audit)

Ensuring our security logs are being captured. 🪵

✅ No known vulnerabilities found (129 packages scanned).

🔍 Lint

Here's the latest update on this check. 🗞️

ruff: issues found — see job log

⚖️ License Check

Ensuring our license headers are up to date for 2024. 📅

✅ No license violations found.

Policy: Apache 2.0 (universal donor). StrongCopyleft / NetworkCopyleft / WeakCopyleft / Other / Error categories fail. MPL allowed.

📊 Coverage

Calculating the test-to-code ratio. ➗

⚠️ 63.0% total coverage

Files below 80% coverage (15 files)
File Coverage Missing lines
ovoscope/simple_listener.py 0.0% 63
ovoscope/version.py 0.0% 5
ovoscope/classic_listener.py 18.2% 117
ovoscope/intent_cases.py 22.3% 153
ovoscope/pytest_plugin.py 47.0% 210
ovoscope/cli.py 47.8% 132
ovoscope/ocp.py 47.9% 61
ovoscope/tts_intelligibility.py 49.0% 102
ovoscope/e2e.py 56.2% 67
ovoscope/media.py 56.7% 101
ovoscope/listener.py 57.1% 127
ovoscope/voice_loop.py 59.0% 119
ovoscope/__init__.py 64.1% 427
ovoscope/audio.py 64.4% 126
ovoscope/media_provider.py 67.6% 23

Full report: download the coverage-report artifact.

🔨 Build Tests

The build process has successfully terminated. 🏁

✅ All versions pass

Python Build Install Tests
3.10
3.11
3.12
3.13
3.14

The silent guardian of the dev branch. 🦇

… say

`mycroft.skills.trained` is emitted before the container it reports has
finished compiling: padatious never trains on the calling thread, so the
event fires while `needs_compile` is still true. A consumer that waits on
the event and then matches an utterance races the compile, and loses under
load.

The wait now reads the trainer. A plugin offering `wait_until_trained` is
asked directly, and `container.needs_compile` decides whether work is
outstanding, so the deadline is pushed forward while a trainer still has
work rather than expiring on a quiet bus.

The wait is skipped when it cannot be needed: no skill registered an intent,
or nothing on the bus trains one. The first of those is not merely an
optimisation. A fresh container reports `needs_compile` before anything is
registered in it, so a boot with no skills at all would otherwise wait for a
compile nobody asked for -- measured at 4.08s against dev's 0.59s on this
host, and the whole of ovoscope's own suite at 117s against dev's 113s once
the skip is in place.

`docs/minicroft.md` stated a 300-second pytest-timeout, which is below the
wait's own worst case. That ceiling is `OVOSCOPE_TRAINED_MAX` plus
`OVOSCOPE_TRAINED_TIMEOUT`, 780 seconds at the shipped defaults, and a
framework timeout below it kills setup mid-wait and reads as a boot failure
-- exactly the misdiagnosis that section exists to prevent. It now names the
sum and asks for 900.

Co-Authored-By: Claude Opus 5 (claude-opus-5) <noreply@anthropic.com>
@JarbasAl
JarbasAl force-pushed the fix/trained-wait-on-engine-state branch from 512f8de to c0a43ce Compare September 9, 2026 08:27
@github-actions github-actions Bot added fix and removed fix labels Sep 9, 2026
@JarbasAl
JarbasAl marked this pull request as ready for review September 9, 2026 11:54
@JarbasAl
JarbasAl merged commit 78a92c1 into dev Sep 9, 2026
14 checks passed
@JarbasAl
JarbasAl deleted the fix/trained-wait-on-engine-state branch September 9, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant