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,116 @@
|
|
|
1
|
+
"""Memory backend factory.
|
|
2
|
+
|
|
3
|
+
This module provides a factory function for creating memory backends.
|
|
4
|
+
Users should use get_backend() to obtain a backend instance rather than
|
|
5
|
+
importing backend classes directly.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
from gobby.memory.backends import get_backend
|
|
9
|
+
|
|
10
|
+
# Get SQLite backend with database connection
|
|
11
|
+
backend = get_backend("sqlite", database=db)
|
|
12
|
+
|
|
13
|
+
# Get null backend for testing
|
|
14
|
+
test_backend = get_backend("null")
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
from gobby.memory.protocol import MemoryBackendProtocol
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from gobby.storage.database import DatabaseProtocol
|
|
25
|
+
|
|
26
|
+
__all__ = ["get_backend"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_backend(backend_type: str, **kwargs: Any) -> MemoryBackendProtocol:
|
|
30
|
+
"""Create a memory backend instance.
|
|
31
|
+
|
|
32
|
+
Factory function for creating memory backends. Use this instead of
|
|
33
|
+
importing backend classes directly.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
backend_type: Type of backend to create:
|
|
37
|
+
- "sqlite": SQLite-based persistent storage (requires database kwarg)
|
|
38
|
+
- "null": No-op backend for testing
|
|
39
|
+
- "mem0": Mem0 cloud-based semantic memory (requires api_key kwarg)
|
|
40
|
+
- "memu": MemU structured memory (optional: database_type, llm_api_key)
|
|
41
|
+
- "openmemory": Self-hosted OpenMemory REST API (requires base_url kwarg)
|
|
42
|
+
|
|
43
|
+
**kwargs: Backend-specific configuration:
|
|
44
|
+
- database: DatabaseProtocol instance (required for "sqlite")
|
|
45
|
+
- api_key: API key (required for "mem0")
|
|
46
|
+
- database_type: "inmemory", "sqlite", or "postgres" (for "memu")
|
|
47
|
+
- database_url: Connection URL (for "memu" sqlite/postgres)
|
|
48
|
+
- llm_api_key: LLM API key for embeddings (optional for "memu")
|
|
49
|
+
- base_url: Server URL (required for "openmemory")
|
|
50
|
+
- user_id: Default user ID (optional for "mem0", "memu", "openmemory")
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
A MemoryBackendProtocol instance
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If backend_type is unknown or required kwargs are missing
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
# SQLite backend
|
|
60
|
+
backend = get_backend("sqlite", database=my_db)
|
|
61
|
+
|
|
62
|
+
# Null backend for testing
|
|
63
|
+
test_backend = get_backend("null")
|
|
64
|
+
"""
|
|
65
|
+
if backend_type == "sqlite":
|
|
66
|
+
from gobby.memory.backends.sqlite import SQLiteBackend
|
|
67
|
+
|
|
68
|
+
database: DatabaseProtocol | None = kwargs.get("database")
|
|
69
|
+
if database is None:
|
|
70
|
+
raise ValueError("SQLite backend requires 'database' parameter")
|
|
71
|
+
return SQLiteBackend(database=database)
|
|
72
|
+
|
|
73
|
+
elif backend_type == "null":
|
|
74
|
+
from gobby.memory.backends.null import NullBackend
|
|
75
|
+
|
|
76
|
+
return NullBackend()
|
|
77
|
+
|
|
78
|
+
elif backend_type == "mem0":
|
|
79
|
+
from gobby.memory.backends.mem0 import Mem0Backend
|
|
80
|
+
|
|
81
|
+
api_key: str | None = kwargs.get("api_key")
|
|
82
|
+
if api_key is None:
|
|
83
|
+
raise ValueError("Mem0 backend requires 'api_key' parameter")
|
|
84
|
+
return Mem0Backend(
|
|
85
|
+
api_key=api_key,
|
|
86
|
+
user_id=kwargs.get("user_id"),
|
|
87
|
+
org_id=kwargs.get("org_id"),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
elif backend_type == "memu":
|
|
91
|
+
from gobby.memory.backends.memu import MemUBackend
|
|
92
|
+
|
|
93
|
+
return MemUBackend(
|
|
94
|
+
database_type=kwargs.get("database_type", "inmemory"),
|
|
95
|
+
database_url=kwargs.get("database_url"),
|
|
96
|
+
llm_api_key=kwargs.get("llm_api_key") or kwargs.get("api_key"),
|
|
97
|
+
llm_base_url=kwargs.get("llm_base_url"),
|
|
98
|
+
user_id=kwargs.get("user_id"),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
elif backend_type == "openmemory":
|
|
102
|
+
from gobby.memory.backends.openmemory import OpenMemoryBackend
|
|
103
|
+
|
|
104
|
+
base_url: str | None = kwargs.get("base_url")
|
|
105
|
+
if base_url is None:
|
|
106
|
+
raise ValueError("OpenMemory backend requires 'base_url' parameter")
|
|
107
|
+
return OpenMemoryBackend(
|
|
108
|
+
base_url=base_url,
|
|
109
|
+
api_key=kwargs.get("api_key"),
|
|
110
|
+
user_id=kwargs.get("user_id"),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
else:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Unknown backend type: '{backend_type}'. Supported types: 'sqlite', 'null', 'mem0', 'memu', 'openmemory'"
|
|
116
|
+
)
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Mem0 memory backend integration.
|
|
2
|
+
|
|
3
|
+
This backend wraps the Mem0 AI memory service to provide a
|
|
4
|
+
MemoryBackendProtocol-compliant interface. Mem0 offers semantic
|
|
5
|
+
search and automatic memory organization.
|
|
6
|
+
|
|
7
|
+
Requires: pip install mem0ai
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
from gobby.memory.backends import get_backend
|
|
11
|
+
|
|
12
|
+
backend = get_backend("mem0", api_key="your-mem0-api-key")
|
|
13
|
+
record = await backend.create("User prefers dark mode")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import hashlib
|
|
20
|
+
from datetime import UTC, datetime
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
from gobby.memory.protocol import (
|
|
24
|
+
MediaAttachment,
|
|
25
|
+
MemoryCapability,
|
|
26
|
+
MemoryQuery,
|
|
27
|
+
MemoryRecord,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from mem0 import MemoryClient
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Mem0Backend:
|
|
35
|
+
"""Mem0-based memory backend.
|
|
36
|
+
|
|
37
|
+
Wraps the Mem0 MemoryClient to provide MemoryBackendProtocol interface.
|
|
38
|
+
Supports semantic search and automatic memory organization.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
api_key: Mem0 API key for authentication
|
|
42
|
+
user_id: Default user ID for memories (optional)
|
|
43
|
+
org_id: Organization ID for multi-tenant use (optional)
|
|
44
|
+
**kwargs: Additional configuration passed to MemoryClient
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
api_key: str,
|
|
50
|
+
user_id: str | None = None,
|
|
51
|
+
org_id: str | None = None,
|
|
52
|
+
**kwargs: Any,
|
|
53
|
+
):
|
|
54
|
+
"""Initialize the Mem0 backend.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
api_key: Mem0 API key
|
|
58
|
+
user_id: Default user ID for operations
|
|
59
|
+
org_id: Organization ID
|
|
60
|
+
**kwargs: Additional MemoryClient configuration
|
|
61
|
+
"""
|
|
62
|
+
# Lazy import to avoid requiring mem0ai when not used
|
|
63
|
+
from mem0 import MemoryClient
|
|
64
|
+
|
|
65
|
+
self._client: MemoryClient = MemoryClient(api_key=api_key, **kwargs)
|
|
66
|
+
self._default_user_id = user_id
|
|
67
|
+
self._org_id = org_id
|
|
68
|
+
|
|
69
|
+
def capabilities(self) -> set[MemoryCapability]:
|
|
70
|
+
"""Return supported capabilities.
|
|
71
|
+
|
|
72
|
+
Mem0 supports semantic search and basic CRUD operations.
|
|
73
|
+
"""
|
|
74
|
+
return {
|
|
75
|
+
# Basic CRUD
|
|
76
|
+
MemoryCapability.CREATE,
|
|
77
|
+
MemoryCapability.READ,
|
|
78
|
+
MemoryCapability.UPDATE,
|
|
79
|
+
MemoryCapability.DELETE,
|
|
80
|
+
# Search
|
|
81
|
+
MemoryCapability.SEARCH_SEMANTIC,
|
|
82
|
+
MemoryCapability.SEARCH,
|
|
83
|
+
# Advanced
|
|
84
|
+
MemoryCapability.LIST,
|
|
85
|
+
# MCP-aligned
|
|
86
|
+
MemoryCapability.REMEMBER,
|
|
87
|
+
MemoryCapability.RECALL,
|
|
88
|
+
MemoryCapability.FORGET,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async def create(
|
|
92
|
+
self,
|
|
93
|
+
content: str,
|
|
94
|
+
memory_type: str = "fact",
|
|
95
|
+
importance: float = 0.5,
|
|
96
|
+
project_id: str | None = None,
|
|
97
|
+
user_id: str | None = None,
|
|
98
|
+
tags: list[str] | None = None,
|
|
99
|
+
source_type: str | None = None,
|
|
100
|
+
source_session_id: str | None = None,
|
|
101
|
+
media: list[MediaAttachment] | None = None,
|
|
102
|
+
metadata: dict[str, Any] | None = None,
|
|
103
|
+
) -> MemoryRecord:
|
|
104
|
+
"""Create a new memory in Mem0.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
content: The memory content text
|
|
108
|
+
memory_type: Type of memory (stored in metadata)
|
|
109
|
+
importance: Importance score (stored in metadata)
|
|
110
|
+
project_id: Associated project ID
|
|
111
|
+
user_id: User ID (uses default if not provided)
|
|
112
|
+
tags: List of tags (stored in metadata)
|
|
113
|
+
source_type: Origin of memory
|
|
114
|
+
source_session_id: Session that created the memory
|
|
115
|
+
media: List of media attachments (stored in metadata)
|
|
116
|
+
metadata: Additional metadata
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The created MemoryRecord
|
|
120
|
+
"""
|
|
121
|
+
effective_user_id = user_id or self._default_user_id or "default"
|
|
122
|
+
|
|
123
|
+
# Build metadata for Mem0
|
|
124
|
+
mem0_metadata: dict[str, Any] = {
|
|
125
|
+
"memory_type": memory_type,
|
|
126
|
+
"importance": importance,
|
|
127
|
+
"source_type": source_type,
|
|
128
|
+
"source_session_id": source_session_id,
|
|
129
|
+
**(metadata or {}),
|
|
130
|
+
}
|
|
131
|
+
if project_id:
|
|
132
|
+
mem0_metadata["project_id"] = project_id
|
|
133
|
+
if tags:
|
|
134
|
+
mem0_metadata["tags"] = tags
|
|
135
|
+
if media:
|
|
136
|
+
# Serialize media attachments
|
|
137
|
+
mem0_metadata["media"] = [
|
|
138
|
+
{
|
|
139
|
+
"media_type": m.media_type,
|
|
140
|
+
"content_path": m.content_path,
|
|
141
|
+
"mime_type": m.mime_type,
|
|
142
|
+
"description": m.description,
|
|
143
|
+
}
|
|
144
|
+
for m in media
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# Mem0 add() expects messages in OpenAI chat format
|
|
148
|
+
messages = [{"role": "user", "content": content}]
|
|
149
|
+
|
|
150
|
+
# Add memory via Mem0 API (run in thread to avoid blocking event loop)
|
|
151
|
+
result = await asyncio.to_thread(
|
|
152
|
+
self._client.add,
|
|
153
|
+
messages=messages,
|
|
154
|
+
user_id=effective_user_id,
|
|
155
|
+
metadata=mem0_metadata,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Extract memory ID from result
|
|
159
|
+
# Mem0 returns {"results": [{"id": "...", "memory": "...", ...}]}
|
|
160
|
+
if result and "results" in result and len(result["results"]) > 0:
|
|
161
|
+
mem0_memory = result["results"][0]
|
|
162
|
+
return self._mem0_to_record(mem0_memory)
|
|
163
|
+
|
|
164
|
+
# Fallback: create a synthetic record
|
|
165
|
+
return MemoryRecord(
|
|
166
|
+
id=result.get("id", "unknown"),
|
|
167
|
+
content=content,
|
|
168
|
+
created_at=datetime.now(UTC),
|
|
169
|
+
memory_type=memory_type,
|
|
170
|
+
importance=importance,
|
|
171
|
+
project_id=project_id,
|
|
172
|
+
user_id=effective_user_id,
|
|
173
|
+
tags=tags or [],
|
|
174
|
+
source_type=source_type,
|
|
175
|
+
source_session_id=source_session_id,
|
|
176
|
+
metadata=mem0_metadata,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
async def get(self, memory_id: str) -> MemoryRecord | None:
|
|
180
|
+
"""Retrieve a memory by ID from Mem0.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
memory_id: The memory ID to retrieve
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
The MemoryRecord if found, None otherwise
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
# Run in thread to avoid blocking event loop
|
|
190
|
+
result = await asyncio.to_thread(self._client.get, memory_id)
|
|
191
|
+
if result:
|
|
192
|
+
return self._mem0_to_record(result)
|
|
193
|
+
return None
|
|
194
|
+
except Exception:
|
|
195
|
+
# Memory not found or API error
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
async def update(
|
|
199
|
+
self,
|
|
200
|
+
memory_id: str,
|
|
201
|
+
content: str | None = None,
|
|
202
|
+
importance: float | None = None,
|
|
203
|
+
tags: list[str] | None = None,
|
|
204
|
+
) -> MemoryRecord:
|
|
205
|
+
"""Update an existing memory in Mem0.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
memory_id: The memory ID to update
|
|
209
|
+
content: New content (optional)
|
|
210
|
+
importance: New importance score (optional)
|
|
211
|
+
tags: New tags (optional)
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
The updated MemoryRecord
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
ValueError: If memory not found
|
|
218
|
+
"""
|
|
219
|
+
# Get existing memory first
|
|
220
|
+
existing = await self.get(memory_id)
|
|
221
|
+
if not existing:
|
|
222
|
+
raise ValueError(f"Memory not found: {memory_id}")
|
|
223
|
+
|
|
224
|
+
# Note: Mem0's update API only supports content updates.
|
|
225
|
+
# Importance and tags changes are reflected in the returned record
|
|
226
|
+
# but not persisted to Mem0 (they're stored in metadata which Mem0
|
|
227
|
+
# doesn't allow updating via the update endpoint).
|
|
228
|
+
|
|
229
|
+
# Update via Mem0 API (run in thread to avoid blocking event loop)
|
|
230
|
+
result = await asyncio.to_thread(
|
|
231
|
+
self._client.update, memory_id, data=content or existing.content
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Return updated record
|
|
235
|
+
if result:
|
|
236
|
+
return self._mem0_to_record(result)
|
|
237
|
+
|
|
238
|
+
# Fallback: return synthetic updated record
|
|
239
|
+
return MemoryRecord(
|
|
240
|
+
id=memory_id,
|
|
241
|
+
content=content or existing.content,
|
|
242
|
+
created_at=existing.created_at,
|
|
243
|
+
memory_type=existing.memory_type,
|
|
244
|
+
importance=importance if importance is not None else existing.importance,
|
|
245
|
+
project_id=existing.project_id,
|
|
246
|
+
user_id=existing.user_id,
|
|
247
|
+
tags=tags if tags is not None else existing.tags,
|
|
248
|
+
source_type=existing.source_type,
|
|
249
|
+
source_session_id=existing.source_session_id,
|
|
250
|
+
metadata=existing.metadata,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
async def delete(self, memory_id: str) -> bool:
|
|
254
|
+
"""Delete a memory from Mem0.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
memory_id: The memory ID to delete
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if deleted, False if not found
|
|
261
|
+
"""
|
|
262
|
+
try:
|
|
263
|
+
# Run in thread to avoid blocking event loop
|
|
264
|
+
await asyncio.to_thread(self._client.delete, memory_id)
|
|
265
|
+
return True
|
|
266
|
+
except Exception:
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
async def search(self, query: MemoryQuery) -> list[MemoryRecord]:
|
|
270
|
+
"""Search for memories using Mem0's semantic search.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
query: Search parameters
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
List of matching MemoryRecords
|
|
277
|
+
"""
|
|
278
|
+
user_id = query.user_id or self._default_user_id or "default"
|
|
279
|
+
|
|
280
|
+
# Build search kwargs
|
|
281
|
+
search_kwargs: dict[str, Any] = {
|
|
282
|
+
"query": query.text or "",
|
|
283
|
+
"user_id": user_id,
|
|
284
|
+
}
|
|
285
|
+
if query.limit:
|
|
286
|
+
search_kwargs["limit"] = query.limit
|
|
287
|
+
|
|
288
|
+
# Execute search via Mem0 API (run in thread to avoid blocking event loop)
|
|
289
|
+
results = await asyncio.to_thread(lambda: self._client.search(**search_kwargs))
|
|
290
|
+
|
|
291
|
+
# Convert results to MemoryRecords
|
|
292
|
+
records = []
|
|
293
|
+
for mem0_memory in results.get("results", []):
|
|
294
|
+
record = self._mem0_to_record(mem0_memory)
|
|
295
|
+
|
|
296
|
+
# Apply additional filters not supported by Mem0 API
|
|
297
|
+
if query.min_importance is not None and record.importance < query.min_importance:
|
|
298
|
+
continue
|
|
299
|
+
if query.memory_type is not None and record.memory_type != query.memory_type:
|
|
300
|
+
continue
|
|
301
|
+
if query.project_id is not None and record.project_id != query.project_id:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
records.append(record)
|
|
305
|
+
|
|
306
|
+
return records
|
|
307
|
+
|
|
308
|
+
async def list_memories(
|
|
309
|
+
self,
|
|
310
|
+
project_id: str | None = None,
|
|
311
|
+
user_id: str | None = None,
|
|
312
|
+
memory_type: str | None = None,
|
|
313
|
+
limit: int = 50,
|
|
314
|
+
offset: int = 0,
|
|
315
|
+
) -> list[MemoryRecord]:
|
|
316
|
+
"""List memories from Mem0 with optional filtering.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
project_id: Filter by project ID (stored in metadata)
|
|
320
|
+
user_id: Filter by user ID
|
|
321
|
+
memory_type: Filter by memory type
|
|
322
|
+
limit: Maximum number of results
|
|
323
|
+
offset: Number of results to skip
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
List of MemoryRecords
|
|
327
|
+
"""
|
|
328
|
+
effective_user_id = user_id or self._default_user_id or "default"
|
|
329
|
+
|
|
330
|
+
# Get all memories for user via Mem0 API (run in thread to avoid blocking event loop)
|
|
331
|
+
results = await asyncio.to_thread(self._client.get_all, user_id=effective_user_id)
|
|
332
|
+
|
|
333
|
+
# Convert and filter results
|
|
334
|
+
records = []
|
|
335
|
+
skipped = 0
|
|
336
|
+
for mem0_memory in results.get("results", []):
|
|
337
|
+
record = self._mem0_to_record(mem0_memory)
|
|
338
|
+
|
|
339
|
+
# Apply filters
|
|
340
|
+
if project_id is not None and record.project_id != project_id:
|
|
341
|
+
continue
|
|
342
|
+
if memory_type is not None and record.memory_type != memory_type:
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
# Handle offset
|
|
346
|
+
if skipped < offset:
|
|
347
|
+
skipped += 1
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
records.append(record)
|
|
351
|
+
|
|
352
|
+
# Handle limit
|
|
353
|
+
if len(records) >= limit:
|
|
354
|
+
break
|
|
355
|
+
|
|
356
|
+
return records
|
|
357
|
+
|
|
358
|
+
def close(self) -> None:
|
|
359
|
+
"""Clean up resources.
|
|
360
|
+
|
|
361
|
+
Called when the backend is no longer needed.
|
|
362
|
+
"""
|
|
363
|
+
# Mem0 client doesn't require explicit cleanup
|
|
364
|
+
pass
|
|
365
|
+
|
|
366
|
+
def _mem0_to_record(
|
|
367
|
+
self,
|
|
368
|
+
mem0_memory: dict[str, Any],
|
|
369
|
+
) -> MemoryRecord:
|
|
370
|
+
"""Convert a Mem0 memory dict to MemoryRecord.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
mem0_memory: Memory dict from Mem0 API
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
MemoryRecord instance
|
|
377
|
+
"""
|
|
378
|
+
# Parse created_at if present
|
|
379
|
+
created_at_str = mem0_memory.get("created_at")
|
|
380
|
+
if created_at_str:
|
|
381
|
+
created_at = datetime.fromisoformat(created_at_str.replace("Z", "+00:00"))
|
|
382
|
+
else:
|
|
383
|
+
created_at = datetime.now(UTC)
|
|
384
|
+
|
|
385
|
+
# Extract metadata fields
|
|
386
|
+
metadata = mem0_memory.get("metadata", {})
|
|
387
|
+
|
|
388
|
+
# Generate deterministic ID from memory content if not provided
|
|
389
|
+
memory_id = mem0_memory.get("id")
|
|
390
|
+
if not memory_id:
|
|
391
|
+
# Create stable hash from memory content for deterministic ID
|
|
392
|
+
content_for_hash = mem0_memory.get("memory", "") + str(mem0_memory.get("user_id", ""))
|
|
393
|
+
hash_digest = hashlib.sha256(content_for_hash.encode()).hexdigest()[:8]
|
|
394
|
+
memory_id = f"mem0-{hash_digest}"
|
|
395
|
+
|
|
396
|
+
return MemoryRecord(
|
|
397
|
+
id=memory_id,
|
|
398
|
+
content=mem0_memory.get("memory", ""),
|
|
399
|
+
created_at=created_at,
|
|
400
|
+
memory_type=metadata.get("memory_type", "fact"),
|
|
401
|
+
importance=metadata.get("importance", 0.5),
|
|
402
|
+
project_id=metadata.get("project_id"),
|
|
403
|
+
user_id=mem0_memory.get("user_id"),
|
|
404
|
+
tags=metadata.get("tags", []),
|
|
405
|
+
source_type=metadata.get("source_type"),
|
|
406
|
+
source_session_id=metadata.get("source_session_id"),
|
|
407
|
+
metadata=metadata,
|
|
408
|
+
)
|