nexo-brain 7.30.29 → 7.30.31
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/bin/nexo-brain.js +14 -0
- package/package.json +1 -1
- package/src/auto_update.py +16 -0
- package/src/db/_schema.py +20 -1
- package/src/plugins/core_rules.py +180 -116
- package/src/plugins/cortex.py +150 -16
- package/src/rules/core-rules.json +342 -4
- package/templates/CLAUDE.md.template +16 -0
- package/templates/CODEX.AGENTS.md.template +16 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.31",
|
|
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 `7.30.
|
|
21
|
+
Version `7.30.31` is the current packaged-runtime line. Patch release over v7.30.30 - Core Rules now reach agents both through a compact managed bootstrap summary and task-specific `cortex/task_open` injection from the protected `core_rules` registry.
|
|
22
|
+
|
|
23
|
+
Previously in `7.30.30`: product-managed Core Rules now sync from `src/rules/core-rules.json` into protected DB rows for bootstrap and product behavior, with provenance, hashes, severity, and install/update synchronization.
|
|
24
|
+
|
|
25
|
+
Previously in `7.30.29`: runtime disk guards now bound hourly database backups and pause Local Memory indexing before disk pressure becomes unsafe.
|
|
22
26
|
|
|
23
27
|
Previously in `7.30.27`: patch release over v7.30.26 - post-update repair now recovers core scripts archived by older F0.6 shim reconciliation and refreshes `core/current` from `core`, so same-version snapshots cannot keep stale watchdog code.
|
|
24
28
|
|
package/bin/nexo-brain.js
CHANGED
|
@@ -3403,6 +3403,20 @@ async function runSetup() {
|
|
|
3403
3403
|
if (fs.existsSync(rulesSrc)) {
|
|
3404
3404
|
copyDirRec(rulesSrc, rulesDest);
|
|
3405
3405
|
log(" Rules updated.");
|
|
3406
|
+
const rulesSyncPython = findVenvPython(NEXO_HOME) || "python3";
|
|
3407
|
+
const rulesSync = spawnSync(rulesSyncPython, [
|
|
3408
|
+
"-c",
|
|
3409
|
+
"import os,sys; sys.path.insert(0, os.environ['NEXO_CODE']); from plugins.core_rules import _sync_rules_from_json; print(_sync_rules_from_json().get('active_total', 0))"
|
|
3410
|
+
], {
|
|
3411
|
+
cwd: srcDir,
|
|
3412
|
+
env: { ...process.env, NEXO_HOME, NEXO_CODE: srcDir, PYTHONPATH: srcDir },
|
|
3413
|
+
encoding: "utf8",
|
|
3414
|
+
timeout: 30000,
|
|
3415
|
+
});
|
|
3416
|
+
if (rulesSync.status !== 0) {
|
|
3417
|
+
throw new Error(`Core rules registry sync failed: ${rulesSync.stderr || rulesSync.stdout || "unknown error"}`);
|
|
3418
|
+
}
|
|
3419
|
+
log(` Core rules registry synced (${String(rulesSync.stdout || "").trim() || "0"} active).`);
|
|
3406
3420
|
}
|
|
3407
3421
|
|
|
3408
3422
|
// Update crons (manifest.json + sync.py — needed by catchup & watchdog)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexo-brain",
|
|
3
|
-
"version": "7.30.
|
|
3
|
+
"version": "7.30.31",
|
|
4
4
|
"mcpName": "io.github.wazionapps/nexo",
|
|
5
5
|
"description": "NEXO Brain — 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/auto_update.py
CHANGED
|
@@ -2593,6 +2593,7 @@ def _run_db_migrations() -> bool:
|
|
|
2593
2593
|
applied = run_migrations(conn)
|
|
2594
2594
|
if applied > 0:
|
|
2595
2595
|
_log(f"Applied {applied} DB migration(s)")
|
|
2596
|
+
_sync_core_rules_registry()
|
|
2596
2597
|
# Plan Consolidado F1 — one-shot legacy email config migration.
|
|
2597
2598
|
# After m46 adds the table, operators installed pre-v6.4.0 still
|
|
2598
2599
|
# keep their data inside ~/.nexo/nexo-email/config.json. If the
|
|
@@ -2620,6 +2621,21 @@ def _run_db_migrations() -> bool:
|
|
|
2620
2621
|
return False
|
|
2621
2622
|
|
|
2622
2623
|
|
|
2624
|
+
def _sync_core_rules_registry() -> None:
|
|
2625
|
+
"""Keep packaged product-core rules installed in the Brain DB."""
|
|
2626
|
+
try:
|
|
2627
|
+
from plugins.core_rules import _sync_rules_from_json
|
|
2628
|
+
result = _sync_rules_from_json()
|
|
2629
|
+
if result.get("status") == "applied":
|
|
2630
|
+
_log(
|
|
2631
|
+
"Core rules registry synced: "
|
|
2632
|
+
f"v{result.get('version_from')} -> v{result.get('version_to')} "
|
|
2633
|
+
f"({result.get('active_total')} active)"
|
|
2634
|
+
)
|
|
2635
|
+
except Exception as exc:
|
|
2636
|
+
raise RuntimeError(f"core rules registry sync failed: {exc}") from exc
|
|
2637
|
+
|
|
2638
|
+
|
|
2623
2639
|
def _maybe_migrate_legacy_email_config() -> None:
|
|
2624
2640
|
"""F1 auto-migrator — idempotent. Runs the helper script the first
|
|
2625
2641
|
time after v6.4.0 lands on an existing runtime."""
|
package/src/db/_schema.py
CHANGED
|
@@ -288,7 +288,13 @@ def _m15_core_rules_tables(conn):
|
|
|
288
288
|
type TEXT NOT NULL DEFAULT 'advisory',
|
|
289
289
|
added_in TEXT DEFAULT '',
|
|
290
290
|
removed_in TEXT DEFAULT NULL,
|
|
291
|
-
is_active INTEGER NOT NULL DEFAULT 1
|
|
291
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
292
|
+
source_artifact TEXT DEFAULT '',
|
|
293
|
+
source_anchor TEXT DEFAULT '',
|
|
294
|
+
content_hash TEXT DEFAULT '',
|
|
295
|
+
protected INTEGER NOT NULL DEFAULT 1,
|
|
296
|
+
severity TEXT DEFAULT 'critical',
|
|
297
|
+
replacement_rule_id TEXT DEFAULT NULL
|
|
292
298
|
)
|
|
293
299
|
""")
|
|
294
300
|
conn.execute("""
|
|
@@ -3063,6 +3069,18 @@ def _m80_opportunity_orchestrator(conn):
|
|
|
3063
3069
|
_migrate_add_index(conn, "idx_nexo_authorizations_scope", "nexo_action_authorizations", "scope, allowed_action_class")
|
|
3064
3070
|
|
|
3065
3071
|
|
|
3072
|
+
def _m81_core_rules_product_metadata(conn):
|
|
3073
|
+
"""Add product-core provenance and protection metadata to core_rules."""
|
|
3074
|
+
_m15_core_rules_tables(conn)
|
|
3075
|
+
_migrate_add_column(conn, "core_rules", "source_artifact", "TEXT DEFAULT ''")
|
|
3076
|
+
_migrate_add_column(conn, "core_rules", "source_anchor", "TEXT DEFAULT ''")
|
|
3077
|
+
_migrate_add_column(conn, "core_rules", "content_hash", "TEXT DEFAULT ''")
|
|
3078
|
+
_migrate_add_column(conn, "core_rules", "protected", "INTEGER NOT NULL DEFAULT 1")
|
|
3079
|
+
_migrate_add_column(conn, "core_rules", "severity", "TEXT DEFAULT 'critical'")
|
|
3080
|
+
_migrate_add_column(conn, "core_rules", "replacement_rule_id", "TEXT DEFAULT NULL")
|
|
3081
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_protected ON core_rules(protected, is_active)")
|
|
3082
|
+
|
|
3083
|
+
|
|
3066
3084
|
MIGRATIONS = [
|
|
3067
3085
|
(1, "learnings_columns", _m1_learnings_columns),
|
|
3068
3086
|
(2, "followups_reasoning", _m2_followups_reasoning),
|
|
@@ -3144,6 +3162,7 @@ MIGRATIONS = [
|
|
|
3144
3162
|
(78, "operational_closure_plane", _m78_operational_closure_plane),
|
|
3145
3163
|
(79, "operational_closure_links_readiness", _m79_operational_closure_links_readiness),
|
|
3146
3164
|
(80, "opportunity_orchestrator", _m80_opportunity_orchestrator),
|
|
3165
|
+
(81, "core_rules_product_metadata", _m81_core_rules_product_metadata),
|
|
3147
3166
|
]
|
|
3148
3167
|
|
|
3149
3168
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Core Rules plugin — query and manage versioned behavioral rules."""
|
|
2
2
|
|
|
3
|
+
import hashlib
|
|
3
4
|
import json
|
|
4
5
|
import os
|
|
5
6
|
|
|
@@ -9,54 +10,167 @@ def _get_db():
|
|
|
9
10
|
return get_db()
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
except Exception:
|
|
19
|
-
# Table doesn't exist yet — create it
|
|
20
|
-
conn.execute("""CREATE TABLE IF NOT EXISTS core_rules (
|
|
21
|
-
id TEXT PRIMARY KEY, category TEXT NOT NULL, rule TEXT NOT NULL,
|
|
22
|
-
why TEXT NOT NULL, importance INTEGER NOT NULL DEFAULT 3,
|
|
23
|
-
type TEXT NOT NULL DEFAULT 'advisory', added_in TEXT DEFAULT '',
|
|
24
|
-
removed_in TEXT DEFAULT NULL, is_active INTEGER NOT NULL DEFAULT 1)""")
|
|
25
|
-
conn.execute("""CREATE TABLE IF NOT EXISTS core_rules_version (
|
|
26
|
-
id INTEGER PRIMARY KEY, version TEXT NOT NULL, updated_at TEXT NOT NULL)""")
|
|
27
|
-
conn.execute("INSERT OR IGNORE INTO core_rules_version (id, version, updated_at) VALUES (1, '0.0.0', datetime('now'))")
|
|
28
|
-
conn.commit()
|
|
29
|
-
count = 0
|
|
30
|
-
if count > 0:
|
|
31
|
-
return
|
|
13
|
+
def _rules_file_path() -> str:
|
|
14
|
+
return os.path.join(
|
|
15
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
16
|
+
"rules",
|
|
17
|
+
"core-rules.json",
|
|
18
|
+
)
|
|
32
19
|
|
|
33
|
-
rules_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
34
|
-
"rules", "core-rules.json")
|
|
35
|
-
if not os.path.exists(rules_file):
|
|
36
|
-
print(f"[core_rules] WARNING: {rules_file} not found, skipping seed", file=sys.stderr)
|
|
37
|
-
return
|
|
38
20
|
|
|
21
|
+
def _load_rules_data() -> dict:
|
|
22
|
+
with open(_rules_file_path()) as f:
|
|
23
|
+
return json.load(f)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _rule_hash(rule: dict, category: str) -> str:
|
|
27
|
+
payload = {
|
|
28
|
+
"category": category,
|
|
29
|
+
"id": rule.get("id", ""),
|
|
30
|
+
"rule": rule.get("rule", ""),
|
|
31
|
+
"why": rule.get("why", ""),
|
|
32
|
+
"importance": rule.get("importance", 0),
|
|
33
|
+
"type": rule.get("type", ""),
|
|
34
|
+
"source_artifact": rule.get("source_artifact", ""),
|
|
35
|
+
"source_anchor": rule.get("source_anchor", ""),
|
|
36
|
+
}
|
|
37
|
+
raw = json.dumps(payload, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
|
|
38
|
+
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _ensure_schema(conn):
|
|
42
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS core_rules (
|
|
43
|
+
id TEXT PRIMARY KEY, category TEXT NOT NULL, rule TEXT NOT NULL,
|
|
44
|
+
why TEXT NOT NULL, importance INTEGER NOT NULL DEFAULT 3,
|
|
45
|
+
type TEXT NOT NULL DEFAULT 'advisory', added_in TEXT DEFAULT '',
|
|
46
|
+
removed_in TEXT DEFAULT NULL, is_active INTEGER NOT NULL DEFAULT 1,
|
|
47
|
+
source_artifact TEXT DEFAULT '', source_anchor TEXT DEFAULT '',
|
|
48
|
+
content_hash TEXT DEFAULT '', protected INTEGER NOT NULL DEFAULT 1,
|
|
49
|
+
severity TEXT DEFAULT 'critical', replacement_rule_id TEXT DEFAULT NULL)""")
|
|
50
|
+
conn.execute("""CREATE TABLE IF NOT EXISTS core_rules_version (
|
|
51
|
+
id INTEGER PRIMARY KEY, version TEXT NOT NULL, updated_at TEXT NOT NULL)""")
|
|
52
|
+
for column, ddl in (
|
|
53
|
+
("source_artifact", "TEXT DEFAULT ''"),
|
|
54
|
+
("source_anchor", "TEXT DEFAULT ''"),
|
|
55
|
+
("content_hash", "TEXT DEFAULT ''"),
|
|
56
|
+
("protected", "INTEGER NOT NULL DEFAULT 1"),
|
|
57
|
+
("severity", "TEXT DEFAULT 'critical'"),
|
|
58
|
+
("replacement_rule_id", "TEXT DEFAULT NULL"),
|
|
59
|
+
):
|
|
60
|
+
try:
|
|
61
|
+
existing = {row[1] for row in conn.execute("PRAGMA table_info(core_rules)").fetchall()}
|
|
62
|
+
if column not in existing:
|
|
63
|
+
conn.execute(f"ALTER TABLE core_rules ADD COLUMN {column} {ddl}")
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
conn.execute("INSERT OR IGNORE INTO core_rules_version (id, version, updated_at) VALUES (1, '0.0.0', datetime('now'))")
|
|
67
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_category ON core_rules(category)")
|
|
68
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_active ON core_rules(is_active)")
|
|
69
|
+
conn.execute("CREATE INDEX IF NOT EXISTS idx_core_rules_protected ON core_rules(protected, is_active)")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _flatten_rules(data: dict) -> dict[str, dict]:
|
|
73
|
+
version = data["_meta"]["version"]
|
|
74
|
+
rules = {}
|
|
75
|
+
for cat_key, cat in data["categories"].items():
|
|
76
|
+
for rule in cat["rules"]:
|
|
77
|
+
severity = rule.get("severity") or ("critical" if rule.get("type") == "blocking" and int(rule.get("importance") or 0) >= 5 else "high")
|
|
78
|
+
rules[rule["id"]] = {
|
|
79
|
+
**rule,
|
|
80
|
+
"category": cat_key,
|
|
81
|
+
"added_in": rule.get("added_in", version),
|
|
82
|
+
"content_hash": _rule_hash(rule, cat_key),
|
|
83
|
+
"protected": 0 if rule.get("protected") is False else 1,
|
|
84
|
+
"severity": severity,
|
|
85
|
+
"source_artifact": rule.get("source_artifact", "core-rules.json"),
|
|
86
|
+
"source_anchor": rule.get("source_anchor", rule["id"]),
|
|
87
|
+
"replacement_rule_id": rule.get("replacement_rule_id"),
|
|
88
|
+
}
|
|
89
|
+
return rules
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _sync_rules_from_json(conn=None, dry_run: bool = False) -> dict:
|
|
93
|
+
conn = conn or _get_db()
|
|
94
|
+
_ensure_schema(conn)
|
|
95
|
+
data = _load_rules_data()
|
|
96
|
+
new_version = data["_meta"]["version"]
|
|
97
|
+
json_rules = _flatten_rules(data)
|
|
98
|
+
|
|
99
|
+
current_version_row = conn.execute("SELECT version FROM core_rules_version WHERE id = 1").fetchone()
|
|
100
|
+
current_version = current_version_row[0] if current_version_row else "0.0.0"
|
|
101
|
+
db_rows = {
|
|
102
|
+
row["id"]: dict(row)
|
|
103
|
+
for row in conn.execute("SELECT * FROM core_rules WHERE is_active = 1").fetchall()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
added = set(json_rules) - set(db_rows)
|
|
107
|
+
removed = set(db_rows) - set(json_rules)
|
|
108
|
+
changed = {
|
|
109
|
+
rid
|
|
110
|
+
for rid in set(json_rules) & set(db_rows)
|
|
111
|
+
if (db_rows[rid].get("content_hash") or "") != json_rules[rid]["content_hash"]
|
|
112
|
+
or current_version != new_version
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
result = {
|
|
116
|
+
"version_from": current_version,
|
|
117
|
+
"version_to": new_version,
|
|
118
|
+
"added": sorted(added),
|
|
119
|
+
"removed": sorted(removed),
|
|
120
|
+
"changed": sorted(changed),
|
|
121
|
+
"active_total": len(json_rules),
|
|
122
|
+
"dry_run": dry_run,
|
|
123
|
+
}
|
|
124
|
+
if dry_run:
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
for rid in sorted(added | changed):
|
|
128
|
+
r = json_rules[rid]
|
|
129
|
+
conn.execute(
|
|
130
|
+
"""INSERT OR REPLACE INTO core_rules
|
|
131
|
+
(id, category, rule, why, importance, type, added_in, removed_in, is_active,
|
|
132
|
+
source_artifact, source_anchor, content_hash, protected, severity, replacement_rule_id)
|
|
133
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 1, ?, ?, ?, ?, ?, ?)""",
|
|
134
|
+
(
|
|
135
|
+
r["id"],
|
|
136
|
+
r["category"],
|
|
137
|
+
r["rule"],
|
|
138
|
+
r["why"],
|
|
139
|
+
r["importance"],
|
|
140
|
+
r["type"],
|
|
141
|
+
r["added_in"],
|
|
142
|
+
r["source_artifact"],
|
|
143
|
+
r["source_anchor"],
|
|
144
|
+
r["content_hash"],
|
|
145
|
+
r["protected"],
|
|
146
|
+
r["severity"],
|
|
147
|
+
r["replacement_rule_id"],
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
for rid in sorted(removed):
|
|
152
|
+
replacement = db_rows.get(rid, {}).get("replacement_rule_id")
|
|
153
|
+
conn.execute(
|
|
154
|
+
"UPDATE core_rules SET is_active = 0, removed_in = ?, replacement_rule_id = COALESCE(?, replacement_rule_id) WHERE id = ?",
|
|
155
|
+
(new_version, replacement, rid),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
conn.execute("UPDATE core_rules_version SET version = ?, updated_at = datetime('now') WHERE id = 1", (new_version,))
|
|
159
|
+
conn.commit()
|
|
160
|
+
result["status"] = "up_to_date" if not (added or removed or changed or current_version != new_version) else "applied"
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _sync_if_needed():
|
|
165
|
+
"""Keep installed DB rules aligned with packaged product-core JSON."""
|
|
166
|
+
import sys
|
|
167
|
+
if not os.path.exists(_rules_file_path()):
|
|
168
|
+
print(f"[core_rules] WARNING: {_rules_file_path()} not found, skipping sync", file=sys.stderr)
|
|
169
|
+
return
|
|
39
170
|
try:
|
|
40
|
-
|
|
41
|
-
data = json.load(f)
|
|
42
|
-
|
|
43
|
-
version = data["_meta"]["version"]
|
|
44
|
-
loaded = 0
|
|
45
|
-
for cat_key, cat in data["categories"].items():
|
|
46
|
-
for rule in cat["rules"]:
|
|
47
|
-
conn.execute(
|
|
48
|
-
"""INSERT OR REPLACE INTO core_rules (id, category, rule, why, importance, type, added_in)
|
|
49
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
|
50
|
-
(rule["id"], cat_key, rule["rule"], rule["why"],
|
|
51
|
-
rule["importance"], rule["type"], rule.get("added_in", version))
|
|
52
|
-
)
|
|
53
|
-
loaded += 1
|
|
54
|
-
|
|
55
|
-
conn.execute("UPDATE core_rules_version SET version = ?, updated_at = datetime('now') WHERE id = 1", (version,))
|
|
56
|
-
conn.commit()
|
|
57
|
-
print(f"[core_rules] Seeded {loaded} rules (v{version})", file=sys.stderr)
|
|
171
|
+
_sync_rules_from_json()
|
|
58
172
|
except Exception as e:
|
|
59
|
-
print(f"[core_rules] ERROR
|
|
173
|
+
print(f"[core_rules] ERROR syncing rules: {e}", file=sys.stderr)
|
|
60
174
|
|
|
61
175
|
|
|
62
176
|
def handle_rules_check(area: str = "", importance_min: int = 0) -> str:
|
|
@@ -70,21 +184,24 @@ def handle_rules_check(area: str = "", importance_min: int = 0) -> str:
|
|
|
70
184
|
Maps to categories: code→execution+integrity, delegation→delegation, etc.
|
|
71
185
|
importance_min: Minimum importance level (1-5, default 0 = all rules)
|
|
72
186
|
"""
|
|
73
|
-
|
|
187
|
+
_sync_if_needed()
|
|
74
188
|
conn = _get_db()
|
|
75
189
|
|
|
76
190
|
area_to_categories = {
|
|
77
|
-
"code": ("integrity", "execution"),
|
|
78
|
-
"edit": ("integrity", "execution"),
|
|
191
|
+
"code": ("integrity", "execution", "product_core", "bootstrap_contract"),
|
|
192
|
+
"edit": ("integrity", "execution", "product_core", "bootstrap_contract"),
|
|
79
193
|
"delegation": ("delegation",),
|
|
80
194
|
"delegate": ("delegation",),
|
|
81
195
|
"subagent": ("delegation",),
|
|
82
|
-
"communication": ("communication",),
|
|
83
|
-
"respond": ("communication",),
|
|
84
|
-
"memory": ("memory",),
|
|
85
|
-
"learn": ("memory",),
|
|
86
|
-
"proactivity": ("proactivity",),
|
|
87
|
-
"protect": ("proactivity",),
|
|
196
|
+
"communication": ("communication", "product_core"),
|
|
197
|
+
"respond": ("communication", "product_core"),
|
|
198
|
+
"memory": ("memory", "product_core", "bootstrap_contract"),
|
|
199
|
+
"learn": ("memory", "product_core"),
|
|
200
|
+
"proactivity": ("proactivity", "product_core"),
|
|
201
|
+
"protect": ("proactivity", "product_core"),
|
|
202
|
+
"support": ("product_core",),
|
|
203
|
+
"capability": ("product_core",),
|
|
204
|
+
"bootstrap": ("bootstrap_contract",),
|
|
88
205
|
}
|
|
89
206
|
|
|
90
207
|
where = "WHERE is_active = 1"
|
|
@@ -146,7 +263,7 @@ def handle_rules_list(
|
|
|
146
263
|
limit: int = 0,
|
|
147
264
|
) -> str:
|
|
148
265
|
"""List all core rules with their status, grouped by category."""
|
|
149
|
-
|
|
266
|
+
_sync_if_needed()
|
|
150
267
|
conn = _get_db()
|
|
151
268
|
|
|
152
269
|
ver = conn.execute("SELECT version FROM core_rules_version WHERE id = 1").fetchone()
|
|
@@ -202,75 +319,22 @@ def handle_rules_migrate(dry_run: bool = False) -> str:
|
|
|
202
319
|
Args:
|
|
203
320
|
dry_run: If True, show what would change without applying
|
|
204
321
|
"""
|
|
205
|
-
|
|
206
|
-
rules_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
207
|
-
"rules", "core-rules.json")
|
|
208
|
-
if not os.path.exists(rules_file):
|
|
322
|
+
if not os.path.exists(_rules_file_path()):
|
|
209
323
|
return "ERROR: core-rules.json not found"
|
|
210
|
-
|
|
211
|
-
with open(rules_file) as f:
|
|
212
|
-
data = json.load(f)
|
|
213
|
-
|
|
214
|
-
new_version = data["_meta"]["version"]
|
|
215
|
-
ver = conn.execute("SELECT version FROM core_rules_version WHERE id = 1").fetchone()
|
|
216
|
-
current_version = ver[0] if ver else "0.0.0"
|
|
217
|
-
|
|
218
|
-
# Collect all rule IDs from JSON
|
|
219
|
-
json_ids = set()
|
|
220
|
-
json_rules = {}
|
|
221
|
-
for cat_key, cat in data["categories"].items():
|
|
222
|
-
for rule in cat["rules"]:
|
|
223
|
-
json_ids.add(rule["id"])
|
|
224
|
-
json_rules[rule["id"]] = {**rule, "category": cat_key}
|
|
225
|
-
|
|
226
|
-
# Collect active IDs from DB
|
|
227
|
-
db_ids = set()
|
|
228
|
-
for r in conn.execute("SELECT id FROM core_rules WHERE is_active = 1").fetchall():
|
|
229
|
-
db_ids.add(r[0])
|
|
230
|
-
|
|
231
|
-
added = json_ids - db_ids
|
|
232
|
-
removed = db_ids - json_ids
|
|
233
|
-
unchanged = json_ids & db_ids
|
|
324
|
+
result = _sync_rules_from_json(dry_run=dry_run)
|
|
234
325
|
|
|
235
326
|
lines = [
|
|
236
|
-
f"RULES MIGRATION: v{
|
|
237
|
-
f" Added: {len(added)} — {', '.join(
|
|
238
|
-
f" Removed: {len(removed)} — {', '.join(
|
|
239
|
-
f"
|
|
327
|
+
f"RULES MIGRATION: v{result['version_from']} → v{result['version_to']}",
|
|
328
|
+
f" Added: {len(result['added'])} — {', '.join(result['added']) if result['added'] else 'none'}",
|
|
329
|
+
f" Removed: {len(result['removed'])} — {', '.join(result['removed']) if result['removed'] else 'none'}",
|
|
330
|
+
f" Changed: {len(result['changed'])} — {', '.join(result['changed']) if result['changed'] else 'none'}",
|
|
331
|
+
f" Active total: {result['active_total']}",
|
|
240
332
|
]
|
|
241
333
|
|
|
242
334
|
if dry_run:
|
|
243
335
|
lines.append(" Mode: DRY RUN (no changes applied)")
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
# Apply additions
|
|
247
|
-
for rid in added:
|
|
248
|
-
r = json_rules[rid]
|
|
249
|
-
conn.execute(
|
|
250
|
-
"""INSERT OR REPLACE INTO core_rules (id, category, rule, why, importance, type, added_in, is_active)
|
|
251
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 1)""",
|
|
252
|
-
(r["id"], r["category"], r["rule"], r["why"], r["importance"], r["type"], r.get("added_in", new_version))
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
# Apply removals (soft delete)
|
|
256
|
-
for rid in removed:
|
|
257
|
-
conn.execute(
|
|
258
|
-
"UPDATE core_rules SET is_active = 0, removed_in = ? WHERE id = ?",
|
|
259
|
-
(new_version, rid)
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
# Update existing rules (content might have changed)
|
|
263
|
-
for rid in unchanged:
|
|
264
|
-
r = json_rules[rid]
|
|
265
|
-
conn.execute(
|
|
266
|
-
"UPDATE core_rules SET rule = ?, why = ?, importance = ?, type = ?, category = ? WHERE id = ?",
|
|
267
|
-
(r["rule"], r["why"], r["importance"], r["type"], r["category"], rid)
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
conn.execute("UPDATE core_rules_version SET version = ?, updated_at = datetime('now') WHERE id = 1", (new_version,))
|
|
271
|
-
conn.commit()
|
|
272
|
-
|
|
273
|
-
lines.append(" Status: APPLIED")
|
|
336
|
+
else:
|
|
337
|
+
lines.append(f" Status: {str(result.get('status') or 'APPLIED').upper()}")
|
|
274
338
|
return "\n".join(lines)
|
|
275
339
|
|
|
276
340
|
|
package/src/plugins/cortex.py
CHANGED
|
@@ -31,25 +31,159 @@ def _get_db():
|
|
|
31
31
|
return get_db()
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
_CLASSIC_RULE_CATEGORIES_BY_TASK = {
|
|
35
|
+
"edit": ["integrity", "execution"],
|
|
36
|
+
"execute": ["integrity", "execution", "delegation"],
|
|
37
|
+
"delegate": ["delegation"],
|
|
38
|
+
"analyze": ["execution", "memory"],
|
|
39
|
+
"answer": ["communication"],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_PRODUCT_RULE_IDS_BY_TASK = {
|
|
43
|
+
"answer": [
|
|
44
|
+
"PC1", # Context before asking
|
|
45
|
+
"PC2", # Capability before delegating work to the user
|
|
46
|
+
"PC4", # Evidence before closure claims
|
|
47
|
+
"PC8", # Do not invent product capabilities
|
|
48
|
+
"PC16", # Continuity of identity and sessions
|
|
49
|
+
"PC19", # Product language, not internal jargon
|
|
50
|
+
"PC24", # Read what NEXO already wrote before acting
|
|
51
|
+
"PC25", # External state claims require live evidence
|
|
52
|
+
"PC28", # Check real capability before denying
|
|
53
|
+
"PC29", # Operational explanation stays simple
|
|
54
|
+
"PC32", # Reuse prior work before researching from zero
|
|
55
|
+
"MEMORY_AUTHORITY",
|
|
56
|
+
"IDENTITY_CONTINUITY",
|
|
57
|
+
"SAFE_AUTONOMY_FIRST",
|
|
58
|
+
"DEFERRED_TOOL_DISCOVERY",
|
|
59
|
+
],
|
|
60
|
+
"analyze": [
|
|
61
|
+
"PC1",
|
|
62
|
+
"PC2",
|
|
63
|
+
"PC5",
|
|
64
|
+
"PC16",
|
|
65
|
+
"PC24",
|
|
66
|
+
"PC25",
|
|
67
|
+
"PC28",
|
|
68
|
+
"PC31",
|
|
69
|
+
"PC32",
|
|
70
|
+
"MEMORY_AUTHORITY",
|
|
71
|
+
"CORE_SYSTEM_AWARENESS",
|
|
72
|
+
"SAFE_AUTONOMY_FIRST",
|
|
73
|
+
],
|
|
74
|
+
"edit": [
|
|
75
|
+
"PC3",
|
|
76
|
+
"PC4",
|
|
77
|
+
"PC5",
|
|
78
|
+
"PC18",
|
|
79
|
+
"PC24",
|
|
80
|
+
"PC25",
|
|
81
|
+
"PC30",
|
|
82
|
+
"PC31",
|
|
83
|
+
"PC32",
|
|
84
|
+
"RUNTIME_CORE_PROTECTED",
|
|
85
|
+
"MEMORY_AUTHORITY",
|
|
86
|
+
"SAFE_AUTONOMY_FIRST",
|
|
87
|
+
],
|
|
88
|
+
"execute": [
|
|
89
|
+
"PC2",
|
|
90
|
+
"PC3",
|
|
91
|
+
"PC4",
|
|
92
|
+
"PC10",
|
|
93
|
+
"PC11",
|
|
94
|
+
"PC12",
|
|
95
|
+
"PC13",
|
|
96
|
+
"PC14",
|
|
97
|
+
"PC15",
|
|
98
|
+
"PC17",
|
|
99
|
+
"PC25",
|
|
100
|
+
"PC26",
|
|
101
|
+
"PC27",
|
|
102
|
+
"RUNTIME_CORE_PROTECTED",
|
|
103
|
+
"SAFE_AUTONOMY_FIRST",
|
|
104
|
+
],
|
|
105
|
+
"delegate": [
|
|
106
|
+
"PC1",
|
|
107
|
+
"PC2",
|
|
108
|
+
"PC10",
|
|
109
|
+
"PC11",
|
|
110
|
+
"PC12",
|
|
111
|
+
"PC16",
|
|
112
|
+
"PC24",
|
|
113
|
+
"PC31",
|
|
114
|
+
"PC32",
|
|
115
|
+
"MEMORY_AUTHORITY",
|
|
116
|
+
"IDENTITY_CONTINUITY",
|
|
117
|
+
],
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_DEFAULT_PRODUCT_RULE_IDS = [
|
|
121
|
+
"PC1",
|
|
122
|
+
"PC2",
|
|
123
|
+
"PC4",
|
|
124
|
+
"PC24",
|
|
125
|
+
"PC25",
|
|
126
|
+
"PC28",
|
|
127
|
+
"PC32",
|
|
128
|
+
"MEMORY_AUTHORITY",
|
|
129
|
+
"SAFE_AUTONOMY_FIRST",
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _sync_core_rules_if_available() -> None:
|
|
134
|
+
try:
|
|
135
|
+
from plugins.core_rules import _sync_if_needed
|
|
136
|
+
_sync_if_needed()
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _rule_rows_for_ids(conn, ids: list[str]) -> list:
|
|
142
|
+
unique_ids = list(dict.fromkeys(ids))
|
|
143
|
+
if not unique_ids:
|
|
144
|
+
return []
|
|
145
|
+
placeholders = ",".join("?" * len(unique_ids))
|
|
146
|
+
rows = conn.execute(
|
|
147
|
+
f"""SELECT id, rule
|
|
148
|
+
FROM core_rules
|
|
149
|
+
WHERE id IN ({placeholders}) AND is_active = 1 AND type = 'blocking'""",
|
|
150
|
+
unique_ids,
|
|
151
|
+
).fetchall()
|
|
152
|
+
by_id = {row["id"]: row for row in rows}
|
|
153
|
+
return [by_id[rule_id] for rule_id in unique_ids if rule_id in by_id]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _classic_rule_rows_for_task(conn, task_type: str, excluded_ids: set[str], limit: int = 5) -> list:
|
|
157
|
+
categories = _CLASSIC_RULE_CATEGORIES_BY_TASK.get(task_type, ["integrity", "execution"])
|
|
158
|
+
placeholders = ",".join("?" * len(categories))
|
|
159
|
+
rows = conn.execute(
|
|
160
|
+
f"""SELECT id, rule
|
|
161
|
+
FROM core_rules
|
|
162
|
+
WHERE category IN ({placeholders})
|
|
163
|
+
AND is_active = 1
|
|
164
|
+
AND type = 'blocking'
|
|
165
|
+
ORDER BY importance DESC, category, id
|
|
166
|
+
LIMIT ?""",
|
|
167
|
+
[*categories, limit + len(excluded_ids)],
|
|
168
|
+
).fetchall()
|
|
169
|
+
filtered = [row for row in rows if row["id"] not in excluded_ids]
|
|
170
|
+
return filtered[:limit]
|
|
171
|
+
|
|
172
|
+
|
|
34
173
|
def _get_core_rules_for_task(task_type: str) -> list[str]:
|
|
35
174
|
"""Get relevant Core Rules for the given task type."""
|
|
36
|
-
conn = _get_db()
|
|
37
175
|
try:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
rows = conn.execute(
|
|
50
|
-
f"SELECT id, rule FROM core_rules WHERE category IN ({placeholders}) AND is_active = 1 AND type = 'blocking' ORDER BY importance DESC LIMIT 5",
|
|
51
|
-
categories
|
|
52
|
-
).fetchall()
|
|
176
|
+
_sync_core_rules_if_available()
|
|
177
|
+
conn = _get_db()
|
|
178
|
+
clean_type = str(task_type or "").strip().lower()
|
|
179
|
+
product_ids = _PRODUCT_RULE_IDS_BY_TASK.get(clean_type, _DEFAULT_PRODUCT_RULE_IDS)
|
|
180
|
+
product_rows = _rule_rows_for_ids(conn, product_ids)
|
|
181
|
+
classic_rows = _classic_rule_rows_for_task(
|
|
182
|
+
conn,
|
|
183
|
+
clean_type,
|
|
184
|
+
{row["id"] for row in product_rows},
|
|
185
|
+
)
|
|
186
|
+
rows = classic_rows + product_rows
|
|
53
187
|
return [f"{r['id']}: {r['rule']}" for r in rows]
|
|
54
188
|
except Exception:
|
|
55
189
|
return []
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_meta": {
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "NEXO Brain Core System Rules — battle-tested behavioral rules that ship with every installation",
|
|
5
5
|
"created": "2026-03-26",
|
|
6
|
-
"source": "Consolidated from
|
|
7
|
-
"total_rules":
|
|
8
|
-
"blocking":
|
|
6
|
+
"source": "Consolidated from production use, managed bootstrap CORE files, ticket/conversation analysis and product-core review",
|
|
7
|
+
"total_rules": 70,
|
|
8
|
+
"blocking": 65,
|
|
9
9
|
"advisory": 5,
|
|
10
10
|
"immutable": true,
|
|
11
11
|
"immutable_note": "Core rules are the DNA of NEXO Brain. They CANNOT be deleted or modified by the user. Only the migration system (version updates from the creators) can add, modify, or remove rules. Users can configure behavioral intensity (autonomy, communication, proactivity) but not the rules themselves."
|
|
@@ -294,6 +294,344 @@
|
|
|
294
294
|
"added_in": "1.0.0"
|
|
295
295
|
}
|
|
296
296
|
]
|
|
297
|
+
},
|
|
298
|
+
"bootstrap_contract": {
|
|
299
|
+
"label": "Managed Bootstrap Contract",
|
|
300
|
+
"description": "Rules imported from managed AGENTS.md / CLAUDE.md CORE templates",
|
|
301
|
+
"rules": [
|
|
302
|
+
{
|
|
303
|
+
"id": "CORE_USER_SEPARATION",
|
|
304
|
+
"rule": "CORE is product-managed and USER is tenant/operator-managed",
|
|
305
|
+
"why": "Product updates may rewrite CORE to ship safer defaults, but must preserve USER verbatim so personal instructions do not leak into global product rules or get overwritten.",
|
|
306
|
+
"importance": 5,
|
|
307
|
+
"type": "blocking",
|
|
308
|
+
"added_in": "1.1.0",
|
|
309
|
+
"source_artifact": "AGENTS.md / CLAUDE.md managed CORE",
|
|
310
|
+
"source_anchor": "CORE vs USER"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"id": "MEMORY_AUTHORITY",
|
|
314
|
+
"rule": "Brain, calibration and profile are authoritative over legacy client memory files",
|
|
315
|
+
"why": "Legacy MEMORY.md or client leftovers are read-only low-authority context. They must not override the shared Brain, calibration, profile, live data or scoped tenant settings.",
|
|
316
|
+
"importance": 5,
|
|
317
|
+
"type": "blocking",
|
|
318
|
+
"added_in": "1.1.0",
|
|
319
|
+
"source_artifact": "AGENTS.md / CLAUDE.md managed CORE",
|
|
320
|
+
"source_anchor": "Memory Authority"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "IDENTITY_CONTINUITY",
|
|
324
|
+
"rule": "NEXO presents one continuous operational identity across supported clients",
|
|
325
|
+
"why": "Before denying memory, authorship, a promise, an action or a result, NEXO must consult continuity sources for the same tenant and treat verified sibling-session work as its own.",
|
|
326
|
+
"importance": 5,
|
|
327
|
+
"type": "blocking",
|
|
328
|
+
"added_in": "1.1.0",
|
|
329
|
+
"source_artifact": "AGENTS.md / CLAUDE.md managed CORE",
|
|
330
|
+
"source_anchor": "Identity continuity across terminals"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"id": "SAFE_AUTONOMY_FIRST",
|
|
334
|
+
"rule": "Try safe available paths before asking the user to do work",
|
|
335
|
+
"why": "NEXO must inspect files, search context, load deferred tools, call APIs, use browsers or write small helpers when safe. The user is asked only for decisions, consent, credentials, approvals or truly unavailable information.",
|
|
336
|
+
"importance": 5,
|
|
337
|
+
"type": "blocking",
|
|
338
|
+
"added_in": "1.1.0",
|
|
339
|
+
"source_artifact": "AGENTS.md / CLAUDE.md managed CORE",
|
|
340
|
+
"source_anchor": "Professional Autonomy And Safety"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"id": "DEFERRED_TOOL_DISCOVERY",
|
|
344
|
+
"rule": "Deferred tools are discovered before being declared unavailable",
|
|
345
|
+
"why": "Clients can expose MCP tools lazily. NEXO must use tool discovery before saying a NEXO tool, capability, card, skill or connector is missing.",
|
|
346
|
+
"importance": 5,
|
|
347
|
+
"type": "blocking",
|
|
348
|
+
"added_in": "1.1.0",
|
|
349
|
+
"source_artifact": "AGENTS.md / CLAUDE.md managed CORE",
|
|
350
|
+
"source_anchor": "Tools availability at startup"
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"id": "CORE_SYSTEM_AWARENESS",
|
|
354
|
+
"rule": "Shared Brain, Deep Sleep, Evolution, Skills, Watchdog and followups are native systems",
|
|
355
|
+
"why": "NEXO must not act like these systems are optional add-ons or unknown features. Product answers and diagnostics must inspect the live subsystem before assuming absence.",
|
|
356
|
+
"importance": 4,
|
|
357
|
+
"type": "blocking",
|
|
358
|
+
"added_in": "1.1.0",
|
|
359
|
+
"source_artifact": "AGENTS.md / CLAUDE.md managed CORE",
|
|
360
|
+
"source_anchor": "Core Systems"
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"id": "RUNTIME_CORE_PROTECTED",
|
|
364
|
+
"rule": "Installed runtime core is protected; product changes ship through source repo, validation and release",
|
|
365
|
+
"why": "Ad-hoc edits inside live installed core trees create drift and break updates. Product fixes must be made in the real repo and delivered by install/update paths.",
|
|
366
|
+
"importance": 5,
|
|
367
|
+
"type": "blocking",
|
|
368
|
+
"added_in": "1.1.0",
|
|
369
|
+
"source_artifact": "AGENTS.md / CLAUDE.md managed CORE",
|
|
370
|
+
"source_anchor": "Agent Contract"
|
|
371
|
+
}
|
|
372
|
+
]
|
|
373
|
+
},
|
|
374
|
+
"product_core": {
|
|
375
|
+
"label": "Product Core Rules",
|
|
376
|
+
"description": "Tenant-generic product rules for memory, autonomy, evidence, capability and scope",
|
|
377
|
+
"rules": [
|
|
378
|
+
{
|
|
379
|
+
"id": "PC1",
|
|
380
|
+
"rule": "Context before asking",
|
|
381
|
+
"why": "Before requesting information, NEXO must search authorized context: current conversation, recent memory, profile, decisions, followups, tickets, allowed files/connectors, configuration, credentials and applicable live sources. Ask only when missing, ambiguous, changed, cross-scope, consent-bound or genuinely human-decision-bound.",
|
|
382
|
+
"importance": 5,
|
|
383
|
+
"type": "blocking",
|
|
384
|
+
"added_in": "1.1.0"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"id": "PC2",
|
|
388
|
+
"rule": "Capability before delegating work to the user",
|
|
389
|
+
"why": "If NEXO can safely do, prepare, check or diagnose something with available tools, it must do that before asking the user for manual work.",
|
|
390
|
+
"importance": 5,
|
|
391
|
+
"type": "blocking",
|
|
392
|
+
"added_in": "1.1.0"
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"id": "PC3",
|
|
396
|
+
"rule": "Prepare up to the safe boundary",
|
|
397
|
+
"why": "When a final step needs permission, NEXO still investigates, drafts, validates, simulates, prepares the patch and gathers evidence before asking for the final decision.",
|
|
398
|
+
"importance": 5,
|
|
399
|
+
"type": "blocking",
|
|
400
|
+
"added_in": "1.1.0"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"id": "PC4",
|
|
404
|
+
"rule": "Evidence before closure claims",
|
|
405
|
+
"why": "NEXO cannot say done, fixed, closed, sent, published, deployed or equivalent without concrete evidence of the real effect. Without evidence, it must say prepared, attempted, pending verification or blocked.",
|
|
406
|
+
"importance": 5,
|
|
407
|
+
"type": "blocking",
|
|
408
|
+
"added_in": "1.1.0"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"id": "PC5",
|
|
412
|
+
"rule": "Memory is useful, not absolute authority",
|
|
413
|
+
"why": "Memory reduces repetition and preserves continuity, but live sources win when state may have changed or conflicts with older memory.",
|
|
414
|
+
"importance": 5,
|
|
415
|
+
"type": "blocking",
|
|
416
|
+
"added_in": "1.1.0"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"id": "PC6",
|
|
420
|
+
"rule": "Record decisions with scope",
|
|
421
|
+
"why": "Explicit decisions must be saved with source, date, tenant/project/account and expiry when relevant, then reused until revoked, expired or contradicted by higher evidence.",
|
|
422
|
+
"importance": 5,
|
|
423
|
+
"type": "blocking",
|
|
424
|
+
"added_in": "1.1.0"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"id": "PC7",
|
|
428
|
+
"rule": "Current reality before ticket decisions",
|
|
429
|
+
"why": "Before keeping, closing, merging or answering a ticket, NEXO must verify current state, latest event, applied fix, recurrence and affected tenant. Old fixed tickets must not contaminate support or briefings.",
|
|
430
|
+
"importance": 5,
|
|
431
|
+
"type": "blocking",
|
|
432
|
+
"added_in": "1.1.0"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"id": "PC8",
|
|
436
|
+
"rule": "Do not invent product capabilities",
|
|
437
|
+
"why": "Before saying NEXO can or cannot do something, check catalog, configuration, provider, plan, permissions, tools and real documentation. Distinguish declared capability, exposed route, available model/provider, visible feature and contracted access.",
|
|
438
|
+
"importance": 5,
|
|
439
|
+
"type": "blocking",
|
|
440
|
+
"added_in": "1.1.0"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"id": "PC9",
|
|
444
|
+
"rule": "Provider-agnostic product experience",
|
|
445
|
+
"why": "For NEXO Credits and similar capabilities, users see credits, result and limits. Provider strategy remains internal unless needed, with tenant isolation, verified cost and traceability.",
|
|
446
|
+
"importance": 5,
|
|
447
|
+
"type": "blocking",
|
|
448
|
+
"added_in": "1.1.0"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"id": "PC10",
|
|
452
|
+
"rule": "Tenant scope is mandatory before action",
|
|
453
|
+
"why": "Before reading, writing, executing or using credentials, NEXO must know the tenant, account, project, host, client or surface. If unclear, ask only for that scope.",
|
|
454
|
+
"importance": 5,
|
|
455
|
+
"type": "blocking",
|
|
456
|
+
"added_in": "1.1.0"
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"id": "PC11",
|
|
460
|
+
"rule": "No cross-tenant leakage",
|
|
461
|
+
"why": "Data, prompts, credentials, configuration, metrics, emails, client details, decisions and identifiable examples must not move between tenants unless explicitly authorized and privacy-compatible.",
|
|
462
|
+
"importance": 5,
|
|
463
|
+
"type": "blocking",
|
|
464
|
+
"added_in": "1.1.0"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"id": "PC12",
|
|
468
|
+
"rule": "Minimum necessary access",
|
|
469
|
+
"why": "NEXO reads only the data, file, thread, account or record needed for the task. Broad browsing of histories, mailboxes, databases or clients is forbidden when a targeted query is enough.",
|
|
470
|
+
"importance": 5,
|
|
471
|
+
"type": "blocking",
|
|
472
|
+
"added_in": "1.1.0"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"id": "PC13",
|
|
476
|
+
"rule": "Check registered credentials before asking for secrets",
|
|
477
|
+
"why": "Before asking for keys, accounts, endpoints or tokens, NEXO checks the credential manager, authorized configuration and atlas/catalog. Existing credentials do not imply permission for sensitive use.",
|
|
478
|
+
"importance": 5,
|
|
479
|
+
"type": "blocking",
|
|
480
|
+
"added_in": "1.1.0"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
"id": "PC14",
|
|
484
|
+
"rule": "Fresh confirmation for high-risk actions",
|
|
485
|
+
"why": "Payments, budget changes, publications, third-party messages, legal/medical acts, destructive changes, credential rotation/use, DNS, production deploys, force-push, cross-tenant access and irreversible actions need explicit current confirmation.",
|
|
486
|
+
"importance": 5,
|
|
487
|
+
"type": "blocking",
|
|
488
|
+
"added_in": "1.1.0"
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"id": "PC15",
|
|
492
|
+
"rule": "Confirmation must include full scope",
|
|
493
|
+
"why": "Approval must cover action, tenant/account, environment, cost or impact, reversibility and affected recipients. A generic OK is valid only when the immediate context already contains those details unambiguously.",
|
|
494
|
+
"importance": 5,
|
|
495
|
+
"type": "blocking",
|
|
496
|
+
"added_in": "1.1.0"
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"id": "PC16",
|
|
500
|
+
"rule": "Continuity of identity and sessions",
|
|
501
|
+
"why": "All NEXO surfaces behave as one operational identity per tenant. Before denying memory, promise, authorship, action or result, NEXO checks cross-session continuity.",
|
|
502
|
+
"importance": 5,
|
|
503
|
+
"type": "blocking",
|
|
504
|
+
"added_in": "1.1.0"
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
"id": "PC17",
|
|
508
|
+
"rule": "Managed capability recovery",
|
|
509
|
+
"why": "If a tool, connector, skill, permission or credential appears missing, NEXO attempts discovery, loading, diagnosis, repair or minimal setup guidance before declaring a blocker.",
|
|
510
|
+
"importance": 5,
|
|
511
|
+
"type": "blocking",
|
|
512
|
+
"added_in": "1.1.0"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"id": "PC18",
|
|
516
|
+
"rule": "No parallel architecture before reviewing existing pieces",
|
|
517
|
+
"why": "Before adding a queue, supervisor, recovery layer, watcher, sync, prompt layer or memory surface, NEXO must locate the existing piece and justify why correcting it is not enough.",
|
|
518
|
+
"importance": 5,
|
|
519
|
+
"type": "blocking",
|
|
520
|
+
"added_in": "1.1.0"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"id": "PC19",
|
|
524
|
+
"rule": "Product language, not internal jargon",
|
|
525
|
+
"why": "NEXO translates internal states into status, evidence, blocker, impact and next action. Internal mechanics are shown only when requested or needed for a technical decision.",
|
|
526
|
+
"importance": 4,
|
|
527
|
+
"type": "blocking",
|
|
528
|
+
"added_in": "1.1.0"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
"id": "PC20",
|
|
532
|
+
"rule": "Global CORE never contains personal configuration",
|
|
533
|
+
"why": "Rules that apply to everyone live in product-core. Person, company, tenant, project, client or installation-specific facts live in scoped profile, configuration, memory or overrides.",
|
|
534
|
+
"importance": 5,
|
|
535
|
+
"type": "blocking",
|
|
536
|
+
"added_in": "1.1.0"
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
"id": "PC21",
|
|
540
|
+
"rule": "Separate human support tickets from auto-incidents",
|
|
541
|
+
"why": "A human ticket and an auto-incident are different channels. Auto-incidents must not email customers, close human conversations or generate visible replies without explicit policy or authorization.",
|
|
542
|
+
"importance": 5,
|
|
543
|
+
"type": "blocking",
|
|
544
|
+
"added_in": "1.1.0"
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"id": "PC22",
|
|
548
|
+
"rule": "Fresh installs and updates never start cold",
|
|
549
|
+
"why": "After install/update, NEXO validates identity, language, CORE, USER, operational memory, deferred tools, authorized base credentials and continuity before declaring readiness.",
|
|
550
|
+
"importance": 5,
|
|
551
|
+
"type": "blocking",
|
|
552
|
+
"added_in": "1.1.0"
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
"id": "PC23",
|
|
556
|
+
"rule": "Reuse OK within its recorded scope",
|
|
557
|
+
"why": "When the user approves an action class, NEXO stores action, scope, tenant, expiry, risk and evidence. It must not ask for the same OK again for the same scope unless risk or scope changes.",
|
|
558
|
+
"importance": 5,
|
|
559
|
+
"type": "blocking",
|
|
560
|
+
"added_in": "1.1.0"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
"id": "PC24",
|
|
564
|
+
"rule": "Read what NEXO already wrote before acting on the same topic",
|
|
565
|
+
"why": "Before acting on a topic with history, NEXO reads its own relevant diaries, memories, learnings, followups, tickets, artifacts or transcripts. Saved memory not consulted later is a product failure.",
|
|
566
|
+
"importance": 5,
|
|
567
|
+
"type": "blocking",
|
|
568
|
+
"added_in": "1.1.0"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
"id": "PC25",
|
|
572
|
+
"rule": "External state claims require live evidence",
|
|
573
|
+
"why": "Sent, submitted, paid, published, uploaded, closed, installed, verified and equivalents require source evidence. If only prepared, queued or buffered, NEXO says that instead.",
|
|
574
|
+
"importance": 5,
|
|
575
|
+
"type": "blocking",
|
|
576
|
+
"added_in": "1.1.0"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"id": "PC26",
|
|
580
|
+
"rule": "Single ledger for actions and promises",
|
|
581
|
+
"why": "Promises, sends, automations, long processes and external actions must have a consultable record shared by sessions. NEXO checks that record before repeating, denying or executing again.",
|
|
582
|
+
"importance": 5,
|
|
583
|
+
"type": "blocking",
|
|
584
|
+
"added_in": "1.1.0"
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
"id": "PC27",
|
|
588
|
+
"rule": "Pending instructions create actionable followups",
|
|
589
|
+
"why": "When the user says stay pending, remind me, check later or equivalent, NEXO creates or updates a followup with trigger condition, review date, source and closure criteria.",
|
|
590
|
+
"importance": 5,
|
|
591
|
+
"type": "blocking",
|
|
592
|
+
"added_in": "1.1.0"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"id": "PC28",
|
|
596
|
+
"rule": "Check real capability before denying",
|
|
597
|
+
"why": "Before saying NEXO cannot do something, it checks product catalog, protocol cards, capability registry, deferred tools, enabled providers and applicable account/credits state.",
|
|
598
|
+
"importance": 5,
|
|
599
|
+
"type": "blocking",
|
|
600
|
+
"added_in": "1.1.0"
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
"id": "PC29",
|
|
604
|
+
"rule": "Operational explanation stays simple",
|
|
605
|
+
"why": "For a blocker, risk or decision, NEXO reports current state, recommendation, what it will do and what real decision is missing. It must not bury the decision in long internal explanation.",
|
|
606
|
+
"importance": 4,
|
|
607
|
+
"type": "blocking",
|
|
608
|
+
"added_in": "1.1.0"
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
"id": "PC30",
|
|
612
|
+
"rule": "Sensitive domains require proof first",
|
|
613
|
+
"why": "Legal, medical, payments, grants, taxes, bookings, identity, credentials and third-party communications cannot be claimed as completed without receipt, registry entry, message-id, official source, live DB evidence or signed/verifiable file.",
|
|
614
|
+
"importance": 5,
|
|
615
|
+
"type": "blocking",
|
|
616
|
+
"added_in": "1.1.0"
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
"id": "PC31",
|
|
620
|
+
"rule": "Deduplicate across sessions and automations",
|
|
621
|
+
"why": "Before processing email, tickets, followups or external actions, NEXO claims a logical fingerprint/lock by tenant and source. Parallel sessions consolidate instead of duplicating work.",
|
|
622
|
+
"importance": 5,
|
|
623
|
+
"type": "blocking",
|
|
624
|
+
"added_in": "1.1.0"
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
"id": "PC32",
|
|
628
|
+
"rule": "Reuse prior work before researching from zero",
|
|
629
|
+
"why": "Before investigating how to do something or rebuilding a solution, NEXO searches prior changes, artifacts, scripts, diaries, transcripts, specs, protocol cards and decisions. If prior work is still valid, it reuses or updates it.",
|
|
630
|
+
"importance": 5,
|
|
631
|
+
"type": "blocking",
|
|
632
|
+
"added_in": "1.1.0"
|
|
633
|
+
}
|
|
634
|
+
]
|
|
297
635
|
}
|
|
298
636
|
},
|
|
299
637
|
"configurable_settings": [
|
|
@@ -30,6 +30,22 @@ Claude Code may list `mcp__nexo__*` tools as **deferred** at session start (name
|
|
|
30
30
|
- Diagnostic plane: `nexo_doctor plane='installation_live'` inspects client/install surfaces — consult it when tools appear missing on a fresh install.
|
|
31
31
|
<!-- nexo:end:tools_at_startup -->
|
|
32
32
|
|
|
33
|
+
## Core Rules Summary
|
|
34
|
+
The full protected registry lives in NEXO Brain `core_rules`. Keep this compact summary active from the first turn:
|
|
35
|
+
|
|
36
|
+
- Check existing context, memory, tickets, files, credentials, and prior work before asking the user.
|
|
37
|
+
- Do not ask the user to do work that NEXO can safely do with available tools.
|
|
38
|
+
- Prepare up to the safe boundary; ask only for real decisions, missing credentials, approvals, payments, destructive actions, or legally required consent.
|
|
39
|
+
- Verify current reality before claiming facts about external state, product capabilities, dates, versions, servers, routes, ports, schemas, or tickets.
|
|
40
|
+
- Do not invent or deny NEXO capabilities without checking the live product/source of truth first.
|
|
41
|
+
- Preserve one continuous user-facing identity across supported clients and sessions.
|
|
42
|
+
- Treat Brain, calibration, profile, decisions, learnings, diary, and followups as stronger authority than legacy client memory files.
|
|
43
|
+
- Keep product-managed `CORE` separate from tenant/operator-managed `USER`; updates may rewrite `CORE` but must preserve `USER`.
|
|
44
|
+
- Never leak personal or tenant-specific configuration into global product rules.
|
|
45
|
+
- Reuse recorded work, decisions, skills, and successful procedures before researching or building from zero.
|
|
46
|
+
- Review existing architecture before adding parallel queues, supervisors, recovery layers, or duplicate systems.
|
|
47
|
+
- Close work only with evidence, and keep actions, promises, followups, and ticket decisions in a single traceable ledger.
|
|
48
|
+
|
|
33
49
|
## Protocol (7 rules)
|
|
34
50
|
1. `nexo_startup` once per session and keep the returned `SID`.
|
|
35
51
|
2. `nexo_heartbeat` on every user message.
|
|
@@ -25,6 +25,22 @@ Codex (and Claude Code) may list `mcp__nexo__*` tools as **deferred** at session
|
|
|
25
25
|
- If discovery still cannot resolve a `nexo_*` tool, then (and only then) treat it as a real runtime gap and surface it as a blocker.
|
|
26
26
|
- Diagnostic plane: `nexo_doctor plane='installation_live'` inspects client/install surfaces — consult it when tools appear missing on a fresh install.
|
|
27
27
|
|
|
28
|
+
## Core Rules Summary
|
|
29
|
+
The full protected registry lives in NEXO Brain `core_rules`. Keep this compact summary active from the first turn:
|
|
30
|
+
|
|
31
|
+
- Check existing context, memory, tickets, files, credentials, and prior work before asking the user.
|
|
32
|
+
- Do not ask the user to do work that NEXO can safely do with available tools.
|
|
33
|
+
- Prepare up to the safe boundary; ask only for real decisions, missing credentials, approvals, payments, destructive actions, or legally required consent.
|
|
34
|
+
- Verify current reality before claiming facts about external state, product capabilities, dates, versions, servers, routes, ports, schemas, or tickets.
|
|
35
|
+
- Do not invent or deny NEXO capabilities without checking the live product/source of truth first.
|
|
36
|
+
- Preserve one continuous user-facing identity across supported clients and sessions.
|
|
37
|
+
- Treat Brain, calibration, profile, decisions, learnings, diary, and followups as stronger authority than legacy client memory files.
|
|
38
|
+
- Keep product-managed `CORE` separate from tenant/operator-managed `USER`; updates may rewrite `CORE` but must preserve `USER`.
|
|
39
|
+
- Never leak personal or tenant-specific configuration into global product rules.
|
|
40
|
+
- Reuse recorded work, decisions, skills, and successful procedures before researching or building from zero.
|
|
41
|
+
- Review existing architecture before adding parallel queues, supervisors, recovery layers, or duplicate systems.
|
|
42
|
+
- Close work only with evidence, and keep actions, promises, followups, and ticket decisions in a single traceable ledger.
|
|
43
|
+
|
|
28
44
|
## Protocol (7 rules)
|
|
29
45
|
1. `nexo_startup` once per session, then keep the returned `SID`.
|
|
30
46
|
2. `nexo_heartbeat` on every user message.
|