What broke, exactly

v5.9.0 introduced four resonance tiers (maximo, alto, medio, bajo) and wrote the user's preference into brain/calibration.json → preferences.default_resonance. v5.10.0 then made that value prevail over the legacy client_runtime_profiles.claude_code.reasoning_effort hint in config/schedule.json.

That was the right call — the legacy setting was a double-writer source of subtle bugs — but it had an edge: operators whose only recorded preference was the legacy reasoning_effort="max" (set via nexo preferences --reasoning-effort max before v5.9.0 shipped) now had no default_resonance. The resolver silently fell back to the module-level DEFAULT_RESONANCE="alto", effectively downgrading them one tier on every interactive call. The most visible symptom: NEXO Desktop's conversation header said POTENCIA: ALTO when the operator had configured max months earlier.

The fix

v5.10.1 adds a one-shot migration inside _run_runtime_post_sync() that runs every time the updater lays down new code into NEXO_HOME:

• Read schedule.json → client_runtime_profiles.claude_code.reasoning_effort.
• If calibration.json → preferences.default_resonance is not already set, write the equivalent tier:

  max → maximo
  xhigh → alto
  high → medio
  medium → bajo

If either calibration.json or schedule.json already declares an explicit default_resonance, the migration is a no-op. Unknown or unsupported effort values are skipped. A corrupt calibration.json gets rewritten safely. All errors are swallowed into a single resonance-migration-warning:* line in the update actions log; the update path never raises on this code.

Idempotent by construction: a second run sees the preference already set and does nothing.

What it does NOT do

• Does not touch schedule.json. The legacy hint stays where it was; it just no longer drives anything.
• Does not overwrite an explicit default_resonance. If you chose bajo in the Desktop Preferences UI, you keep bajo, full stop.
• Does not affect system-owned callers. Every cron and background script keeps its fixed tier in SYSTEM_OWNED_CALLERS. Only interactive user-facing callers (nexo chat, Desktop new conversation, interactive nexo update) honour the user's default.
• Does not run inside MCP tools or inside a running session. It runs inside the updater, once per nexo update.

Tests

10 new cases in tests/test_auto_update_migrate_effort.py covering each mapping, the two no-op paths (calibration pref set / schedule pref set), the "no hint" and "unknown hint" paths, idempotency on a second run, and the corrupt-JSON recovery.

The release also fixes a latent test-harness bug: test_cron_recovery.py::test_catchup_script_runs_directly_from_runtime_root (and two sibling tests) copied a minimal src/ subset into a tmp runtime root but missed modules introduced in v5.9.x / v5.10.0 (model_defaults, resonance_map, db, enforcement_engine, bootstrap_docs, constants). Consolidated the copy step into a shared helper and added the missing files. Pre-existing failure against main; now passes without touching production code.

Full suite: 1011 passed, 1 skipped.

Upgrading

Run nexo update. If your previous install had reasoning_effort="max" in schedule.json and nothing in calibration.json, the very next update will silently restore default_resonance="maximo", and your next Desktop conversation will show POTENCIA: MÁXIMO in the header again. No manual step required.