runtime-memory 3.0.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.
Files changed (54) hide show
  1. runtime_memory/__init__.py +28 -0
  2. runtime_memory/claude_code/__init__.py +48 -0
  3. runtime_memory/claude_code/commands.py +698 -0
  4. runtime_memory/claude_code/daemon.py +852 -0
  5. runtime_memory/claude_code/hooks.py +722 -0
  6. runtime_memory/cli/__init__.py +8 -0
  7. runtime_memory/cli/main.py +1936 -0
  8. runtime_memory/core/__init__.py +216 -0
  9. runtime_memory/core/config.py +473 -0
  10. runtime_memory/core/embeddings.py +908 -0
  11. runtime_memory/core/engine.py +1007 -0
  12. runtime_memory/core/exceptions.py +547 -0
  13. runtime_memory/core/legacy_env.py +39 -0
  14. runtime_memory/core/logging.py +160 -0
  15. runtime_memory/core/models.py +1051 -0
  16. runtime_memory/core/observability.py +725 -0
  17. runtime_memory/core/paths.py +30 -0
  18. runtime_memory/core/resilience.py +511 -0
  19. runtime_memory/core/retrieval.py +819 -0
  20. runtime_memory/core/storage.py +1105 -0
  21. runtime_memory/extraction/__init__.py +36 -0
  22. runtime_memory/extraction/extractor.py +1143 -0
  23. runtime_memory/hermes/__init__.py +39 -0
  24. runtime_memory/hermes/_base.py +154 -0
  25. runtime_memory/hermes/bridge.py +119 -0
  26. runtime_memory/hermes/plugin.yaml +13 -0
  27. runtime_memory/hermes/provider.py +536 -0
  28. runtime_memory/hermes/tools.py +230 -0
  29. runtime_memory/hermes/trace.py +177 -0
  30. runtime_memory/plugin/__init__.py +646 -0
  31. runtime_memory/sdk/__init__.py +97 -0
  32. runtime_memory/sdk/client.py +1577 -0
  33. runtime_memory/server/__init__.py +75 -0
  34. runtime_memory/server/api.py +1665 -0
  35. runtime_memory/server/mcp.py +1574 -0
  36. runtime_memory/server/static/css/styles.css +1110 -0
  37. runtime_memory/server/static/index.html +264 -0
  38. runtime_memory/server/static/js/api.js +294 -0
  39. runtime_memory/server/static/js/app.js +771 -0
  40. runtime_memory/tasks/__init__.py +114 -0
  41. runtime_memory/tasks/adapter.py +501 -0
  42. runtime_memory/tasks/claude_code_adapter.py +495 -0
  43. runtime_memory/tasks/claude_code_parser.py +339 -0
  44. runtime_memory/tasks/cli_bridge.py +415 -0
  45. runtime_memory/tasks/linking.py +397 -0
  46. runtime_memory/tasks/models.py +520 -0
  47. runtime_memory/tasks/outcomes.py +320 -0
  48. runtime_memory/tasks/parser.py +305 -0
  49. runtime_memory/tasks/unified_adapter.py +661 -0
  50. runtime_memory-3.0.0.dist-info/METADATA +497 -0
  51. runtime_memory-3.0.0.dist-info/RECORD +54 -0
  52. runtime_memory-3.0.0.dist-info/WHEEL +4 -0
  53. runtime_memory-3.0.0.dist-info/entry_points.txt +6 -0
  54. runtime_memory-3.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,39 @@
