@smilintux/skcapstone 0.4.6 → 0.5.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.
Files changed (77) hide show
  1. package/.github/workflows/publish.yml +8 -1
  2. package/docs/CUSTOM_AGENT.md +184 -0
  3. package/docs/GETTING_STARTED.md +3 -0
  4. package/launchd/com.skcapstone.daemon.plist +52 -0
  5. package/launchd/com.skcapstone.memory-compress.plist +45 -0
  6. package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
  7. package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
  8. package/launchd/install-launchd.sh +156 -0
  9. package/package.json +1 -1
  10. package/pyproject.toml +1 -1
  11. package/scripts/archive-sessions.sh +88 -0
  12. package/scripts/install.sh +39 -8
  13. package/scripts/notion-api.py +259 -0
  14. package/scripts/nvidia-proxy.mjs +878 -0
  15. package/scripts/proxy-monitor.sh +89 -0
  16. package/scripts/refresh-anthropic-token.sh +94 -0
  17. package/scripts/skgateway.mjs +856 -0
  18. package/scripts/telegram-catchup-all.sh +136 -0
  19. package/scripts/watch-anthropic-token.sh +117 -0
  20. package/src/skcapstone/__init__.py +1 -1
  21. package/src/skcapstone/_cli_monolith.py +4 -4
  22. package/src/skcapstone/api.py +36 -35
  23. package/src/skcapstone/auction.py +8 -8
  24. package/src/skcapstone/blueprint_registry.py +2 -2
  25. package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
  26. package/src/skcapstone/brain_first.py +238 -0
  27. package/src/skcapstone/chat.py +4 -4
  28. package/src/skcapstone/cli/__init__.py +2 -0
  29. package/src/skcapstone/cli/agents_spawner.py +5 -2
  30. package/src/skcapstone/cli/chat.py +5 -2
  31. package/src/skcapstone/cli/consciousness.py +5 -2
  32. package/src/skcapstone/cli/daemon.py +116 -41
  33. package/src/skcapstone/cli/itil.py +434 -0
  34. package/src/skcapstone/cli/memory.py +4 -4
  35. package/src/skcapstone/cli/skills_cmd.py +2 -2
  36. package/src/skcapstone/cli/soul.py +5 -2
  37. package/src/skcapstone/cli/status.py +11 -8
  38. package/src/skcapstone/cli/upgrade_cmd.py +7 -4
  39. package/src/skcapstone/cli/watch_cmd.py +9 -6
  40. package/src/skcapstone/config_validator.py +7 -4
  41. package/src/skcapstone/consciousness_config.py +27 -0
  42. package/src/skcapstone/consciousness_loop.py +20 -18
  43. package/src/skcapstone/coordination.py +6 -2
  44. package/src/skcapstone/daemon.py +51 -42
  45. package/src/skcapstone/dashboard.py +8 -8
  46. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +42 -0
  47. package/src/skcapstone/doctor.py +5 -2
  48. package/src/skcapstone/dreaming.py +1440 -0
  49. package/src/skcapstone/emotion_tracker.py +2 -2
  50. package/src/skcapstone/export.py +2 -2
  51. package/src/skcapstone/fuse_mount.py +21 -13
  52. package/src/skcapstone/heartbeat.py +33 -29
  53. package/src/skcapstone/itil.py +1104 -0
  54. package/src/skcapstone/launchd.py +426 -0
  55. package/src/skcapstone/mcp_server.py +306 -4
  56. package/src/skcapstone/mcp_tools/__init__.py +4 -0
  57. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  58. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  59. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  60. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  61. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  62. package/src/skcapstone/mcp_tools/did_tools.py +9 -6
  63. package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
  64. package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
  65. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  66. package/src/skcapstone/mcp_tools/soul_tools.py +6 -2
  67. package/src/skcapstone/mdns_discovery.py +2 -2
  68. package/src/skcapstone/metrics.py +8 -8
  69. package/src/skcapstone/migrate_memories.py +2 -2
  70. package/src/skcapstone/models.py +14 -0
  71. package/src/skcapstone/onboard.py +137 -14
  72. package/src/skcapstone/peer_directory.py +2 -2
  73. package/src/skcapstone/providers/docker.py +2 -2
  74. package/src/skcapstone/scheduled_tasks.py +107 -0
  75. package/src/skcapstone/service_health.py +83 -4
  76. package/src/skcapstone/sync_watcher.py +2 -2
  77. package/src/skcapstone/systemd.py +17 -0
