@smilintux/skcapstone 0.4.7 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.openclaw-workspace.json +1 -1
  2. package/docs/BOND_WITH_GROK.md +1 -1
  3. package/package.json +1 -1
  4. package/pyproject.toml +1 -1
  5. package/scripts/check-updates.py +1 -1
  6. package/scripts/install-bundle.sh +2 -2
  7. package/scripts/install.ps1 +11 -10
  8. package/scripts/install.sh +1 -1
  9. package/scripts/nvidia-proxy.mjs +32 -10
  10. package/scripts/refresh-anthropic-token.sh +94 -0
  11. package/scripts/watch-anthropic-token.sh +117 -0
  12. package/src/skcapstone/__init__.py +1 -1
  13. package/src/skcapstone/_cli_monolith.py +5 -5
  14. package/src/skcapstone/api.py +36 -35
  15. package/src/skcapstone/auction.py +8 -8
  16. package/src/skcapstone/blueprint_registry.py +2 -2
  17. package/src/skcapstone/brain_first.py +238 -0
  18. package/src/skcapstone/chat.py +4 -4
  19. package/src/skcapstone/cli/agents_spawner.py +5 -2
  20. package/src/skcapstone/cli/chat.py +5 -2
  21. package/src/skcapstone/cli/consciousness.py +5 -2
  22. package/src/skcapstone/cli/memory.py +4 -4
  23. package/src/skcapstone/cli/skills_cmd.py +2 -2
  24. package/src/skcapstone/cli/soul.py +5 -2
  25. package/src/skcapstone/cli/status.py +11 -8
  26. package/src/skcapstone/cli/test_cmd.py +1 -1
  27. package/src/skcapstone/cli/upgrade_cmd.py +19 -10
  28. package/src/skcapstone/cli/watch_cmd.py +9 -6
  29. package/src/skcapstone/config_validator.py +7 -4
  30. package/src/skcapstone/consciousness_loop.py +20 -18
  31. package/src/skcapstone/coordination.py +5 -2
  32. package/src/skcapstone/daemon.py +32 -31
  33. package/src/skcapstone/dashboard.py +8 -8
  34. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +42 -0
  35. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
  36. package/src/skcapstone/discovery.py +1 -1
  37. package/src/skcapstone/doctor.py +5 -2
  38. package/src/skcapstone/dreaming.py +730 -51
  39. package/src/skcapstone/emotion_tracker.py +2 -2
  40. package/src/skcapstone/export.py +2 -2
  41. package/src/skcapstone/itil.py +2 -2
  42. package/src/skcapstone/launchd.py +2 -2
  43. package/src/skcapstone/mcp_server.py +48 -4
  44. package/src/skcapstone/mcp_tools/__init__.py +2 -0
  45. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  46. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  47. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  48. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  49. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  50. package/src/skcapstone/mcp_tools/did_tools.py +9 -6
  51. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  52. package/src/skcapstone/mcp_tools/soul_tools.py +6 -2
  53. package/src/skcapstone/mdns_discovery.py +2 -2
  54. package/src/skcapstone/metrics.py +8 -8
  55. package/src/skcapstone/migrate_memories.py +2 -2
  56. package/src/skcapstone/models.py +14 -0
  57. package/src/skcapstone/onboard.py +7 -4
  58. package/src/skcapstone/peer_directory.py +2 -2
  59. package/src/skcapstone/providers/docker.py +2 -2
  60. package/src/skcapstone/register.py +2 -2
  61. package/src/skcapstone/service_health.py +2 -2
  62. package/src/skcapstone/sync_watcher.py +2 -2
  63. package/src/skcapstone/testrunner.py +1 -1
@@ -392,8 +392,8 @@ class EmotionTracker:
392
392
  if item.get("timestamp", "") >= cutoff:
393
393
  try:
394
394
  entries.append(EmotionEntry(**item))
395
- except Exception:
396
- pass
395
+ except Exception as exc:
396
+ logger.warning("Failed to parse emotion log entry: %s", exc)
397
397
  return entries
398
398
  except Exception as exc:
399
399
  logger.debug("Failed to load emotion log: %s", exc)
