The three cuts this release closes
v5.9.x introduced the resonance map (four tiers, explicit per-caller assignment) and the nexo preferences --resonance selector. What was still loose:
1. Deep-sleep extract was slow. Each claude -p child reloaded an 11 KB ~/.claude/CLAUDE.md bootstrap, synced plugins, probed the macOS keychain, primed background prefetches. About 7 s of overhead before producing a single token. With 20 sessions to extract, Session 1 alone could take 57 minutes on some installs.
2. caller= was still optional. run_automation_prompt fell back to the legacy task_profile path whenever a caller was not registered, so a sloppily-written new script could inherit the wrong reasoning budget silently.
3. Operators' personal scripts were not on the map. email-monitor, followup-runner, github-monitor, post-x, orchestrator-v2 — they all invoke the automation backend and were being resolved via the global default.
bare_mode for JSON-only callers
Claude CLI 2.1.x has a --bare flag that skips CLAUDE.md auto-discovery, hook execution, LSP, plugin sync, background prefetches, and keychain reads. It is exactly the right shape for an extractor that only needs Read,Grep,Bash to crunch a transcript into JSON. The tradeoff: --bare disables keychain auth too, so the child must see ANTHROPIC_API_KEY in its environment.
v5.10.0 wires the whole path in src/agent_runner.py:
• New bare_mode kwarg on run_automation_prompt (default None = auto).
• New BARE_MODE_SAFE_CALLERS frozenset: deep-sleep/extract and deep-sleep/synthesize. Auto-enabled when a key is resolvable.
• New _resolve_anthropic_api_key() looks at ANTHROPIC_API_KEY env → ~/.claude/anthropic-api-key.txt → ~/.nexo/config/anthropic-api-key.txt.
• If bare mode was requested but no key is available, the call falls back silently to the normal path. No hard failure.
Measured on the reference install: a single call dropped from ~9.5 s to ~2.2 s. About 4.3× faster per child.
caller= is mandatory now
run_automation_prompt raises UnregisteredCallerError when called without a caller argument or with a caller that is not in resonance_map.py. No silent fallback to the task profile. Every automation subprocess is traceable to a named tier, by construction.
This is a breaking change for any external script that still calls into the motor without a registered caller. The 13 callers updated in v5.9.0 were re-verified. The 5 personal scripts joined the map and were patched to pass their caller=. Adding a new script is now a deliberate act: register an entry in SYSTEM_OWNED_CALLERS, pick a tier, pass caller= at the call site.
Personal scripts on the map
The five operator-owned LaunchAgents (Francisco ships all five, María ships two of them) now show up in SYSTEM_OWNED_CALLERS:
• personal/email-monitor → alto (answers real user emails; quality matters)
• personal/github-monitor → alto (reasons about issues/PRs)
• personal/post-x → alto (public-facing copy)
• personal/followup-runner → alto (executes due followups; output user-visible)
• personal/orchestrator-v2 → maximo (autonomous orchestration; critical reasoning)
All five use mcp__nexo__* tools, so none are bare-mode-safe.
Quality-first tier review
A pass through the map re-evaluated the gbp/* marketing callers. They produce ~200 chars of public-facing copy — cheap is tempting but a mediocre post embarrasses the brand. All five gbp callers moved from medio to alto. No other tiers changed.
65 legacy protocol debts closed
The protocol layer had accumulated 65 open debts between 2026-04-13 and 2026-04-17 (missing_cortex_evaluation, unacknowledged_guard_blocking, release_channel_alignment_incomplete, codex_session_missing_startup, claimed_done_without_evidence). The patterns that generated them are now structurally closed by mandatory caller= + the unified session log + bare_mode. Bulk-resolved with a shared reference to this audit. The raw records stay in the log; the doctor's open-debt count drops to zero.
Upgrading
Run nexo update. Migration-free — nothing to run at schema level. The new run_automation_prompt signature is backward-compatible for any caller already passing caller= (every shipped script in the manifest does). Any third-party script that still calls without caller= needs to register an entry in resonance_map.py and pass the id.