@@ -55,6 +55,16 @@ Tools:
55
55
  kms_list_keys — List all KMS keys
56
56
  kms_rotate — Rotate a KMS key
57
57
  model_route — Route task to optimal model tier/name
58
+ itil_incident_create — Create ITIL incident
59
+ itil_incident_update — Update incident status/severity
60
+ itil_incident_list — List/filter incidents
61
+ itil_problem_create — Create problem record
62
+ itil_problem_update — Update problem/root cause
63
+ itil_change_propose — Propose change (RFC)
64
+ itil_change_update — Update change status
65
+ itil_cab_vote — Submit CAB vote
66
+ itil_status — ITIL dashboard
67
+ itil_kedb_search — Search Known Error Database
58
68
 
59
69
  Invocation (all equivalent):
60
70
  skcapstone mcp serve # CLI entry point
@@ -132,8 +142,8 @@ def _get_agent_name(home: Path) -> str:
132
142
  try:
133
143
  data = json.loads(identity_path.read_text(encoding="utf-8"))
134
144
  return data.get("name", "anonymous")
135
- except Exception:
136
- pass
145
+ except Exception as exc:
146
+ logger.warning("Failed to read agent name from identity.json: %s", exc)
137
147
  return "anonymous"
138
148
 
139
149
 
@@ -2470,6 +2480,213 @@ async def list_tools() -> list[Tool]:
2470
2480
  "required": ["name"],
2471
2481
  },
2472
2482
  ),
