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.
- runtime_memory/__init__.py +28 -0
- runtime_memory/claude_code/__init__.py +48 -0
- runtime_memory/claude_code/commands.py +698 -0
- runtime_memory/claude_code/daemon.py +852 -0
- runtime_memory/claude_code/hooks.py +722 -0
- runtime_memory/cli/__init__.py +8 -0
- runtime_memory/cli/main.py +1936 -0
- runtime_memory/core/__init__.py +216 -0
- runtime_memory/core/config.py +473 -0
- runtime_memory/core/embeddings.py +908 -0
- runtime_memory/core/engine.py +1007 -0
- runtime_memory/core/exceptions.py +547 -0
- runtime_memory/core/legacy_env.py +39 -0
- runtime_memory/core/logging.py +160 -0
- runtime_memory/core/models.py +1051 -0
- runtime_memory/core/observability.py +725 -0
- runtime_memory/core/paths.py +30 -0
- runtime_memory/core/resilience.py +511 -0
- runtime_memory/core/retrieval.py +819 -0
- runtime_memory/core/storage.py +1105 -0
- runtime_memory/extraction/__init__.py +36 -0
- runtime_memory/extraction/extractor.py +1143 -0
- runtime_memory/hermes/__init__.py +39 -0
- runtime_memory/hermes/_base.py +154 -0
- runtime_memory/hermes/bridge.py +119 -0
- runtime_memory/hermes/plugin.yaml +13 -0
- runtime_memory/hermes/provider.py +536 -0
- runtime_memory/hermes/tools.py +230 -0
- runtime_memory/hermes/trace.py +177 -0
- runtime_memory/plugin/__init__.py +646 -0
- runtime_memory/sdk/__init__.py +97 -0
- runtime_memory/sdk/client.py +1577 -0
- runtime_memory/server/__init__.py +75 -0
- runtime_memory/server/api.py +1665 -0
- runtime_memory/server/mcp.py +1574 -0
- runtime_memory/server/static/css/styles.css +1110 -0
- runtime_memory/server/static/index.html +264 -0
- runtime_memory/server/static/js/api.js +294 -0
- runtime_memory/server/static/js/app.js +771 -0
- runtime_memory/tasks/__init__.py +114 -0
- runtime_memory/tasks/adapter.py +501 -0
- runtime_memory/tasks/claude_code_adapter.py +495 -0
- runtime_memory/tasks/claude_code_parser.py +339 -0
- runtime_memory/tasks/cli_bridge.py +415 -0
- runtime_memory/tasks/linking.py +397 -0
- runtime_memory/tasks/models.py +520 -0
- runtime_memory/tasks/outcomes.py +320 -0
- runtime_memory/tasks/parser.py +305 -0
- runtime_memory/tasks/unified_adapter.py +661 -0
- runtime_memory-3.0.0.dist-info/METADATA +497 -0
- runtime_memory-3.0.0.dist-info/RECORD +54 -0
- runtime_memory-3.0.0.dist-info/WHEEL +4 -0
- runtime_memory-3.0.0.dist-info/entry_points.txt +6 -0
- runtime_memory-3.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Task tracker integration for Runtime Memory.
|
|
2
|
+
|
|
3
|
+
This module provides integration with task tracking systems,
|
|
4
|
+
enabling automatic outcome capture when tasks complete.
|
|
5
|
+
|
|
6
|
+
Supported task systems:
|
|
7
|
+
- Beads task tracker (.beads/ directory)
|
|
8
|
+
- Claude Code todos (~/.claude/todos/)
|
|
9
|
+
|
|
10
|
+
Key Features:
|
|
11
|
+
- Parse task data from multiple sources
|
|
12
|
+
- Link memories to tasks they're used for
|
|
13
|
+
- Auto-record outcomes when tasks complete
|
|
14
|
+
- Unified context combining tasks + memories
|
|
15
|
+
- Unified adapter for all task sources
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> from runtime_memory.tasks import UnifiedTaskAdapter
|
|
19
|
+
>>> adapter = UnifiedTaskAdapter(engine)
|
|
20
|
+
>>> await adapter.initialize()
|
|
21
|
+
>>> tasks = adapter.list_tasks() # From all sources
|
|
22
|
+
>>> result = await adapter.sync_all()
|
|
23
|
+
>>> print(f"Recorded {result.total_outcomes_recorded} outcomes")
|
|
24
|
+
|
|
25
|
+
# Or use specific adapters:
|
|
26
|
+
>>> from runtime_memory.tasks import BeadsAdapter, ClaudeCodeAdapter
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from runtime_memory.tasks.adapter import BeadsAdapter, NullBeadsAdapter, create_adapter
|
|
30
|
+
from runtime_memory.tasks.claude_code_adapter import (
|
|
31
|
+
ClaudeCodeAdapter,
|
|
32
|
+
NullClaudeCodeAdapter,
|
|
33
|
+
create_claude_code_adapter,
|
|
34
|
+
)
|
|
35
|
+
from runtime_memory.tasks.claude_code_parser import (
|
|
36
|
+
ClaudeCodeDirectoryNotFoundError,
|
|
37
|
+
ClaudeCodeParser,
|
|
38
|
+
)
|
|
39
|
+
from runtime_memory.tasks.cli_bridge import BeadsCLI, get_beads_cli
|
|
40
|
+
from runtime_memory.tasks.linking import TaskMemoryLinker
|
|
41
|
+
from runtime_memory.tasks.models import (
|
|
42
|
+
CANCELLED_TASK_PENALTY,
|
|
43
|
+
CLAUDE_CODE_STATUS_TO_OUTCOME,
|
|
44
|
+
# Constants
|
|
45
|
+
TASK_STATUS_TO_OUTCOME,
|
|
46
|
+
BeadsSyncResult,
|
|
47
|
+
# Beads models
|
|
48
|
+
BeadsTask,
|
|
49
|
+
# Enums
|
|
50
|
+
BeadsTaskStatus,
|
|
51
|
+
# Claude Code models
|
|
52
|
+
ClaudeCodeTask,
|
|
53
|
+
ClaudeCodeTaskStatus,
|
|
54
|
+
# Shared models
|
|
55
|
+
Task,
|
|
56
|
+
TaskContext,
|
|
57
|
+
TaskMemoryLink,
|
|
58
|
+
TaskSource,
|
|
59
|
+
TaskSyncResult,
|
|
60
|
+
)
|
|
61
|
+
from runtime_memory.tasks.outcomes import OutcomeCapture, auto_capture_outcome
|
|
62
|
+
from runtime_memory.tasks.parser import BeadsDirectoryNotFoundError, BeadsParser
|
|
63
|
+
from runtime_memory.tasks.unified_adapter import (
|
|
64
|
+
UnifiedSyncResult,
|
|
65
|
+
UnifiedTask,
|
|
66
|
+
UnifiedTaskAdapter,
|
|
67
|
+
create_unified_adapter,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
__all__ = [
|
|
71
|
+
# === Enums ===
|
|
72
|
+
"BeadsTaskStatus",
|
|
73
|
+
"ClaudeCodeTaskStatus",
|
|
74
|
+
"TaskSource",
|
|
75
|
+
# === Task Models ===
|
|
76
|
+
"BeadsTask",
|
|
77
|
+
"ClaudeCodeTask",
|
|
78
|
+
"Task", # Type alias for BeadsTask | ClaudeCodeTask
|
|
79
|
+
"TaskMemoryLink",
|
|
80
|
+
"TaskContext",
|
|
81
|
+
# === Sync Results ===
|
|
82
|
+
"BeadsSyncResult",
|
|
83
|
+
"TaskSyncResult",
|
|
84
|
+
"UnifiedSyncResult",
|
|
85
|
+
# === Parsers ===
|
|
86
|
+
"BeadsParser",
|
|
87
|
+
"BeadsDirectoryNotFoundError",
|
|
88
|
+
"ClaudeCodeParser",
|
|
89
|
+
"ClaudeCodeDirectoryNotFoundError",
|
|
90
|
+
# === CLI Bridge ===
|
|
91
|
+
"BeadsCLI",
|
|
92
|
+
"get_beads_cli",
|
|
93
|
+
# === Linking ===
|
|
94
|
+
"TaskMemoryLinker",
|
|
95
|
+
# === Outcome Capture ===
|
|
96
|
+
"OutcomeCapture",
|
|
97
|
+
"auto_capture_outcome",
|
|
98
|
+
# === Beads Adapter ===
|
|
99
|
+
"BeadsAdapter",
|
|
100
|
+
"NullBeadsAdapter",
|
|
101
|
+
"create_adapter",
|
|
102
|
+
# === Claude Code Adapter ===
|
|
103
|
+
"ClaudeCodeAdapter",
|
|
104
|
+
"NullClaudeCodeAdapter",
|
|
105
|
+
"create_claude_code_adapter",
|
|
106
|
+
# === Unified Adapter (main entry point) ===
|
|
107
|
+
"UnifiedTaskAdapter",
|
|
108
|
+
"UnifiedTask",
|
|
109
|
+
"create_unified_adapter",
|
|
110
|
+
# === Constants ===
|
|
111
|
+
"TASK_STATUS_TO_OUTCOME",
|
|
112
|
+
"CLAUDE_CODE_STATUS_TO_OUTCOME",
|
|
113
|
+
"CANCELLED_TASK_PENALTY",
|
|
114
|
+
]
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""Unified Beads adapter combining all task integration components.
|
|
2
|
+
|
|
3
|
+
This module provides a single interface for all Beads task tracker
|
|
4
|
+
integration functionality, combining:
|
|
5
|
+
- Task parsing from .beads/ directory
|
|
6
|
+
- Task-memory linking
|
|
7
|
+
- Automatic outcome capture
|
|
8
|
+
- Context generation
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> from runtime_memory.tasks import BeadsAdapter
|
|
12
|
+
>>> adapter = BeadsAdapter(engine)
|
|
13
|
+
>>> await adapter.initialize()
|
|
14
|
+
>>>
|
|
15
|
+
>>> # Sync outcomes for completed tasks
|
|
16
|
+
>>> result = await adapter.sync()
|
|
17
|
+
>>> print(f"Recorded {result.outcomes_recorded} outcomes")
|
|
18
|
+
>>>
|
|
19
|
+
>>> # Get context for current task
|
|
20
|
+
>>> context = await adapter.get_unified_context()
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import logging
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import TYPE_CHECKING
|
|
28
|
+
|
|
29
|
+
from runtime_memory.core.models import Memory
|
|
30
|
+
from runtime_memory.tasks.linking import TaskMemoryLinker
|
|
31
|
+
from runtime_memory.tasks.models import (
|
|
32
|
+
BeadsSyncResult,
|
|
33
|
+
BeadsTask,
|
|
34
|
+
BeadsTaskStatus,
|
|
35
|
+
TaskContext,
|
|
36
|
+
)
|
|
37
|
+
from runtime_memory.tasks.outcomes import OutcomeCapture
|
|
38
|
+
from runtime_memory.tasks.parser import BeadsParser
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from runtime_memory.core.engine import MemoryEngine
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BeadsAdapter:
|
|
47
|
+
"""Unified adapter for Beads task tracker integration.
|
|
48
|
+
|
|
49
|
+
Combines:
|
|
50
|
+
- BeadsParser: Read tasks from .beads/ directory
|
|
51
|
+
- TaskMemoryLinker: Track which memories are used per task
|
|
52
|
+
- OutcomeCapture: Auto-record outcomes when tasks complete
|
|
53
|
+
|
|
54
|
+
This is the main entry point for all Beads integration functionality.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
engine: MemoryEngine,
|
|
60
|
+
beads_dir: Path | str | None = None,
|
|
61
|
+
auto_outcome_enabled: bool = True,
|
|
62
|
+
outcome_on_cancel: bool = False,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Initialize the Beads adapter.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
engine: The Runtime Memory engine.
|
|
68
|
+
beads_dir: Explicit path to .beads/ directory (auto-discovers if None).
|
|
69
|
+
auto_outcome_enabled: Whether to auto-record outcomes on task completion.
|
|
70
|
+
outcome_on_cancel: Whether to record "failed" when tasks are cancelled.
|
|
71
|
+
"""
|
|
72
|
+
self._engine = engine
|
|
73
|
+
self._beads_dir = Path(beads_dir) if beads_dir else None
|
|
74
|
+
|
|
75
|
+
# Initialize components
|
|
76
|
+
self._parser = BeadsParser(self._beads_dir)
|
|
77
|
+
self._linker: TaskMemoryLinker | None = None
|
|
78
|
+
self._outcome_capture: OutcomeCapture | None = None
|
|
79
|
+
|
|
80
|
+
# Configuration
|
|
81
|
+
self.auto_outcome_enabled = auto_outcome_enabled
|
|
82
|
+
self.outcome_on_cancel = outcome_on_cancel
|
|
83
|
+
|
|
84
|
+
self._initialized = False
|
|
85
|
+
|
|
86
|
+
async def initialize(self) -> None:
|
|
87
|
+
"""Initialize all components.
|
|
88
|
+
|
|
89
|
+
Creates the linker table and sets up outcome capture.
|
|
90
|
+
Must be called before using the adapter.
|
|
91
|
+
"""
|
|
92
|
+
if self._initialized:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
# Get db_path from engine's storage
|
|
96
|
+
db_path = self._engine._storage.db_path
|
|
97
|
+
|
|
98
|
+
# Initialize linker
|
|
99
|
+
self._linker = TaskMemoryLinker(db_path)
|
|
100
|
+
await self._linker.initialize()
|
|
101
|
+
|
|
102
|
+
# Initialize outcome capture
|
|
103
|
+
self._outcome_capture = OutcomeCapture(
|
|
104
|
+
engine=self._engine,
|
|
105
|
+
linker=self._linker,
|
|
106
|
+
parser=self._parser,
|
|
107
|
+
auto_outcome_enabled=self.auto_outcome_enabled,
|
|
108
|
+
outcome_on_cancel=self.outcome_on_cancel,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
self._initialized = True
|
|
112
|
+
logger.debug("BeadsAdapter initialized")
|
|
113
|
+
|
|
114
|
+
def _ensure_initialized(self) -> None:
|
|
115
|
+
"""Ensure the adapter is initialized."""
|
|
116
|
+
if not self._initialized:
|
|
117
|
+
raise RuntimeError("BeadsAdapter not initialized. Call initialize() first.")
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def is_available(self) -> bool:
|
|
121
|
+
"""Check if Beads is available (directory exists)."""
|
|
122
|
+
return self._parser.is_available()
|
|
123
|
+
|
|
124
|
+
# =========================================================================
|
|
125
|
+
# Task Operations
|
|
126
|
+
# =========================================================================
|
|
127
|
+
|
|
128
|
+
def get_task(self, task_id: str) -> BeadsTask | None:
|
|
129
|
+
"""Get a task by ID.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
task_id: The Beads task ID (e.g., "bd-a3f8").
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The task or None if not found.
|
|
136
|
+
"""
|
|
137
|
+
return self._parser.get_task(task_id)
|
|
138
|
+
|
|
139
|
+
def list_tasks(
|
|
140
|
+
self,
|
|
141
|
+
status: BeadsTaskStatus | None = None,
|
|
142
|
+
) -> list[BeadsTask]:
|
|
143
|
+
"""List all tasks, optionally filtered by status.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
status: Optional status filter.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of matching tasks.
|
|
150
|
+
"""
|
|
151
|
+
return self._parser.list_tasks(status=status)
|
|
152
|
+
|
|
153
|
+
def get_ready_tasks(self) -> list[BeadsTask]:
|
|
154
|
+
"""Get tasks that are ready to work on.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
List of tasks with no blockers and pending status.
|
|
158
|
+
"""
|
|
159
|
+
return self._parser.get_ready_tasks()
|
|
160
|
+
|
|
161
|
+
def get_current_task(self) -> BeadsTask | None:
|
|
162
|
+
"""Get the currently active task (in_progress).
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
The in-progress task, or None if no task is active.
|
|
166
|
+
"""
|
|
167
|
+
in_progress = self._parser.get_in_progress_tasks()
|
|
168
|
+
return in_progress[0] if in_progress else None
|
|
169
|
+
|
|
170
|
+
# =========================================================================
|
|
171
|
+
# Memory Linking
|
|
172
|
+
# =========================================================================
|
|
173
|
+
|
|
174
|
+
async def link_memory_to_task(
|
|
175
|
+
self,
|
|
176
|
+
task_id: str,
|
|
177
|
+
memory_id: str,
|
|
178
|
+
context: str | None = None,
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Link a memory to a task.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
task_id: The Beads task ID.
|
|
184
|
+
memory_id: The Runtime Memory memory ID.
|
|
185
|
+
context: Optional context about how memory was used.
|
|
186
|
+
"""
|
|
187
|
+
self._ensure_initialized()
|
|
188
|
+
await self._linker.link(task_id, memory_id, context)
|
|
189
|
+
|
|
190
|
+
async def link_memories_to_task(
|
|
191
|
+
self,
|
|
192
|
+
task_id: str,
|
|
193
|
+
memory_ids: list[str],
|
|
194
|
+
context: str | None = None,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Link multiple memories to a task.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
task_id: The Beads task ID.
|
|
200
|
+
memory_ids: List of Runtime Memory memory IDs.
|
|
201
|
+
context: Optional context about how memories were used.
|
|
202
|
+
"""
|
|
203
|
+
self._ensure_initialized()
|
|
204
|
+
await self._linker.link_many(task_id, memory_ids, context)
|
|
205
|
+
|
|
206
|
+
async def get_task_memories(self, task_id: str) -> list[Memory]:
|
|
207
|
+
"""Get all memories linked to a task.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
task_id: The Beads task ID.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
List of Memory objects linked to the task.
|
|
214
|
+
"""
|
|
215
|
+
self._ensure_initialized()
|
|
216
|
+
links = await self._linker.get_memories_for_task(task_id)
|
|
217
|
+
memories = []
|
|
218
|
+
for link in links:
|
|
219
|
+
try:
|
|
220
|
+
memory = await self._engine.get(link.memory_id)
|
|
221
|
+
memories.append(memory)
|
|
222
|
+
except Exception:
|
|
223
|
+
# Memory might not exist anymore
|
|
224
|
+
logger.debug(f"Memory {link.memory_id} not found, skipping")
|
|
225
|
+
return memories
|
|
226
|
+
|
|
227
|
+
async def auto_link_search_results(
|
|
228
|
+
self,
|
|
229
|
+
task_id: str,
|
|
230
|
+
memory_ids: list[str],
|
|
231
|
+
) -> None:
|
|
232
|
+
"""Automatically link search results to the current task.
|
|
233
|
+
|
|
234
|
+
Called after a search to track which memories were surfaced.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
task_id: The Beads task ID.
|
|
238
|
+
memory_ids: Memory IDs from search results.
|
|
239
|
+
"""
|
|
240
|
+
self._ensure_initialized()
|
|
241
|
+
await self._linker.link_many(task_id, memory_ids, context="search_result")
|
|
242
|
+
|
|
243
|
+
# =========================================================================
|
|
244
|
+
# Outcome Capture
|
|
245
|
+
# =========================================================================
|
|
246
|
+
|
|
247
|
+
async def on_task_done(self, task_id: str) -> int:
|
|
248
|
+
"""Handle a task being marked as done.
|
|
249
|
+
|
|
250
|
+
Records "worked" outcome for all linked memories.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
task_id: The Beads task ID.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Number of memories that had outcomes recorded.
|
|
257
|
+
"""
|
|
258
|
+
self._ensure_initialized()
|
|
259
|
+
return await self._outcome_capture.on_task_completed(task_id)
|
|
260
|
+
|
|
261
|
+
async def on_task_cancelled(self, task_id: str) -> int:
|
|
262
|
+
"""Handle a task being cancelled.
|
|
263
|
+
|
|
264
|
+
Records "failed" outcome for linked memories (if enabled).
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
task_id: The Beads task ID.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Number of memories that had outcomes recorded.
|
|
271
|
+
"""
|
|
272
|
+
self._ensure_initialized()
|
|
273
|
+
return await self._outcome_capture.on_task_failed(task_id)
|
|
274
|
+
|
|
275
|
+
async def on_task_blocked(self, task_id: str) -> int:
|
|
276
|
+
"""Handle a task being blocked.
|
|
277
|
+
|
|
278
|
+
Records "partial" outcome for linked memories.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
task_id: The Beads task ID.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Number of memories that had outcomes recorded.
|
|
285
|
+
"""
|
|
286
|
+
self._ensure_initialized()
|
|
287
|
+
return await self._outcome_capture.on_task_blocked(task_id)
|
|
288
|
+
|
|
289
|
+
async def sync(self) -> BeadsSyncResult:
|
|
290
|
+
"""Sync outcomes for all completed tasks.
|
|
291
|
+
|
|
292
|
+
Scans completed tasks and records outcomes for any that
|
|
293
|
+
have unresolved memory links.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
BeadsSyncResult with sync statistics.
|
|
297
|
+
"""
|
|
298
|
+
self._ensure_initialized()
|
|
299
|
+
return await self._outcome_capture.sync_completed_tasks()
|
|
300
|
+
|
|
301
|
+
# =========================================================================
|
|
302
|
+
# Context Generation
|
|
303
|
+
# =========================================================================
|
|
304
|
+
|
|
305
|
+
async def get_unified_context(
|
|
306
|
+
self,
|
|
307
|
+
task_id: str | None = None,
|
|
308
|
+
max_memories: int = 10,
|
|
309
|
+
) -> TaskContext | None:
|
|
310
|
+
"""Get unified context combining task info and relevant memories.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
task_id: The task ID to get context for. If None, uses current task.
|
|
314
|
+
max_memories: Maximum number of memories to include.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
TaskContext object or None if no task found.
|
|
318
|
+
"""
|
|
319
|
+
self._ensure_initialized()
|
|
320
|
+
|
|
321
|
+
# Get task
|
|
322
|
+
if task_id:
|
|
323
|
+
task = self.get_task(task_id)
|
|
324
|
+
else:
|
|
325
|
+
task = self.get_current_task()
|
|
326
|
+
|
|
327
|
+
if not task:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
# Get linked memories
|
|
331
|
+
memories = await self.get_task_memories(task.id)
|
|
332
|
+
|
|
333
|
+
# If no linked memories, search for relevant ones
|
|
334
|
+
if not memories and task.title:
|
|
335
|
+
search_query = f"{task.title} {task.description[:100] if task.description else ''}"
|
|
336
|
+
results = await self._engine.search(
|
|
337
|
+
search_query,
|
|
338
|
+
limit=max_memories,
|
|
339
|
+
min_score=0.5, # Only include memories with >50% relevance
|
|
340
|
+
track_usage=False, # Don't track this as usage
|
|
341
|
+
)
|
|
342
|
+
# Filter to only highly relevant results
|
|
343
|
+
memories = [r.memory for r in results if r.score >= 0.5]
|
|
344
|
+
|
|
345
|
+
# Limit memories
|
|
346
|
+
memories = memories[:max_memories]
|
|
347
|
+
|
|
348
|
+
# Create context
|
|
349
|
+
context = TaskContext(
|
|
350
|
+
task=task,
|
|
351
|
+
memories=memories,
|
|
352
|
+
)
|
|
353
|
+
context.formatted = context.to_markdown()
|
|
354
|
+
|
|
355
|
+
return context
|
|
356
|
+
|
|
357
|
+
async def get_context_for_injection(
|
|
358
|
+
self,
|
|
359
|
+
task_id: str | None = None,
|
|
360
|
+
max_memories: int = 10,
|
|
361
|
+
) -> str:
|
|
362
|
+
"""Get formatted context string for injection into prompts.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
task_id: The task ID to get context for. If None, uses current task.
|
|
366
|
+
max_memories: Maximum number of memories to include.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Formatted markdown string for context injection.
|
|
370
|
+
"""
|
|
371
|
+
context = await self.get_unified_context(task_id, max_memories)
|
|
372
|
+
if not context:
|
|
373
|
+
return ""
|
|
374
|
+
return context.formatted
|
|
375
|
+
|
|
376
|
+
# =========================================================================
|
|
377
|
+
# Statistics
|
|
378
|
+
# =========================================================================
|
|
379
|
+
|
|
380
|
+
async def get_stats(self) -> dict:
|
|
381
|
+
"""Get statistics about Beads integration.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dict with statistics.
|
|
385
|
+
"""
|
|
386
|
+
self._ensure_initialized()
|
|
387
|
+
|
|
388
|
+
# Task stats
|
|
389
|
+
all_tasks = self.list_tasks()
|
|
390
|
+
task_stats = {
|
|
391
|
+
"total_tasks": len(all_tasks),
|
|
392
|
+
"by_status": {},
|
|
393
|
+
}
|
|
394
|
+
for task in all_tasks:
|
|
395
|
+
status = task.status.value
|
|
396
|
+
task_stats["by_status"][status] = task_stats["by_status"].get(status, 0) + 1
|
|
397
|
+
|
|
398
|
+
# Link stats
|
|
399
|
+
link_stats = await self._linker.get_stats()
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
"beads_available": self.is_available,
|
|
403
|
+
"beads_dir": str(self._parser.beads_dir) if self._parser.beads_dir else None,
|
|
404
|
+
"tasks": task_stats,
|
|
405
|
+
"links": link_stats,
|
|
406
|
+
"auto_outcome_enabled": self.auto_outcome_enabled,
|
|
407
|
+
"outcome_on_cancel": self.outcome_on_cancel,
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
def refresh(self) -> int:
|
|
411
|
+
"""Refresh task cache from disk.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Number of tasks loaded.
|
|
415
|
+
"""
|
|
416
|
+
return self._parser.refresh()
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class NullBeadsAdapter:
|
|
420
|
+
"""Null adapter for when Beads is not available.
|
|
421
|
+
|
|
422
|
+
Provides the same interface but does nothing, allowing graceful
|
|
423
|
+
degradation when Beads integration is not configured.
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
def is_available(self) -> bool:
|
|
428
|
+
return False
|
|
429
|
+
|
|
430
|
+
async def initialize(self) -> None:
|
|
431
|
+
pass
|
|
432
|
+
|
|
433
|
+
def get_task(self, task_id: str) -> None:
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
def list_tasks(self, status: BeadsTaskStatus | None = None) -> list:
|
|
437
|
+
return []
|
|
438
|
+
|
|
439
|
+
def get_ready_tasks(self) -> list:
|
|
440
|
+
return []
|
|
441
|
+
|
|
442
|
+
def get_current_task(self) -> None:
|
|
443
|
+
return None
|
|
444
|
+
|
|
445
|
+
async def link_memory_to_task(self, *args, **kwargs) -> None:
|
|
446
|
+
pass
|
|
447
|
+
|
|
448
|
+
async def link_memories_to_task(self, *args, **kwargs) -> None:
|
|
449
|
+
pass
|
|
450
|
+
|
|
451
|
+
async def get_task_memories(self, task_id: str) -> list:
|
|
452
|
+
return []
|
|
453
|
+
|
|
454
|
+
async def on_task_done(self, task_id: str) -> int:
|
|
455
|
+
return 0
|
|
456
|
+
|
|
457
|
+
async def on_task_cancelled(self, task_id: str) -> int:
|
|
458
|
+
return 0
|
|
459
|
+
|
|
460
|
+
async def on_task_blocked(self, task_id: str) -> int:
|
|
461
|
+
return 0
|
|
462
|
+
|
|
463
|
+
async def sync(self) -> BeadsSyncResult:
|
|
464
|
+
result = BeadsSyncResult()
|
|
465
|
+
result.warnings.append("Beads integration not available")
|
|
466
|
+
return result
|
|
467
|
+
|
|
468
|
+
async def get_unified_context(self, *args, **kwargs) -> None:
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
async def get_context_for_injection(self, *args, **kwargs) -> str:
|
|
472
|
+
return ""
|
|
473
|
+
|
|
474
|
+
async def get_stats(self) -> dict:
|
|
475
|
+
return {"beads_available": False}
|
|
476
|
+
|
|
477
|
+
def refresh(self) -> int:
|
|
478
|
+
return 0
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def create_adapter(
|
|
482
|
+
engine: MemoryEngine,
|
|
483
|
+
beads_dir: Path | str | None = None,
|
|
484
|
+
**kwargs,
|
|
485
|
+
) -> BeadsAdapter | NullBeadsAdapter:
|
|
486
|
+
"""Factory function to create the appropriate adapter.
|
|
487
|
+
|
|
488
|
+
Returns a NullBeadsAdapter if Beads is not available.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
engine: The Runtime Memory engine.
|
|
492
|
+
beads_dir: Explicit path to .beads/ directory.
|
|
493
|
+
**kwargs: Additional arguments for BeadsAdapter.
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
BeadsAdapter if Beads is available, NullBeadsAdapter otherwise.
|
|
497
|
+
"""
|
|
498
|
+
adapter = BeadsAdapter(engine, beads_dir, **kwargs)
|
|
499
|
+
if adapter.is_available:
|
|
500
|
+
return adapter
|
|
501
|
+
return NullBeadsAdapter()
|