v5.4.0 — Calibration migration + runtime events + notify, health, logs

v5.3.30 gave external UIs a static description of the Brain — schema, identity, onboarding, profile. That unblocks rendering, but it does not let a UI react to anything. A user interface that can only read configuration is still a waiting room.

v5.4.0 closes that gap. There is now a runtime event bus, a snapshot of subsystem health, a single way to tail logs, and a migration that quietly cleans up older installs before they ossify around a legacy shape.

1. Calibration migration, silent and reversible

Older NEXO installs wrote calibration.json with flat top-level keys — user_name, autonomy, role, and so on. Newer installs nest those inside user.*, personality.*, preferences.*, and meta.*. Both shapes worked, but every downstream client had to re-implement the probe — and some wrote back to the wrong place.

v5.4.0 ships a migration that runs automatically inside nexo update, once per user, with a pre-migrate backup (calibration.json.pre-migrate-5.4.0). Unknown keys are preserved under legacy_unmapped. If the write step fails, the backup is restored. If the shape is already nested, the migration is a no-op. You can also run it explicitly with nexo doctor --migrate-calibration, or preview it with --calibration-dry-run.

The loader in user_context.py still reads both shapes, so nothing breaks during the release window. The flat shape just goes extinct naturally.

2. ~/.nexo/runtime/events.ndjson — an append-only runtime bus

The new event bus is deliberately simple: one JSON object per line, monotonic id, unix ts, a stable envelope {id, ts, type, priority, text, reason, source, extra}. Writes are protected by an fcntl lock; the file rotates automatically at 5 MB. Any UI can tail it the same way tail -f does.

Event types are intentionally few and stable:

Priorities are low | normal | high | urgent. That is the whole contract.

3. nexo notify — one-shot emitter

nexo notify proactive_message --text "An important email from Maria just arrived, should we look?" --reason "email-alerts" --priority high --json returns the full event. That is what a recovery script or followup runner calls when it needs the user's attention, instead of silently modifying state and hoping someone notices.

4. nexo health --json — one snapshot, all subsystems

Runtime, database, crons, MCP wiring, recent errors, and the event bus — rolled up into a single status: ok | degraded | error. A panel in NEXO Desktop can render that in red/yellow/green without stack traces. A CI cron can diff successive snapshots without teaching itself where every log lives.

5. nexo logs --tail — structured log access

Last N entries across the event bus and ~/.nexo/operations/*.log, JSON by default. Point at a specific file with --source. This is the line between "Maria asks Francisco to open a terminal" and "Maria reads the error herself from Desktop." One command, two consumers.

What stays the same

No existing commands changed behavior. The MCP server, crons, hooks, and startup contract are untouched. This release is additive surface plus one quiet, safe migration that brings older installs into line with the canonical schema.

Update

npm update -g nexo-brain
nexo update

If the install was already on the nested calibration shape, update stays boring. If it was on the flat shape, you will see one extra line: calibration.json migrated flat → nested. Boring is the feature.