1
+ """runtime-memory as a Hermes Agent memory provider.
2
+
3
+ Hermes finds this package through the ``hermes_agent.memory_providers`` entry
4
+ point declared in ``pyproject.toml``, so installing runtime-memory into the Hermes
5
+ environment is enough to make it selectable:
6
+
7
+ pip install git+https://github.com/runtimenoteslabs/memory-layer.git
8
+ hermes config set memory.provider runtimememory
9
+
10
+ See ``docs/hermes.md`` for configuration and the trace format.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any
16
+
17
+ from runtime_memory.hermes._base import HERMES_AVAILABLE
18
+ from runtime_memory.hermes.provider import (
19
+ PROVIDER_NAME,
20
+ RuntimeMemoryProvider,
21
+ )
22
+
23
+
24
+ def register(ctx: Any) -> None:
25
+ """Register the provider with Hermes' plugin loader.
26
+
27
+ Args:
28
+ ctx: The plugin context Hermes passes in. Only
29
+ ``register_memory_provider`` is used.
30
+ """
31
+ ctx.register_memory_provider(RuntimeMemoryProvider())
32
+
33
+
34
+ __all__ = [
35
+ "HERMES_AVAILABLE",
36
+ "PROVIDER_NAME",
37
+ "RuntimeMemoryProvider",
38
+ "register",
39
+ ]
@@ -0,0 +1,154 @@
1
+ """Hermes plugin base classes, with a standalone fallback.
2
+
3
+ The provider subclasses ``agent.memory_provider.MemoryProvider``, which only
4
+ exists inside a Hermes Agent installation. runtime-memory is also installed on its
5
+ own, so importing this package must not require Hermes. When Hermes is absent we
6
+ fall back to a shim carrying the same surface, which keeps ``runtime_memory.hermes``
7
+ importable and unit-testable anywhere.
8
+
9
+ The shim is deliberately a copy of the contract, not a reimplementation of it. If
10
+ Hermes changes the ABC, the real import is what the provider is validated
11
+ against; ``HERMES_AVAILABLE`` tells tests which one is in play.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any
17
+
18
+ try:
19
+ from agent.memory_provider import ( # type: ignore[import-not-found]
20
+ INDICATOR_GLYPH,
21
+ MemoryProvider,
22
+ RecallStatus,
23
+ is_trivial_prompt,
24
+ )
25
+
26
+ HERMES_AVAILABLE = True
27
+ except ImportError: # pragma: no cover - exercised only outside Hermes
28
+ HERMES_AVAILABLE = False
29
+
30
+ import re
31
+ from abc import ABC, abstractmethod
32
+ from dataclasses import dataclass
33
+
34
+ INDICATOR_GLYPH = "\N{BRAIN}"
35
+
36
+ @dataclass(frozen=True)
37
+ class RecallStatus: # type: ignore[no-redef]
38
+ """What the last prefetch injected, for the recall indicator."""
39
+
40
+ provider_label: str
41
+ count: int
42
+ glyph: str = INDICATOR_GLYPH
43
+
44
+ _TRIVIAL_PROMPT_RE = re.compile(
45
+ r"^(yes|no|ok|okay|sure|thanks|thank you|y|n|yep|nope|yeah|nah|"
46
+ r"hi|hey|hello|yo|sup|"
47
+ r"continue|go ahead|do it|proceed|got it|cool|nice|great|done|next|lgtm|k)"
48
+ r"[\s!?.:;,\"'~]*$",
49
+ re.IGNORECASE,
50
+ )
51
+
52
+ def is_trivial_prompt(text: str | None) -> bool: # type: ignore[misc]
53
+ """True for empty input, slash commands and bare acknowledgements."""
54
+ stripped = (text or "").strip()
55
+ if not stripped or stripped.startswith("/"):
56
+ return True
57
+ return bool(_TRIVIAL_PROMPT_RE.match(stripped))
58
+
59
+ class MemoryProvider(ABC): # type: ignore[no-redef]
60
+ """Minimal stand-in for the Hermes memory provider contract.
61
+
62
+ The optional hooks below are deliberately concrete no-ops rather than
63
+ abstract methods: a provider overrides only the ones it needs, exactly as
64
+ in the real contract this mirrors.
65
+ """
66
+
67
+ pre_compress_checkpoint_api_version = 1
68
+
69
+ @property
70
+ @abstractmethod
71
+ def name(self) -> str: ...
72
+
73
+ @abstractmethod
74
+ def is_available(self) -> bool: ...
75
+
76
+ @abstractmethod
77
+ def initialize(self, session_id: str, **kwargs: Any) -> None: ...
78
+
79
+ @abstractmethod
80
+ def get_tool_schemas(self) -> list[dict[str, Any]]: ...
81
+
82
+ def unavailable_reason(self) -> str:
83
+ return ""
84
+
85
+ def system_prompt_block(self) -> str:
86
+ return ""
87
+
88
+ def prefetch(self, query: str, *, session_id: str = "") -> str:
89
+ return ""
90
+
91
+ def queue_prefetch(self, query: str, *, session_id: str = "") -> None: ...
92
+
93
+ def recall_status(self) -> RecallStatus | None:
94
+ return None
95
+
96
+ def sync_turn(
97
+ self,
98
+ user_content: str,
99
+ assistant_content: str,
100
+ *,
101
+ session_id: str = "",
102
+ messages: list[dict[str, Any]] | None = None,
103
+ ) -> None: ...
104
+
105
+ def handle_tool_call(self, tool_name: str, args: dict[str, Any], **kwargs: Any) -> str:
106
+ raise NotImplementedError
107
+
108
+ def shutdown(self) -> None: ...
109
+
110
+ def on_turn_start(self, turn_number: int, message: str, **kwargs: Any) -> None: ...
111
+
112
+ def on_session_end(self, messages: list[dict[str, Any]]) -> None: ...
113
+
114
+ def on_session_switch(
115
+ self,
116
+ new_session_id: str,
117
+ *,
118
+ parent_session_id: str = "",
119
+ reset: bool = False,
120
+ rewound: bool = False,
121
+ **kwargs: Any,
122
+ ) -> None: ...
123
+
124
+ def on_pre_compress(self, messages: list[dict[str, Any]]) -> str:
125
+ return ""
126
+
127
+ def on_delegation(
128
+ self, task: str, result: str, *, child_session_id: str = "", **kwargs: Any
129
+ ) -> None: ...
130
+
131
+ def get_config_schema(self) -> list[dict[str, Any]]:
132
+ return []
133
+
134
+ def save_config(self, values: dict[str, Any], hermes_home: str) -> None: ...
135
+
136
+ def on_memory_write(
137
+ self,
138
+ action: str,
139
+ target: str,
140
+ content: str,
141
+ metadata: dict[str, Any] | None = None,
142
+ ) -> None: ...
143
+
144
+ def backup_paths(self) -> list[str]:
145
+ return []
146
+
147
+
148
+ __all__ = [
149
+ "HERMES_AVAILABLE",
150
+ "INDICATOR_GLYPH",
151
+ "MemoryProvider",
152
+ "RecallStatus",
153
+ "is_trivial_prompt",
154
+ ]
@@ -0,0 +1,119 @@
1
+ """Sync-to-async bridge for the Hermes memory provider.
2
+
3
+ Every method on the Hermes ``MemoryProvider`` contract is synchronous, while
4
+ ``MemoryEngine`` is fully async. The bridge owns one long-lived event loop on a
5
+ daemon thread and marshals coroutines onto it.
6
+
7
+ The loop is module-global and is deliberately never stopped by a provider's
8
+ ``shutdown()``. Hermes builds one provider per agent and one agent per concurrent
9
+ chat session, so a per-provider teardown would strand every sibling provider's
10
+ engine on a dead loop. The loop is a daemon thread and dies with the process.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import contextvars
17
+ import threading
18
+ from typing import TYPE_CHECKING, Any, TypeVar
19
+
20
+ from runtime_memory.core.logging import get_logger
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Coroutine
24
+
25
+ logger = get_logger(__name__)
26
+
27
+ T = TypeVar("T")
28
+
29
+ _loop: asyncio.AbstractEventLoop | None = None
30
+ _loop_thread: threading.Thread | None = None
31
+ _loop_lock = threading.Lock()
32
+
33
+ DEFAULT_TIMEOUT = 30.0
34
+ """Seconds to wait for a bridged call before giving up."""
35
+
36
+
37
+ def get_loop() -> asyncio.AbstractEventLoop:
38
+ """Return the shared background event loop, starting it if needed."""
39
+ global _loop, _loop_thread
40
+
41
+ with _loop_lock:
42
+ if _loop is not None and _loop.is_running():
43
+ return _loop
44
+
45
+ loop = asyncio.new_event_loop()
46
+
47
+ def _run() -> None:
48
+ asyncio.set_event_loop(loop)
49
+ loop.run_forever()
50
+
51
+ thread = threading.Thread(target=_run, daemon=True, name="memory-layer-loop")
52
+ thread.start()
53
+
54
+ _loop, _loop_thread = loop, thread
55
+ return loop
56
+
57
+
58
+ def run_sync(coro: Coroutine[Any, Any, T], timeout: float = DEFAULT_TIMEOUT) -> T:
59
+ """Run *coro* on the shared loop and block until it finishes.
60
+
61
+ Args:
62
+ coro: The coroutine to run.
63
+ timeout: Seconds to wait before raising ``TimeoutError``.
64
+
65
+ Returns:
66
+ Whatever the coroutine returned.
67
+
68
+ Raises:
69
+ TimeoutError: If the coroutine does not finish within *timeout*.
70
+ """
71
+ future = asyncio.run_coroutine_threadsafe(coro, get_loop())
72
+ try:
73
+ return future.result(timeout=timeout)
74
+ except TimeoutError:
75
+ future.cancel()
76
+ raise
77
+
78
+
79
+ def spawn(coro: Coroutine[Any, Any, Any], *, label: str = "task") -> None:
80
+ """Fire *coro* onto the shared loop without waiting for it.
81
+
82
+ Used for the write path, which must not block the agent's reply. Failures are
83
+ logged rather than raised, since there is no caller left to receive them.
84
+ """
85
+ future = asyncio.run_coroutine_threadsafe(coro, get_loop())
86
+
87
+ def _report(fut: Any) -> None:
88
+ try:
89
+ fut.result()
90
+ except Exception as exc: # background work: log it, there is no caller left
91
+ logger.warning(f"Background {label} failed: {exc}")
92
+
93
+ future.add_done_callback(_report)
94
+
95
+
96
+ def context_thread(target: Any, name: str) -> threading.Thread:
97
+ """Daemon thread running *target* inside a copy of the caller's context.
98
+
99
+ Threads otherwise start with an empty ``contextvars`` context, which loses the
100
+ profile scoping Hermes sets up for multi-profile installs.
101
+ """
102
+ return threading.Thread(
103
+ target=contextvars.copy_context().run,
104
+ args=(target,),
105
+ daemon=True,
106
+ name=name,
107
+ )
108
+
109
+
110
+ def _reset_for_tests() -> None:
111
+ """Stop and clear the shared loop. Test-only."""
112
+ global _loop, _loop_thread
113
+
114
+ with _loop_lock:
115
+ if _loop is not None and _loop.is_running():
116
+ _loop.call_soon_threadsafe(_loop.stop)
117
+ if _loop_thread is not None:
118
+ _loop_thread.join(timeout=5.0)
119
+ _loop, _loop_thread = None, None
@@ -0,0 +1,13 @@
1
+ name: runtimememory
2
+ version: 1.0.0
3
+ description: "Runtime Memory - local SQLite memory with hybrid retrieval and outcome-based learning, shared across Claude Code, MCP clients and Hermes."
4
+
5
+ # Hermes checks each entry by importing `dist_name.replace("-", "_")` unless it
6
+ # has a mapping for the name. `runtime-memory` imports as `runtime_memory`, so
7
+ # it resolves without an upstream mapping, unlike the bundled providers that
8
+ # need one (honcho-ai -> honcho, mem0ai -> mem0).
9
+ pip_dependencies: ["runtime-memory"]
10
+
11
+ requires_env: []
12
+ hooks:
13
+ - on_session_end