The two problems this release closes
First: before v5.9.0 every script that called Claude or Codex picked its own (model, reasoning_effort) pair — either explicitly or by falling back to the global defaults. That meant nexo chat (a human waiting at a terminal, reasoning budget matters) and a 4am daily GBP post (generate 200 chars of marketing copy, run it cheap) shared the same tier. Changing one moved both.
Second: the interactive surfaces — nexo chat and NEXO Desktop's “new conversation” — bypassed automation_runs entirely because they spawn claude directly to keep interactive stdio. The Brain had no record of when the user actually talked to Claude. Every cron was in the log; every user conversation was a blind spot.
The resonance map
New file: src/resonance_map.py. Four tiers (MAXIMO / ALTO / MEDIO / BAJO) each map to a concrete (model, reasoning_effort) pair per backend. Today all four point at Opus 4.7 (1M) for Claude and GPT-5.4 for Codex, with the effort knob doing the tier differentiation (max / xhigh / high / medium). Future backends with different model families just update this one table.
Every caller is registered in one of two dicts. USER_FACING_CALLERS holds three entries: nexo_chat, desktop_new_session, nexo_update_interactive. These read the user's default_resonance preference at runtime. SYSTEM_OWNED_CALLERS holds everything else: crons, background scripts, tool helpers. Each one is pinned to a tier we picked per caller — the user's preference cannot downgrade it. deep-sleep/synthesize runs MAXIMO because cross-session synthesis benefits from every token of reasoning. deep-sleep/extract runs ALTO. gbp/* marketing posts run MEDIO (short copy but user-visible, we don't want embarrassing outputs from low-effort).
Unknown callers raise UnregisteredCallerError. There is no silent default tier. Every automation call is auditable back to a named, tiered caller.
The unified session log
Migration #41 extends automation_runs with six columns: caller, session_type (headless / interactive_chat / interactive_desktop), started_at, ended_at, pid, resonance_tier. Idempotent: _migrate_add_column is a no-op on existing columns, pre-v5.9.0 rows get empty values.
The monolithic _record_automation_run() splits into _record_automation_start() (INSERT with ended_at=NULL, return row_id) + _record_automation_end() (UPDATE by row_id, or fall back to INSERT if start failed). Long-running jobs and interactive sessions now show up in the log while they are still in flight.
run_automation_interactive() is a new sibling of run_automation_prompt. It spawns an interactive child with the user's terminal stdio inherited, records the row at spawn, updates it on exit. launch_interactive_client() (the path nexo chat uses) now routes through it.
For NEXO Desktop, which spawns claude from its TypeScript process without touching agent_runner, two new MCP tools expose the same start/end surface: nexo_session_log_create and nexo_session_log_close. Desktop calls create before spawning, stores the returned session_id, calls close when the conversation ends.
Changing your default
New CLI subcommand:
• nexo preferences --resonance maximo to set.
• nexo preferences --show to read.
• nexo preferences --resonance --json for programmatic output.
Writes default_resonance into schedule.json. The three user-facing callers read it at runtime through the resonance map. Default default remains ALTO.
What this unlocks
A single query now answers “what did NEXO do with Claude today, broken down by subsystem?” — including interactive chats and Desktop conversations. Tier decisions move out of individual scripts and into one reviewable file. Promoting Opus 4.7 to the next model only touches _RESONANCE_TABLE. Downgrading a chatty cron from ALTO to MEDIO is one line change that ripples nowhere else.
Upgrading
Run nexo update. Migration #41 runs on the next boot (no-op on pre-v5.8.x installs because the columns did not exist yet; additive on v5.8.x). Previously-working crons keep working — the 13 updated in this release now pass caller=, the rest either still ride the legacy task_profile path or will be migrated in a follow-up pass along with Maria's personal scripts.