2483
+ # ── ITIL Service Management ─────────────────────────────
2484
+ Tool(
2485
+ name="itil_incident_create",
2486
+ description=(
2487
+ "Create a new ITIL incident for a service disruption. "
2488
+ "Auto-creates a linked GTD item (next-action for sev1/sev2, inbox for sev3/sev4)."
2489
+ ),
2490
+ inputSchema={
2491
+ "type": "object",
2492
+ "properties": {
2493
+ "title": {"type": "string", "description": "Brief description of the incident"},
2494
+ "severity": {"type": "string", "enum": ["sev1", "sev2", "sev3", "sev4"], "description": "Severity level (default: sev3)"},
2495
+ "source": {"type": "string", "enum": ["service_health", "dreaming", "manual", "daemon_error", "heartbeat"], "description": "Detection source (default: manual)"},
2496
+ "affected_services": {"type": "array", "items": {"type": "string"}, "description": "List of affected service names"},
2497
+ "impact": {"type": "string", "description": "Business impact description"},
2498
+ "managed_by": {"type": "string", "description": "Agent responsible for managing this incident"},
2499
+ "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for categorization"},
2500
+ },
2501
+ "required": ["title"],
2502
+ },
2503
+ ),
2504
+ Tool(
2505
+ name="itil_incident_update",
2506
+ description=(
2507
+ "Update an incident: transition status, escalate severity, "
2508
+ "add timeline notes, or resolve. Valid status transitions: "
2509
+ "detected->acknowledged->investigating->resolved->closed."
2510
+ ),
2511
+ inputSchema={
2512
+ "type": "object",
2513
+ "properties": {
2514
+ "incident_id": {"type": "string", "description": "Incident ID (e.g. inc-a1b2c3d4)"},
2515
+ "agent": {"type": "string", "description": "Agent making the update"},
2516
+ "new_status": {"type": "string", "enum": ["acknowledged", "investigating", "escalated", "resolved", "closed"], "description": "New status"},
2517
+ "severity": {"type": "string", "enum": ["sev1", "sev2", "sev3", "sev4"], "description": "New severity"},
2518
+ "note": {"type": "string", "description": "Timeline note"},
2519
+ "resolution_summary": {"type": "string", "description": "Resolution summary (when resolving)"},
2520
+ "related_problem_id": {"type": "string", "description": "Link to a related problem record"},
2521
+ },
2522
+ "required": ["incident_id", "agent"],
2523
+ },
2524
+ ),
2525
+ Tool(
2526
+ name="itil_incident_list",
2527
+ description="List ITIL incidents filtered by status, severity, or affected service.",
2528
+ inputSchema={
2529
+ "type": "object",
2530
+ "properties": {
2531
+ "status": {"type": "string", "enum": ["detected", "acknowledged", "investigating", "escalated", "resolved", "closed"], "description": "Filter by status"},
2532
+ "severity": {"type": "string", "enum": ["sev1", "sev2", "sev3", "sev4"], "description": "Filter by severity"},
2533
+ "service": {"type": "string", "description": "Filter by affected service name"},
2534
+ },
2535
+ "required": [],
2536
+ },
2537
+ ),
2538
+ Tool(
2539
+ name="itil_problem_create",
2540
+ description=(
2541
+ "Create a new ITIL problem record to investigate root cause. "
2542
+ "Links to related incidents and auto-creates a GTD project."
2543
+ ),
2544
+ inputSchema={
2545
+ "type": "object",
2546
+ "properties": {
2547
+ "title": {"type": "string", "description": "Problem title"},
2548
+ "managed_by": {"type": "string", "description": "Agent responsible for investigation"},
2549
+ "related_incident_ids": {"type": "array", "items": {"type": "string"}, "description": "Related incident IDs"},
2550
+ "workaround": {"type": "string", "description": "Known workaround if any"},
2551
+ "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for categorization"},
2552
+ },
2553
+ "required": ["title"],
2554
+ },
2555
+ ),
2556
+ Tool(
2557
+ name="itil_problem_update",
2558
+ description=(
2559
+ "Update a problem record: transition status, set root cause, "
2560
+ "add workaround, optionally create a KEDB entry. "
2561
+ "Valid transitions: identified->analyzing->known_error->resolved."
2562
+ ),
2563
+ inputSchema={
2564
+ "type": "object",
2565
+ "properties": {
2566
+ "problem_id": {"type": "string", "description": "Problem ID (e.g. prb-e5f6g7h8)"},
2567
+ "agent": {"type": "string", "description": "Agent making the update"},
2568
+ "new_status": {"type": "string", "enum": ["analyzing", "known_error", "resolved"], "description": "New status"},
2569
+ "root_cause": {"type": "string", "description": "Root cause description"},
2570
+ "workaround": {"type": "string", "description": "Workaround description"},
2571
+ "note": {"type": "string", "description": "Timeline note"},
2572
+ "create_kedb": {"type": "boolean", "description": "Create a KEDB entry from this problem"},
2573
+ },
2574
+ "required": ["problem_id", "agent"],
2575
+ },
2576
+ ),
2577
+ Tool(
2578
+ name="itil_change_propose",
2579
+ description=(
2580
+ "Propose a change (RFC). Standard changes auto-approve. "
2581
+ "Normal changes require CAB approval. Emergency changes have a "
2582
+ "15-min timeout before auto-approval."
2583
+ ),
2584
+ inputSchema={
2585
+ "type": "object",
2586
+ "properties": {
2587
+ "title": {"type": "string", "description": "Change title"},
2588
+ "change_type": {"type": "string", "enum": ["standard", "normal", "emergency"], "description": "Type of change (default: normal)"},
2589
+ "risk": {"type": "string", "enum": ["low", "medium", "high"], "description": "Risk level (default: medium)"},
2590
+ "rollback_plan": {"type": "string", "description": "How to roll back if the change fails"},
2591
+ "test_plan": {"type": "string", "description": "How to verify the change works"},
2592
+ "managed_by": {"type": "string", "description": "Agent managing the change"},
2593
+ "implementer": {"type": "string", "description": "Agent who will implement the change"},
2594
+ "related_problem_id": {"type": "string", "description": "Related problem ID if applicable"},
2595
+ "tags": {"type": "array", "items": {"type": "string"}, "description": "Tags for categorization"},
2596
+ },
2597
+ "required": ["title"],
2598
+ },
2599
+ ),
2600
+ Tool(
2601
+ name="itil_change_update",
2602
+ description=(
2603
+ "Update a change: transition status (implementing, deployed, "
2604
+ "verified, failed, closed) or add timeline notes."
2605
+ ),
2606
+ inputSchema={
2607
+ "type": "object",
2608
+ "properties": {
2609
+ "change_id": {"type": "string", "description": "Change ID (e.g. chg-i1j2k3l4)"},
2610
+ "agent": {"type": "string", "description": "Agent making the update"},
2611
+ "new_status": {"type": "string", "enum": ["reviewing", "approved", "rejected", "implementing", "deployed", "verified", "failed", "closed"], "description": "New status"},
2612
+ "note": {"type": "string", "description": "Timeline note"},
2613
+ },
2614
+ "required": ["change_id", "agent"],
2615
+ },
2616
+ ),
2617
+ Tool(
2618
+ name="itil_cab_vote",
2619
+ description=(
2620
+ "Submit a CAB (Change Advisory Board) vote for a proposed change. "
2621
+ "Each agent writes its own vote file (conflict-free). "
2622
+ "A human rejection blocks the change; a human approval unblocks it."
2623
+ ),
2624
+ inputSchema={
2625
+ "type": "object",
2626
+ "properties": {
2627
+ "change_id": {"type": "string", "description": "Change ID to vote on"},
2628
+ "agent": {"type": "string", "description": "Voting agent name"},
2629
+ "decision": {"type": "string", "enum": ["approved", "rejected", "abstain"], "description": "Vote decision (default: abstain)"},
2630
+ "conditions": {"type": "string", "description": "Conditions for approval"},
2631
+ },
2632
+ "required": ["change_id", "agent"],
2633
+ },
2634
+ ),
2635
+ Tool(
2636
+ name="itil_status",
2637
+ description=(
2638
+ "ITIL dashboard: open incidents by severity, active problems, "
2639
+ "pending changes, and KEDB count."
2640
+ ),
2641
+ inputSchema={"type": "object", "properties": {}, "required": []},
2642
+ ),
2643
+ Tool(
2644
+ name="itil_kedb_search",
2645
+ description=(
2646
+ "Search the Known Error Database by symptoms, service name, "
2647
+ "or keywords. Returns matching entries with workarounds."
2648
+ ),
2649
+ inputSchema={
2650
+ "type": "object",
2651
+ "properties": {
2652
+ "query": {"type": "string", "description": "Search query (matches title, symptoms, root cause, tags)"},
2653
+ },
2654
+ "required": ["query"],
2655
+ },
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
+ ),
2473
2690
  ]
