v6.0.0 is a BREAKING release. It collapses three separate v5.x surfaces into clean contracts: a single tier that drives every backend, a single hook manifest that both plugin and npm installs read from, and a single TTY test that decides protocol strictness. It also ships two new hooks that v5.x kept asking for (Notification, SubagentStop) and finally wires auto_capture.py into live conversation events with an automatic learning write on correction matches.
This release must be installed before NEXO Desktop v0.12.0 — Desktop consumes two new files this Brain publishes: src/resonance_tiers.json and ~/.nexo/hooks_status.json.
One Tier, One Source of Truth
v5.9.0 introduced the resonance map with four tiers (maximo / alto / medio / bajo), but the installer still asked operators for model and reasoning effort per backend, then saved them separately into schedule.json. The two sources of truth drifted — a user who picked maximo in the Desktop selector could still have a stale reasoning_effort="max" in schedule.json, and nothing enforced consistency. v5.10.1 patched the specific reasoning_effort=max case with a silent migration, but the underlying duplication was still there.
v6.0.0 removes the duplication. The installer asks one question:
¿Qué nivel de potencia quieres por defecto para tus conversaciones?
1. máximo
2. alto (recomendado)
3. medio
4. bajo
The answer writes preferences.default_resonance into brain/calibration.json. The per-backend model and effort now live in src/resonance_tiers.json, which src/resonance_map.py reads at import time via a new public load_resonance_table(). The map still distinguishes user-facing callers (nexo chat, Desktop new conversation, interactive nexo update) that honour the user's tier from system-owned callers (every cron and background script) that stay pinned at their pre-decided tier.
Legacy client_runtime_profiles.{claude_code,codex}.{model,reasoning_effort} are purged from schedule.json silently on upgrade. preferences.show_pending_at_start moves to NEXO Desktop's electron-store (Brain stops reading and writing it; Desktop ≥0.11.2 keeps the toggle in its own UI). None of these are mapped to new values — that would resurrect the reasoning_effort=max → maximo footgun we closed in v5.10.1. They are dropped, and the canonical defaults apply.
One Hook Manifest, Both Modes
Before v6.0.0 the plugin mode (hooks/hooks.json) and npm mode (bin/nexo-brain.js ALL_CORE_HOOKS) shipped different hook lists. Each release asked the maintainer to update both; they drifted several times (learnings #23 and #169 both exist because of this). v6.0.0 puts the canonical list in src/hooks/manifest.json:
{
"version": "1.0",
"hooks": [
{ "event": "SessionStart", "handler": "src/hooks/session_start.py", "critical": true },
{ "event": "UserPromptSubmit", "handler": "src/hooks/auto_capture.py", "critical": false },
{ "event": "PostToolUse", "handler": "src/hooks/post_tool_use.py", "critical": false },
{ "event": "PreCompact", "handler": "src/hooks/pre_compact.py", "critical": true },
{ "event": "Stop", "handler": "src/hooks/stop.py", "critical": true },
{ "event": "Notification", "handler": "src/hooks/notification.py", "critical": false },
{ "event": "SubagentStop", "handler": "src/hooks/subagent_stop.py", "critical": false }
]
}
Both hooks/hooks.json (plugin) and bin/nexo-brain.js registerAllCoreHooks() (npm) now build their command list from this manifest. registerAllCoreHooks() also prunes v5.x legacy shell hook commands (heartbeat-posttool.sh, protocol-guardrail.sh, inbox-hook.sh, …) from ~/.claude/settings.json while leaving user-custom hooks alone — the upgrade path, once and for all, consolidates to the manifest.
Notification and SubagentStop
Two new hooks ship:
Notification— Claude Code fires this every time the agent surfaces a notice (permission prompt, token warning, plugin alert). v6.0.0 wires it to a newhook_observability.record_activity()helper soauto_close_sessionshas a cheap "session is alive" signal. Before this, a long interactive session with no tool calls for ~30 min could be pruned by the auto-close cron as stale.SubagentStop— when a subagent terminates without callingnexo_task_close, its protocol tasks used to stayopenforever, distorting every dashboard. The new hook reads the payload, finds every openprotocol_tasksrow tied to(session_id, subagent_id), and closes them withoutcome='done'and a clear note. Idempotent by construction.
auto_capture Goes Live
auto_capture.py has existed since v4.x, but it was not connected to any hook — a cron scanned archived session logs after the fact. v6.0.0 wires it as the UserPromptSubmit handler and also pipes tool results through it from post_tool_use.py. The classifier still produces decision / correction / explicit facts, but now:
- The dedup gate is persistent. A new
auto_capture_dedupSQLite table stores content hashes (keyed byfact_type) for one hour. A corrective line repeated three times in ten minutes writes one memory, not three. - On
correctionmatches, the hook also callsnexo_learning_add(categoryauto, prioritymedium) exactly once per hash per hour. The learning lands in the same table the guard check consults — so a correction you made today starts protecting tomorrow's edits automatically, without Francisco having to type "save this as a learning" every time.
The hook never raises. If tools_learnings is unavailable (fresh install, tests), the call degrades silently and the pipeline stays up.
Protocol Strictness Follows the TTY
v5.x gave users three knobs (env, calibration preference, aliases) to tune strict/lenient/learning. Nobody used them except Francisco, and he reported confusion every time he forgot which one he had set. v6.0.0 removes the knobs:
- Interactive TTY session →
strict. - Non-TTY (cron, pipe, test, headless) →
lenient.
NEXO_PROTOCOL_STRICTNESS is gone. preferences.protocol_strictness is gone. The default/normal/off/warn/soft aliases are gone. VALID_PROTOCOL_STRICTNESS still exposes learning for internal code paths that want it, but no configuration surface touches that value. Every test and every cron inherits the right mode automatically.
Hooks Status Published for Desktop
After every registerAllCoreHooks() invocation, the installer writes ~/.nexo/hooks_status.json:
{
"generated_at": "2026-04-17T13:30:00Z",
"nexo_version": "6.0.0",
"total": 7,
"registered": 7,
"healthy": true,
"hooks": [
{"event": "SessionStart", "handler": "session_start.py", "status": "active"},
{"event": "UserPromptSubmit", "handler": "auto_capture.py", "status": "active"},
...
]
}
NEXO Desktop ≥0.12.0 reads this file to render Hooks activos X/Y in the Estado del sistema tab. If any handler file is missing on disk, its row becomes status: "error" and healthy flips to false — Desktop then shows a red dot and a one-click fix button. No more silent hook breakage between releases.
Also
nexo-brain --skipis a new flag (alias of--yes/--defaults). All three produce the same zero-prompt install with tieralto+ Deep scan + Caffeinate (macOS) + Dashboard on.- The interactive prompts for Deep scan, Caffeinate, and the Dashboard now default to yes on bare ENTER. Explicit
2ornstill turns them off. - The "What's your name?" prompt falls through to the literal
"Usuario"when the operator presses ENTER without typing anything, socalibration.user.namealways ships with a concrete value and downstream tooling can drop itsif name is Noneguards. calibration.jsonis written in the canonical nested shape on fresh installs (user.*,personality.*,preferences.*,meta.*).calibration_migration.apply_v6_purge()runs exactly once pernexo update, seedspreferences.default_resonance="alto"only when missing, and never overwrites an existing value on subsequent updates.
Tests
Seven new test modules cover the new surface: test_resonance_loader.py, test_migration_legacy_to_v6.py, test_auto_capture_correction_learning.py, test_protocol_strictness_tty.py, test_hooks_status_publish.py, test_v6_hooks_manifest_parity.py, and test_v6_fresh_install_skip.py. Full suite: 1057 passed, 1 skipped.