nexo-brain 2.6.20 → 2.7.0
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 +16 -17
- package/package.json +1 -1
- package/src/agent_runner.py +6 -2
- package/src/cli.py +98 -4
- package/src/client_preferences.py +21 -0
- package/src/dashboard/app.py +124 -0
- package/src/dashboard/templates/dashboard.html +59 -1
- package/src/doctor/providers/runtime.py +181 -0
- package/src/scripts/deep-sleep/apply_findings.py +713 -10
- package/src/scripts/deep-sleep/synthesize-prompt.md +23 -0
- package/src/scripts/deep-sleep/synthesize.py +94 -0
- package/templates/nexo_helper.py +44 -0
|
@@ -30,6 +30,15 @@ Synthesize across all sessions:
|
|
|
30
30
|
- Cross-domain connections where an older learning or session sample explains a current issue
|
|
31
31
|
- Topics repeatedly mentioned over time but never formalized into a learning or followup
|
|
32
32
|
- Project pressure that is rising because of repeated diary mentions, open followups, or adverse outcomes
|
|
33
|
+
- For medium/high-severity patterns, propose a concrete fix artifact:
|
|
34
|
+
- script
|
|
35
|
+
- hook
|
|
36
|
+
- checklist
|
|
37
|
+
- validation step
|
|
38
|
+
- workflow change
|
|
39
|
+
- guardrail
|
|
40
|
+
|
|
41
|
+
Do not stop at diagnosis. Turn repeated problems into concrete engineering work.
|
|
33
42
|
|
|
34
43
|
### 2. Morning Agenda
|
|
35
44
|
Generate a prioritized agenda for the next morning:
|
|
@@ -126,6 +135,14 @@ Merge and deduplicate all findings into a final action list. Each action should
|
|
|
126
135
|
- `dedupe_key`: deterministic key for idempotency
|
|
127
136
|
- `content`: the actual data to write
|
|
128
137
|
|
|
138
|
+
When generating `followup_create`, prefer descriptions that start with a concrete verb and include the deliverable:
|
|
139
|
+
- "Add a pre-release validation script ..."
|
|
140
|
+
- "Implement a guard hook that ..."
|
|
141
|
+
- "Create a checklist for ..."
|
|
142
|
+
- "Write a watchdog check that ..."
|
|
143
|
+
|
|
144
|
+
Avoid vague followups that merely restate the diagnosis.
|
|
145
|
+
|
|
129
146
|
## Output Format
|
|
130
147
|
|
|
131
148
|
Return ONLY valid JSON. No markdown code fences. No explanation text.
|
|
@@ -140,6 +157,12 @@ Return ONLY valid JSON. No markdown code fences. No explanation text.
|
|
|
140
157
|
"pattern": "Description of the pattern",
|
|
141
158
|
"sessions": ["session1.jsonl", "session2.jsonl"],
|
|
142
159
|
"severity": "low|medium|high",
|
|
160
|
+
"proposed_fix": {
|
|
161
|
+
"title": "Short concrete fix title",
|
|
162
|
+
"description": "Concrete engineering change to make",
|
|
163
|
+
"deliverable": "script|hook|checklist|workflow|guardrail",
|
|
164
|
+
"confidence": 0.0
|
|
165
|
+
},
|
|
143
166
|
"evidence": [
|
|
144
167
|
{"type": "transcript", "session_id": "...", "message_index": 42, "quote": "..."}
|
|
145
168
|
]
|
|
@@ -14,6 +14,7 @@ import json
|
|
|
14
14
|
import os
|
|
15
15
|
import subprocess
|
|
16
16
|
import sys
|
|
17
|
+
import hashlib
|
|
17
18
|
from datetime import datetime
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
|
|
@@ -28,6 +29,7 @@ if str(NEXO_CODE) not in sys.path:
|
|
|
28
29
|
from agent_runner import AutomationBackendUnavailableError, run_automation_prompt
|
|
29
30
|
|
|
30
31
|
CLAUDE_TIMEOUT = 21600 # 3h safety net (prevents zombie processes)
|
|
32
|
+
ACTION_VERBS = {"add", "implement", "create", "write", "build", "enforce", "automate", "validate", "guard", "fix", "review"}
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def extract_json_from_response(text: str) -> dict | None:
|
|
@@ -86,6 +88,96 @@ def collect_skill_runtime_candidates(target_date: str) -> tuple[Path, dict]:
|
|
|
86
88
|
return output_file, payload
|
|
87
89
|
|
|
88
90
|
|
|
91
|
+
def _normalize_action_text(value: str) -> str:
|
|
92
|
+
return " ".join(str(value or "").strip().lower().split())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _looks_concrete_action(text: str) -> bool:
|
|
96
|
+
words = {word.strip(".,:;()[]{}").lower() for word in str(text or "").split()}
|
|
97
|
+
return bool(words & ACTION_VERBS)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _pattern_followup_from_fix(pattern: dict) -> dict | None:
|
|
101
|
+
severity = str(pattern.get("severity", "") or "").lower()
|
|
102
|
+
sessions = pattern.get("sessions", []) or []
|
|
103
|
+
if severity not in {"medium", "high"} and len(sessions) < 2:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
proposed_fix = pattern.get("proposed_fix") or {}
|
|
107
|
+
pattern_text = str(pattern.get("pattern", "") or "").strip()
|
|
108
|
+
title = str(proposed_fix.get("title", "") or "").strip()
|
|
109
|
+
description = str(proposed_fix.get("description", "") or "").strip()
|
|
110
|
+
deliverable = str(proposed_fix.get("deliverable", "") or proposed_fix.get("artifact", "") or "").strip()
|
|
111
|
+
|
|
112
|
+
if title and description:
|
|
113
|
+
if _looks_concrete_action(description):
|
|
114
|
+
followup_description = description
|
|
115
|
+
else:
|
|
116
|
+
followup_description = f"{title}: {description}"
|
|
117
|
+
elif description:
|
|
118
|
+
followup_description = description
|
|
119
|
+
elif title:
|
|
120
|
+
followup_description = title
|
|
121
|
+
elif pattern_text:
|
|
122
|
+
followup_description = (
|
|
123
|
+
f"Implement a concrete guardrail for recurring issue: {pattern_text}. "
|
|
124
|
+
"Deliverable should be a script, hook, checklist, or automated validation that prevents the same failure from repeating."
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
if deliverable and deliverable.lower() not in followup_description.lower():
|
|
130
|
+
followup_description = f"{followup_description} Deliverable: {deliverable}."
|
|
131
|
+
if not _looks_concrete_action(followup_description):
|
|
132
|
+
followup_description = f"Implement this fix: {followup_description}"
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"action_type": "followup_create",
|
|
136
|
+
"action_class": "auto_apply" if severity == "high" else "draft_for_morning",
|
|
137
|
+
"confidence": round(max(float(proposed_fix.get("confidence", 0.0) or 0.0), 0.86 if severity == "high" else 0.78), 2),
|
|
138
|
+
"impact": "high" if severity == "high" else "medium",
|
|
139
|
+
"reversibility": "reversible",
|
|
140
|
+
"evidence": pattern.get("evidence", []) or [],
|
|
141
|
+
"dedupe_key": "engineering-fix:" + hashlib.md5(
|
|
142
|
+
_normalize_action_text(followup_description).encode("utf-8")
|
|
143
|
+
).hexdigest()[:16],
|
|
144
|
+
"content": {
|
|
145
|
+
"title": title or f"Engineering fix for: {pattern_text[:90]}",
|
|
146
|
+
"description": followup_description,
|
|
147
|
+
"date": "",
|
|
148
|
+
"reasoning": f"Deep Sleep engineering followup from recurring pattern: {pattern_text}",
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def backfill_engineering_actions(payload: dict) -> dict:
|
|
154
|
+
if not isinstance(payload, dict):
|
|
155
|
+
return payload
|
|
156
|
+
actions = payload.get("actions")
|
|
157
|
+
if not isinstance(actions, list):
|
|
158
|
+
actions = []
|
|
159
|
+
payload["actions"] = actions
|
|
160
|
+
|
|
161
|
+
existing_keys = {str(action.get("dedupe_key", "") or "") for action in actions}
|
|
162
|
+
existing_descriptions = {
|
|
163
|
+
_normalize_action_text(action.get("content", {}).get("description", ""))
|
|
164
|
+
for action in actions
|
|
165
|
+
if isinstance(action, dict)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for pattern in payload.get("cross_session_patterns", []) or []:
|
|
169
|
+
action = _pattern_followup_from_fix(pattern)
|
|
170
|
+
if not action:
|
|
171
|
+
continue
|
|
172
|
+
description = _normalize_action_text(action["content"]["description"])
|
|
173
|
+
if action["dedupe_key"] in existing_keys or description in existing_descriptions:
|
|
174
|
+
continue
|
|
175
|
+
actions.append(action)
|
|
176
|
+
existing_keys.add(action["dedupe_key"])
|
|
177
|
+
existing_descriptions.add(description)
|
|
178
|
+
return payload
|
|
179
|
+
|
|
180
|
+
|
|
89
181
|
def main():
|
|
90
182
|
target_date = sys.argv[1] if len(sys.argv) > 1 else datetime.now().strftime("%Y-%m-%d")
|
|
91
183
|
|
|
@@ -177,6 +269,8 @@ def main():
|
|
|
177
269
|
print(f"[synthesize] Failed to parse JSON. Raw output saved to {debug_file}", file=sys.stderr)
|
|
178
270
|
sys.exit(1)
|
|
179
271
|
|
|
272
|
+
parsed = backfill_engineering_actions(parsed)
|
|
273
|
+
|
|
180
274
|
# Write synthesis output
|
|
181
275
|
output_file = DEEP_SLEEP_DIR / f"{target_date}-synthesis.json"
|
|
182
276
|
with open(output_file, "w") as f:
|
package/templates/nexo_helper.py
CHANGED
|
@@ -16,6 +16,36 @@ from pathlib import Path
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
NEXO_HOME = Path(os.environ.get("NEXO_HOME", str(Path.home() / ".nexo")))
|
|
19
|
+
DEFAULT_ALLOWED_TOOLS = "Read,Write,Edit,Glob,Grep,Bash,mcp__nexo__*"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load_schedule() -> dict:
|
|
23
|
+
schedule_path = NEXO_HOME / "config" / "schedule.json"
|
|
24
|
+
if not schedule_path.is_file():
|
|
25
|
+
return {}
|
|
26
|
+
try:
|
|
27
|
+
return json.loads(schedule_path.read_text())
|
|
28
|
+
except Exception:
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _resolve_automation_backend() -> str:
|
|
33
|
+
data = _load_schedule()
|
|
34
|
+
return str(data.get("automation_backend", "claude_code") or "claude_code")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_bootstrap_prompt() -> str:
|
|
38
|
+
backend = _resolve_automation_backend()
|
|
39
|
+
if backend == "codex":
|
|
40
|
+
path = Path.home() / ".codex" / "AGENTS.md"
|
|
41
|
+
else:
|
|
42
|
+
path = Path.home() / ".claude" / "CLAUDE.md"
|
|
43
|
+
if not path.is_file():
|
|
44
|
+
return ""
|
|
45
|
+
try:
|
|
46
|
+
return path.read_text()
|
|
47
|
+
except Exception:
|
|
48
|
+
return ""
|
|
19
49
|
|
|
20
50
|
|
|
21
51
|
def run_nexo(args: list[str]) -> str:
|
|
@@ -57,6 +87,9 @@ def run_automation_text(
|
|
|
57
87
|
model: str = "",
|
|
58
88
|
reasoning_effort: str = "",
|
|
59
89
|
cwd: str = "",
|
|
90
|
+
allowed_tools: str = DEFAULT_ALLOWED_TOOLS,
|
|
91
|
+
append_system_prompt: str = "",
|
|
92
|
+
include_bootstrap: bool = True,
|
|
60
93
|
) -> str:
|
|
61
94
|
"""Run the configured NEXO automation backend and return text output.
|
|
62
95
|
|
|
@@ -75,6 +108,17 @@ def run_automation_text(
|
|
|
75
108
|
cmd.extend(["--reasoning-effort", reasoning_effort])
|
|
76
109
|
if cwd:
|
|
77
110
|
cmd.extend(["--cwd", cwd])
|
|
111
|
+
merged_system_prompt = []
|
|
112
|
+
if include_bootstrap:
|
|
113
|
+
bootstrap = _load_bootstrap_prompt()
|
|
114
|
+
if bootstrap:
|
|
115
|
+
merged_system_prompt.append(bootstrap)
|
|
116
|
+
if append_system_prompt:
|
|
117
|
+
merged_system_prompt.append(append_system_prompt)
|
|
118
|
+
if merged_system_prompt:
|
|
119
|
+
cmd.extend(["--append-system-prompt", "\n\n".join(merged_system_prompt)])
|
|
120
|
+
if allowed_tools:
|
|
121
|
+
cmd.extend(["--allowed-tools", allowed_tools])
|
|
78
122
|
|
|
79
123
|
env = os.environ.copy()
|
|
80
124
|
env.setdefault("NEXO_HOME", str(NEXO_HOME))
|