superlocalmemory 3.4.10 → 3.4.12

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.
Files changed (47) hide show
  1. package/README.md +17 -11
  2. package/docs/skill-evolution.md +77 -10
  3. package/ide/hooks/tool-event-hook.sh +4 -4
  4. package/package.json +1 -1
  5. package/pyproject.toml +3 -2
  6. package/src/superlocalmemory/cli/commands.py +170 -0
  7. package/src/superlocalmemory/cli/main.py +21 -0
  8. package/src/superlocalmemory/cli/setup_wizard.py +54 -11
  9. package/src/superlocalmemory/core/config.py +35 -0
  10. package/src/superlocalmemory/core/consolidation_engine.py +128 -0
  11. package/src/superlocalmemory/core/embedding_worker.py +1 -1
  12. package/src/superlocalmemory/core/engine.py +12 -0
  13. package/src/superlocalmemory/core/fact_consolidator.py +425 -0
  14. package/src/superlocalmemory/core/graph_pruner.py +290 -0
  15. package/src/superlocalmemory/core/maintenance_scheduler.py +20 -0
  16. package/src/superlocalmemory/core/recall_pipeline.py +9 -0
  17. package/src/superlocalmemory/core/tier_manager.py +325 -0
  18. package/src/superlocalmemory/encoding/entity_resolver.py +6 -5
  19. package/src/superlocalmemory/evolution/__init__.py +29 -0
  20. package/src/superlocalmemory/evolution/blind_verifier.py +115 -0
  21. package/src/superlocalmemory/evolution/evolution_store.py +302 -0
  22. package/src/superlocalmemory/evolution/mutation_generator.py +181 -0
  23. package/src/superlocalmemory/evolution/skill_evolver.py +555 -0
  24. package/src/superlocalmemory/evolution/triggers.py +367 -0
  25. package/src/superlocalmemory/evolution/types.py +92 -0
  26. package/src/superlocalmemory/hooks/hook_handlers.py +13 -0
  27. package/src/superlocalmemory/learning/skill_performance_miner.py +44 -11
  28. package/src/superlocalmemory/mcp/server.py +4 -0
  29. package/src/superlocalmemory/mcp/tools_evolution.py +338 -0
  30. package/src/superlocalmemory/retrieval/engine.py +98 -11
  31. package/src/superlocalmemory/retrieval/entity_channel.py +118 -0
  32. package/src/superlocalmemory/retrieval/forgetting_filter.py +22 -7
  33. package/src/superlocalmemory/retrieval/strategy.py +2 -2
  34. package/src/superlocalmemory/server/routes/behavioral.py +19 -15
  35. package/src/superlocalmemory/server/routes/evolution.py +213 -0
  36. package/src/superlocalmemory/server/routes/tiers.py +195 -0
  37. package/src/superlocalmemory/server/unified_daemon.py +39 -5
  38. package/src/superlocalmemory/storage/schema_v3411.py +149 -0
  39. package/src/superlocalmemory/ui/index.html +5 -2
  40. package/src/superlocalmemory/ui/js/lifecycle.js +83 -0
  41. package/src/superlocalmemory/ui/js/ng-skills.js +394 -10
  42. package/src/superlocalmemory.egg-info/PKG-INFO +614 -0
  43. package/src/superlocalmemory.egg-info/SOURCES.txt +335 -0
  44. package/src/superlocalmemory.egg-info/dependency_links.txt +1 -0
  45. package/src/superlocalmemory.egg-info/entry_points.txt +2 -0
  46. package/src/superlocalmemory.egg-info/requires.txt +55 -0
  47. package/src/superlocalmemory.egg-info/top_level.txt +1 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <h1 align="center">SuperLocalMemory V3.4</h1>
6
6
  <p align="center"><strong>Every other AI forgets. Yours won't.</strong><br/><em>Infinite memory for Claude Code, Cursor, Windsurf & 17+ AI tools.</em></p>
7
- <p align="center"><code>v3.4.4 "Neural Glass"</code> — Install once. Every session remembers the last. Automatically.</p>
7
+ <p align="center"><code>v3.4.11</code> — Install once. Every session remembers the last. Automatically.</p>
8
8
  <p align="center"><strong>Backed by 3 peer-reviewed research papers</strong> · <a href="https://arxiv.org/abs/2603.02240">arXiv:2603.02240</a> · <a href="https://arxiv.org/abs/2603.14588">arXiv:2603.14588</a> · <a href="https://arxiv.org/abs/2604.04514">arXiv:2604.04514</a></p>