@@ -423,8 +423,8 @@ def _import_conversations(
423
423
  existing_data = json.loads(peer_file.read_text(encoding="utf-8"))
424
424
  if isinstance(existing_data, list):
425
425
  existing_messages = existing_data
426
- except Exception:
427
- pass
426
+ except Exception as exc:
427
+ logger.warning("Failed to read existing conversation for peer %s: %s", peer, exc)
428
428
 
429
429
  # Deduplicate by (role, content, timestamp) tuple
430
430
  existing_keys = {
@@ -1100,5 +1100,5 @@ class ITILManager:
1100
1100
  try:
1101
1101
  from . import activity
1102
1102
  activity.push(topic, payload)
1103
- except Exception:
1104
- pass
1103
+ except Exception as exc:
1104
+ logger.warning("Failed to push ITIL event %s to activity bus: %s", topic, exc)
@@ -420,7 +420,7 @@ def service_logs(suffix: str = "daemon", lines: int = 50) -> str:
420
420
  )
421
421
  if r.stdout.strip():
422
422
  output_parts.append(f"--- {log_path.name} ---\n{r.stdout}")
423
- except Exception:
424
- pass
423
+ except Exception as exc:
424
+ logger.warning("Failed to read launchd log %s: %s", log_path, exc)
425
425
 
426
426
  return "\n".join(output_parts) if output_parts else f"No logs found in {logs_dir}"
@@ -142,8 +142,8 @@ def _get_agent_name(home: Path) -> str:
142
142
  try:
143
143
  data = json.loads(identity_path.read_text(encoding="utf-8"))
144
144
  return data.get("name", "anonymous")
145
- except Exception:
146
- pass
145
+ except Exception as exc:
146
+ logger.warning("Failed to read agent name from identity.json: %s", exc)
147
147
  return "anonymous"
148
148
 
149
149
 
@@ -2654,6 +2654,39 @@ async def list_tools() -> list[Tool]:
2654
2654
  "required": ["query"],
2655
2655
  },
2656
2656
  ),
2657
+ # Brain-First Protocol
2658
+ Tool(
2659
+ name="brain_first_check",
2660
+ description=(
2661
+ "Brain-First Protocol: consult the agent's memory before "
2662
+ "acting on a task. Extracts keywords from the given context, "
2663
+ "searches memory for relevant prior knowledge, and returns "
2664
+ "any matching memories. Use this before starting new work to "
2665
+ "avoid duplicating effort or missing prior decisions."
2666
+ ),
2667
+ inputSchema={
2668
+ "type": "object",
2669
+ "properties": {
2670
+ "context": {
2671
+ "type": "string",
2672
+ "description": (
2673
+ "The task description, prompt, or action context "
2674
+ "to search memory for"
2675
+ ),
2676
+ },
2677
+ "tags": {
2678
+ "type": "array",
2679
+ "items": {"type": "string"},
2680
+ "description": "Optional tag filter for the memory search",
2681
+ },
2682
+ "max_results": {
2683
+ "type": "integer",
2684
+ "description": "Max memories to return (default: from config, usually 5)",
2685
+ },
2686
+ },
2687
+ "required": ["context"],
2688
+ },
2689
+ ),
2657
2690
  ]
2658
2691
 
2659
2692
 
@@ -2813,6 +2846,8 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
2813
2846
  "itil_cab_vote": _handle_itil_cab_vote,
2814
2847
  "itil_status": _handle_itil_status,
2815
2848
  "itil_kedb_search": _handle_itil_kedb_search,
2849
+ # Brain-First Protocol
2850
+ "brain_first_check": _handle_brain_first_check,
2816
2851
  }
2817
2852
  handler = handlers.get(name)
2818
2853
  if handler is None:
@@ -3154,8 +3189,8 @@ async def _handle_coord_complete(args: dict) -> list[TextContent]:
3154
3189
  t.priority.value, ("community", "support_ticket", 50)
3155
3190
  )
3156
3191
  break
3157
- except Exception:
3158
- pass
3192
+ except Exception as exc:
3193
+ logger.warning("Failed to calculate joules for completed task %s: %s", task_id, exc)
3159
3194
 