2474
2691
 
2475
2692
 
@@ -2618,6 +2835,19 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
2618
2835
  # Soul Blueprint Registry
2619
2836
  "soul_registry_search": _handle_soul_registry_search,
2620
2837
  "soul_registry_publish": _handle_soul_registry_publish,
2838
+ # ITIL Service Management
2839
+ "itil_incident_create": _handle_itil_incident_create,
2840
+ "itil_incident_update": _handle_itil_incident_update,
2841
+ "itil_incident_list": _handle_itil_incident_list,
2842
+ "itil_problem_create": _handle_itil_problem_create,
2843
+ "itil_problem_update": _handle_itil_problem_update,
2844
+ "itil_change_propose": _handle_itil_change_propose,
2845
+ "itil_change_update": _handle_itil_change_update,
2846
+ "itil_cab_vote": _handle_itil_cab_vote,
2847
+ "itil_status": _handle_itil_status,
2848
+ "itil_kedb_search": _handle_itil_kedb_search,
2849
+ # Brain-First Protocol
2850
+ "brain_first_check": _handle_brain_first_check,
2621
2851
  }
2622
2852
  handler = handlers.get(name)
2623
2853
  if handler is None:
@@ -2959,8 +3189,8 @@ async def _handle_coord_complete(args: dict) -> list[TextContent]:
2959
3189
  t.priority.value, ("community", "support_ticket", 50)
2960
3190
  )
2961
3191
  break
2962
- except Exception:
2963
- pass
3192
+ except Exception as exc:
3193
+ logger.warning("Failed to calculate joules for completed task %s: %s", task_id, exc)
2964
3194
 