9
9
 
10
10
  <p align="center">
@@ -378,14 +378,20 @@ Auto-capture hooks: `slm hooks install` + `slm observe` + `slm session-context`.
378
378
  - Auto-learned soft prompts injected into agent context
379
379
  - Behavioral pattern detection and outcome tracking
380
380
 
381
- ### Skill Evolution (NEW in v3.4.10)
382
- - **Per-skill performance tracking** — automatically tracks which skills succeed and which fail, across sessions
383
- - **Execution trace analysis** — mines tool usage patterns around skill invocations to determine effectiveness
384
- - **Skill entities in Entity Explorer** — each skill becomes a browsable entity with performance facts and evolution history
385
- - **Dedicated Skill Evolution dashboard tab** — overview cards, performance assertions, skill correlations
386
- - **Behavioral assertions for skill routing** — soft prompts recommend high-performing skills in future sessions
387
- - **ECC integration** — enhanced observation support with [Everything Claude Code](https://github.com/affaan-m/everything-claude-code) via `slm ingest --source ecc`
388
- - **IDE compatibility:** Skill tracking currently works with Claude Code. The `/api/v3/tool-event` endpoint accepts events from any IDE client adapters for other IDEs in future releases.
381
+ ### Skill Evolution
382
+ - **Per-skill performance tracking** — tracks which skills succeed and fail across sessions (zero-LLM, always on)
383
+ - **Evolution engine** — 3-trigger system with blind verification. Off by default enable via `slm config set evolution.enabled true`
384
+ - **MCP tools** — `evolve_skill`, `skill_health`, `skill_lineage` for programmatic access
385
+ - **Lineage DAG** — visual evolution history in the dashboard
386
+ - **CLI config** — `slm config get/set` for all evolution settings
387
+ - **Post-session triggers** — automatic analysis on session end via Stop hook
388
+ - **[ECC](https://github.com/affaan-m/everything-claude-code) integration** optional enhanced observations via `slm ingest --source ecc`
389
+
390
+ ### Tiered Storage & Scaling
391
+ - **4-tier lifecycle** — active, warm, cold, archived with automatic promotion/demotion
392
+ - **Deep recall** — archived facts searchable at reduced weight
393
+ - **Graph pruning** — automatic cleanup of orphan edges, self-loops, duplicates
394
+ - **Fact consolidation** — clusters related facts into consolidated summaries
389
395
 
390
396
  ### Trust & Security
391
397
  - Bayesian Beta-distribution trust scoring (per-agent, per-fact)
@@ -396,9 +402,9 @@ Auto-capture hooks: `slm hooks install` + `slm observe` + `slm session-context`.
396
402
  ### Infrastructure
397
403
  - 23-tab web dashboard with real-time visualization
398
404
  - 17+ IDE integrations (Claude, Cursor, Windsurf, VS Code, JetBrains, Zed, etc.)
399
- - 35 MCP tools + 7 MCP resources
405
+ - 38 MCP tools + 7 MCP resources
400
406
  - Profile isolation (independent memory spaces)
401
- - 1400+ tests, AGPL v3, cross-platform (Mac/Linux/Windows)
407
+ - 2,900+ tests, AGPL v3, cross-platform (Mac/Linux/Windows)
402
408
  - CPU-only — no GPU required
403
409
  - Automatic orphaned process cleanup
404
410
 
@@ -70,8 +70,10 @@ The dedicated **Skill Evolution** tab in the SLM dashboard shows:
70
70
 
71
71
  - **Overview cards** — Total skill events, unique skills, performance assertions, skill correlations
72
72
  - **Skill performance cards** — Per-skill effective score, invocation count, confidence level
73
+ - **Evolution Engine status** — Backend detection, enable/disable toggle, run button
74
+ - **Skill Lineage DAG** — Visual graph of evolved skill versions (parent → child relationships)
75
+ - **Lineage table** — Clickable rows showing evolution type, status, verification result
73
76
  - **Skill correlations** — Which skills work well together
74
- - **Compatibility notice** — Current IDE support status
75
77
 
76
78
  Access: Open `http://localhost:8765` and navigate to the Skill Evolution tab in the sidebar.
77
79
 
@@ -132,23 +134,69 @@ This reads ECC's observation files from `~/.claude/homunculus/projects/*/observa
132
134
 
133
135
  ## Configuration
134
136
 
135
- ### Enable/Disable Skill Tracking
137
+ ### Skill Tracking (C1 — always on)
136
138
 
137
- Skill tracking is enabled by default when the SLM hook is registered. To check:
139
+ Skill performance tracking is enabled by default when the SLM hook is registered. Zero-LLM, zero-cost. Runs as Step 10 in the consolidation pipeline.
138
140
 
139
141
  ```bash
140
142
  slm status # Shows hook registration status
143
+ slm consolidate --cognitive # Trigger manual consolidation
141
144
  ```
142
145
 
143
- ### Consolidation
146
+ ### Skill Evolution (C2 — off by default)
144
147
 
145
- Skill performance mining runs as Step 10 in the consolidation pipeline. It processes only new events since the last run (incremental). To trigger manually:
148
+ The Skill Evolution Engine uses LLM calls to generate improved skill versions. **It is OFF by default** end users must opt in.
149
+
150
+ **Why off by default:** Evolution makes LLM calls (confirmation gate + mutation + blind verification). Even with budget caps, users should consciously enable this and configure their LLM backend.
151
+
152
+ #### Enable via CLI
146
153
 
147
154
  ```bash
148
- slm consolidate --cognitive
155
+ slm config set evolution.enabled true
156
+ ```
157
+
158
+ #### Enable via Interactive Installer
159
+
160
+ ```bash
161
+ slm setup # Interactive wizard includes evolution opt-in
162
+ ```
163
+
164
+ #### Enable via Dashboard
165
+
166
+ Navigate to Settings → Skill Evolution → Enable.
167
+
168
+ ### LLM Backend — Auto-Detect
169
+
170
+ Evolution uses a single auto-detect chain. No manual configuration needed for most users:
171
+
172
+ ```
173
+ Priority 1: `claude` CLI available → spawn `claude --model haiku` (FREE, best quality)
174
+ Priority 2: Ollama running → use Ollama (FREE, local)
175
+ Priority 3: API key set → use Anthropic/OpenAI API (paid)
176
+ Priority 4: Nothing available → dashboard-only (show candidates, manual evolution)
149
177
  ```
150
178
 
151
- ### Minimum Thresholds
179
+ This means:
180
+ - **Claude Code users:** Evolution works for free — uses your existing Claude subscription
181
+ - **Other IDE users with Ollama:** Evolution works for free — uses local Ollama
182
+ - **Advanced users:** Can point at Anthropic/OpenAI API if preferred
183
+
184
+ ```bash
185
+ # Override auto-detect (optional — most users never need this)
186
+ slm config set evolution.backend claude
187
+ slm config set evolution.backend ollama
188
+ slm config set evolution.backend anthropic
189
+ ```
190
+
191
+ ### Full Evolution Config Reference
192
+
193
+ | Key | Default | Description |
194
+ |-----|---------|-------------|
195
+ | `evolution.enabled` | `false` | Master switch — off by default, opt-in |
196
+ | `evolution.backend` | `auto` | LLM backend: `auto`, `claude`, `ollama`, `anthropic`, `openai` |
197
+ | `evolution.max_evolutions_per_cycle` | `3` | Budget cap per consolidation cycle |
198
+
199
+ ### Tracking Thresholds (C1)
152
200
 
153
201
  | Parameter | Default | Description |
154
202
  |-----------|---------|-------------|
@@ -172,11 +220,30 @@ SLM's skill evolution system draws from:
172
220
 
173
221
  ---
174
222
 
223
+ ## MCP Tools
224
+
225
+ Three MCP tools are available for programmatic access:
226
+
227
+ | Tool | Description |
228
+ |------|-------------|
229
+ | `evolve_skill` | Manually trigger evolution for a specific skill |
230
+ | `skill_health` | Get health metrics (invocations, error rate, status) for skills |
231
+ | `skill_lineage` | Get evolution lineage tree for a skill |
232
+
233
+ These tools are registered automatically and available in all supported IDEs.
234
+
235
+ ## CLI Commands
236
+
237
+ ```bash
238
+ slm config get evolution.enabled # Check if evolution is enabled
239
+ slm config set evolution.enabled true # Enable evolution
240
+ slm config set evolution.backend auto # Set LLM backend
241
+ ```
242
+
175
243
  ## What's Next
176
244
 
177
- - **C2: Arsenal Evolution Engine** — Automatic skill mutation with 3-trigger system, LLM confirmation gate, and blind verification. Evolved skills stored in `~/.claude/skills/evolved/`.
178
- - **C3: SLM Productization** — New MCP tools (`evolve_skill`, `skill_health`, `skill_lineage`), new fact types, full dashboard evolution history.
179
- - **IDE Adapters** — Cursor, Windsurf, VS Code Copilot, JetBrains support.
245
+ - **IDE Adapters** — Cursor, Windsurf, VS Code Copilot, JetBrains support for skill tracking.
246
+ - **Skill lineage visualization improvements** — Richer DAG with performance history overlay.
180
247
 
181
248
  ---
182
249
 
@@ -76,14 +76,14 @@ import json, sys, os, re
76
76
 
77
77
  # Secret scrubbing pattern (adopted from ECC observe.sh)
78
78
  _SECRET_RE = re.compile(
79
- r"(?i)(api[_-]?key|token|secret|password|authorization|credentials?|auth)"
79
+ r"(?i)(api[_-]?key|[a-z_]*_key|token|secret|password|pass\b|authorization|credentials?|auth)"
80
80
  r"""([\"'"'"'"'"'"'\s:=]+)"""
81
81
  r"([A-Za-z]+\s+)?"
82
82
  r"([A-Za-z0-9_\-/.+=]{8,})"
83
83
  )
84
84
 
85
85
  def scrub(val, max_len=500):
86
- """Truncate and scrub secrets from a value."""
86
+ """Scrub secrets first, then truncate."""
87
87
  if val is None:
88
88
  return ""
89
89
  if isinstance(val, dict):
@@ -92,10 +92,10 @@ def scrub(val, max_len=500):
92
92
  s = json.dumps(val, default=str)
93
93
  else:
94
94
  s = str(val)
95
- s = s[:max_len]
96
- return _SECRET_RE.sub(
95
+ s = _SECRET_RE.sub(
97
96
  lambda m: m.group(1) + m.group(2) + (m.group(3) or "") + "[REDACTED]", s
98
97
  )
98
+ return s[:max_len]
99
99
 
100
100
  try:
101
101
  data = json.load(sys.stdin)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "3.4.10",
3
+ "version": "3.4.12",
4
4
  "description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
5
5
  "keywords": [
6
6
  "ai-memory",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "3.4.10"
3
+ version = "3.4.12"
4
4
  description = "Information-geometric agent memory with mathematical guarantees"
5
5
  readme = "README.md"
6
6
  license = {text = "AGPL-3.0-or-later"}
@@ -112,8 +112,9 @@ superlocalmemory = ["ui/**/*"]
112
112
  [tool.pytest.ini_options]
113
113
  testpaths = ["tests"]
114
114
  pythonpath = ["src"]
115
+ addopts = "-m 'not slow and not ollama'"
115
116
  markers = [
116
- "slow: marks tests as slow (deselect with '-m \"not slow\"')",
117
+ "slow: marks tests as slow — real engine/model loading (run with: pytest -m slow)",
117
118
  "ollama: marks tests that require a running Ollama instance",
118
119
  ]
119
120
  filterwarnings = [
@@ -64,6 +64,9 @@ def dispatch(args: Namespace) -> None:
64
64
  "adapters": cmd_adapters,
65
65
  # V3.4.8 external observation ingestion
66
66
  "ingest": cmd_ingest,
67
+ # V3.4.11 skill evolution
68
+ "config": cmd_config,
69
+ "evolve": cmd_evolve,
67
70
  }
68
71
  handler = handlers.get(args.command)
69
72
  if handler:
@@ -341,6 +344,173 @@ def cmd_ingest(args: Namespace) -> None:
341
344
  _ingest(args)
342
345
 
343
346
 
347
+ # -- Config & Evolution (V3.4.11) -----------------------------------------------
348
+
349
+
350
+ def cmd_config(args: Namespace) -> None:
351
+ """Get or set config values (dot-notation).
352
+
353
+ Usage:
354
+ slm config set evolution.enabled true
355
+ slm config set evolution.backend auto
356
+ slm config get evolution.enabled
357
+ slm config get evolution.backend
358
+ """
359
+ import json
360
+ from pathlib import Path
361
+
362
+ use_json = getattr(args, "json", False)
363
+ action = getattr(args, "action", "get")
364
+ key = getattr(args, "key", "")
365
+ value = getattr(args, "value", None)
366
+
367
+ config_path = Path.home() / ".superlocalmemory" / "config.json"
368
+
369
+ # Read existing config
370
+ cfg: dict = {}
371
+ if config_path.exists():
372
+ try:
373
+ cfg = json.loads(config_path.read_text())
374
+ except (json.JSONDecodeError, OSError):
375
+ pass
376
+
377
+ if action == "get":
378
+ # Parse dot-notation key (e.g. "evolution.enabled")
379
+ parts = key.split(".") if key else []
380
+ node = cfg
381
+ for part in parts:
382
+ if isinstance(node, dict) and part in node:
383
+ node = node[part]
384
+ else:
385
+ node = None
386
+ break
387
+
388
+ if use_json:
389
+ from superlocalmemory.cli.json_output import json_print
390
+ json_print("config", data={"key": key, "value": node})
391
+ else:
392
+ if node is None:
393
+ print(f"{key}: (not set)")
394
+ else:
395
+ print(f"{key}: {node}")
396
+
397
+ elif action == "set":
398
+ _ALLOWED_CONFIG_KEYS = {
399
+ "evolution.enabled", "evolution.backend", "evolution.max_evolutions_per_cycle",
400
+ "mesh_enabled", "daemon_idle_timeout", "entity_compilation_enabled",
401
+ }
402
+ if key not in _ALLOWED_CONFIG_KEYS:
403
+ if use_json:
404
+ from superlocalmemory.cli.json_output import json_print
405
+ json_print("config", error={
406
+ "code": "DISALLOWED_KEY",
407
+ "message": f"'{key}' is not a configurable key. Allowed: {', '.join(sorted(_ALLOWED_CONFIG_KEYS))}",
408
+ })
409
+ else:
410
+ print(f"Error: '{key}' is not a configurable key. Allowed: {', '.join(sorted(_ALLOWED_CONFIG_KEYS))}")
411
+ sys.exit(1)
412
+
413
+ if not key or value is None:
414
+ if use_json:
415
+ from superlocalmemory.cli.json_output import json_print
416
+ json_print("config", error={
417
+ "code": "INVALID_INPUT",
418
+ "message": "Usage: slm config set <key> <value>",
419
+ })
420
+ else:
421
+ print("Usage: slm config set <key> <value>")
422
+ sys.exit(1)
423
+
424
+ # Parse value: booleans, numbers, strings
425
+ parsed_value: object
426
+ if value.lower() in ("true", "yes", "on"):
427
+ parsed_value = True
428
+ elif value.lower() in ("false", "no", "off"):
429
+ parsed_value = False
430
+ else:
431
+ try:
432
+ parsed_value = int(value)
433
+ except ValueError:
434
+ try:
435
+ parsed_value = float(value)
436
+ except ValueError:
437
+ parsed_value = value
438
+
439
+ # Set via dot-notation (e.g. "evolution.enabled" -> cfg["evolution"]["enabled"])
440
+ parts = key.split(".")
441
+ node = cfg
442
+ for part in parts[:-1]:
443
+ if part not in node or not isinstance(node.get(part), dict):
444
+ node[part] = {}
445
+ node = node[part]
446
+ old_value = node.get(parts[-1])
447
+ node[parts[-1]] = parsed_value
448
+
449
+ # Write back
450
+ config_path.parent.mkdir(parents=True, exist_ok=True)
451
+ config_path.write_text(json.dumps(cfg, indent=2) + "\n")
452
+
453
+ if use_json:
454
+ from superlocalmemory.cli.json_output import json_print
455
+ json_print("config", data={
456
+ "key": key, "old_value": old_value, "new_value": parsed_value,
457
+ })
458
+ else:
459
+ print(f"{key}: {old_value} -> {parsed_value}")
460
+
461
+ else:
462
+ if use_json:
463
+ from superlocalmemory.cli.json_output import json_print
464
+ json_print("config", error={
465
+ "code": "UNKNOWN_ACTION",
466
+ "message": f"Unknown action: {action}. Use 'get' or 'set'.",
467
+ })
468
+ else:
469
+ print(f"Unknown config action: {action}. Use 'get' or 'set'.")
470
+ sys.exit(1)
471
+
472
+
473
+ def cmd_evolve(args: Namespace) -> None:
474
+ """Run skill evolution for a session (called from Stop hook).
475
+
476
+ Reads config.json to check if evolution is enabled.
477
+ If enabled, imports SkillEvolver and runs run_post_session().
478
+ If disabled, exits silently (zero output for fire-and-forget).
479
+ """
480
+ import json
481
+ from pathlib import Path
482
+
483
+ session_id = getattr(args, "session", "") or ""
484
+ profile = getattr(args, "profile", "default") or "default"
485
+
486
+ if not session_id:
487
+ return # Silent exit — nothing to do without a session
488
+
489
+ # Check if evolution is enabled via config.json
490
+ config_path = Path.home() / ".superlocalmemory" / "config.json"
491
+ try:
492
+ cfg = json.loads(config_path.read_text()) if config_path.exists() else {}
493
+ except (json.JSONDecodeError, OSError):
494
+ return # Config unreadable — silent exit
495
+
496
+ evolution_cfg = cfg.get("evolution", {})
497
+ if not evolution_cfg.get("enabled", False):
498
+ return # Disabled — silent exit
499
+
500
+ # Heavy imports only if enabled (this runs as a Popen child)
501
+ try:
502
+ from superlocalmemory.evolution.skill_evolver import SkillEvolver
503
+
504
+ db_path = Path.home() / ".superlocalmemory" / "memory.db"
505
+ if not db_path.exists():
506
+ return
507
+
508
+ evolver = SkillEvolver(db_path)
509
+ evolver.run_post_session(session_id, profile)
510
+ except Exception:
511
+ pass # Best-effort — don't crash the Stop hook
512
+
513
+
344
514
  def cmd_adapters(args: Namespace) -> None:
345
515
  """Manage ingestion adapters (Gmail, Calendar, Transcript).
346
516
 
@@ -313,6 +313,26 @@ def main() -> None:
313
313
  )
314
314
  ingest_p.add_argument("--json", action="store_true", help="Output structured JSON")
315
315
 
316
+ # V3.4.11: Config get/set (dot-notation)
317
+ config_p = sub.add_parser(
318
+ "config",
319
+ help="Get or set config values (e.g. slm config set evolution.enabled true)",
320
+ )
321
+ config_p.add_argument(
322
+ "action", choices=["get", "set"], help="Action: get or set",
323
+ )
324
+ config_p.add_argument("key", help="Config key in dot notation (e.g. evolution.enabled)")
325
+ config_p.add_argument("value", nargs="?", default=None, help="Value to set (for 'set' action)")
326
+ config_p.add_argument("--json", action="store_true", help="Output structured JSON")
327
+
328
+ # V3.4.11: Skill evolution (called from Stop hook, fire-and-forget)
329
+ evolve_p = sub.add_parser(
330
+ "evolve",
331
+ help="Run post-session skill evolution (internal, called by Stop hook)",
332
+ )
333
+ evolve_p.add_argument("--session", default="", help="Session ID to process")
334
+ evolve_p.add_argument("--profile", default="default", help="Profile ID")
335
+
316
336
  args = parser.parse_args()
317
337
 
318
338
  if not args.command:
@@ -328,6 +348,7 @@ def main() -> None:
328
348
  # Cross-platform: macOS + Windows + Linux.
329
349
  _NO_DAEMON_COMMANDS = {
330
350
  "setup", "mode", "provider", "connect", "migrate", "mcp", "warmup",
351
+ "config", "evolve",
331
352
  }
332
353
  if args.command not in _NO_DAEMON_COMMANDS:
333
354
  try:
@@ -265,7 +265,7 @@ def run_wizard(auto: bool = False) -> None:
265
265
  print()
266
266
 
267
267
  # -- Step 1: System check --
268
- print("─── Step 1/6: System Check ───")
268
+ print("─── Step 1/10: System Check ───")
269
269
  print()
270
270
  py_ver = platform.python_version()
271
271
  py_ok = sys.version_info >= (3, 11)
@@ -293,7 +293,7 @@ def run_wizard(auto: bool = False) -> None:
293
293
 
294
294
  # -- Step 2: Mode selection --
295
295
  print()
296
- print("─── Step 2/6: Choose Operating Mode ───")
296
+ print("─── Step 2/10: Choose Operating Mode ───")
297
297
  print()
298
298
  print(" [A] Local Guardian (recommended)")
299
299
  print(" Zero cloud. Zero LLM. Full privacy.")
@@ -342,7 +342,7 @@ def run_wizard(auto: bool = False) -> None:
342
342
 
343
343
  # -- Step 3: Code Knowledge Graph --
344
344
  print()
345
- print("─── Step 3/6: Code Knowledge Graph ───")
345
+ print("─── Step 3/10: Code Knowledge Graph ───")
346
346
  print()
347
347
  print(" CodeGraph builds a structural map of your codebase using Tree-sitter.")
348
348
  print(" It gives your AI assistant blast-radius analysis, call graphs,")
@@ -375,7 +375,7 @@ def run_wizard(auto: bool = False) -> None:
375
375
 
376
376
  # -- Step 4: Download models --
377
377
  print()
378
- print("─── Step 4/9: Download Embedding Model ───")
378
+ print("─── Step 4/10: Download Embedding Model ───")
379
379
 
380
380
  if not st_ok:
381
381
  print(" ⚠ Skipped (sentence-transformers not installed)")
@@ -386,7 +386,7 @@ def run_wizard(auto: bool = False) -> None:
386
386
  print(" ⚠ Model will download on first use (may take a few minutes)")
387
387
 
388
388
  print()
389
- print("─── Step 4b/9: Download Reranker Model ───")
389
+ print("─── Step 4b/10: Download Reranker Model ───")
390
390
 
391
391
  if not st_ok:
392
392
  print(" ⚠ Skipped (sentence-transformers not installed)")
@@ -395,7 +395,7 @@ def run_wizard(auto: bool = False) -> None:
395
395
 
396
396
  # -- Step 5: Daemon Configuration (v3.4.3) --
397
397
  print()
398
- print("─── Step 5/9: Daemon Configuration ───")
398
+ print("─── Step 5/10: Daemon Configuration ───")
399
399
  print()
400
400
  print(" The SLM daemon runs in the background for instant memory access.")
401
401
  print()
@@ -425,7 +425,7 @@ def run_wizard(auto: bool = False) -> None:
425
425
 
426
426
  # -- Step 6: Mesh Communication (v3.4.3) --
427
427
  print()
428
- print("─── Step 6/9: Mesh Communication ───")
428
+ print("─── Step 6/10: Mesh Communication ───")
429
429
  print()
430
430
  print(" SLM Mesh enables agent-to-agent P2P communication.")
431
431
  print(" Multiple AI sessions can share knowledge in real-time.")
@@ -446,7 +446,7 @@ def run_wizard(auto: bool = False) -> None:
446
446
 
447
447
  # -- Step 7: Ingestion Adapters (v3.4.3) --
448
448
  print()
449
- print("─── Step 7/9: Ingestion Adapters ───")
449
+ print("─── Step 7/10: Ingestion Adapters ───")
450
450
  print()
451
451
  print(" These let SLM learn from your email, calendar, and meetings.")
452
452
  print(" All adapters are OFF by default. You can enable them later.")
@@ -486,7 +486,7 @@ def run_wizard(auto: bool = False) -> None:
486
486
 
487
487
  # -- Step 8: Entity Compilation (v3.4.3) --
488
488
  print()
489
- print("─── Step 8/9: Entity Compilation ───")
489
+ print("─── Step 8/10: Entity Compilation ───")
490
490
  print()
491
491
  print(" Entity compilation builds knowledge summaries per person,")
492
492
  print(" project, and concept. Runs automatically during consolidation.")
@@ -505,9 +505,50 @@ def run_wizard(auto: bool = False) -> None:
505
505
  config.save()
506
506
  print(f"\n ✓ Entity compilation {'enabled' if config.entity_compilation_enabled else 'disabled'}")
507
507
 
508
- # -- Step 9: Verification --
508
+ # -- Step 9: Skill Evolution (v3.4.11) --
509
509
  print()
510
- print("─── Step 9/9: Verification ───")
510
+ print("─── Step 9/10: Skill Evolution ───")
511
+ print()
512
+ print(" SLM can automatically evolve skills that underperform.")
513
+ print(" It detects degradation, generates improvements, and verifies them blindly.")
514
+ print(" Requires an LLM backend (Claude CLI, Ollama, or API key).")
515
+ print()
516
+ print(" [Y] Enable Skill Evolution")
517
+ print(" [N] Disable (can enable later: slm config set evolution.enabled true)")
518
+ print()
519
+
520
+ if interactive:
521
+ evo_choice = _prompt(" Enable Skill Evolution? [Y/n] (default: Y): ", "y").lower()
522
+ else:
523
+ evo_choice = "y"
524
+ print(" Auto-enabling Skill Evolution (non-interactive)")
525
+
526
+ evolution_enabled = evo_choice in ("", "y", "yes")
527
+
528
+ # Write evolution config to config.json directly
529
+ # (SLMConfig.save() doesn't serialize evolution)
530
+ _SLM_HOME.mkdir(parents=True, exist_ok=True)
531
+ evo_config_path = _SLM_HOME / "config.json"
532
+ evo_cfg: dict = {}
533
+ if evo_config_path.exists():
534
+ try:
535
+ evo_cfg = json.loads(evo_config_path.read_text())
536
+ except (json.JSONDecodeError, OSError):
537
+ pass
538
+ evo_cfg["evolution"] = {
539
+ "enabled": evolution_enabled,
540
+ "backend": "auto",
541
+ }
542
+ evo_config_path.write_text(json.dumps(evo_cfg, indent=2) + "\n")
543
+
544
+ if evolution_enabled:
545
+ print(f"\n ✓ Skill Evolution enabled (backend: auto-detect)")
546
+ else:
547
+ print(f"\n ✓ Skill Evolution disabled")
548
+
549
+ # -- Step 10: Verification --
550
+ print()
551
+ print("─── Step 10/10: Verification ───")
511
552
 
512
553
  if st_ok:
513
554
  verified = _verify_installation()
@@ -537,6 +578,8 @@ def run_wizard(auto: bool = False) -> None:
537
578
  print(", Entity Compilation", end="")
538
579
  if code_graph_enabled:
539
580
  print(", CodeGraph", end="")
581
+ if evolution_enabled:
582
+ print(", Skill Evolution", end="")
540
583
  print()
541
584
  if enabled_adapters:
542
585
  print(f" Adapters: {', '.join(enabled_adapters)}")
@@ -490,6 +490,25 @@ class TemporalValidatorConfig:
490
490
  include_expired_in_history: bool = True
491
491
 
492
492
 
493
+ @dataclass(frozen=True)
494
+ class EvolutionConfig:
495
+ """Configuration for Skill Evolution Engine (v3.4.10).
496
+
497
+ OFF by default — opt in via `slm setup` (interactive) or
498
+ `slm config set evolution.enabled true` (CLI).
499
+
500
+ Backend auto-detection priority:
501
+ 1. `claude` CLI available → spawn `claude --model haiku` (ECC pattern, free)
502
+ 2. Ollama running → use Ollama (free, local)
503
+ 3. API key set → use Anthropic/OpenAI API (paid)
504
+ 4. Nothing → dashboard-only (show candidates, manual evolution)
505
+ """
506
+
507
+ enabled: bool = False # OFF by default, opt-in
508
+ backend: str = "auto" # auto, claude, ollama, anthropic, openai
509
+ max_evolutions_per_cycle: int = 3 # Budget cap per consolidation
510
+
511
+
493
512
  @dataclass(frozen=True)
494
513
  class AutoInvokeConfig:
495
514
  """Configuration for the Auto-Invoke Engine (Phase 2).
@@ -580,6 +599,7 @@ class SLMConfig:
580
599
  parameterization: ParameterizationConfig = field(
581
600
  default_factory=ParameterizationConfig,
582
601
  )
602
+ evolution: EvolutionConfig = field(default_factory=EvolutionConfig)
583
603
 
584
604
  # v3.4.3: Daemon configuration
585
605
  daemon_idle_timeout: int = 0 # 0 = 24/7 (no auto-kill). >0 = seconds before auto-kill.
@@ -657,6 +677,14 @@ class SLMConfig:
657
677
  )
658
678
  config.mesh_enabled = data.get("mesh_enabled", True)
659
679
 
680
+ # V3.4.10: Evolution config
681
+ evo = data.get("evolution", {})
682
+ if evo:
683
+ config.evolution = EvolutionConfig(**{
684
+ k: v for k, v in evo.items()
685
+ if k in EvolutionConfig.__dataclass_fields__
686
+ })
687
+
660
688
  return config
661
689
 
662
690
  def save(self, config_path: Path | None = None) -> None:
@@ -696,6 +724,13 @@ class SLMConfig:
696
724
  },
697
725
  }
698
726
 
727
+ # V3.4.11: Persist evolution config (C-CONFIGSAVE fix)
728
+ data["evolution"] = {
729
+ "enabled": self.evolution.enabled,
730
+ "backend": self.evolution.backend,
731
+ "max_evolutions_per_cycle": self.evolution.max_evolutions_per_cycle,
732
+ }
733
+
699
734
  # Preserve existing V3.3 config sections that aren't in for_mode()
700
735
  for key in ("forgetting", "quantization", "sagq", "embedding_signature", "auto_invoke"):
701
736
  if key in existing: