claude-mpm 4.7.5__py3-none-any.whl → 4.7.7__py3-none-any.whl
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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/configure.py +196 -38
- claude_mpm/hooks/__init__.py +6 -0
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +27 -10
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +2 -2
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory_hook_service.py +85 -3
- claude_mpm/utils/agent_dependency_loader.py +38 -18
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.7.dist-info}/METADATA +1 -1
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.7.dist-info}/RECORD +17 -15
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.7.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.7.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.7.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.5.dist-info → claude_mpm-4.7.7.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.7.
|
1
|
+
4.7.7
|
@@ -374,7 +374,9 @@ class ConfigureCommand(BaseCommand):
|
|
374
374
|
self.console.print(menu_panel)
|
375
375
|
self.console.print()
|
376
376
|
|
377
|
-
|
377
|
+
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="q")
|
378
|
+
# Strip whitespace to handle leading/trailing spaces
|
379
|
+
return choice.strip().lower()
|
378
380
|
|
379
381
|
def _manage_agents(self) -> None:
|
380
382
|
"""Agent management interface."""
|
@@ -388,12 +390,38 @@ class ConfigureCommand(BaseCommand):
|
|
388
390
|
|
389
391
|
# Show agent menu
|
390
392
|
self.console.print("\n[bold]Agent Management Options:[/bold]")
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
self.console.print(
|
393
|
+
|
394
|
+
# Use Text objects to properly display shortcuts with styling
|
395
|
+
text_e = Text(" ")
|
396
|
+
text_e.append("[e]", style="cyan bold")
|
397
|
+
text_e.append(" Enable an agent")
|
398
|
+
self.console.print(text_e)
|
399
|
+
|
400
|
+
text_d = Text(" ")
|
401
|
+
text_d.append("[d]", style="cyan bold")
|
402
|
+
text_d.append(" Disable an agent")
|
403
|
+
self.console.print(text_d)
|
404
|
+
|
405
|
+
text_c = Text(" ")
|
406
|
+
text_c.append("[c]", style="cyan bold")
|
407
|
+
text_c.append(" Customize agent template")
|
408
|
+
self.console.print(text_c)
|
409
|
+
|
410
|
+
text_v = Text(" ")
|
411
|
+
text_v.append("[v]", style="cyan bold")
|
412
|
+
text_v.append(" View agent details")
|
413
|
+
self.console.print(text_v)
|
414
|
+
|
415
|
+
text_r = Text(" ")
|
416
|
+
text_r.append("[r]", style="cyan bold")
|
417
|
+
text_r.append(" Reset agent to defaults")
|
418
|
+
self.console.print(text_r)
|
419
|
+
|
420
|
+
text_b = Text(" ")
|
421
|
+
text_b.append("[b]", style="cyan bold")
|
422
|
+
text_b.append(" Back to main menu")
|
423
|
+
self.console.print(text_b)
|
424
|
+
|
397
425
|
self.console.print()
|
398
426
|
|
399
427
|
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="b")
|
@@ -425,7 +453,7 @@ class ConfigureCommand(BaseCommand):
|
|
425
453
|
table.add_column("ID", style="dim", width=3)
|
426
454
|
table.add_column("Name", style="cyan", width=22)
|
427
455
|
table.add_column("Status", width=12)
|
428
|
-
table.add_column("Description", style="
|
456
|
+
table.add_column("Description", style="bold cyan", width=45)
|
429
457
|
table.add_column("Model/Tools", style="dim", width=20)
|
430
458
|
|
431
459
|
for idx, agent in enumerate(agents, 1):
|
@@ -456,12 +484,11 @@ class ConfigureCommand(BaseCommand):
|
|
456
484
|
except Exception:
|
457
485
|
tools_display = "Default"
|
458
486
|
|
459
|
-
# Truncate description for table display
|
460
|
-
|
461
|
-
agent.description[:42]
|
462
|
-
|
463
|
-
|
464
|
-
)
|
487
|
+
# Truncate description for table display with bright styling
|
488
|
+
if len(agent.description) > 42:
|
489
|
+
desc_display = f"[cyan]{agent.description[:42]}[/cyan][dim]...[/dim]"
|
490
|
+
else:
|
491
|
+
desc_display = f"[cyan]{agent.description}[/cyan]"
|
465
492
|
|
466
493
|
table.add_row(str(idx), agent.name, status, desc_display, tools_display)
|
467
494
|
|
@@ -612,14 +639,41 @@ class ConfigureCommand(BaseCommand):
|
|
612
639
|
# Editing options
|
613
640
|
self.console.print("[bold]Editing Options:[/bold]")
|
614
641
|
if not is_system:
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
self.console.print(
|
642
|
+
text_1 = Text(" ")
|
643
|
+
text_1.append("[1]", style="cyan bold")
|
644
|
+
text_1.append(" Edit in external editor")
|
645
|
+
self.console.print(text_1)
|
646
|
+
|
647
|
+
text_2 = Text(" ")
|
648
|
+
text_2.append("[2]", style="cyan bold")
|
649
|
+
text_2.append(" Add/modify a field")
|
650
|
+
self.console.print(text_2)
|
651
|
+
|
652
|
+
text_3 = Text(" ")
|
653
|
+
text_3.append("[3]", style="cyan bold")
|
654
|
+
text_3.append(" Remove a field")
|
655
|
+
self.console.print(text_3)
|
656
|
+
|
657
|
+
text_4 = Text(" ")
|
658
|
+
text_4.append("[4]", style="cyan bold")
|
659
|
+
text_4.append(" Reset to defaults")
|
660
|
+
self.console.print(text_4)
|
619
661
|
else:
|
620
|
-
|
621
|
-
|
622
|
-
|
662
|
+
text_1 = Text(" ")
|
663
|
+
text_1.append("[1]", style="cyan bold")
|
664
|
+
text_1.append(" Create customized copy")
|
665
|
+
self.console.print(text_1)
|
666
|
+
|
667
|
+
text_2 = Text(" ")
|
668
|
+
text_2.append("[2]", style="cyan bold")
|
669
|
+
text_2.append(" View full template")
|
670
|
+
self.console.print(text_2)
|
671
|
+
|
672
|
+
text_b = Text(" ")
|
673
|
+
text_b.append("[b]", style="cyan bold")
|
674
|
+
text_b.append(" Back")
|
675
|
+
self.console.print(text_b)
|
676
|
+
|
623
677
|
self.console.print()
|
624
678
|
|
625
679
|
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="b")
|
@@ -821,6 +875,61 @@ class ConfigureCommand(BaseCommand):
|
|
821
875
|
with self.console.pager():
|
822
876
|
self.console.print(syntax)
|
823
877
|
|
878
|
+
def _reset_agent_defaults(self, agents: List[AgentConfig]) -> None:
|
879
|
+
"""Reset an agent to default enabled state and remove custom template.
|
880
|
+
|
881
|
+
This method:
|
882
|
+
- Prompts for agent ID
|
883
|
+
- Resets agent to enabled state
|
884
|
+
- Removes any custom template overrides
|
885
|
+
- Shows success/error messages
|
886
|
+
"""
|
887
|
+
agent_id = Prompt.ask("Enter agent ID to reset to defaults")
|
888
|
+
|
889
|
+
try:
|
890
|
+
idx = int(agent_id) - 1
|
891
|
+
if 0 <= idx < len(agents):
|
892
|
+
agent = agents[idx]
|
893
|
+
|
894
|
+
# Confirm the reset action
|
895
|
+
if not Confirm.ask(
|
896
|
+
f"[yellow]Reset '{agent.name}' to defaults? This will:[/yellow]\n"
|
897
|
+
" - Enable the agent\n"
|
898
|
+
" - Remove custom template (if any)\n"
|
899
|
+
"[yellow]Continue?[/yellow]"
|
900
|
+
):
|
901
|
+
self.console.print("[yellow]Reset cancelled.[/yellow]")
|
902
|
+
Prompt.ask("Press Enter to continue")
|
903
|
+
return
|
904
|
+
|
905
|
+
# Enable the agent
|
906
|
+
self.agent_manager.set_agent_enabled(agent.name, True)
|
907
|
+
|
908
|
+
# Remove custom template if exists
|
909
|
+
template_path = self._get_agent_template_path(agent.name)
|
910
|
+
if template_path.exists() and not str(template_path).startswith(
|
911
|
+
str(self.agent_manager.templates_dir)
|
912
|
+
):
|
913
|
+
# This is a custom template, remove it
|
914
|
+
template_path.unlink(missing_ok=True)
|
915
|
+
self.console.print(
|
916
|
+
f"[green]✓ Removed custom template for '{agent.name}'[/green]"
|
917
|
+
)
|
918
|
+
|
919
|
+
self.console.print(
|
920
|
+
f"[green]✓ Agent '{agent.name}' reset to defaults![/green]"
|
921
|
+
)
|
922
|
+
self.console.print(
|
923
|
+
"[dim]Agent is now enabled with system template.[/dim]"
|
924
|
+
)
|
925
|
+
else:
|
926
|
+
self.console.print("[red]Invalid agent ID.[/red]")
|
927
|
+
|
928
|
+
except ValueError:
|
929
|
+
self.console.print("[red]Invalid input. Please enter a number.[/red]")
|
930
|
+
|
931
|
+
Prompt.ask("Press Enter to continue")
|
932
|
+
|
824
933
|
def _view_agent_details(self, agents: List[AgentConfig]) -> None:
|
825
934
|
"""View detailed information about an agent."""
|
826
935
|
agent_id = Prompt.ask("Enter agent ID to view")
|
@@ -917,11 +1026,32 @@ class ConfigureCommand(BaseCommand):
|
|
917
1026
|
|
918
1027
|
# Show behavior menu
|
919
1028
|
self.console.print("\n[bold]Options:[/bold]")
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
self.console.print(
|
1029
|
+
|
1030
|
+
text_1 = Text(" ")
|
1031
|
+
text_1.append("[1]", style="cyan bold")
|
1032
|
+
text_1.append(" Edit identity configuration")
|
1033
|
+
self.console.print(text_1)
|
1034
|
+
|
1035
|
+
text_2 = Text(" ")
|
1036
|
+
text_2.append("[2]", style="cyan bold")
|
1037
|
+
text_2.append(" Edit workflow configuration")
|
1038
|
+
self.console.print(text_2)
|
1039
|
+
|
1040
|
+
text_3 = Text(" ")
|
1041
|
+
text_3.append("[3]", style="cyan bold")
|
1042
|
+
text_3.append(" Import behavior file")
|
1043
|
+
self.console.print(text_3)
|
1044
|
+
|
1045
|
+
text_4 = Text(" ")
|
1046
|
+
text_4.append("[4]", style="cyan bold")
|
1047
|
+
text_4.append(" Export behavior file")
|
1048
|
+
self.console.print(text_4)
|
1049
|
+
|
1050
|
+
text_b = Text(" ")
|
1051
|
+
text_b.append("[b]", style="cyan bold")
|
1052
|
+
text_b.append(" Back to main menu")
|
1053
|
+
self.console.print(text_b)
|
1054
|
+
|
925
1055
|
self.console.print()
|
926
1056
|
|
927
1057
|
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="b")
|
@@ -1208,9 +1338,22 @@ class ConfigureCommand(BaseCommand):
|
|
1208
1338
|
self.console.print(table)
|
1209
1339
|
self.console.print("\n[bold]Commands:[/bold]")
|
1210
1340
|
self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
|
1211
|
-
|
1212
|
-
|
1213
|
-
|
1341
|
+
|
1342
|
+
text_a = Text(" ")
|
1343
|
+
text_a.append("[a]", style="cyan bold")
|
1344
|
+
text_a.append(" Enable all")
|
1345
|
+
self.console.print(text_a)
|
1346
|
+
|
1347
|
+
text_n = Text(" ")
|
1348
|
+
text_n.append("[n]", style="cyan bold")
|
1349
|
+
text_n.append(" Disable all")
|
1350
|
+
self.console.print(text_n)
|
1351
|
+
|
1352
|
+
text_b = Text(" ")
|
1353
|
+
text_b.append("[b]", style="cyan bold")
|
1354
|
+
text_b.append(" Back to previous menu")
|
1355
|
+
self.console.print(text_b)
|
1356
|
+
|
1214
1357
|
self.console.print()
|
1215
1358
|
|
1216
1359
|
choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
|
@@ -1275,9 +1418,22 @@ class ConfigureCommand(BaseCommand):
|
|
1275
1418
|
self.console.print(table)
|
1276
1419
|
self.console.print("\n[bold]Commands:[/bold]")
|
1277
1420
|
self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1421
|
+
|
1422
|
+
text_a = Text(" ")
|
1423
|
+
text_a.append("[a]", style="cyan bold")
|
1424
|
+
text_a.append(" Enable all")
|
1425
|
+
self.console.print(text_a)
|
1426
|
+
|
1427
|
+
text_n = Text(" ")
|
1428
|
+
text_n.append("[n]", style="cyan bold")
|
1429
|
+
text_n.append(" Disable all")
|
1430
|
+
self.console.print(text_n)
|
1431
|
+
|
1432
|
+
text_b = Text(" ")
|
1433
|
+
text_b.append("[b]", style="cyan bold")
|
1434
|
+
text_b.append(" Back to previous menu")
|
1435
|
+
self.console.print(text_b)
|
1436
|
+
|
1281
1437
|
self.console.print()
|
1282
1438
|
|
1283
1439
|
choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
|
@@ -1331,7 +1487,7 @@ class ConfigureCommand(BaseCommand):
|
|
1331
1487
|
table.add_column("ID", style="dim", width=5)
|
1332
1488
|
table.add_column("Agent", style="cyan", width=25)
|
1333
1489
|
table.add_column("Status", width=15)
|
1334
|
-
table.add_column("Description", style="
|
1490
|
+
table.add_column("Description", style="bold cyan", width=45)
|
1335
1491
|
|
1336
1492
|
for idx, agent in enumerate(agents, 1):
|
1337
1493
|
# Agent is ENABLED if NOT in disabled list
|
@@ -1341,11 +1497,13 @@ class ConfigureCommand(BaseCommand):
|
|
1341
1497
|
if is_enabled
|
1342
1498
|
else "[red]✗ Disabled[/red]"
|
1343
1499
|
)
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1500
|
+
# Format description with bright styling
|
1501
|
+
if len(agent.description) > 42:
|
1502
|
+
desc_display = (
|
1503
|
+
f"[cyan]{agent.description[:42]}[/cyan][dim]...[/dim]"
|
1504
|
+
)
|
1505
|
+
else:
|
1506
|
+
desc_display = f"[cyan]{agent.description}[/cyan]"
|
1349
1507
|
table.add_row(str(idx), agent.name, status, desc_display)
|
1350
1508
|
|
1351
1509
|
self.console.print(table)
|
claude_mpm/hooks/__init__.py
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
"""Hook system for claude-mpm."""
|
2
2
|
|
3
3
|
from .base_hook import BaseHook, HookContext, HookResult, HookType
|
4
|
+
from .kuzu_enrichment_hook import KuzuEnrichmentHook, get_kuzu_enrichment_hook
|
4
5
|
from .kuzu_memory_hook import KuzuMemoryHook, get_kuzu_memory_hook
|
6
|
+
from .kuzu_response_hook import KuzuResponseHook, get_kuzu_response_hook
|
5
7
|
|
6
8
|
__all__ = [
|
7
9
|
"BaseHook",
|
8
10
|
"HookContext",
|
9
11
|
"HookResult",
|
10
12
|
"HookType",
|
13
|
+
"KuzuEnrichmentHook",
|
11
14
|
"KuzuMemoryHook",
|
15
|
+
"KuzuResponseHook",
|
16
|
+
"get_kuzu_enrichment_hook",
|
12
17
|
"get_kuzu_memory_hook",
|
18
|
+
"get_kuzu_response_hook",
|
13
19
|
]
|
@@ -0,0 +1,263 @@
|
|
1
|
+
"""
|
2
|
+
Kuzu-Memory Pre-Delegation Enrichment Hook
|
3
|
+
==========================================
|
4
|
+
|
5
|
+
Enriches agent delegation context with relevant memories from kuzu-memory
|
6
|
+
before the agent receives the task. This is the READ side of bidirectional
|
7
|
+
enrichment.
|
8
|
+
|
9
|
+
WHY: Agents need access to relevant historical knowledge when performing tasks.
|
10
|
+
This hook retrieves memories from kuzu-memory and injects them into the
|
11
|
+
delegation context.
|
12
|
+
|
13
|
+
DESIGN DECISIONS:
|
14
|
+
- Priority 10 to run early, before other context modifications
|
15
|
+
- Reuses KuzuMemoryHook's retrieval methods for consistency
|
16
|
+
- Injects memories as a dedicated section in agent context
|
17
|
+
- Falls back gracefully if kuzu-memory is not available
|
18
|
+
"""
|
19
|
+
|
20
|
+
import logging
|
21
|
+
from typing import Any, Dict, Optional
|
22
|
+
|
23
|
+
from claude_mpm.hooks.base_hook import HookContext, HookResult, PreDelegationHook
|
24
|
+
from claude_mpm.hooks.kuzu_memory_hook import get_kuzu_memory_hook
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
|
29
|
+
class KuzuEnrichmentHook(PreDelegationHook):
|
30
|
+
"""
|
31
|
+
Hook that enriches agent delegation context with kuzu-memory.
|
32
|
+
|
33
|
+
This hook:
|
34
|
+
1. Extracts the task/prompt from delegation context
|
35
|
+
2. Retrieves relevant memories from kuzu-memory
|
36
|
+
3. Injects memories into agent context
|
37
|
+
4. Formats memories for optimal agent understanding
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self):
|
41
|
+
"""Initialize the kuzu-memory enrichment hook."""
|
42
|
+
super().__init__(name="kuzu_memory_enrichment", priority=10)
|
43
|
+
|
44
|
+
# Reuse the kuzu-memory hook instance for retrieval
|
45
|
+
self.kuzu_hook = get_kuzu_memory_hook()
|
46
|
+
self.enabled = self.kuzu_hook.enabled
|
47
|
+
|
48
|
+
if not self.enabled:
|
49
|
+
logger.info(
|
50
|
+
"Kuzu-memory enrichment hook disabled (kuzu-memory not available)"
|
51
|
+
)
|
52
|
+
else:
|
53
|
+
logger.info("Kuzu-memory enrichment hook enabled")
|
54
|
+
|
55
|
+
def validate(self, context: HookContext) -> bool:
|
56
|
+
"""
|
57
|
+
Validate if hook should process this context.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
context: Hook context to validate
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
True if hook should execute
|
64
|
+
"""
|
65
|
+
if not self.enabled:
|
66
|
+
return False
|
67
|
+
|
68
|
+
# Check base validation (enabled, correct hook type, has agent)
|
69
|
+
if not super().validate(context):
|
70
|
+
return False
|
71
|
+
|
72
|
+
# Must have agent and context data
|
73
|
+
if not context.data.get("agent"):
|
74
|
+
return False
|
75
|
+
|
76
|
+
return True
|
77
|
+
|
78
|
+
def execute(self, context: HookContext) -> HookResult:
|
79
|
+
"""
|
80
|
+
Enrich delegation context with relevant memories.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
context: Hook context containing delegation data
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
HookResult with enriched context
|
87
|
+
"""
|
88
|
+
if not self.enabled:
|
89
|
+
return HookResult(success=True, data=context.data, modified=False)
|
90
|
+
|
91
|
+
try:
|
92
|
+
# Extract query for memory retrieval
|
93
|
+
query = self._extract_query_from_context(context.data)
|
94
|
+
|
95
|
+
if not query:
|
96
|
+
logger.debug("No query extracted from context for memory retrieval")
|
97
|
+
return HookResult(success=True, data=context.data, modified=False)
|
98
|
+
|
99
|
+
# Retrieve relevant memories
|
100
|
+
memories = self.kuzu_hook._retrieve_memories(query)
|
101
|
+
|
102
|
+
if not memories:
|
103
|
+
logger.debug("No relevant memories found")
|
104
|
+
return HookResult(success=True, data=context.data, modified=False)
|
105
|
+
|
106
|
+
# Enrich context with memories
|
107
|
+
enriched_data = self._enrich_delegation_context(
|
108
|
+
context.data, memories, context.data.get("agent", "Agent")
|
109
|
+
)
|
110
|
+
|
111
|
+
logger.info(
|
112
|
+
f"Enriched delegation context with {len(memories)} memories for {context.data.get('agent')}"
|
113
|
+
)
|
114
|
+
|
115
|
+
return HookResult(
|
116
|
+
success=True,
|
117
|
+
data=enriched_data,
|
118
|
+
modified=True,
|
119
|
+
metadata={
|
120
|
+
"memories_added": len(memories),
|
121
|
+
"memory_source": "kuzu",
|
122
|
+
"agent": context.data.get("agent"),
|
123
|
+
},
|
124
|
+
)
|
125
|
+
|
126
|
+
except Exception as e:
|
127
|
+
logger.error(f"Error in kuzu enrichment hook: {e}")
|
128
|
+
# Don't fail the delegation if memory enrichment fails
|
129
|
+
return HookResult(
|
130
|
+
success=True,
|
131
|
+
data=context.data,
|
132
|
+
modified=False,
|
133
|
+
error=f"Memory enrichment failed: {e}",
|
134
|
+
)
|
135
|
+
|
136
|
+
def _extract_query_from_context(self, data: Dict[str, Any]) -> Optional[str]:
|
137
|
+
"""
|
138
|
+
Extract query text for memory retrieval.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
data: Delegation context data
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
Query string or None
|
145
|
+
"""
|
146
|
+
# Try various context fields
|
147
|
+
delegation_context = data.get("context", {})
|
148
|
+
|
149
|
+
# Handle string context
|
150
|
+
if isinstance(delegation_context, str):
|
151
|
+
return delegation_context
|
152
|
+
|
153
|
+
# Handle dict context
|
154
|
+
if isinstance(delegation_context, dict):
|
155
|
+
# Try common fields
|
156
|
+
for field in ["prompt", "task", "query", "user_request", "description"]:
|
157
|
+
if field in delegation_context:
|
158
|
+
value = delegation_context[field]
|
159
|
+
if isinstance(value, str):
|
160
|
+
return value
|
161
|
+
|
162
|
+
# If no specific field, join all string values
|
163
|
+
text_parts = [
|
164
|
+
str(v) for v in delegation_context.values() if isinstance(v, str)
|
165
|
+
]
|
166
|
+
if text_parts:
|
167
|
+
return " ".join(text_parts)
|
168
|
+
|
169
|
+
# Fallback: try to get task or instruction directly
|
170
|
+
if "task" in data and isinstance(data["task"], str):
|
171
|
+
return data["task"]
|
172
|
+
|
173
|
+
if "instruction" in data and isinstance(data["instruction"], str):
|
174
|
+
return data["instruction"]
|
175
|
+
|
176
|
+
return None
|
177
|
+
|
178
|
+
def _enrich_delegation_context(
|
179
|
+
self, original_data: Dict[str, Any], memories: list, agent_name: str
|
180
|
+
) -> Dict[str, Any]:
|
181
|
+
"""
|
182
|
+
Enrich delegation context with memories.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
original_data: Original delegation data
|
186
|
+
memories: Retrieved memories
|
187
|
+
agent_name: Name of the agent
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Enriched delegation data
|
191
|
+
"""
|
192
|
+
# Format memories
|
193
|
+
memory_section = self._format_memory_section(memories, agent_name)
|
194
|
+
|
195
|
+
# Create enriched data
|
196
|
+
enriched_data = original_data.copy()
|
197
|
+
|
198
|
+
# Get existing context
|
199
|
+
delegation_context = enriched_data.get("context", {})
|
200
|
+
if isinstance(delegation_context, str):
|
201
|
+
delegation_context = {"prompt": delegation_context}
|
202
|
+
|
203
|
+
# Add memory section
|
204
|
+
if isinstance(delegation_context, dict):
|
205
|
+
# Prepend memory section to context
|
206
|
+
delegation_context["kuzu_memories"] = memory_section
|
207
|
+
|
208
|
+
# If there's a main prompt/task, prepend memory note
|
209
|
+
for field in ["prompt", "task", "instruction"]:
|
210
|
+
if field in delegation_context and isinstance(
|
211
|
+
delegation_context[field], str
|
212
|
+
):
|
213
|
+
delegation_context[field] = (
|
214
|
+
f"{memory_section}\n\n{delegation_context[field]}"
|
215
|
+
)
|
216
|
+
break
|
217
|
+
else:
|
218
|
+
# If context is not dict, create new dict with memory
|
219
|
+
delegation_context = {
|
220
|
+
"kuzu_memories": memory_section,
|
221
|
+
"original_context": delegation_context,
|
222
|
+
}
|
223
|
+
|
224
|
+
enriched_data["context"] = delegation_context
|
225
|
+
enriched_data["_kuzu_enriched"] = True
|
226
|
+
|
227
|
+
return enriched_data
|
228
|
+
|
229
|
+
def _format_memory_section(self, memories: list, agent_name: str) -> str:
|
230
|
+
"""
|
231
|
+
Format memories into a readable section.
|
232
|
+
|
233
|
+
Args:
|
234
|
+
memories: List of memory dictionaries
|
235
|
+
agent_name: Name of the agent
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
Formatted memory section
|
239
|
+
"""
|
240
|
+
memory_text = self.kuzu_hook._format_memories(memories)
|
241
|
+
|
242
|
+
return f"""
|
243
|
+
=== RELEVANT KNOWLEDGE FROM KUZU MEMORY ===
|
244
|
+
{agent_name}, you have access to these relevant memories from the knowledge graph:
|
245
|
+
|
246
|
+
{memory_text}
|
247
|
+
|
248
|
+
INSTRUCTIONS: Review these memories before proceeding. Apply learned patterns and avoid known mistakes.
|
249
|
+
Use this knowledge to provide more informed and contextual responses.
|
250
|
+
===========================================
|
251
|
+
"""
|
252
|
+
|
253
|
+
|
254
|
+
# Create a singleton instance
|
255
|
+
_kuzu_enrichment_hook = None
|
256
|
+
|
257
|
+
|
258
|
+
def get_kuzu_enrichment_hook() -> KuzuEnrichmentHook:
|
259
|
+
"""Get the singleton kuzu-memory enrichment hook instance."""
|
260
|
+
global _kuzu_enrichment_hook
|
261
|
+
if _kuzu_enrichment_hook is None:
|
262
|
+
_kuzu_enrichment_hook = KuzuEnrichmentHook()
|
263
|
+
return _kuzu_enrichment_hook
|
@@ -157,9 +157,13 @@ class KuzuMemoryHook(SubmitHook):
|
|
157
157
|
List of relevant memory dictionaries
|
158
158
|
"""
|
159
159
|
try:
|
160
|
-
#
|
160
|
+
# Type narrowing: ensure kuzu_memory_cmd is not None before using
|
161
|
+
if self.kuzu_memory_cmd is None:
|
162
|
+
return []
|
163
|
+
|
164
|
+
# Use kuzu-memory recall command (v1.2.7+ syntax)
|
161
165
|
result = subprocess.run(
|
162
|
-
[self.kuzu_memory_cmd, "recall", query, "--format", "json"],
|
166
|
+
[self.kuzu_memory_cmd, "memory", "recall", query, "--format", "json"],
|
163
167
|
capture_output=True,
|
164
168
|
text=True,
|
165
169
|
timeout=5,
|
@@ -168,10 +172,21 @@ class KuzuMemoryHook(SubmitHook):
|
|
168
172
|
)
|
169
173
|
|
170
174
|
if result.returncode == 0 and result.stdout:
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
+
try:
|
176
|
+
# Parse JSON with strict=False to handle control characters
|
177
|
+
data = json.loads(result.stdout, strict=False)
|
178
|
+
# v1.2.7 returns dict with 'memories' key, not array
|
179
|
+
if isinstance(data, dict):
|
180
|
+
memories = data.get("memories", [])
|
181
|
+
else:
|
182
|
+
memories = data if isinstance(data, list) else []
|
183
|
+
return memories
|
184
|
+
except json.JSONDecodeError as e:
|
185
|
+
logger.warning(f"Failed to parse kuzu-memory JSON output: {e}")
|
186
|
+
logger.debug(f"Raw output: {result.stdout[:200]}")
|
187
|
+
return [] # Graceful fallback
|
188
|
+
|
189
|
+
except (subprocess.TimeoutExpired, Exception) as e:
|
175
190
|
logger.debug(f"Memory retrieval failed: {e}")
|
176
191
|
|
177
192
|
return []
|
@@ -255,12 +270,12 @@ Note: Use the memories above to provide more informed and contextual responses.
|
|
255
270
|
Returns:
|
256
271
|
True if storage was successful
|
257
272
|
"""
|
258
|
-
if not self.enabled:
|
273
|
+
if not self.enabled or self.kuzu_memory_cmd is None:
|
259
274
|
return False
|
260
275
|
|
261
276
|
try:
|
262
|
-
# Use kuzu-memory
|
263
|
-
cmd = [self.kuzu_memory_cmd, "
|
277
|
+
# Use kuzu-memory store command (v1.2.7+ syntax)
|
278
|
+
cmd = [self.kuzu_memory_cmd, "memory", "store", content]
|
264
279
|
|
265
280
|
# Execute store command in project directory
|
266
281
|
result = subprocess.run(
|
@@ -273,8 +288,10 @@ Note: Use the memories above to provide more informed and contextual responses.
|
|
273
288
|
)
|
274
289
|
|
275
290
|
if result.returncode == 0:
|
276
|
-
logger.debug(f"Stored memory: {content[:50]}...")
|
291
|
+
logger.debug(f"Stored memory in kuzu: {content[:50]}...")
|
277
292
|
return True
|
293
|
+
logger.warning(f"Failed to store memory in kuzu: {result.stderr}")
|
294
|
+
return False
|
278
295
|
|
279
296
|
except Exception as e:
|
280
297
|
logger.error(f"Failed to store memory: {e}")
|