3160
3195
  return _json_response({
3161
3196
  "completed": True,
@@ -4937,6 +4972,15 @@ async def _handle_itil_kedb_search(args: dict) -> list[TextContent]:
4937
4972
  return await _impl(args)
4938
4973
 
4939
4974
 
4975
+ # ── Brain-First Protocol ──────────────────────────────────
4976
+
4977
+
4978
+ async def _handle_brain_first_check(args: dict) -> list[TextContent]:
4979
+ """Consult memory before acting on a task."""
4980
+ from .mcp_tools.brain_first_tools import _handle_brain_first_check as _impl
4981
+ return await _impl(args)
4982
+
4983
+
4940
4984
  # ═══════════════════════════════════════════════════════════
4941
4985
  # Entry Point
4942
4986
  # ═══════════════════════════════════════════════════════════
@@ -17,6 +17,7 @@ from mcp.types import TextContent, Tool
17
17
  from . import (
18
18
  agent_tools,
19
19
  ansible_tools,
20
+ brain_first_tools,
20
21
  chat_tools,
21
22
  comm_tools,
22
23
  consciousness_tools,
@@ -49,6 +50,7 @@ from . import (
49
50
  # Ordered list of all tool-group modules.
50
51
  _MODULES = [
51
52
  agent_tools,
53
+ brain_first_tools,
52
54
  memory_tools,
53
55
  comm_tools,
54
56
  sync_tools,
@@ -46,6 +46,6 @@ def _get_agent_name(home: Path) -> str:
46
46
  try:
47
47
  data = json.loads(identity_path.read_text(encoding="utf-8"))
48
48
  return data.get("name", "anonymous")
49
- except Exception:
50
- pass
49
+ except Exception as exc:
50
+ logger.warning("Failed to read agent name from identity.json: %s", exc)
51
51
  return "anonymous"
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  import asyncio
12
12
  import json
13
+ import logging
13
14
  import shutil
14
15
  import uuid
15
16
  from pathlib import Path
@@ -18,6 +19,8 @@ from mcp.types import TextContent, Tool
18
19
 
19
20
  from ._helpers import _error_response, _home, _json_response
20
21
 
22
+ logger = logging.getLogger(__name__)
23
+
21
24
  TOOLS: list[Tool] = [
22
25
  Tool(
23
26
  name="run_ansible_playbook",
@@ -146,8 +149,8 @@ async def _handle_run_ansible_playbook(args: dict) -> list[TextContent]:
146
149
  try:
147
150
  if _activity is not None:
148
151
  _activity.push(event_type, {"run_id": run_id, "line": line})
149
- except Exception:
150
- pass
152
+ except Exception as exc:
153
+ logger.warning("Failed to push ansible line event for run %s: %s", run_id, exc)
151
154
 
152
155
  await asyncio.gather(
153
156
  _drain(proc.stdout, stdout_lines, "ansible.playbook.line"),
@@ -184,8 +187,8 @@ async def _handle_run_ansible_playbook(args: dict) -> list[TextContent]:
184
187
  try:
185
188
  if _activity is not None:
186
189
  _activity.push("ansible.playbook.done", summary)
187
- except Exception:
188
- pass
190
+ except Exception as exc:
191
+ logger.warning("Failed to push ansible.playbook.done event for run %s: %s", run_id, exc)
189
192
 
190
193
  # --- store in memory with tag=ansible-run ---
191
194
  try:
@@ -0,0 +1,90 @@
1
+ """Brain-First Protocol MCP tools.
2
+
3
+ Exposes the brain-first memory consultation as an MCP tool so that
4
+ any MCP client can ask "what do I already know about this?" before
5
+ acting on a task.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from mcp.types import TextContent, Tool
11
+
12
+ from ._helpers import _json_response
13
+
14
+ TOOLS: list[Tool] = [
15
+ Tool(
16
+ name="brain_first_check",
17
+ description=(
18
+ "Brain-First Protocol: consult the agent's memory before "
19
+ "acting on a task. Extracts keywords from the given context, "
20
+ "searches memory for relevant prior knowledge, and returns "
21
+ "any matching memories. Use this before starting new work to "
22
+ "avoid duplicating effort or missing prior decisions."
23
+ ),
24
+ inputSchema={
25
+ "type": "object",
26
+ "properties": {
27
+ "context": {
28
+ "type": "string",
29
+ "description": (
30
+ "The task description, prompt, or action context "
31
+ "to search memory for"
32
+ ),
33
+ },
34
+ "tags": {
35
+ "type": "array",
36
+ "items": {"type": "string"},
37
+ "description": "Optional tag filter for the memory search",
38
+ },
39
+ "max_results": {
40
+ "type": "integer",
41
+ "description": "Max memories to return (default: from config, usually 5)",
42
+ },
43
+ },
44
+ "required": ["context"],
45
+ },
46
+ ),
47
+ ]
48
+
49
+
50
+ async def _handle_brain_first_check(args: dict) -> list[TextContent]:
51
+ """Run a brain-first memory consultation."""
52
+ from ..brain_first import BrainFirstConfig, brain_first_check, _load_config
53
+
54
+ context = args.get("context", "")
55
+ if not context:
56
+ return _json_response({"error": "context is required"})
57
+
58
+ config = _load_config()
59
+
60
+ # Allow per-call override of max_results
61
+ max_results = args.get("max_results")
62
+ if max_results is not None:
63
+ config.max_results = max_results
64
+
65
+ result = brain_first_check(
66
+ context=context,
67
+ config=config,
68
+ tags=args.get("tags"),
69
+ )
70
+
71
+ response = {
72
+ "enabled": result.enabled,
73
+ "query": result.query,
74
+ "keywords": result.keywords,
75
+ "memories_found": len(result.memories),
76
+ "memories": result.memories,
77
+ }
78
+
79
+ if result.error:
80
+ response["warning"] = result.error
81
+
82
+ if result.has_memories:
83
+ response["context_block"] = result.as_context()
84
+
85
+ return _json_response(response)
86
+
87
+
88
+ HANDLERS: dict = {
89
+ "brain_first_check": _handle_brain_first_check,
90
+ }
@@ -8,11 +8,14 @@ Exposes two tools:
8
8
  from __future__ import annotations
9
9
 
10
10
  import json as _json
11
+ import logging
11
12
 
12
13
  from mcp.types import TextContent, Tool
13
14
 
14
15
  from ._helpers import _error_response, _home, _json_response
15
16
 
17
+ logger = logging.getLogger(__name__)
18
+
16
19
  TOOLS: list[Tool] = [
17
20
  Tool(
18
21
  name="capauth_status",
@@ -82,8 +85,8 @@ async def _handle_capauth_status(_args: dict) -> list[TextContent]:
82
85
  if did_key_file.exists():
83
86
  try:
84
87
  result["did_key_file"] = did_key_file.read_text(encoding="utf-8").strip()
85
- except Exception:
86
- pass
88
+ except Exception as exc:
89
+ logger.warning("Failed to read DID key file: %s", exc)
87
90
 
88
91
  # Check identity file
89
92
  identity_file = home / "identity" / "identity.json"
@@ -92,8 +95,8 @@ async def _handle_capauth_status(_args: dict) -> list[TextContent]:
92
95
  ident = _json.loads(identity_file.read_text(encoding="utf-8"))
93
96
  result["identity_name"] = ident.get("name")
94
97
  result["identity_fingerprint"] = ident.get("fingerprint")
95
- except Exception:
96
- pass
98
+ except Exception as exc:
99
+ logger.warning("Failed to read identity.json for capauth status: %s", exc)
97
100
 
98
101
  return _json_response(result)
99
102
 
@@ -2,10 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
6
+
5
7
  from mcp.types import TextContent, Tool
6
8
 
7
9
  from ._helpers import _error_response, _home, _json_response, _shared_root
8
10
 
11
+ logger = logging.getLogger(__name__)
12
+
9
13
  TOOLS: list[Tool] = [
10
14
  Tool(
11
15
  name="coord_status",
@@ -149,8 +153,8 @@ async def _handle_coord_claim(args: dict) -> list[TextContent]:
149
153
  try:
150
154
  from .. import activity
151
155
  activity.push("task.claimed", {"task_id": task_id, "agent": agent_name})
152
- except Exception:
153
- pass
156
+ except Exception as exc:
157
+ logger.warning("Failed to push task.claimed activity for %s: %s", task_id, exc)
154
158
  return _json_response({
155
159
  "claimed": True,
156
160
  "task_id": task_id,
@@ -175,8 +179,8 @@ async def _handle_coord_complete(args: dict) -> list[TextContent]:
175
179
  try:
176
180
  from .. import activity
177
181
  activity.push("task.completed", {"task_id": task_id, "agent": agent_name})
178
- except Exception:
179
- pass
182
+ except Exception as exc:
183
+ logger.warning("Failed to push task.completed activity for %s: %s", task_id, exc)
180
184
  return _json_response({
181
185
  "completed": True,
182
186
  "task_id": task_id,
@@ -10,6 +10,7 @@ Exposes four tools:
10
10
  from __future__ import annotations
11
11
 
12
12
  import json as _json
13
+ import logging
13
14
  import os as _os
14
15
  import socket as _socket
15
16
  from pathlib import Path
@@ -18,6 +19,8 @@ from mcp.types import TextContent, Tool
18
19
 
19
20
  from ._helpers import _error_response, _home, _json_response
20
21
 
22
+ logger = logging.getLogger(__name__)
23
+
21
24
  TOOLS: list[Tool] = [
22
25
  Tool(
23
26
  name="did_show",
@@ -165,8 +168,8 @@ def _load_policy() -> dict:
165
168
  if p.exists():
166
169
  try:
167
170
  return _json.loads(p.read_text(encoding="utf-8"))
168
- except Exception:
169
- pass
171
+ except Exception as exc:
172
+ logger.warning("Failed to load DID policy from %s: %s", p, exc)
170
173
  return dict(_POLICY_DEFAULT)
171
174
 
172
175
 
@@ -191,8 +194,8 @@ def _resolve_tailnet(tailnet_hostname: str, tailnet_name: str) -> tuple[str, str
191
194
  if not tailnet_hostname:
192
195
  try:
193
196
  tailnet_hostname = _socket.gethostname()
194
- except Exception:
195
- pass
197
+ except Exception as exc:
198
+ logger.warning("Failed to resolve hostname for tailnet DID: %s", exc)
196
199
  if not tailnet_name:
197
200
  tailnet_name = _os.environ.get("SKWORLD_TAILNET", "")
198
201
  return tailnet_hostname, tailnet_name
@@ -297,8 +300,8 @@ async def _handle_did_verify_peer(args: dict) -> list[TextContent]:
297
300
  _json.dumps(peer_data, indent=2, default=str),
298
301
  encoding="utf-8",
299
302
  )
300
- except Exception:
301
- pass
303
+ except Exception as exc:
304
+ logger.warning("Failed to cache did:key back to peer file %s: %s", peer_file, exc)
302
305
 
303
306
  return _json_response({
304
307
  "name": name,
@@ -2,10 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
6
+
5
7
  from mcp.types import TextContent, Tool
6
8
 
7
9
  from ._helpers import _error_response, _home, _json_response
8
10
 
11
+ logger = logging.getLogger(__name__)
12
+
9
13
  TOOLS: list[Tool] = [
10
14
  Tool(
11
15
  name="memory_store",
@@ -130,8 +134,8 @@ async def _handle_memory_store(args: dict) -> list[TextContent]:
130
134
  "importance": entry.importance,
131
135
  "tags": entry.tags,
132
136
  })
133
- except Exception:
134
- pass
137
+ except Exception as exc:
138
+ logger.warning("Failed to push memory.stored activity for %s: %s", entry.memory_id, exc)
135
139
  return _json_response({
136
140
  "memory_id": entry.memory_id,
137
141
  "layer": entry.layer.value,
@@ -2,10 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
6
+
5
7
  from mcp.types import TextContent, Tool
6
8
 
7
9
  from ._helpers import _error_response, _home, _json_response, _text_response
8
10
 
11
+ logger = logging.getLogger(__name__)
12
+
9
13
  TOOLS: list[Tool] = [
10
14
  Tool(
11
15
  name="soul_list",
@@ -250,8 +254,8 @@ async def _handle_soul_list(args: dict) -> list[TextContent]:
250
254
  "source": "installed",
251
255
  "active": name == state.active_soul,
252
256
  })
253
- except Exception:
254
- pass
257
+ except Exception as exc:
258
+ logger.warning("Failed to list installed soul blueprints: %s", exc)
255
259
 
256
260
  # 2) Blueprints repo
257
261
  blueprints_repo = Path.home() / "clawd" / "soul-blueprints" / "blueprints"
@@ -238,8 +238,8 @@ class MDNSDiscovery:
238
238
  agent_name,
239
239
  )
240
240
  return
241
- except Exception:
242
- pass
241
+ except Exception as exc:
242
+ logger.warning("Failed to read existing mDNS heartbeat for %s: %s", agent_name, exc)
243
243
 
244
244
  heartbeat = {
245
245
  "agent_name": agent_name,
@@ -358,8 +358,8 @@ class MetricsCollector:
358
358
  1 for t in transports.values()
359
359
  if isinstance(t, dict) and t.get("enabled", True)
360
360
  )
361
- except Exception:
362
- pass
361
+ except Exception as exc:
362
+ logger.warning("Failed to parse skcomm transport config: %s", exc)
363
363
 
364
364
  report.transport = TransportMetrics(
365
365
  available=True,
@@ -474,8 +474,8 @@ class MetricsCollector:
474
474
  if state_path.exists():
475
475
  try:
476
476
  state = json.loads(state_path.read_text(encoding="utf-8"))
477
- except Exception:
478
- pass
477
+ except Exception as exc:
478
+ logger.warning("Failed to read sync_state.json: %s", exc)
479
479
 
480
480
  report.sync = SyncMetrics(
481
481
  available=True,
@@ -510,8 +510,8 @@ class MetricsCollector:
510
510
  try:
511
511
  subs = json.loads(subs_file.read_text(encoding="utf-8"))
512
512
  sub_count = len(subs)
513
- except Exception:
514
- pass
513
+ except Exception as exc:
514
+ logger.warning("Failed to read pubsub subscriptions.json: %s", exc)
515
515
 
516
516
  report.pubsub = PubSubMetrics(
517
517
  available=True,
@@ -546,8 +546,8 @@ class MetricsCollector:
546
546
  try:
547
547
  rot_data = json.loads(rot_log.read_text(encoding="utf-8"))
548
548
  rotations = len(rot_data)
549
- except Exception:
550
- pass
549
+ except Exception as exc:
550
+ logger.warning("Failed to read KMS rotation log: %s", exc)
551
551
 
552
552
  report.kms = KmsMetrics(
553
553
  available=True,
@@ -100,8 +100,8 @@ def migrate(
100
100
  try:
101
101
  existing = store.list_memories(limit=10000)
102
102
  existing_ids = {m.id for m in existing}
103
- except Exception:
104
- pass
103
+ except Exception as exc:
104
+ logger.warning("Failed to load existing memory IDs for deduplication: %s", exc)
105
105
 
106
106
  for entry in entries:
107
107
  if entry.memory_id in existing_ids:
@@ -238,6 +238,19 @@ class SyncConfig(BaseModel):
238
238
  git_remote: Optional[str] = None
239
239
 
240
240
 
241
+ class BrainFirstConfig(BaseModel):
242
+ """Configuration for the brain-first protocol.
243
+
244
+ When enabled, agents consult memory before acting on tasks
245
+ to surface prior knowledge and avoid redundant work.
246
+ """
247
+
248
+ enabled: bool = True
249
+ max_results: int = 5
250
+ min_importance: float = 0.3
251
+ auto_inject: bool = False
252
+
253
+
241
254
  class AgentConfig(BaseModel):
242
255
  """Persistent configuration for the agent runtime."""
243
256
 
@@ -249,6 +262,7 @@ class AgentConfig(BaseModel):
249
262
  trust_home: Path = Path("~/.cloud9")
250
263
  default_connector: Optional[str] = None
251
264
  sync: SyncConfig = Field(default_factory=SyncConfig)
265
+ brain_first: BrainFirstConfig = Field(default_factory=BrainFirstConfig)
252
266
  capabilities: list[str] = Field(
253
267
  default_factory=lambda: ["consciousness", "code", "chat", "memory"]
254
268
  )
@@ -21,6 +21,7 @@ Steps:
21
21
  from __future__ import annotations
22
22
 
23
23
  import json
24
+ import logging
24
25
  import sys
25
26
  import time
26
27
  from datetime import datetime, timezone
@@ -28,6 +29,8 @@ from pathlib import Path
28
29
  from typing import Optional
29
30
 
30
31
  import click
32
+
33
+ logger = logging.getLogger(__name__)
31
34
  from rich.console import Console
32
35
  from rich.panel import Panel
33
36
  from rich.prompt import Confirm, Prompt
@@ -503,8 +506,8 @@ def _step_ollama_models(prereqs: dict) -> bool:
503
506
  if DEFAULT_MODEL in (r.stdout or ""):
504
507
  click.echo(click.style(" ✓ ", fg="green") + f"{DEFAULT_MODEL} already present")
505
508
  return True
506
- except Exception:
507
- pass
509
+ except Exception as exc:
510
+ logger.debug("Failed to check ollama model list: %s", exc)
508
511
 
509
512
  if not click.confirm(f" Pull default model ({DEFAULT_MODEL}, ~2 GB)?", default=True):
510
513
  click.echo(click.style(" ↷ ", fg="bright_black") + f"Skipped — pull later: ollama pull {DEFAULT_MODEL}")
@@ -991,8 +994,8 @@ def run_onboard(home: Optional[str] = None) -> None:
991
994
  soul = load_soul()
992
995
  if soul and soul.boot_message:
993
996
  boot_message = soul.boot_message
994
- except Exception:
995
- pass
997
+ except Exception as exc:
998
+ logger.debug("Failed to load soul boot message, using default: %s", exc)
996
999
 
997
1000
  # -----------------------------------------------------------------------
998
1001
  # Summary table
@@ -250,8 +250,8 @@ class PeerDirectory:
250
250
  ts = data.get("timestamp", "")
251
251
  if ts:
252
252
  self._entries[agent_name].last_seen = ts
253
- except Exception:
254
- pass
253
+ except Exception as exc:
254
+ logger.warning("Failed to update last_seen from heartbeat for %s: %s", agent_name, exc)
255
255
  continue
256
256
 
257
257
  try:
@@ -307,8 +307,8 @@ class DockerProvider(ProviderBackend):
307
307
  old = client.containers.get(container_name)
308
308
  logger.warning("Removing stale container: %s", container_name)
309
309
  old.remove(force=True)
310
- except Exception:
311
- pass
310
+ except Exception as exc:
311
+ logger.debug("No stale container to remove for %s (expected if first run): %s", container_name, exc)
312
312
 
313
313
  # Ensure named volume for agent state persistence
314
314
  try:
@@ -88,7 +88,7 @@ def _build_package_registry(workspace: Optional[Path] = None) -> list[dict]:
88
88
  "mcp_cmd": None,
89
89
  "mcp_args": None,
90
90
  "mcp_env": None,
91
- "openclaw_plugin_path": workspace / "pillar-repos" / "cloud9-python" / "openclaw-plugin" / "src" / "index.ts",
91
+ "openclaw_plugin_path": workspace / "pillar-repos" / "cloud9" / "openclaw-plugin-python" / "src" / "index.ts",
92
92
  },
93
93
  {
94
94
  "name": "sksecurity",
@@ -124,7 +124,7 @@ _PILLAR_DIR_MAP: dict[str, Optional[str]] = {
124
124
  "skcomm": "skcomm",
125
125
  "skchat": "skchat",
126
126
  "capauth": "capauth",
127
- "cloud9": "cloud9-python",
127
+ "cloud9": "cloud9",
128
128
  "sksecurity": "sksecurity",
129
129
  "skseed": "skseed",
130
130
  "skgit": None, # skill dir only, no pillar repo
@@ -76,8 +76,8 @@ def _http_check(
76
76
  try:
77
77
  body = json.loads(resp.read().decode("utf-8"))
78
78
  result["version"] = body.get(version_key)
79
- except Exception:
80
- pass
79
+ except Exception as exc:
80
+ logger.warning("Failed to parse version from service health response: %s", exc)
81
81
  except urllib.error.HTTPError as exc:
82
82
  latency = (time.monotonic() - t0) * 1000
83
83
  result["latency_ms"] = round(latency, 1)
@@ -574,8 +574,8 @@ class SyncWatcher:
574
574
  try:
575
575
  self._observer.stop()
576
576
  self._observer.join(timeout=5)
577
- except Exception:
578
- pass
577
+ except Exception as exc:
578
+ logger.warning("Error stopping SyncWatcher observer: %s", exc)
579
579
  self._observer = None
580
580
  logger.info("SyncWatcher stopped.")
581
581