gobby 0.2.5__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.
- gobby/__init__.py +3 -0
- gobby/adapters/__init__.py +30 -0
- gobby/adapters/base.py +93 -0
- gobby/adapters/claude_code.py +276 -0
- gobby/adapters/codex.py +1292 -0
- gobby/adapters/gemini.py +343 -0
- gobby/agents/__init__.py +37 -0
- gobby/agents/codex_session.py +120 -0
- gobby/agents/constants.py +112 -0
- gobby/agents/context.py +362 -0
- gobby/agents/definitions.py +133 -0
- gobby/agents/gemini_session.py +111 -0
- gobby/agents/registry.py +618 -0
- gobby/agents/runner.py +968 -0
- gobby/agents/session.py +259 -0
- gobby/agents/spawn.py +916 -0
- gobby/agents/spawners/__init__.py +77 -0
- gobby/agents/spawners/base.py +142 -0
- gobby/agents/spawners/cross_platform.py +266 -0
- gobby/agents/spawners/embedded.py +225 -0
- gobby/agents/spawners/headless.py +226 -0
- gobby/agents/spawners/linux.py +125 -0
- gobby/agents/spawners/macos.py +277 -0
- gobby/agents/spawners/windows.py +308 -0
- gobby/agents/tty_config.py +319 -0
- gobby/autonomous/__init__.py +32 -0
- gobby/autonomous/progress_tracker.py +447 -0
- gobby/autonomous/stop_registry.py +269 -0
- gobby/autonomous/stuck_detector.py +383 -0
- gobby/cli/__init__.py +67 -0
- gobby/cli/__main__.py +8 -0
- gobby/cli/agents.py +529 -0
- gobby/cli/artifacts.py +266 -0
- gobby/cli/daemon.py +329 -0
- gobby/cli/extensions.py +526 -0
- gobby/cli/github.py +263 -0
- gobby/cli/init.py +53 -0
- gobby/cli/install.py +614 -0
- gobby/cli/installers/__init__.py +37 -0
- gobby/cli/installers/antigravity.py +65 -0
- gobby/cli/installers/claude.py +363 -0
- gobby/cli/installers/codex.py +192 -0
- gobby/cli/installers/gemini.py +294 -0
- gobby/cli/installers/git_hooks.py +377 -0
- gobby/cli/installers/shared.py +737 -0
- gobby/cli/linear.py +250 -0
- gobby/cli/mcp.py +30 -0
- gobby/cli/mcp_proxy.py +698 -0
- gobby/cli/memory.py +304 -0
- gobby/cli/merge.py +384 -0
- gobby/cli/projects.py +79 -0
- gobby/cli/sessions.py +622 -0
- gobby/cli/tasks/__init__.py +30 -0
- gobby/cli/tasks/_utils.py +658 -0
- gobby/cli/tasks/ai.py +1025 -0
- gobby/cli/tasks/commits.py +169 -0
- gobby/cli/tasks/crud.py +685 -0
- gobby/cli/tasks/deps.py +135 -0
- gobby/cli/tasks/labels.py +63 -0
- gobby/cli/tasks/main.py +273 -0
- gobby/cli/tasks/search.py +178 -0
- gobby/cli/tui.py +34 -0
- gobby/cli/utils.py +513 -0
- gobby/cli/workflows.py +927 -0
- gobby/cli/worktrees.py +481 -0
- gobby/config/__init__.py +129 -0
- gobby/config/app.py +551 -0
- gobby/config/extensions.py +167 -0
- gobby/config/features.py +472 -0
- gobby/config/llm_providers.py +98 -0
- gobby/config/logging.py +66 -0
- gobby/config/mcp.py +346 -0
- gobby/config/persistence.py +247 -0
- gobby/config/servers.py +141 -0
- gobby/config/sessions.py +250 -0
- gobby/config/tasks.py +784 -0
- gobby/hooks/__init__.py +104 -0
- gobby/hooks/artifact_capture.py +213 -0
- gobby/hooks/broadcaster.py +243 -0
- gobby/hooks/event_handlers.py +723 -0
- gobby/hooks/events.py +218 -0
- gobby/hooks/git.py +169 -0
- gobby/hooks/health_monitor.py +171 -0
- gobby/hooks/hook_manager.py +856 -0
- gobby/hooks/hook_types.py +575 -0
- gobby/hooks/plugins.py +813 -0
- gobby/hooks/session_coordinator.py +396 -0
- gobby/hooks/verification_runner.py +268 -0
- gobby/hooks/webhooks.py +339 -0
- gobby/install/claude/commands/gobby/bug.md +51 -0
- gobby/install/claude/commands/gobby/chore.md +51 -0
- gobby/install/claude/commands/gobby/epic.md +52 -0
- gobby/install/claude/commands/gobby/eval.md +235 -0
- gobby/install/claude/commands/gobby/feat.md +49 -0
- gobby/install/claude/commands/gobby/nit.md +52 -0
- gobby/install/claude/commands/gobby/ref.md +52 -0
- gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
- gobby/install/claude/hooks/hook_dispatcher.py +364 -0
- gobby/install/claude/hooks/validate_settings.py +102 -0
- gobby/install/claude/hooks-template.json +118 -0
- gobby/install/codex/hooks/hook_dispatcher.py +153 -0
- gobby/install/codex/prompts/forget.md +7 -0
- gobby/install/codex/prompts/memories.md +7 -0
- gobby/install/codex/prompts/recall.md +7 -0
- gobby/install/codex/prompts/remember.md +13 -0
- gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
- gobby/install/gemini/hooks-template.json +138 -0
- gobby/install/shared/plugins/code_guardian.py +456 -0
- gobby/install/shared/plugins/example_notify.py +331 -0
- gobby/integrations/__init__.py +10 -0
- gobby/integrations/github.py +145 -0
- gobby/integrations/linear.py +145 -0
- gobby/llm/__init__.py +40 -0
- gobby/llm/base.py +120 -0
- gobby/llm/claude.py +578 -0
- gobby/llm/claude_executor.py +503 -0
- gobby/llm/codex.py +322 -0
- gobby/llm/codex_executor.py +513 -0
- gobby/llm/executor.py +316 -0
- gobby/llm/factory.py +34 -0
- gobby/llm/gemini.py +258 -0
- gobby/llm/gemini_executor.py +339 -0
- gobby/llm/litellm.py +287 -0
- gobby/llm/litellm_executor.py +303 -0
- gobby/llm/resolver.py +499 -0
- gobby/llm/service.py +236 -0
- gobby/mcp_proxy/__init__.py +29 -0
- gobby/mcp_proxy/actions.py +175 -0
- gobby/mcp_proxy/daemon_control.py +198 -0
- gobby/mcp_proxy/importer.py +436 -0
- gobby/mcp_proxy/lazy.py +325 -0
- gobby/mcp_proxy/manager.py +798 -0
- gobby/mcp_proxy/metrics.py +609 -0
- gobby/mcp_proxy/models.py +139 -0
- gobby/mcp_proxy/registries.py +215 -0
- gobby/mcp_proxy/schema_hash.py +381 -0
- gobby/mcp_proxy/semantic_search.py +706 -0
- gobby/mcp_proxy/server.py +549 -0
- gobby/mcp_proxy/services/__init__.py +0 -0
- gobby/mcp_proxy/services/fallback.py +306 -0
- gobby/mcp_proxy/services/recommendation.py +224 -0
- gobby/mcp_proxy/services/server_mgmt.py +214 -0
- gobby/mcp_proxy/services/system.py +72 -0
- gobby/mcp_proxy/services/tool_filter.py +231 -0
- gobby/mcp_proxy/services/tool_proxy.py +309 -0
- gobby/mcp_proxy/stdio.py +565 -0
- gobby/mcp_proxy/tools/__init__.py +27 -0
- gobby/mcp_proxy/tools/agents.py +1103 -0
- gobby/mcp_proxy/tools/artifacts.py +207 -0
- gobby/mcp_proxy/tools/hub.py +335 -0
- gobby/mcp_proxy/tools/internal.py +337 -0
- gobby/mcp_proxy/tools/memory.py +543 -0
- gobby/mcp_proxy/tools/merge.py +422 -0
- gobby/mcp_proxy/tools/metrics.py +283 -0
- gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
- gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
- gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
- gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
- gobby/mcp_proxy/tools/orchestration/review.py +736 -0
- gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
- gobby/mcp_proxy/tools/session_messages.py +1056 -0
- gobby/mcp_proxy/tools/task_dependencies.py +219 -0
- gobby/mcp_proxy/tools/task_expansion.py +591 -0
- gobby/mcp_proxy/tools/task_github.py +393 -0
- gobby/mcp_proxy/tools/task_linear.py +379 -0
- gobby/mcp_proxy/tools/task_orchestration.py +77 -0
- gobby/mcp_proxy/tools/task_readiness.py +522 -0
- gobby/mcp_proxy/tools/task_sync.py +351 -0
- gobby/mcp_proxy/tools/task_validation.py +843 -0
- gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
- gobby/mcp_proxy/tools/tasks/_context.py +112 -0
- gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
- gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
- gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
- gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
- gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
- gobby/mcp_proxy/tools/tasks/_search.py +215 -0
- gobby/mcp_proxy/tools/tasks/_session.py +125 -0
- gobby/mcp_proxy/tools/workflows.py +973 -0
- gobby/mcp_proxy/tools/worktrees.py +1264 -0
- gobby/mcp_proxy/transports/__init__.py +0 -0
- gobby/mcp_proxy/transports/base.py +95 -0
- gobby/mcp_proxy/transports/factory.py +44 -0
- gobby/mcp_proxy/transports/http.py +139 -0
- gobby/mcp_proxy/transports/stdio.py +213 -0
- gobby/mcp_proxy/transports/websocket.py +136 -0
- gobby/memory/backends/__init__.py +116 -0
- gobby/memory/backends/mem0.py +408 -0
- gobby/memory/backends/memu.py +485 -0
- gobby/memory/backends/null.py +111 -0
- gobby/memory/backends/openmemory.py +537 -0
- gobby/memory/backends/sqlite.py +304 -0
- gobby/memory/context.py +87 -0
- gobby/memory/manager.py +1001 -0
- gobby/memory/protocol.py +451 -0
- gobby/memory/search/__init__.py +66 -0
- gobby/memory/search/text.py +127 -0
- gobby/memory/viz.py +258 -0
- gobby/prompts/__init__.py +13 -0
- gobby/prompts/defaults/expansion/system.md +119 -0
- gobby/prompts/defaults/expansion/user.md +48 -0
- gobby/prompts/defaults/external_validation/agent.md +72 -0
- gobby/prompts/defaults/external_validation/external.md +63 -0
- gobby/prompts/defaults/external_validation/spawn.md +83 -0
- gobby/prompts/defaults/external_validation/system.md +6 -0
- gobby/prompts/defaults/features/import_mcp.md +22 -0
- gobby/prompts/defaults/features/import_mcp_github.md +17 -0
- gobby/prompts/defaults/features/import_mcp_search.md +16 -0
- gobby/prompts/defaults/features/recommend_tools.md +32 -0
- gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
- gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
- gobby/prompts/defaults/features/server_description.md +20 -0
- gobby/prompts/defaults/features/server_description_system.md +6 -0
- gobby/prompts/defaults/features/task_description.md +31 -0
- gobby/prompts/defaults/features/task_description_system.md +6 -0
- gobby/prompts/defaults/features/tool_summary.md +17 -0
- gobby/prompts/defaults/features/tool_summary_system.md +6 -0
- gobby/prompts/defaults/research/step.md +58 -0
- gobby/prompts/defaults/validation/criteria.md +47 -0
- gobby/prompts/defaults/validation/validate.md +38 -0
- gobby/prompts/loader.py +346 -0
- gobby/prompts/models.py +113 -0
- gobby/py.typed +0 -0
- gobby/runner.py +488 -0
- gobby/search/__init__.py +23 -0
- gobby/search/protocol.py +104 -0
- gobby/search/tfidf.py +232 -0
- gobby/servers/__init__.py +7 -0
- gobby/servers/http.py +636 -0
- gobby/servers/models.py +31 -0
- gobby/servers/routes/__init__.py +23 -0
- gobby/servers/routes/admin.py +416 -0
- gobby/servers/routes/dependencies.py +118 -0
- gobby/servers/routes/mcp/__init__.py +24 -0
- gobby/servers/routes/mcp/hooks.py +135 -0
- gobby/servers/routes/mcp/plugins.py +121 -0
- gobby/servers/routes/mcp/tools.py +1337 -0
- gobby/servers/routes/mcp/webhooks.py +159 -0
- gobby/servers/routes/sessions.py +582 -0
- gobby/servers/websocket.py +766 -0
- gobby/sessions/__init__.py +13 -0
- gobby/sessions/analyzer.py +322 -0
- gobby/sessions/lifecycle.py +240 -0
- gobby/sessions/manager.py +563 -0
- gobby/sessions/processor.py +225 -0
- gobby/sessions/summary.py +532 -0
- gobby/sessions/transcripts/__init__.py +41 -0
- gobby/sessions/transcripts/base.py +125 -0
- gobby/sessions/transcripts/claude.py +386 -0
- gobby/sessions/transcripts/codex.py +143 -0
- gobby/sessions/transcripts/gemini.py +195 -0
- gobby/storage/__init__.py +21 -0
- gobby/storage/agents.py +409 -0
- gobby/storage/artifact_classifier.py +341 -0
- gobby/storage/artifacts.py +285 -0
- gobby/storage/compaction.py +67 -0
- gobby/storage/database.py +357 -0
- gobby/storage/inter_session_messages.py +194 -0
- gobby/storage/mcp.py +680 -0
- gobby/storage/memories.py +562 -0
- gobby/storage/merge_resolutions.py +550 -0
- gobby/storage/migrations.py +860 -0
- gobby/storage/migrations_legacy.py +1359 -0
- gobby/storage/projects.py +166 -0
- gobby/storage/session_messages.py +251 -0
- gobby/storage/session_tasks.py +97 -0
- gobby/storage/sessions.py +817 -0
- gobby/storage/task_dependencies.py +223 -0
- gobby/storage/tasks/__init__.py +42 -0
- gobby/storage/tasks/_aggregates.py +180 -0
- gobby/storage/tasks/_crud.py +449 -0
- gobby/storage/tasks/_id.py +104 -0
- gobby/storage/tasks/_lifecycle.py +311 -0
- gobby/storage/tasks/_manager.py +889 -0
- gobby/storage/tasks/_models.py +300 -0
- gobby/storage/tasks/_ordering.py +119 -0
- gobby/storage/tasks/_path_cache.py +110 -0
- gobby/storage/tasks/_queries.py +343 -0
- gobby/storage/tasks/_search.py +143 -0
- gobby/storage/workflow_audit.py +393 -0
- gobby/storage/worktrees.py +547 -0
- gobby/sync/__init__.py +29 -0
- gobby/sync/github.py +333 -0
- gobby/sync/linear.py +304 -0
- gobby/sync/memories.py +284 -0
- gobby/sync/tasks.py +641 -0
- gobby/tasks/__init__.py +8 -0
- gobby/tasks/build_verification.py +193 -0
- gobby/tasks/commits.py +633 -0
- gobby/tasks/context.py +747 -0
- gobby/tasks/criteria.py +342 -0
- gobby/tasks/enhanced_validator.py +226 -0
- gobby/tasks/escalation.py +263 -0
- gobby/tasks/expansion.py +626 -0
- gobby/tasks/external_validator.py +764 -0
- gobby/tasks/issue_extraction.py +171 -0
- gobby/tasks/prompts/expand.py +327 -0
- gobby/tasks/research.py +421 -0
- gobby/tasks/tdd.py +352 -0
- gobby/tasks/tree_builder.py +263 -0
- gobby/tasks/validation.py +712 -0
- gobby/tasks/validation_history.py +357 -0
- gobby/tasks/validation_models.py +89 -0
- gobby/tools/__init__.py +0 -0
- gobby/tools/summarizer.py +170 -0
- gobby/tui/__init__.py +5 -0
- gobby/tui/api_client.py +281 -0
- gobby/tui/app.py +327 -0
- gobby/tui/screens/__init__.py +25 -0
- gobby/tui/screens/agents.py +333 -0
- gobby/tui/screens/chat.py +450 -0
- gobby/tui/screens/dashboard.py +377 -0
- gobby/tui/screens/memory.py +305 -0
- gobby/tui/screens/metrics.py +231 -0
- gobby/tui/screens/orchestrator.py +904 -0
- gobby/tui/screens/sessions.py +412 -0
- gobby/tui/screens/tasks.py +442 -0
- gobby/tui/screens/workflows.py +289 -0
- gobby/tui/screens/worktrees.py +174 -0
- gobby/tui/widgets/__init__.py +21 -0
- gobby/tui/widgets/chat.py +210 -0
- gobby/tui/widgets/conductor.py +104 -0
- gobby/tui/widgets/menu.py +132 -0
- gobby/tui/widgets/message_panel.py +160 -0
- gobby/tui/widgets/review_gate.py +224 -0
- gobby/tui/widgets/task_tree.py +99 -0
- gobby/tui/widgets/token_budget.py +166 -0
- gobby/tui/ws_client.py +258 -0
- gobby/utils/__init__.py +3 -0
- gobby/utils/daemon_client.py +235 -0
- gobby/utils/git.py +222 -0
- gobby/utils/id.py +38 -0
- gobby/utils/json_helpers.py +161 -0
- gobby/utils/logging.py +376 -0
- gobby/utils/machine_id.py +135 -0
- gobby/utils/metrics.py +589 -0
- gobby/utils/project_context.py +182 -0
- gobby/utils/project_init.py +263 -0
- gobby/utils/status.py +256 -0
- gobby/utils/validation.py +80 -0
- gobby/utils/version.py +23 -0
- gobby/workflows/__init__.py +4 -0
- gobby/workflows/actions.py +1310 -0
- gobby/workflows/approval_flow.py +138 -0
- gobby/workflows/artifact_actions.py +103 -0
- gobby/workflows/audit_helpers.py +110 -0
- gobby/workflows/autonomous_actions.py +286 -0
- gobby/workflows/context_actions.py +394 -0
- gobby/workflows/definitions.py +130 -0
- gobby/workflows/detection_helpers.py +208 -0
- gobby/workflows/engine.py +485 -0
- gobby/workflows/evaluator.py +669 -0
- gobby/workflows/git_utils.py +96 -0
- gobby/workflows/hooks.py +169 -0
- gobby/workflows/lifecycle_evaluator.py +613 -0
- gobby/workflows/llm_actions.py +70 -0
- gobby/workflows/loader.py +333 -0
- gobby/workflows/mcp_actions.py +60 -0
- gobby/workflows/memory_actions.py +272 -0
- gobby/workflows/premature_stop.py +164 -0
- gobby/workflows/session_actions.py +139 -0
- gobby/workflows/state_actions.py +123 -0
- gobby/workflows/state_manager.py +104 -0
- gobby/workflows/stop_signal_actions.py +163 -0
- gobby/workflows/summary_actions.py +344 -0
- gobby/workflows/task_actions.py +249 -0
- gobby/workflows/task_enforcement_actions.py +901 -0
- gobby/workflows/templates.py +52 -0
- gobby/workflows/todo_actions.py +84 -0
- gobby/workflows/webhook.py +223 -0
- gobby/workflows/webhook_executor.py +399 -0
- gobby/worktrees/__init__.py +5 -0
- gobby/worktrees/git.py +690 -0
- gobby/worktrees/merge/__init__.py +20 -0
- gobby/worktrees/merge/conflict_parser.py +177 -0
- gobby/worktrees/merge/resolver.py +485 -0
- gobby-0.2.5.dist-info/METADATA +351 -0
- gobby-0.2.5.dist-info/RECORD +383 -0
- gobby-0.2.5.dist-info/WHEEL +5 -0
- gobby-0.2.5.dist-info/entry_points.txt +2 -0
- gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
- gobby-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"""MemU memory backend integration.
|
|
2
|
+
|
|
3
|
+
This backend wraps the MemU SDK (NevaMind-AI/memU via memu-py) to provide a
|
|
4
|
+
MemoryBackendProtocol-compliant interface. MemU offers structured memory
|
|
5
|
+
storage with semantic search and categorization.
|
|
6
|
+
|
|
7
|
+
Requires: pip install memu-py
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
from gobby.memory.backends import get_backend
|
|
11
|
+
|
|
12
|
+
backend = get_backend("memu", database_type="inmemory")
|
|
13
|
+
record = await backend.create("User prefers dark mode")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
from datetime import UTC, datetime
|
|
20
|
+
from typing import TYPE_CHECKING, Any
|
|
21
|
+
from uuid import uuid4
|
|
22
|
+
|
|
23
|
+
from gobby.memory.protocol import (
|
|
24
|
+
MediaAttachment,
|
|
25
|
+
MemoryCapability,
|
|
26
|
+
MemoryQuery,
|
|
27
|
+
MemoryRecord,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from memu.app.service import MemoryService
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MemUBackend:
|
|
35
|
+
"""MemU-based memory backend.
|
|
36
|
+
|
|
37
|
+
Wraps the MemU SDK (memu-py) to provide MemoryBackendProtocol interface.
|
|
38
|
+
Supports structured memory storage with semantic search.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
database_type: Database type - "inmemory", "sqlite", or "postgres"
|
|
42
|
+
database_url: Database URL (for sqlite/postgres)
|
|
43
|
+
llm_api_key: API key for LLM provider (OpenAI, etc.)
|
|
44
|
+
llm_base_url: Base URL for LLM API
|
|
45
|
+
embedding_api_key: API key for embedding provider
|
|
46
|
+
embedding_base_url: Base URL for embedding API
|
|
47
|
+
user_id: Default user ID for memories
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
database_type: str = "inmemory",
|
|
53
|
+
database_url: str | None = None,
|
|
54
|
+
llm_api_key: str | None = None,
|
|
55
|
+
llm_base_url: str | None = None,
|
|
56
|
+
embedding_api_key: str | None = None,
|
|
57
|
+
embedding_base_url: str | None = None,
|
|
58
|
+
user_id: str | None = None,
|
|
59
|
+
**kwargs: Any,
|
|
60
|
+
):
|
|
61
|
+
"""Initialize the MemU backend.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
database_type: Database backend type
|
|
65
|
+
database_url: Connection URL for database
|
|
66
|
+
llm_api_key: LLM API key
|
|
67
|
+
llm_base_url: LLM API base URL
|
|
68
|
+
embedding_api_key: Embedding API key
|
|
69
|
+
embedding_base_url: Embedding API base URL
|
|
70
|
+
user_id: Default user ID for operations
|
|
71
|
+
**kwargs: Additional configuration
|
|
72
|
+
"""
|
|
73
|
+
# Lazy import to avoid circular dependencies
|
|
74
|
+
from memu.app.service import MemoryService
|
|
75
|
+
|
|
76
|
+
# Build configuration
|
|
77
|
+
config: dict[str, Any] = {}
|
|
78
|
+
|
|
79
|
+
# Database configuration
|
|
80
|
+
if database_type == "inmemory":
|
|
81
|
+
config["database_config"] = {"type": "inmemory"}
|
|
82
|
+
elif database_type == "sqlite":
|
|
83
|
+
config["database_config"] = {
|
|
84
|
+
"type": "sqlite",
|
|
85
|
+
"url": database_url or "sqlite:///memu.db",
|
|
86
|
+
}
|
|
87
|
+
elif database_type == "postgres":
|
|
88
|
+
if not database_url:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"database_url is required when database_type is 'postgres'. "
|
|
91
|
+
"Please provide a valid PostgreSQL connection URL."
|
|
92
|
+
)
|
|
93
|
+
config["database_config"] = {
|
|
94
|
+
"type": "postgres",
|
|
95
|
+
"url": database_url,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# LLM configuration (optional - uses OpenAI by default)
|
|
99
|
+
if llm_api_key or llm_base_url:
|
|
100
|
+
config["llm_profiles"] = {
|
|
101
|
+
"default": {
|
|
102
|
+
"api_key": llm_api_key,
|
|
103
|
+
"base_url": llm_base_url,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Embedding configuration (optional - uses OpenAI by default)
|
|
108
|
+
if embedding_api_key or embedding_base_url:
|
|
109
|
+
config["embedding_profiles"] = {
|
|
110
|
+
"default": {
|
|
111
|
+
"api_key": embedding_api_key,
|
|
112
|
+
"base_url": embedding_base_url,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
self._service: MemoryService = MemoryService(**config)
|
|
117
|
+
self._default_user_id = user_id
|
|
118
|
+
|
|
119
|
+
def capabilities(self) -> set[MemoryCapability]:
|
|
120
|
+
"""Return supported capabilities.
|
|
121
|
+
|
|
122
|
+
MemU supports semantic search and structured CRUD operations.
|
|
123
|
+
"""
|
|
124
|
+
return {
|
|
125
|
+
# Basic CRUD
|
|
126
|
+
MemoryCapability.CREATE,
|
|
127
|
+
MemoryCapability.READ,
|
|
128
|
+
MemoryCapability.UPDATE,
|
|
129
|
+
MemoryCapability.DELETE,
|
|
130
|
+
# Search
|
|
131
|
+
MemoryCapability.SEARCH_SEMANTIC,
|
|
132
|
+
MemoryCapability.SEARCH,
|
|
133
|
+
# Advanced
|
|
134
|
+
MemoryCapability.LIST,
|
|
135
|
+
# MCP-aligned
|
|
136
|
+
MemoryCapability.REMEMBER,
|
|
137
|
+
MemoryCapability.RECALL,
|
|
138
|
+
MemoryCapability.FORGET,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async def create(
|
|
142
|
+
self,
|
|
143
|
+
content: str,
|
|
144
|
+
memory_type: str = "fact",
|
|
145
|
+
importance: float = 0.5,
|
|
146
|
+
project_id: str | None = None,
|
|
147
|
+
user_id: str | None = None,
|
|
148
|
+
tags: list[str] | None = None,
|
|
149
|
+
source_type: str | None = None,
|
|
150
|
+
source_session_id: str | None = None,
|
|
151
|
+
media: list[MediaAttachment] | None = None,
|
|
152
|
+
metadata: dict[str, Any] | None = None,
|
|
153
|
+
) -> MemoryRecord:
|
|
154
|
+
"""Create a new memory in MemU.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
content: The memory content text
|
|
158
|
+
memory_type: Type of memory (mapped to MemU memory types)
|
|
159
|
+
importance: Importance score (stored in metadata)
|
|
160
|
+
project_id: Associated project ID
|
|
161
|
+
user_id: User ID (uses default if not provided)
|
|
162
|
+
tags: List of tags (used as categories)
|
|
163
|
+
source_type: Origin of memory
|
|
164
|
+
source_session_id: Session that created the memory
|
|
165
|
+
media: List of media attachments (stored in metadata)
|
|
166
|
+
metadata: Additional metadata
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
The created MemoryRecord
|
|
170
|
+
"""
|
|
171
|
+
effective_user_id = user_id or self._default_user_id or "default"
|
|
172
|
+
|
|
173
|
+
# Map memory_type to MemU MemoryType
|
|
174
|
+
memu_type = self._map_memory_type(memory_type)
|
|
175
|
+
|
|
176
|
+
# Use tags as categories, or create from memory_type
|
|
177
|
+
categories = tags if tags else [memory_type]
|
|
178
|
+
|
|
179
|
+
# Create user scope
|
|
180
|
+
user_scope = {"user_id": effective_user_id}
|
|
181
|
+
if project_id:
|
|
182
|
+
user_scope["project_id"] = project_id
|
|
183
|
+
|
|
184
|
+
# Create memory via MemU service (run in thread to avoid blocking event loop)
|
|
185
|
+
result = await asyncio.to_thread(
|
|
186
|
+
self._service.create_memory_item,
|
|
187
|
+
memory_type=memu_type,
|
|
188
|
+
memory_content=content,
|
|
189
|
+
memory_categories=categories,
|
|
190
|
+
user=user_scope,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Extract memory ID from result
|
|
194
|
+
memory_id = result.get("id") or result.get("memory_id") or str(uuid4())
|
|
195
|
+
|
|
196
|
+
return MemoryRecord(
|
|
197
|
+
id=memory_id,
|
|
198
|
+
content=content,
|
|
199
|
+
created_at=datetime.now(UTC),
|
|
200
|
+
memory_type=memory_type,
|
|
201
|
+
importance=importance,
|
|
202
|
+
project_id=project_id,
|
|
203
|
+
user_id=effective_user_id,
|
|
204
|
+
tags=tags or [],
|
|
205
|
+
source_type=source_type,
|
|
206
|
+
source_session_id=source_session_id,
|
|
207
|
+
metadata=metadata or {},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
async def get(self, memory_id: str) -> MemoryRecord | None:
|
|
211
|
+
"""Retrieve a memory by ID from MemU.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
memory_id: The memory ID to retrieve
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
The MemoryRecord if found, None otherwise
|
|
218
|
+
"""
|
|
219
|
+
# Try direct lookup first (O(1) if SDK supports it)
|
|
220
|
+
try:
|
|
221
|
+
# Run in thread to avoid blocking event loop
|
|
222
|
+
result = await asyncio.to_thread(self._service.get_memory_item, memory_id=memory_id)
|
|
223
|
+
if result:
|
|
224
|
+
return self._memu_to_record(result)
|
|
225
|
+
return None
|
|
226
|
+
except AttributeError:
|
|
227
|
+
# SDK may not have get_memory_item, fall back to list scan
|
|
228
|
+
pass
|
|
229
|
+
except Exception:
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
# Fallback: list and filter (O(n))
|
|
233
|
+
try:
|
|
234
|
+
# Run in thread to avoid blocking event loop
|
|
235
|
+
result = await asyncio.to_thread(self._service.list_memory_items)
|
|
236
|
+
items = result.get("items", result.get("memories", []))
|
|
237
|
+
|
|
238
|
+
for item in items:
|
|
239
|
+
if item.get("id") == memory_id or item.get("memory_id") == memory_id:
|
|
240
|
+
return self._memu_to_record(item)
|
|
241
|
+
|
|
242
|
+
return None
|
|
243
|
+
except Exception:
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
async def update(
|
|
247
|
+
self,
|
|
248
|
+
memory_id: str,
|
|
249
|
+
content: str | None = None,
|
|
250
|
+
importance: float | None = None,
|
|
251
|
+
tags: list[str] | None = None,
|
|
252
|
+
) -> MemoryRecord:
|
|
253
|
+
"""Update an existing memory in MemU.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
memory_id: The memory ID to update
|
|
257
|
+
content: New content (optional)
|
|
258
|
+
importance: New importance score (optional, stored in metadata)
|
|
259
|
+
tags: New tags/categories (optional)
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
The updated MemoryRecord
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
ValueError: If memory not found
|
|
266
|
+
"""
|
|
267
|
+
existing = await self.get(memory_id)
|
|
268
|
+
if not existing:
|
|
269
|
+
raise ValueError(f"Memory not found: {memory_id}")
|
|
270
|
+
|
|
271
|
+
# Build update kwargs
|
|
272
|
+
update_kwargs: dict[str, Any] = {"memory_id": memory_id}
|
|
273
|
+
if content is not None:
|
|
274
|
+
update_kwargs["memory_content"] = content
|
|
275
|
+
if tags is not None:
|
|
276
|
+
update_kwargs["memory_categories"] = tags
|
|
277
|
+
|
|
278
|
+
# Run in thread to avoid blocking event loop
|
|
279
|
+
result = await asyncio.to_thread(lambda: self._service.update_memory_item(**update_kwargs))
|
|
280
|
+
|
|
281
|
+
if result:
|
|
282
|
+
# Re-fetch to get updated record
|
|
283
|
+
updated = await self.get(memory_id)
|
|
284
|
+
if updated:
|
|
285
|
+
return updated
|
|
286
|
+
|
|
287
|
+
# Fallback: return synthetic updated record
|
|
288
|
+
return MemoryRecord(
|
|
289
|
+
id=memory_id,
|
|
290
|
+
content=content or existing.content,
|
|
291
|
+
created_at=existing.created_at,
|
|
292
|
+
memory_type=existing.memory_type,
|
|
293
|
+
importance=importance if importance is not None else existing.importance,
|
|
294
|
+
project_id=existing.project_id,
|
|
295
|
+
user_id=existing.user_id,
|
|
296
|
+
tags=tags if tags is not None else existing.tags,
|
|
297
|
+
source_type=existing.source_type,
|
|
298
|
+
source_session_id=existing.source_session_id,
|
|
299
|
+
metadata=existing.metadata,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
async def delete(self, memory_id: str) -> bool:
|
|
303
|
+
"""Delete a memory from MemU.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
memory_id: The memory ID to delete
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
True if deleted, False if not found
|
|
310
|
+
"""
|
|
311
|
+
try:
|
|
312
|
+
# Run in thread to avoid blocking event loop
|
|
313
|
+
await asyncio.to_thread(self._service.delete_memory_item, memory_id=memory_id)
|
|
314
|
+
return True
|
|
315
|
+
except Exception:
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
async def search(self, query: MemoryQuery) -> list[MemoryRecord]:
|
|
319
|
+
"""Search for memories using MemU's semantic search.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
query: Search parameters
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of matching MemoryRecords
|
|
326
|
+
"""
|
|
327
|
+
user_id = query.user_id or self._default_user_id or "default"
|
|
328
|
+
|
|
329
|
+
# Build query for MemU retrieve
|
|
330
|
+
queries = [{"role": "user", "content": query.text or ""}]
|
|
331
|
+
|
|
332
|
+
# Build where filter
|
|
333
|
+
where_filter: dict[str, Any] = {"user_id": user_id}
|
|
334
|
+
if query.project_id:
|
|
335
|
+
where_filter["project_id"] = query.project_id
|
|
336
|
+
|
|
337
|
+
# Run in thread to avoid blocking event loop
|
|
338
|
+
results = await asyncio.to_thread(
|
|
339
|
+
self._service.retrieve, queries=queries, where=where_filter
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
records = []
|
|
343
|
+
items = results.get("items", results.get("memories", []))
|
|
344
|
+
|
|
345
|
+
for memu_item in items: # Don't slice here - filter first, then limit
|
|
346
|
+
record = self._memu_to_record(memu_item)
|
|
347
|
+
|
|
348
|
+
# Apply additional filters not supported by MemU API
|
|
349
|
+
if query.min_importance is not None and record.importance < query.min_importance:
|
|
350
|
+
continue
|
|
351
|
+
if query.memory_type is not None and record.memory_type != query.memory_type:
|
|
352
|
+
continue
|
|
353
|
+
|
|
354
|
+
records.append(record)
|
|
355
|
+
|
|
356
|
+
# Apply limit after filtering
|
|
357
|
+
if query.limit and len(records) >= query.limit:
|
|
358
|
+
break
|
|
359
|
+
|
|
360
|
+
return records
|
|
361
|
+
|
|
362
|
+
async def list_memories(
|
|
363
|
+
self,
|
|
364
|
+
project_id: str | None = None,
|
|
365
|
+
user_id: str | None = None,
|
|
366
|
+
memory_type: str | None = None,
|
|
367
|
+
limit: int = 50,
|
|
368
|
+
offset: int = 0,
|
|
369
|
+
) -> list[MemoryRecord]:
|
|
370
|
+
"""List memories from MemU with optional filtering.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
project_id: Filter by project ID
|
|
374
|
+
user_id: Filter by user ID
|
|
375
|
+
memory_type: Filter by memory type
|
|
376
|
+
limit: Maximum number of results
|
|
377
|
+
offset: Number of results to skip
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
List of MemoryRecords
|
|
381
|
+
"""
|
|
382
|
+
effective_user_id = user_id or self._default_user_id or "default"
|
|
383
|
+
|
|
384
|
+
# Build where filter
|
|
385
|
+
where_filter: dict[str, Any] = {"user_id": effective_user_id}
|
|
386
|
+
if project_id:
|
|
387
|
+
where_filter["project_id"] = project_id
|
|
388
|
+
|
|
389
|
+
# Run in thread to avoid blocking event loop
|
|
390
|
+
results = await asyncio.to_thread(self._service.list_memory_items, where=where_filter)
|
|
391
|
+
|
|
392
|
+
items = results.get("items", results.get("memories", []))
|
|
393
|
+
|
|
394
|
+
# First filter all items by memory_type
|
|
395
|
+
filtered_items = []
|
|
396
|
+
for memu_item in items:
|
|
397
|
+
record = self._memu_to_record(memu_item)
|
|
398
|
+
if memory_type is not None and record.memory_type != memory_type:
|
|
399
|
+
continue
|
|
400
|
+
filtered_items.append(record)
|
|
401
|
+
|
|
402
|
+
# Then apply pagination
|
|
403
|
+
return filtered_items[offset : offset + limit]
|
|
404
|
+
|
|
405
|
+
def close(self) -> None:
|
|
406
|
+
"""Clean up resources.
|
|
407
|
+
|
|
408
|
+
Called when the backend is no longer needed.
|
|
409
|
+
"""
|
|
410
|
+
# MemU service doesn't require explicit cleanup
|
|
411
|
+
pass
|
|
412
|
+
|
|
413
|
+
def _map_memory_type(self, memory_type: str) -> str:
|
|
414
|
+
"""Map our memory types to MemU MemoryType strings.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
memory_type: Our memory type string
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
MemU MemoryType string (one of: profile, event, knowledge, behavior, skill)
|
|
421
|
+
"""
|
|
422
|
+
# MemU uses Literal type: Literal["profile", "event", "knowledge", "behavior", "skill"]
|
|
423
|
+
type_mapping = {
|
|
424
|
+
"fact": "knowledge",
|
|
425
|
+
"knowledge": "knowledge",
|
|
426
|
+
"preference": "profile",
|
|
427
|
+
"profile": "profile",
|
|
428
|
+
"event": "event",
|
|
429
|
+
"skill": "skill",
|
|
430
|
+
"behavior": "behavior",
|
|
431
|
+
}
|
|
432
|
+
return type_mapping.get(memory_type, "knowledge")
|
|
433
|
+
|
|
434
|
+
def _memu_to_record(
|
|
435
|
+
self,
|
|
436
|
+
memu_item: dict[str, Any],
|
|
437
|
+
) -> MemoryRecord:
|
|
438
|
+
"""Convert a MemU memory dict to MemoryRecord.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
memu_item: Memory dict from MemU API
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
MemoryRecord instance
|
|
445
|
+
"""
|
|
446
|
+
created_at_str = memu_item.get("created_at")
|
|
447
|
+
if created_at_str:
|
|
448
|
+
if isinstance(created_at_str, str):
|
|
449
|
+
created_at = datetime.fromisoformat(created_at_str.replace("Z", "+00:00"))
|
|
450
|
+
else:
|
|
451
|
+
created_at = created_at_str
|
|
452
|
+
else:
|
|
453
|
+
created_at = datetime.now(UTC)
|
|
454
|
+
|
|
455
|
+
# Extract memory type from MemU's memory_type field
|
|
456
|
+
memu_type = memu_item.get("memory_type", "knowledge")
|
|
457
|
+
if hasattr(memu_type, "value"):
|
|
458
|
+
memu_type = memu_type.value
|
|
459
|
+
|
|
460
|
+
# Map MemU types back to our types
|
|
461
|
+
type_mapping = {
|
|
462
|
+
"knowledge": "fact",
|
|
463
|
+
"profile": "preference",
|
|
464
|
+
"event": "event",
|
|
465
|
+
"skill": "skill",
|
|
466
|
+
"behavior": "behavior",
|
|
467
|
+
}
|
|
468
|
+
memory_type = type_mapping.get(str(memu_type), "fact")
|
|
469
|
+
|
|
470
|
+
# Get categories as tags
|
|
471
|
+
categories = memu_item.get("categories", memu_item.get("memory_categories", []))
|
|
472
|
+
|
|
473
|
+
return MemoryRecord(
|
|
474
|
+
id=memu_item.get("id") or memu_item.get("memory_id", "unknown"),
|
|
475
|
+
content=memu_item.get("content") or memu_item.get("memory_content", ""),
|
|
476
|
+
created_at=created_at,
|
|
477
|
+
memory_type=memory_type,
|
|
478
|
+
importance=memu_item.get("importance", 0.5),
|
|
479
|
+
project_id=memu_item.get("project_id"),
|
|
480
|
+
user_id=memu_item.get("user_id"),
|
|
481
|
+
tags=categories if isinstance(categories, list) else [],
|
|
482
|
+
source_type=memu_item.get("source_type"),
|
|
483
|
+
source_session_id=memu_item.get("source_session_id"),
|
|
484
|
+
metadata=memu_item.get("metadata", {}),
|
|
485
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Null memory backend for testing.
|
|
2
|
+
|
|
3
|
+
This backend provides a no-op implementation that satisfies the protocol
|
|
4
|
+
but doesn't persist any data. Useful for:
|
|
5
|
+
- Unit tests that don't need real storage
|
|
6
|
+
- Integration tests with isolated memory
|
|
7
|
+
- Dry-run scenarios
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from datetime import UTC, datetime
|
|
13
|
+
from typing import Any
|
|
14
|
+
from uuid import uuid4
|
|
15
|
+
|
|
16
|
+
from gobby.memory.protocol import (
|
|
17
|
+
MediaAttachment,
|
|
18
|
+
MemoryCapability,
|
|
19
|
+
MemoryQuery,
|
|
20
|
+
MemoryRecord,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NullBackend:
|
|
25
|
+
"""A no-op memory backend for testing.
|
|
26
|
+
|
|
27
|
+
Creates memories in-memory but doesn't persist them.
|
|
28
|
+
Searches always return empty results.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def capabilities(self) -> set[MemoryCapability]:
|
|
32
|
+
"""Return supported capabilities."""
|
|
33
|
+
return {
|
|
34
|
+
MemoryCapability.CREATE,
|
|
35
|
+
MemoryCapability.READ,
|
|
36
|
+
MemoryCapability.UPDATE,
|
|
37
|
+
MemoryCapability.DELETE,
|
|
38
|
+
MemoryCapability.SEARCH,
|
|
39
|
+
MemoryCapability.LIST,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async def create(
|
|
43
|
+
self,
|
|
44
|
+
content: str,
|
|
45
|
+
memory_type: str = "fact",
|
|
46
|
+
importance: float = 0.5,
|
|
47
|
+
project_id: str | None = None,
|
|
48
|
+
user_id: str | None = None,
|
|
49
|
+
tags: list[str] | None = None,
|
|
50
|
+
source_type: str | None = None,
|
|
51
|
+
source_session_id: str | None = None,
|
|
52
|
+
media: list[MediaAttachment] | None = None,
|
|
53
|
+
metadata: dict[str, Any] | None = None,
|
|
54
|
+
) -> MemoryRecord:
|
|
55
|
+
"""Create a memory record (in-memory only, not persisted)."""
|
|
56
|
+
now = datetime.now(UTC)
|
|
57
|
+
return MemoryRecord(
|
|
58
|
+
id=f"null-{uuid4().hex[:8]}",
|
|
59
|
+
content=content,
|
|
60
|
+
created_at=now,
|
|
61
|
+
memory_type=memory_type,
|
|
62
|
+
importance=importance,
|
|
63
|
+
project_id=project_id,
|
|
64
|
+
user_id=user_id,
|
|
65
|
+
tags=tags or [],
|
|
66
|
+
source_type=source_type,
|
|
67
|
+
source_session_id=source_session_id,
|
|
68
|
+
media=media or [],
|
|
69
|
+
metadata=metadata or {},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def get(self, memory_id: str) -> MemoryRecord | None:
|
|
73
|
+
"""Get a memory by ID (always returns None - no persistence)."""
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
async def update(
|
|
77
|
+
self,
|
|
78
|
+
memory_id: str,
|
|
79
|
+
content: str | None = None,
|
|
80
|
+
importance: float | None = None,
|
|
81
|
+
tags: list[str] | None = None,
|
|
82
|
+
) -> MemoryRecord:
|
|
83
|
+
"""Update a memory (creates a new record since nothing is persisted)."""
|
|
84
|
+
now = datetime.now(UTC)
|
|
85
|
+
return MemoryRecord(
|
|
86
|
+
id=memory_id,
|
|
87
|
+
content=content or "",
|
|
88
|
+
created_at=now,
|
|
89
|
+
updated_at=now,
|
|
90
|
+
importance=importance if importance is not None else 0.5,
|
|
91
|
+
tags=tags or [],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
async def delete(self, memory_id: str) -> bool:
|
|
95
|
+
"""Delete a memory (always returns False - nothing to delete)."""
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
async def search(self, query: MemoryQuery) -> list[MemoryRecord]:
|
|
99
|
+
"""Search for memories (always returns empty list)."""
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
async def list_memories(
|
|
103
|
+
self,
|
|
104
|
+
project_id: str | None = None,
|
|
105
|
+
user_id: str | None = None,
|
|
106
|
+
memory_type: str | None = None,
|
|
107
|
+
limit: int = 50,
|
|
108
|
+
offset: int = 0,
|
|
109
|
+
) -> list[MemoryRecord]:
|
|
110
|
+
"""List memories (always returns empty list)."""
|
|
111
|
+
return []
|