memgraph-context-graph 0.5.0__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.
- memgraph_context_graph/__init__.py +71 -0
- memgraph_context_graph/adapters/__init__.py +0 -0
- memgraph_context_graph/adapters/_identity.py +520 -0
- memgraph_context_graph/adapters/_opencode_plugin.js +112 -0
- memgraph_context_graph/adapters/_spec.py +441 -0
- memgraph_context_graph/adapters/antigravity_cli.py +152 -0
- memgraph_context_graph/adapters/claude.py +297 -0
- memgraph_context_graph/adapters/claude_code.py +216 -0
- memgraph_context_graph/adapters/claude_code_memory.py +181 -0
- memgraph_context_graph/adapters/codex.py +118 -0
- memgraph_context_graph/adapters/codex_memory.py +185 -0
- memgraph_context_graph/adapters/copilot_cli.py +236 -0
- memgraph_context_graph/adapters/cursor.py +189 -0
- memgraph_context_graph/adapters/grok.py +187 -0
- memgraph_context_graph/adapters/openai.py +240 -0
- memgraph_context_graph/adapters/opencode.py +168 -0
- memgraph_context_graph/cli.py +845 -0
- memgraph_context_graph/events.py +210 -0
- memgraph_context_graph/hooks/__init__.py +1 -0
- memgraph_context_graph/hooks/cli.py +184 -0
- memgraph_context_graph/hooks/runner.py +311 -0
- memgraph_context_graph/hooks/runtime_plugin.py +85 -0
- memgraph_context_graph/link.py +50 -0
- memgraph_context_graph/mcp_server.py +102 -0
- memgraph_context_graph/memory_backend.py +144 -0
- memgraph_context_graph/protocols.py +67 -0
- memgraph_context_graph/spaces.py +214 -0
- memgraph_context_graph/tools.py +130 -0
- memgraph_context_graph-0.5.0.dist-info/METADATA +718 -0
- memgraph_context_graph-0.5.0.dist-info/RECORD +33 -0
- memgraph_context_graph-0.5.0.dist-info/WHEEL +4 -0
- memgraph_context_graph-0.5.0.dist-info/entry_points.txt +11 -0
- memgraph_context_graph-0.5.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Common event protocol for agent runtime actions.
|
|
2
|
+
|
|
3
|
+
This module defines runtime-agnostic event types that any adapter can emit
|
|
4
|
+
and any graph connector can consume. Events capture the essential
|
|
5
|
+
information about agent lifecycle moments without coupling to a specific runtime.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EventType(str, Enum):
|
|
17
|
+
"""Types of events that can occur during an agent run."""
|
|
18
|
+
|
|
19
|
+
SESSION_START = "session_start"
|
|
20
|
+
SESSION_END = "session_end"
|
|
21
|
+
TURN_END = "turn_end"
|
|
22
|
+
TOOL_START = "tool_start"
|
|
23
|
+
TOOL_END = "tool_end"
|
|
24
|
+
AGENT_START = "agent_start"
|
|
25
|
+
AGENT_END = "agent_end"
|
|
26
|
+
LLM_START = "llm_start"
|
|
27
|
+
LLM_END = "llm_end"
|
|
28
|
+
HANDOFF = "handoff"
|
|
29
|
+
MESSAGE = "message"
|
|
30
|
+
ERROR = "error"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Event:
|
|
35
|
+
"""Base event emitted by runtime adapters.
|
|
36
|
+
|
|
37
|
+
Every concrete event subclass carries a fixed ``event_type`` and
|
|
38
|
+
runtime-agnostic payload fields. Adapters populate these from their
|
|
39
|
+
respective runtime callbacks or hook payloads, and connectors decide which fields are
|
|
40
|
+
relevant to them.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
event_type: EventType
|
|
44
|
+
session_id: str
|
|
45
|
+
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
46
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
#: The SDK that produced this event (e.g. "claude", "openai").
|
|
49
|
+
source_sdk: str = ""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ------------------------------------------------------------------
|
|
53
|
+
# Session events
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class SessionStartEvent(Event):
|
|
59
|
+
"""Fired when an agent session begins."""
|
|
60
|
+
|
|
61
|
+
event_type: EventType = field(default=EventType.SESSION_START, init=False)
|
|
62
|
+
model: str | None = None
|
|
63
|
+
working_directory: str | None = None
|
|
64
|
+
user_id: str | None = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class SessionEndEvent(Event):
|
|
69
|
+
"""Fired when an agent session ends."""
|
|
70
|
+
|
|
71
|
+
event_type: EventType = field(default=EventType.SESSION_END, init=False)
|
|
72
|
+
status: str = "completed"
|
|
73
|
+
total_cost_usd: float | None = None
|
|
74
|
+
total_input_tokens: int | None = None
|
|
75
|
+
total_output_tokens: int | None = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class TurnEndEvent(Event):
|
|
80
|
+
"""Fired when the agent finishes one turn and the session stays open.
|
|
81
|
+
|
|
82
|
+
Runtimes without a session-end hook (Codex, Antigravity, ``opencode run``)
|
|
83
|
+
only ever signal this, so connectors must not treat it as the session
|
|
84
|
+
ending. ``reason`` is the runtime's own stop reason, when it gives one.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
event_type: EventType = field(default=EventType.TURN_END, init=False)
|
|
88
|
+
reason: str | None = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
# Tool events
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class ToolStartEvent(Event):
|
|
98
|
+
"""Fired before a tool/function is invoked."""
|
|
99
|
+
|
|
100
|
+
event_type: EventType = field(default=EventType.TOOL_START, init=False)
|
|
101
|
+
tool_name: str = ""
|
|
102
|
+
tool_input: Any = None
|
|
103
|
+
tool_use_id: str | None = None
|
|
104
|
+
agent_name: str | None = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class ToolEndEvent(Event):
|
|
109
|
+
"""Fired after a tool/function returns."""
|
|
110
|
+
|
|
111
|
+
event_type: EventType = field(default=EventType.TOOL_END, init=False)
|
|
112
|
+
tool_name: str = ""
|
|
113
|
+
tool_use_id: str | None = None
|
|
114
|
+
result: Any = None
|
|
115
|
+
is_error: bool = False
|
|
116
|
+
error_message: str | None = None
|
|
117
|
+
agent_name: str | None = None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
# Agent events
|
|
122
|
+
# ------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class AgentStartEvent(Event):
|
|
127
|
+
"""Fired when an agent (or subagent) begins execution."""
|
|
128
|
+
|
|
129
|
+
event_type: EventType = field(default=EventType.AGENT_START, init=False)
|
|
130
|
+
agent_name: str = ""
|
|
131
|
+
agent_type: str = ""
|
|
132
|
+
parent_agent_name: str | None = None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class AgentEndEvent(Event):
|
|
137
|
+
"""Fired when an agent (or subagent) finishes execution."""
|
|
138
|
+
|
|
139
|
+
event_type: EventType = field(default=EventType.AGENT_END, init=False)
|
|
140
|
+
agent_name: str = ""
|
|
141
|
+
agent_type: str = ""
|
|
142
|
+
output: Any = None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------------------
|
|
146
|
+
# LLM events
|
|
147
|
+
# ------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass
|
|
151
|
+
class LLMStartEvent(Event):
|
|
152
|
+
"""Fired before an LLM call is made."""
|
|
153
|
+
|
|
154
|
+
event_type: EventType = field(default=EventType.LLM_START, init=False)
|
|
155
|
+
agent_name: str | None = None
|
|
156
|
+
system_prompt: str | None = None
|
|
157
|
+
input_items: list[Any] = field(default_factory=list)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class LLMEndEvent(Event):
|
|
162
|
+
"""Fired after an LLM call returns."""
|
|
163
|
+
|
|
164
|
+
event_type: EventType = field(default=EventType.LLM_END, init=False)
|
|
165
|
+
agent_name: str | None = None
|
|
166
|
+
model: str | None = None
|
|
167
|
+
input_tokens: int | None = None
|
|
168
|
+
output_tokens: int | None = None
|
|
169
|
+
response: Any = None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# ------------------------------------------------------------------
|
|
173
|
+
# Communication events
|
|
174
|
+
# ------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class HandoffEvent(Event):
|
|
179
|
+
"""Fired when one agent hands off to another."""
|
|
180
|
+
|
|
181
|
+
event_type: EventType = field(default=EventType.HANDOFF, init=False)
|
|
182
|
+
from_agent: str = ""
|
|
183
|
+
to_agent: str = ""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass
|
|
187
|
+
class MessageEvent(Event):
|
|
188
|
+
"""Fired when a message is sent (user, assistant, system)."""
|
|
189
|
+
|
|
190
|
+
event_type: EventType = field(default=EventType.MESSAGE, init=False)
|
|
191
|
+
role: str = ""
|
|
192
|
+
content: Any = None
|
|
193
|
+
model: str | None = None
|
|
194
|
+
agent_name: str | None = None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ------------------------------------------------------------------
|
|
198
|
+
# Error events
|
|
199
|
+
# ------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class ErrorOccurredEvent(Event):
|
|
204
|
+
"""Fired when an error occurs during agent execution."""
|
|
205
|
+
|
|
206
|
+
event_type: EventType = field(default=EventType.ERROR, init=False)
|
|
207
|
+
error_type: str = ""
|
|
208
|
+
error_message: str = ""
|
|
209
|
+
error_details: dict[str, Any] = field(default_factory=dict)
|
|
210
|
+
recoverable: bool = True
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command hook entry points for memgraph-context-graph runtimes."""
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Generic command hook CLI for memgraph-context-graph runtimes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
_HELP = """usage: memgraph-context-graph hook <command> [options]
|
|
14
|
+
|
|
15
|
+
Bridge agent command hooks to memgraph-context-graph.
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
init <runtime> Generate a runtime's private hook config, if it supports one.
|
|
19
|
+
run <runtime> Run a runtime's command-hook adapter.
|
|
20
|
+
|
|
21
|
+
Runtimes are discovered via the `memgraph_context_graph.runtimes` entry point --
|
|
22
|
+
run `memgraph-context-graph hook run --help` (or see `runtime_plugin.py`) to see
|
|
23
|
+
what's registered in this environment.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
28
|
+
"""Dispatch to a runtime-specific command hook adapter."""
|
|
29
|
+
args = list(argv) if argv is not None else sys.argv[1:]
|
|
30
|
+
if not args:
|
|
31
|
+
print(_HELP)
|
|
32
|
+
return 2
|
|
33
|
+
if args[0] in {"-h", "--help"}:
|
|
34
|
+
print(_HELP)
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
command = args[0]
|
|
38
|
+
if command == "init":
|
|
39
|
+
return _init(args[1:])
|
|
40
|
+
|
|
41
|
+
if command == "run":
|
|
42
|
+
if len(args) == 1:
|
|
43
|
+
print("usage: memgraph-context-graph hook run <runtime> [options]", file=sys.stderr)
|
|
44
|
+
return 2
|
|
45
|
+
runtime = args[1]
|
|
46
|
+
runtime_args = args[2:]
|
|
47
|
+
return _run_runtime(runtime, runtime_args)
|
|
48
|
+
|
|
49
|
+
# Backward-compatible module form:
|
|
50
|
+
# python -m memgraph_context_graph.hooks.cli codex --connector skills-graph
|
|
51
|
+
runtime = command
|
|
52
|
+
runtime_args = args[1:]
|
|
53
|
+
return _run_runtime(runtime, runtime_args)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _run_runtime(runtime: str, runtime_args: list[str]) -> int:
|
|
57
|
+
from memgraph_context_graph.hooks.runner import run_hook
|
|
58
|
+
from memgraph_context_graph.hooks.runtime_plugin import UnknownRuntimeError, get_runtime_plugin
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
plugin = get_runtime_plugin(runtime)
|
|
62
|
+
except UnknownRuntimeError as exc:
|
|
63
|
+
print(str(exc), file=sys.stderr)
|
|
64
|
+
return 2
|
|
65
|
+
|
|
66
|
+
return run_hook(plugin, runtime_args)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _init(argv: list[str]) -> int:
|
|
70
|
+
if not argv:
|
|
71
|
+
print("usage: memgraph-context-graph hook init <runtime> [options]", file=sys.stderr)
|
|
72
|
+
return 2
|
|
73
|
+
|
|
74
|
+
from memgraph_context_graph.hooks.runtime_plugin import UnknownRuntimeError, get_runtime_plugin
|
|
75
|
+
|
|
76
|
+
runtime, init_argv = argv[0], argv[1:]
|
|
77
|
+
try:
|
|
78
|
+
plugin = get_runtime_plugin(runtime)
|
|
79
|
+
except UnknownRuntimeError as exc:
|
|
80
|
+
print(str(exc), file=sys.stderr)
|
|
81
|
+
return 2
|
|
82
|
+
|
|
83
|
+
if any(arg.split("=", 1)[0] == "--memory-backend" for arg in init_argv):
|
|
84
|
+
return _switch_memory_backend(plugin, init_argv)
|
|
85
|
+
|
|
86
|
+
init = getattr(plugin, "init", None)
|
|
87
|
+
if init is None:
|
|
88
|
+
print(f"{plugin.name} hook setup is not implemented yet.", file=sys.stderr)
|
|
89
|
+
return 2
|
|
90
|
+
|
|
91
|
+
return _run_generic_init(plugin.name, init, init_argv)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _switch_memory_backend(plugin: Any, argv: list[str]) -> int:
|
|
95
|
+
"""``setup <runtime> --memory-backend context-graph|native``: whose memory the harness's model uses."""
|
|
96
|
+
from memgraph_context_graph.adapters._identity import MEMORY_BACKEND_GRAPH, MEMORY_BACKEND_NATIVE
|
|
97
|
+
from memgraph_context_graph.memory_backend import MemoryBackendError, use_graph_memory, use_native_memory
|
|
98
|
+
|
|
99
|
+
parser = argparse.ArgumentParser(
|
|
100
|
+
prog=f"memgraph-context-graph setup {plugin.name}",
|
|
101
|
+
description=(
|
|
102
|
+
f"Make Context Graph {plugin.name}'s memory (turning its built-in memory off and importing it), "
|
|
103
|
+
"or hand memory back to the built-in one."
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
parser.add_argument("--memory-backend", required=True, choices=[MEMORY_BACKEND_GRAPH, MEMORY_BACKEND_NATIVE])
|
|
107
|
+
args = parser.parse_args(argv)
|
|
108
|
+
switch = use_graph_memory if args.memory_backend == MEMORY_BACKEND_GRAPH else use_native_memory
|
|
109
|
+
try:
|
|
110
|
+
lines = switch(plugin)
|
|
111
|
+
except MemoryBackendError as exc:
|
|
112
|
+
print(str(exc), file=sys.stderr)
|
|
113
|
+
return 1
|
|
114
|
+
print("\n".join(lines))
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _run_generic_init(runtime_name: str, init: Any, argv: list[str]) -> int:
|
|
119
|
+
"""Parse the shared ``hook init`` flags and delegate to *init*.
|
|
120
|
+
|
|
121
|
+
The flag set is generic across runtimes. Each plugin's ``init`` writes its
|
|
122
|
+
own config from the project dir, connectors, hook command, timeout, and
|
|
123
|
+
force flag; ``--setup-schema`` (with the ``--memgraph-*`` overrides) is
|
|
124
|
+
handled here, identically for every runtime.
|
|
125
|
+
"""
|
|
126
|
+
parser = argparse.ArgumentParser(description=f"Generate a private {runtime_name} hook config.")
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--connector",
|
|
129
|
+
action="append",
|
|
130
|
+
default=None,
|
|
131
|
+
help="Graph connector to enable. Defaults to skills-graph, actions-graph, sessions-graph.",
|
|
132
|
+
)
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"--project-dir",
|
|
135
|
+
default=".",
|
|
136
|
+
help="Project directory where the runtime's config should be generated.",
|
|
137
|
+
)
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"--hook-command",
|
|
140
|
+
default=None,
|
|
141
|
+
help="Full command to place in the runtime's hook config. Defaults to this installed CLI.",
|
|
142
|
+
)
|
|
143
|
+
parser.add_argument("--memgraph-url", default=None, help="Memgraph Bolt URL for --setup-schema.")
|
|
144
|
+
parser.add_argument("--memgraph-user", default=None, help="Memgraph username for --setup-schema.")
|
|
145
|
+
parser.add_argument(
|
|
146
|
+
"--memgraph-password", default=None, help="Memgraph password for --setup-schema. Never written to disk."
|
|
147
|
+
)
|
|
148
|
+
parser.add_argument("--memgraph-database", default=None, help="Memgraph database for --setup-schema.")
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"--setup-schema",
|
|
151
|
+
action="store_true",
|
|
152
|
+
help="Connect to Memgraph now and initialize enabled graph connector schemas.",
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument("--timeout", type=int, default=30, help="Hook timeout in seconds.")
|
|
155
|
+
parser.add_argument("--force", action="store_true", help="Overwrite existing generated config.")
|
|
156
|
+
args = parser.parse_args(argv)
|
|
157
|
+
|
|
158
|
+
connectors = args.connector or ["skills-graph", "actions-graph", "sessions-graph"]
|
|
159
|
+
project_dir = Path(args.project_dir).expanduser().resolve()
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
init(project_dir, connectors, hook_command=args.hook_command, timeout=args.timeout, force=args.force)
|
|
163
|
+
except FileExistsError as exc:
|
|
164
|
+
print(str(exc), file=sys.stderr)
|
|
165
|
+
print("Re-run with --force to replace generated files.", file=sys.stderr)
|
|
166
|
+
return 1
|
|
167
|
+
|
|
168
|
+
if args.setup_schema:
|
|
169
|
+
from memgraph_context_graph.adapters._identity import resolve_memgraph_env
|
|
170
|
+
from memgraph_context_graph.hooks.runner import setup_connector_schemas
|
|
171
|
+
|
|
172
|
+
memgraph_env = resolve_memgraph_env(
|
|
173
|
+
url=args.memgraph_url,
|
|
174
|
+
user=args.memgraph_user,
|
|
175
|
+
password=args.memgraph_password,
|
|
176
|
+
database=args.memgraph_database,
|
|
177
|
+
)
|
|
178
|
+
setup_connector_schemas(connectors, memgraph_env=memgraph_env)
|
|
179
|
+
print(f"Initialized schema on {memgraph_env['MEMGRAPH_URL']} ({memgraph_env['MEMGRAPH_DATABASE']})")
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
raise SystemExit(main())
|