2965
3195
  return _json_response({
2966
3196
  "completed": True,
@@ -4679,6 +4909,78 @@ async def _handle_soul_registry_publish(args: dict) -> list[TextContent]:
4679
4909
  return _error_response(f"Registry publish failed: {exc}")
4680
4910
 
4681
4911
 
4912
+ # ── ITIL handlers ─────────────────────────────────────────
4913
+
4914
+
4915
+ async def _handle_itil_incident_create(args: dict) -> list[TextContent]:
4916
+ """Create a new ITIL incident."""
4917
+ from .mcp_tools.itil_tools import _handle_itil_incident_create as _impl
4918
+ return await _impl(args)
4919
+
4920
+
4921
+ async def _handle_itil_incident_update(args: dict) -> list[TextContent]:
4922
+ """Update an ITIL incident."""
4923
+ from .mcp_tools.itil_tools import _handle_itil_incident_update as _impl
4924
+ return await _impl(args)
4925
+
4926
+
4927
+ async def _handle_itil_incident_list(args: dict) -> list[TextContent]:
4928
+ """List ITIL incidents."""
4929
+ from .mcp_tools.itil_tools import _handle_itil_incident_list as _impl
4930
+ return await _impl(args)
4931
+
4932
+
4933
+ async def _handle_itil_problem_create(args: dict) -> list[TextContent]:
4934
+ """Create a new ITIL problem."""
4935
+ from .mcp_tools.itil_tools import _handle_itil_problem_create as _impl
4936
+ return await _impl(args)
4937
+
4938
+
4939
+ async def _handle_itil_problem_update(args: dict) -> list[TextContent]:
4940
+ """Update an ITIL problem."""
4941
+ from .mcp_tools.itil_tools import _handle_itil_problem_update as _impl
4942
+ return await _impl(args)
4943
+
4944
+
4945
+ async def _handle_itil_change_propose(args: dict) -> list[TextContent]:
4946
+ """Propose a change (RFC)."""
4947
+ from .mcp_tools.itil_tools import _handle_itil_change_propose as _impl
4948
+ return await _impl(args)
4949
+
4950
+
4951
+ async def _handle_itil_change_update(args: dict) -> list[TextContent]:
4952
+ """Update a change status."""
4953
+ from .mcp_tools.itil_tools import _handle_itil_change_update as _impl
4954
+ return await _impl(args)
4955
+
4956
+
4957
+ async def _handle_itil_cab_vote(args: dict) -> list[TextContent]:
4958
+ """Submit a CAB vote."""
4959
+ from .mcp_tools.itil_tools import _handle_itil_cab_vote as _impl
4960
+ return await _impl(args)
4961
+
4962
+
4963
+ async def _handle_itil_status(args: dict) -> list[TextContent]:
4964
+ """ITIL dashboard status."""
4965
+ from .mcp_tools.itil_tools import _handle_itil_status as _impl
4966
+ return await _impl(args)
4967
+
4968
+
4969
+ async def _handle_itil_kedb_search(args: dict) -> list[TextContent]:
4970
+ """Search the Known Error Database."""
4971
+ from .mcp_tools.itil_tools import _handle_itil_kedb_search as _impl
4972
+ return await _impl(args)
4973
+
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
+
4682
4984
  # ═══════════════════════════════════════════════════════════
4683
4985
  # Entry Point
4684
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,
@@ -29,6 +30,7 @@ from . import (
29
30
  gtd_tools,
30
31
  health_tools,
31
32
  heartbeat_tools,
33
+ itil_tools,
32
34
  kms_tools,
33
35
  memory_tools,
34
36
  model_tools,
@@ -48,6 +50,7 @@ from . import (
48
50
  # Ordered list of all tool-group modules.
49
51
  _MODULES = [
50
52
  agent_tools,
53
+ brain_first_tools,
51
54
  memory_tools,
52
55
  comm_tools,
53
56
  sync_tools,
@@ -63,6 +66,7 @@ _MODULES = [
63
66
  heartbeat_tools,
64
67
  file_tools,
65
68
  gtd_tools,
69
+ itil_tools,
66
70
  pubsub_tools,
67
71
  fortress_tools,
68
72
  promoter_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,
@@ -21,7 +21,7 @@ _GTD_LISTS = {
21
21
  "someday-maybe": "someday-maybe.json",
22
22
  }
23
23
 
24
- _VALID_SOURCES = {"manual", "telegram", "email", "voice"}
24
+ _VALID_SOURCES = {"manual", "telegram", "email", "voice", "itil"}
25
25
  _VALID_PRIVACY = {"private", "team", "community", "public"}
26
26
  _VALID_STATUSES = {"inbox", "next", "project", "waiting", "someday", "reference", "done"}
27
27
  _VALID_PRIORITIES = {"critical", "high", "medium", "low"}