nexo-brain 5.9.1 → 5.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/package.json +1 -1
- package/src/agent_runner.py +123 -30
- package/src/auto_update.py +111 -0
- package/src/resonance_map.py +19 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.1",
|
|
4
4
|
"description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "NEXO Brain",
|
package/README.md
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
[Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
|
|
20
20
|
|
|
21
|
-
Version `5.
|
|
21
|
+
Version `5.10.1` is the current packaged-runtime line: silent, one-shot migration that recovers legacy `reasoning_effort="max"` (written by `nexo preferences --reasoning-effort max` before v5.9.0) into the new `preferences.default_resonance` map — any user who had configured `max` before v5.9.0 and never touched the new selector was silently falling back to `DEFAULT_RESONANCE="alto"` on interactive calls since the v5.10.0 update. Now `_run_runtime_post_sync()` runs `_migrate_effort_to_resonance()` exactly once: `max→maximo`, `xhigh→alto`, `high→medio`, `medium→bajo`. No-op when calibration or schedule already declares an explicit `default_resonance`; idempotent; conservative; never raises.
|
|
22
|
+
|
|
23
|
+
Previously in `5.10.0`: fixes the deep-sleep extract bloat that made Session 1 take ~57 minutes on some installs (new `bare_mode` on `run_automation_prompt` wires `claude --bare` for JSON-only extractor callers — ~4.3× faster per child, sourced from `ANTHROPIC_API_KEY` env or `~/.claude/anthropic-api-key.txt`). `caller=` is now **mandatory** on `run_automation_prompt` — no silent fallback; every automation subprocess traces back to a registered caller with a deliberate tier. Five personal scripts (`personal/email-monitor`, `personal/github-monitor`, `personal/post-x`, `personal/followup-runner`, `personal/orchestrator-v2`) joined the resonance map with tiers picked per caller based on what each one does. gbp/* marketing posts bumped from `medio` to `alto` (public-facing copy, quality first over speed). 65 legacy protocol debts bulk-resolved as part of the audit — the patterns that generated them are structurally closed by mandatory `caller=` + unified session log + bare_mode.
|
|
24
|
+
|
|
25
|
+
Previously in `5.9.1`: adds `default_resonance` to `brain/calibration.json` via the Desktop-facing schema (`nexo schema --json`), so NEXO Desktop's Preferences dialog renders a select with `Máximo` / `Alto (recomendado)` / `Medio` / `Bajo` automatically — no Desktop release needed. `resolve_tier_for_caller` reads calibration first and falls back to the legacy `schedule.json` location. `nexo preferences --resonance` writes both. The UI control only affects interactive sessions (`nexo chat`, Desktop new conversation, interactive `nexo update`); crons and background processes stay pinned per caller in `resonance_map.py`.
|
|
22
26
|
|
|
23
27
|
Previously in `5.9.0`: every Claude/Codex invocation now flows through a central **resonance map** and a **unified session log**. Four tiers (`MAXIMO` / `ALTO` / `MEDIO` / `BAJO`) each resolve to a concrete `(model, reasoning_effort)` pair per backend. User-facing callers (`nexo chat`, Desktop new conversation, interactive `nexo update`) honour the user's `default_resonance` preference; system-owned callers (deep-sleep, evolution, catchup, GBP posts, …) run at a fixed tier chosen per caller in `src/resonance_map.py` — the user's preference never downgrades a cron we decided needs `MAXIMO`. Unknown callers raise `UnregisteredCallerError`. Migration #41 adds `caller`, `session_type`, `started_at`, `ended_at`, `pid`, `resonance_tier` to `automation_runs`; interactive sessions record a row at spawn (with `ended_at=NULL`) and update it on close, so the Brain now has a single source of truth for every Claude/Codex call regardless of origin. New `nexo preferences --resonance` CLI. New MCP tools `nexo_session_log_create` / `nexo_session_log_close` let NEXO Desktop (which spawns `claude` directly from its TypeScript process) feed the same log.
|
|
24
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.1",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain \u2014 Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
|
|
6
6
|
"homepage": "https://nexo-brain.com",
|
package/src/agent_runner.py
CHANGED
|
@@ -792,6 +792,52 @@ def _build_enforcement_system_prompt() -> str:
|
|
|
792
792
|
return "\n".join(lines) if (must_rules or should_rules) else ""
|
|
793
793
|
|
|
794
794
|
|
|
795
|
+
_ANTHROPIC_API_KEY_SEARCH_PATHS = (
|
|
796
|
+
Path.home() / ".claude" / "anthropic-api-key.txt",
|
|
797
|
+
Path.home() / ".nexo" / "config" / "anthropic-api-key.txt",
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def _resolve_anthropic_api_key() -> str:
|
|
802
|
+
"""Locate an Anthropic API key for bare-mode invocations.
|
|
803
|
+
|
|
804
|
+
``claude --bare`` skips macOS Keychain auth entirely, so the child
|
|
805
|
+
must find the API key in ``ANTHROPIC_API_KEY`` or via ``apiKeyHelper``.
|
|
806
|
+
Rather than forcing the operator to export the key in every shell, we
|
|
807
|
+
check the two conventional NEXO locations:
|
|
808
|
+
|
|
809
|
+
~/.claude/anthropic-api-key.txt
|
|
810
|
+
~/.nexo/config/anthropic-api-key.txt
|
|
811
|
+
|
|
812
|
+
Returns the trimmed key string, or "" if none is available. Callers
|
|
813
|
+
that want to run in bare mode must fall back to non-bare execution
|
|
814
|
+
when this returns empty.
|
|
815
|
+
"""
|
|
816
|
+
env_key = os.environ.get("ANTHROPIC_API_KEY", "").strip()
|
|
817
|
+
if env_key:
|
|
818
|
+
return env_key
|
|
819
|
+
for path in _ANTHROPIC_API_KEY_SEARCH_PATHS:
|
|
820
|
+
try:
|
|
821
|
+
if path.is_file():
|
|
822
|
+
key = path.read_text().strip()
|
|
823
|
+
if key:
|
|
824
|
+
return key
|
|
825
|
+
except OSError:
|
|
826
|
+
continue
|
|
827
|
+
return ""
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
# Callers for which bare_mode=True is safe. The child's only allowed_tools
|
|
831
|
+
# must be file/grep/shell (no ``mcp__nexo__*``), otherwise --bare's opt-out
|
|
832
|
+
# of plugin sync / MCP bootstrap breaks the run. Extract/synthesize fit
|
|
833
|
+
# this profile: they read transcripts + shared-context and emit JSON, no
|
|
834
|
+
# NEXO tool calls.
|
|
835
|
+
BARE_MODE_SAFE_CALLERS: frozenset[str] = frozenset({
|
|
836
|
+
"deep-sleep/extract",
|
|
837
|
+
"deep-sleep/synthesize",
|
|
838
|
+
})
|
|
839
|
+
|
|
840
|
+
|
|
795
841
|
def run_automation_prompt(
|
|
796
842
|
prompt: str,
|
|
797
843
|
*,
|
|
@@ -807,6 +853,7 @@ def run_automation_prompt(
|
|
|
807
853
|
append_system_prompt: str = "",
|
|
808
854
|
allowed_tools: str = "",
|
|
809
855
|
extra_args: list[str] | tuple[str, ...] | None = None,
|
|
856
|
+
bare_mode: bool | None = None,
|
|
810
857
|
) -> subprocess.CompletedProcess:
|
|
811
858
|
prefs = load_client_preferences()
|
|
812
859
|
selected_backend = backend or resolve_automation_backend(preferences=prefs)
|
|
@@ -822,35 +869,40 @@ def run_automation_prompt(
|
|
|
822
869
|
reasoning_effort = profile["reasoning_effort"]
|
|
823
870
|
selected_backend = _resolve_available_backend(selected_backend, preferences=prefs)
|
|
824
871
|
|
|
825
|
-
# Resonance map
|
|
826
|
-
#
|
|
827
|
-
#
|
|
828
|
-
#
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
872
|
+
# Resonance map decides (model, effort) for every call. ``caller`` is
|
|
873
|
+
# MANDATORY — every script that invokes the automation backend must be
|
|
874
|
+
# registered in src/resonance_map.py so its reasoning budget is a
|
|
875
|
+
# deliberate choice rather than an accident of the global default.
|
|
876
|
+
#
|
|
877
|
+
# Explicit ``model`` or ``reasoning_effort`` arguments still override
|
|
878
|
+
# the mapped value (required for edge cases like the fallback JSON-
|
|
879
|
+
# conversion call inside extract.py that runs on a shorter budget).
|
|
880
|
+
from resonance_map import (
|
|
881
|
+
resolve_model_and_effort,
|
|
882
|
+
resolve_tier_for_caller,
|
|
883
|
+
UnregisteredCallerError,
|
|
884
|
+
)
|
|
885
|
+
if not caller:
|
|
886
|
+
raise UnregisteredCallerError(
|
|
887
|
+
"run_automation_prompt requires caller=. Register a new entry in "
|
|
888
|
+
"src/resonance_map.py under USER_FACING_CALLERS or "
|
|
889
|
+
"SYSTEM_OWNED_CALLERS and pass the id here."
|
|
890
|
+
)
|
|
891
|
+
user_default = ""
|
|
892
|
+
if isinstance(prefs, dict):
|
|
893
|
+
user_default = str(prefs.get("default_resonance") or "").strip()
|
|
894
|
+
# This raises UnregisteredCallerError if caller is unknown — the
|
|
895
|
+
# same fail-closed rule we wanted. No silent fallback.
|
|
896
|
+
resonance_tier = resolve_tier_for_caller(
|
|
897
|
+
caller, user_default=user_default or None
|
|
898
|
+
)
|
|
899
|
+
mapped_model, mapped_effort = resolve_model_and_effort(
|
|
900
|
+
caller, selected_backend, user_default=user_default or None
|
|
901
|
+
)
|
|
902
|
+
if mapped_model and not model:
|
|
903
|
+
model = mapped_model
|
|
904
|
+
if mapped_effort and not reasoning_effort:
|
|
905
|
+
reasoning_effort = mapped_effort
|
|
854
906
|
|
|
855
907
|
enforcement_fragment = _build_enforcement_system_prompt()
|
|
856
908
|
if enforcement_fragment:
|
|
@@ -877,6 +929,39 @@ def run_automation_prompt(
|
|
|
877
929
|
raise AutomationBackendUnavailableError(
|
|
878
930
|
"Claude Code automation backend selected but `claude` is not installed."
|
|
879
931
|
)
|
|
932
|
+
|
|
933
|
+
# bare_mode: when the caller only needs the model (no MCP tool
|
|
934
|
+
# calls, no CLAUDE.md context, no plugins) we pass Claude CLI's
|
|
935
|
+
# --bare flag. That skips hooks, LSP, plugin sync, CLAUDE.md
|
|
936
|
+
# auto-discovery, keychain reads, and background prefetches —
|
|
937
|
+
# reducing the per-invocation overhead from ~9s to ~2s on 2.1.x.
|
|
938
|
+
# Concretely: deep-sleep/extract was running 57min on Session 1
|
|
939
|
+
# because each extract attempt inherited ~15KB of NEXO CLAUDE.md
|
|
940
|
+
# system prompt it did not need. With --bare that extraction
|
|
941
|
+
# completes in seconds.
|
|
942
|
+
#
|
|
943
|
+
# Selection rules:
|
|
944
|
+
# - bare_mode=True explicit → trust the caller.
|
|
945
|
+
# - bare_mode=None (default) + caller in BARE_MODE_SAFE_CALLERS
|
|
946
|
+
# → auto-enable.
|
|
947
|
+
# - bare_mode=False → never.
|
|
948
|
+
# - --bare disables keychain auth, so we must provide an
|
|
949
|
+
# ANTHROPIC_API_KEY. If one cannot be located, fall back to
|
|
950
|
+
# normal mode with a warning on stderr rather than failing.
|
|
951
|
+
resolved_bare = False
|
|
952
|
+
if bare_mode is True:
|
|
953
|
+
resolved_bare = True
|
|
954
|
+
elif bare_mode is None and caller in BARE_MODE_SAFE_CALLERS:
|
|
955
|
+
resolved_bare = True
|
|
956
|
+
|
|
957
|
+
bare_api_key = ""
|
|
958
|
+
if resolved_bare:
|
|
959
|
+
bare_api_key = _resolve_anthropic_api_key()
|
|
960
|
+
if not bare_api_key:
|
|
961
|
+
# Silent fallback: we would rather take the slower path
|
|
962
|
+
# than force the caller to fail-closed on an env quirk.
|
|
963
|
+
resolved_bare = False
|
|
964
|
+
|
|
880
965
|
# Headless claude -p does NOT reliably honour permissions.allow from
|
|
881
966
|
# settings.json for MCP tool calls — it can stall waiting for an
|
|
882
967
|
# approval that will never come in non-interactive mode. All NEXO
|
|
@@ -885,7 +970,15 @@ def run_automation_prompt(
|
|
|
885
970
|
# so the process actually runs instead of zombying. Interactive
|
|
886
971
|
# sessions (`nexo chat`) never go through this code path and keep
|
|
887
972
|
# their normal approval prompts.
|
|
888
|
-
cmd = [claude_bin, "-p", prompt
|
|
973
|
+
cmd = [claude_bin, "-p", prompt]
|
|
974
|
+
if resolved_bare:
|
|
975
|
+
cmd.append("--bare")
|
|
976
|
+
# Guarantee the child sees the API key regardless of how the
|
|
977
|
+
# parent's env was sanitized by _headless_env.
|
|
978
|
+
run_env = dict(run_env)
|
|
979
|
+
run_env["ANTHROPIC_API_KEY"] = bare_api_key
|
|
980
|
+
else:
|
|
981
|
+
cmd.append("--dangerously-skip-permissions")
|
|
889
982
|
if resolved_model:
|
|
890
983
|
cmd.extend(["--model", resolved_model])
|
|
891
984
|
if resolved_effort:
|
package/src/auto_update.py
CHANGED
|
@@ -875,6 +875,104 @@ def _purge_zero_byte_db_files() -> list[Path]:
|
|
|
875
875
|
return removed
|
|
876
876
|
|
|
877
877
|
|
|
878
|
+
def _migrate_effort_to_resonance(dest: Path = NEXO_HOME) -> list[str]:
|
|
879
|
+
"""Auto-migrate legacy ``reasoning_effort`` preference into the new
|
|
880
|
+
``preferences.default_resonance`` knob.
|
|
881
|
+
|
|
882
|
+
Context: before v5.9.0 the user's power-level preference lived in
|
|
883
|
+
``config/schedule.json`` under
|
|
884
|
+
``client_runtime_profiles.claude_code.reasoning_effort`` — one of
|
|
885
|
+
``max`` / ``xhigh`` / ``high`` / ``medium``. v5.9.0 introduced the
|
|
886
|
+
resonance map, with ``preferences.default_resonance`` (one of
|
|
887
|
+
``maximo`` / ``alto`` / ``medio`` / ``bajo``) written to
|
|
888
|
+
``brain/calibration.json``. When the new code path kicks in, a user
|
|
889
|
+
whose only recorded preference is the legacy effort silently falls
|
|
890
|
+
back to ``DEFAULT_RESONANCE`` (``alto``), losing their prior
|
|
891
|
+
preference. v5.10.0 shipped without a migration for this; v5.10.1
|
|
892
|
+
adds it here.
|
|
893
|
+
|
|
894
|
+
The migration is idempotent and conservative: it only runs when the
|
|
895
|
+
user has NOT set ``default_resonance`` explicitly anywhere (neither
|
|
896
|
+
in ``calibration.json`` nor in ``schedule.json``). That means users
|
|
897
|
+
who already adjusted their preference through the Desktop UI or the
|
|
898
|
+
``nexo preferences --resonance`` CLI keep whatever they chose; this
|
|
899
|
+
migration only recovers the legacy preference for users who never
|
|
900
|
+
touched either.
|
|
901
|
+
|
|
902
|
+
Mapping (mirrors ``_RESONANCE_TABLE`` in ``src/resonance_map.py``):
|
|
903
|
+
max → maximo
|
|
904
|
+
xhigh → alto
|
|
905
|
+
high → medio
|
|
906
|
+
medium → bajo
|
|
907
|
+
|
|
908
|
+
Returns the list of actions taken for logging. Failures are
|
|
909
|
+
swallowed: migration must never block an update.
|
|
910
|
+
"""
|
|
911
|
+
import json as _json
|
|
912
|
+
|
|
913
|
+
actions: list[str] = []
|
|
914
|
+
|
|
915
|
+
cal_path = dest / "brain" / "calibration.json"
|
|
916
|
+
sched_path = dest / "config" / "schedule.json"
|
|
917
|
+
|
|
918
|
+
try:
|
|
919
|
+
cal = _json.loads(cal_path.read_text()) if cal_path.exists() else {}
|
|
920
|
+
if not isinstance(cal, dict):
|
|
921
|
+
cal = {}
|
|
922
|
+
except Exception:
|
|
923
|
+
cal = {}
|
|
924
|
+
|
|
925
|
+
try:
|
|
926
|
+
sched = _json.loads(sched_path.read_text()) if sched_path.exists() else {}
|
|
927
|
+
if not isinstance(sched, dict):
|
|
928
|
+
sched = {}
|
|
929
|
+
except Exception:
|
|
930
|
+
sched = {}
|
|
931
|
+
|
|
932
|
+
existing_cal_pref = ""
|
|
933
|
+
if isinstance(cal.get("preferences"), dict):
|
|
934
|
+
existing_cal_pref = str(cal["preferences"].get("default_resonance") or "").strip().lower()
|
|
935
|
+
existing_sched_pref = str(sched.get("default_resonance") or "").strip().lower()
|
|
936
|
+
|
|
937
|
+
valid_tiers = {"maximo", "alto", "medio", "bajo"}
|
|
938
|
+
if existing_cal_pref in valid_tiers or existing_sched_pref in valid_tiers:
|
|
939
|
+
return actions # user already has an explicit resonance preference
|
|
940
|
+
|
|
941
|
+
# Look up the legacy effort hint.
|
|
942
|
+
effort = ""
|
|
943
|
+
profiles = sched.get("client_runtime_profiles")
|
|
944
|
+
if isinstance(profiles, dict):
|
|
945
|
+
cc = profiles.get("claude_code") if isinstance(profiles.get("claude_code"), dict) else {}
|
|
946
|
+
effort = str(cc.get("reasoning_effort") or "").strip().lower()
|
|
947
|
+
|
|
948
|
+
legacy_map = {
|
|
949
|
+
"max": "maximo",
|
|
950
|
+
"xhigh": "alto",
|
|
951
|
+
"high": "medio",
|
|
952
|
+
"medium": "bajo",
|
|
953
|
+
}
|
|
954
|
+
target_tier = legacy_map.get(effort)
|
|
955
|
+
if not target_tier:
|
|
956
|
+
return actions # nothing usable to migrate from
|
|
957
|
+
|
|
958
|
+
# Write into calibration.json (canonical location for v5.9.1+).
|
|
959
|
+
try:
|
|
960
|
+
cal_path.parent.mkdir(parents=True, exist_ok=True)
|
|
961
|
+
prefs = cal.get("preferences")
|
|
962
|
+
if not isinstance(prefs, dict):
|
|
963
|
+
prefs = {}
|
|
964
|
+
prefs["default_resonance"] = target_tier
|
|
965
|
+
cal["preferences"] = prefs
|
|
966
|
+
cal_path.write_text(_json.dumps(cal, indent=2, ensure_ascii=False) + "\n")
|
|
967
|
+
actions.append(
|
|
968
|
+
f"resonance-migration:{effort}->{target_tier}"
|
|
969
|
+
)
|
|
970
|
+
except Exception as exc:
|
|
971
|
+
actions.append(f"resonance-migration-warning:{exc.__class__.__name__}")
|
|
972
|
+
|
|
973
|
+
return actions
|
|
974
|
+
|
|
975
|
+
|
|
878
976
|
def _heal_deep_sleep_runtime(dest: Path = NEXO_HOME) -> list[str]:
|
|
879
977
|
"""Repair deep-sleep state that older runtimes left in a bad shape.
|
|
880
978
|
|
|
@@ -2697,6 +2795,19 @@ def _run_runtime_post_sync(dest: Path = NEXO_HOME, progress_fn=None) -> tuple[bo
|
|
|
2697
2795
|
except Exception as exc:
|
|
2698
2796
|
actions.append(f"deep-sleep-heal-warning:{exc.__class__.__name__}")
|
|
2699
2797
|
|
|
2798
|
+
# Recover the user's legacy reasoning_effort preference into the new
|
|
2799
|
+
# default_resonance knob. v5.10.0 left this gap — users who had
|
|
2800
|
+
# `reasoning_effort="max"` in schedule.json silently degraded to
|
|
2801
|
+
# DEFAULT_RESONANCE=alto when the resonance map took over. This
|
|
2802
|
+
# migration restores their choice exactly once.
|
|
2803
|
+
try:
|
|
2804
|
+
_emit_progress(progress_fn, "Migrating legacy effort preference to resonance...")
|
|
2805
|
+
mig_actions = _migrate_effort_to_resonance(dest)
|
|
2806
|
+
for action in mig_actions:
|
|
2807
|
+
actions.append(action)
|
|
2808
|
+
except Exception as exc:
|
|
2809
|
+
actions.append(f"resonance-migration-warning:{exc.__class__.__name__}")
|
|
2810
|
+
|
|
2700
2811
|
_emit_progress(progress_fn, "Verifying runtime imports...")
|
|
2701
2812
|
verify = subprocess.run(
|
|
2702
2813
|
[sys.executable, "-c", "import server"],
|
package/src/resonance_map.py
CHANGED
|
@@ -143,14 +143,25 @@ SYSTEM_OWNED_CALLERS: dict[str, str] = {
|
|
|
143
143
|
"tools/drive_search": "medio",
|
|
144
144
|
|
|
145
145
|
# ---- Marketing automation ---------------------------------------------
|
|
146
|
-
# These
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
"gbp/
|
|
151
|
-
"gbp/
|
|
152
|
-
"gbp/
|
|
153
|
-
"gbp/
|
|
146
|
+
# These post to Google Business Profile on behalf of Francisco's
|
|
147
|
+
# businesses. Short copy, but user-visible on a public surface; a
|
|
148
|
+
# mediocre post embarrasses the brand. Running them ALTO even though
|
|
149
|
+
# it's ~200 chars keeps the output quality tight.
|
|
150
|
+
"gbp/daily_post": "alto",
|
|
151
|
+
"gbp/post_wazion": "alto",
|
|
152
|
+
"gbp/post_psicologa": "alto",
|
|
153
|
+
"gbp/monthly_audit": "alto",
|
|
154
|
+
"gbp/reviews_watch": "alto",
|
|
155
|
+
|
|
156
|
+
# ---- Personal scripts (operators' own LaunchAgents) -------------------
|
|
157
|
+
# Francisco + Maria ship the same set of personal scripts via
|
|
158
|
+
# ~/.nexo/scripts (installed per-user, not through the core manifest).
|
|
159
|
+
# They all call into mcp__nexo__* so they cannot run under --bare.
|
|
160
|
+
"personal/email-monitor": "alto", # answer real user emails, quality matters
|
|
161
|
+
"personal/github-monitor": "alto", # reason about issues/PRs, not mechanical
|
|
162
|
+
"personal/post-x": "alto", # public-facing copy
|
|
163
|
+
"personal/followup-runner": "alto", # executes due followups, output is user-visible
|
|
164
|
+
"personal/orchestrator-v2": "maximo", # autonomous orchestration, critical reasoning
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
ALL_REGISTERED_CALLERS: frozenset[str] = frozenset(
|