Two bugs that blocked real users across fresh installs and updates. Both closed in one release.
Fix 1 — resonance_tiers.json at the right path
v6.0.0 defined the public contract path as ~/.nexo/brain/resonance_tiers.json. NEXO Desktop ≥ 0.12.0 reads exactly that file on every startup to resolve the tier → (model, effort) pair before spawning Claude. The v6.0.0/6.0.1/6.0.2 installer, however, kept writing the file to the legacy flat-file layout at ~/.nexo/resonance_tiers.json. Desktop therefore failed to start Claude with:
Error: No puedo arrancar Claude: NEXO Brain contract missing:
/Users/<you>/.nexo/brain/resonance_tiers.json.
Install or upgrade NEXO Brain to >=6.0.0.
The symptom only surfaced for Desktop users because the Brain's own Python runtime read the file from __file__.parent / "resonance_tiers.json", which happened to be ~/.nexo/. Headless users never saw it.
v6.0.3 fixes this in three layers:
bin/nexo-brain.jsgains a new helperpublishBrainContracts(srcDir, nexoHome)that writesresonance_tiers.jsonstraight into~/.nexo/brain/on install and on update, then unlinks the legacy copy at~/.nexo/resonance_tiers.jsonif it still exists.src/resonance_map.pynow resolves the contract file via a three-step walk:NEXO_HOME/brain/resonance_tiers.json→ legacyNEXO_HOME/resonance_tiers.json→src/resonance_tiers.json(dev checkouts). It honours$NEXO_HOMEexplicitly.src/auto_update.pyships a new_relocate_resonance_tiers_contract(dest)migration that runs duringnexo updateand promotes a legacy file intobrain/if the contract slot is empty, then unlinks the legacy copy. Idempotent; never raises; logged asresonance-contract-relocate:moved-to-brainor:legacy-removed.
Net effect: fresh installs land the file in the right place from day one, and existing runtimes get reconciled on the next nexo update without operator intervention.
Fix 2 — guard_checks.session_id actually carries the SID
v6.0.2 introduced a regression in nexo_guard_check: the insert into guard_checks hardcoded session_id = "". Every row landed with an empty SID. Downstream, hook_guardrails._session_has_guard_check(conn, sid) queries WHERE session_id = ? with the current session's SID — which never matched a row — so the missing_file_guard hook kept blocking strict-protocol sessions with "no guard_check seen for this session" immediately after a successful nexo_guard_check call.
v6.0.3 adds _resolve_active_sid(conn) in src/plugins/guard.py. It walks four candidates and returns the first match:
NEXO_SIDenv var (headless crons and the wrapped CLI set this).CLAUDE_SESSION_IDenv var translated viasessions.external_session_id/sessions.claude_session_id.- The most-recently-updated row in
sessions(fallback for MCP tool calls where the broker does not forward env vars but a single session is obviously active). - Empty string — only written when
sessionsis literally empty, which is the correct "nothing to guard" signal.
Strict-protocol sessions stop tripping the spurious block as soon as the Brain runtime picks up the new plugins/guard.py. No migration is needed for historical rows: they stay in the table as session_id = "", and they are simply invisible to hook_guardrails for any new session's SID — which is the right behaviour.
Tests
Nine new pytest cases across two files. tests/test_auto_update_relocate_resonance.py covers the contract relocation migration (promotion from legacy, legacy cleanup when the contract already exists, idempotency, absence-of-files no-op, exception safety under an un-writable brain/). tests/test_guard.py gains four SID-resolution cases (env, CLAUDE_SESSION_ID translation, most-recent-session fallback, empty-sessions edge). Full suite stays green.
Upgrade
If you are on v6.0.0 / v6.0.1 / v6.0.2 and see "NEXO Brain contract missing" in NEXO Desktop, run nexo update. The installer writes the contract file to ~/.nexo/brain/, the migration removes the legacy copy, and you need only restart Desktop. Headless users get the guard_checks.session_id fix on the same update — no restart of hooks or crons required, because the next invocation loads the refreshed plugins/guard.py.