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.
Files changed (383) hide show
  1. gobby/__init__.py +3 -0
  2. gobby/adapters/__init__.py +30 -0
  3. gobby/adapters/base.py +93 -0
  4. gobby/adapters/claude_code.py +276 -0
  5. gobby/adapters/codex.py +1292 -0
  6. gobby/adapters/gemini.py +343 -0
  7. gobby/agents/__init__.py +37 -0
  8. gobby/agents/codex_session.py +120 -0
  9. gobby/agents/constants.py +112 -0
  10. gobby/agents/context.py +362 -0
  11. gobby/agents/definitions.py +133 -0
  12. gobby/agents/gemini_session.py +111 -0
  13. gobby/agents/registry.py +618 -0
  14. gobby/agents/runner.py +968 -0
  15. gobby/agents/session.py +259 -0
  16. gobby/agents/spawn.py +916 -0
  17. gobby/agents/spawners/__init__.py +77 -0
  18. gobby/agents/spawners/base.py +142 -0
  19. gobby/agents/spawners/cross_platform.py +266 -0
  20. gobby/agents/spawners/embedded.py +225 -0
  21. gobby/agents/spawners/headless.py +226 -0
  22. gobby/agents/spawners/linux.py +125 -0
  23. gobby/agents/spawners/macos.py +277 -0
  24. gobby/agents/spawners/windows.py +308 -0
  25. gobby/agents/tty_config.py +319 -0
  26. gobby/autonomous/__init__.py +32 -0
  27. gobby/autonomous/progress_tracker.py +447 -0
  28. gobby/autonomous/stop_registry.py +269 -0
  29. gobby/autonomous/stuck_detector.py +383 -0
  30. gobby/cli/__init__.py +67 -0
  31. gobby/cli/__main__.py +8 -0
  32. gobby/cli/agents.py +529 -0
  33. gobby/cli/artifacts.py +266 -0
  34. gobby/cli/daemon.py +329 -0
  35. gobby/cli/extensions.py +526 -0
  36. gobby/cli/github.py +263 -0
  37. gobby/cli/init.py +53 -0
  38. gobby/cli/install.py +614 -0
  39. gobby/cli/installers/__init__.py +37 -0
  40. gobby/cli/installers/antigravity.py +65 -0
  41. gobby/cli/installers/claude.py +363 -0
  42. gobby/cli/installers/codex.py +192 -0
  43. gobby/cli/installers/gemini.py +294 -0
  44. gobby/cli/installers/git_hooks.py +377 -0
  45. gobby/cli/installers/shared.py +737 -0
  46. gobby/cli/linear.py +250 -0
  47. gobby/cli/mcp.py +30 -0
  48. gobby/cli/mcp_proxy.py +698 -0
  49. gobby/cli/memory.py +304 -0
  50. gobby/cli/merge.py +384 -0
  51. gobby/cli/projects.py +79 -0
  52. gobby/cli/sessions.py +622 -0
  53. gobby/cli/tasks/__init__.py +30 -0
  54. gobby/cli/tasks/_utils.py +658 -0
  55. gobby/cli/tasks/ai.py +1025 -0
  56. gobby/cli/tasks/commits.py +169 -0
  57. gobby/cli/tasks/crud.py +685 -0
  58. gobby/cli/tasks/deps.py +135 -0
  59. gobby/cli/tasks/labels.py +63 -0
  60. gobby/cli/tasks/main.py +273 -0
  61. gobby/cli/tasks/search.py +178 -0
  62. gobby/cli/tui.py +34 -0
  63. gobby/cli/utils.py +513 -0
  64. gobby/cli/workflows.py +927 -0
  65. gobby/cli/worktrees.py +481 -0
  66. gobby/config/__init__.py +129 -0
  67. gobby/config/app.py +551 -0
  68. gobby/config/extensions.py +167 -0
  69. gobby/config/features.py +472 -0
  70. gobby/config/llm_providers.py +98 -0
  71. gobby/config/logging.py +66 -0
  72. gobby/config/mcp.py +346 -0
  73. gobby/config/persistence.py +247 -0
  74. gobby/config/servers.py +141 -0
  75. gobby/config/sessions.py +250 -0
  76. gobby/config/tasks.py +784 -0
  77. gobby/hooks/__init__.py +104 -0
  78. gobby/hooks/artifact_capture.py +213 -0
  79. gobby/hooks/broadcaster.py +243 -0
  80. gobby/hooks/event_handlers.py +723 -0
  81. gobby/hooks/events.py +218 -0
  82. gobby/hooks/git.py +169 -0
  83. gobby/hooks/health_monitor.py +171 -0
  84. gobby/hooks/hook_manager.py +856 -0
  85. gobby/hooks/hook_types.py +575 -0
  86. gobby/hooks/plugins.py +813 -0
  87. gobby/hooks/session_coordinator.py +396 -0
  88. gobby/hooks/verification_runner.py +268 -0
  89. gobby/hooks/webhooks.py +339 -0
  90. gobby/install/claude/commands/gobby/bug.md +51 -0
  91. gobby/install/claude/commands/gobby/chore.md +51 -0
  92. gobby/install/claude/commands/gobby/epic.md +52 -0
  93. gobby/install/claude/commands/gobby/eval.md +235 -0
  94. gobby/install/claude/commands/gobby/feat.md +49 -0
  95. gobby/install/claude/commands/gobby/nit.md +52 -0
  96. gobby/install/claude/commands/gobby/ref.md +52 -0
  97. gobby/install/claude/hooks/HOOK_SCHEMAS.md +632 -0
  98. gobby/install/claude/hooks/hook_dispatcher.py +364 -0
  99. gobby/install/claude/hooks/validate_settings.py +102 -0
  100. gobby/install/claude/hooks-template.json +118 -0
  101. gobby/install/codex/hooks/hook_dispatcher.py +153 -0
  102. gobby/install/codex/prompts/forget.md +7 -0
  103. gobby/install/codex/prompts/memories.md +7 -0
  104. gobby/install/codex/prompts/recall.md +7 -0
  105. gobby/install/codex/prompts/remember.md +13 -0
  106. gobby/install/gemini/hooks/hook_dispatcher.py +268 -0
  107. gobby/install/gemini/hooks-template.json +138 -0
  108. gobby/install/shared/plugins/code_guardian.py +456 -0
  109. gobby/install/shared/plugins/example_notify.py +331 -0
  110. gobby/integrations/__init__.py +10 -0
  111. gobby/integrations/github.py +145 -0
  112. gobby/integrations/linear.py +145 -0
  113. gobby/llm/__init__.py +40 -0
  114. gobby/llm/base.py +120 -0
  115. gobby/llm/claude.py +578 -0
  116. gobby/llm/claude_executor.py +503 -0
  117. gobby/llm/codex.py +322 -0
  118. gobby/llm/codex_executor.py +513 -0
  119. gobby/llm/executor.py +316 -0
  120. gobby/llm/factory.py +34 -0
  121. gobby/llm/gemini.py +258 -0
  122. gobby/llm/gemini_executor.py +339 -0
  123. gobby/llm/litellm.py +287 -0
  124. gobby/llm/litellm_executor.py +303 -0
  125. gobby/llm/resolver.py +499 -0
  126. gobby/llm/service.py +236 -0
  127. gobby/mcp_proxy/__init__.py +29 -0
  128. gobby/mcp_proxy/actions.py +175 -0
  129. gobby/mcp_proxy/daemon_control.py +198 -0
  130. gobby/mcp_proxy/importer.py +436 -0
  131. gobby/mcp_proxy/lazy.py +325 -0
  132. gobby/mcp_proxy/manager.py +798 -0
  133. gobby/mcp_proxy/metrics.py +609 -0
  134. gobby/mcp_proxy/models.py +139 -0
  135. gobby/mcp_proxy/registries.py +215 -0
  136. gobby/mcp_proxy/schema_hash.py +381 -0
  137. gobby/mcp_proxy/semantic_search.py +706 -0
  138. gobby/mcp_proxy/server.py +549 -0
  139. gobby/mcp_proxy/services/__init__.py +0 -0
  140. gobby/mcp_proxy/services/fallback.py +306 -0
  141. gobby/mcp_proxy/services/recommendation.py +224 -0
  142. gobby/mcp_proxy/services/server_mgmt.py +214 -0
  143. gobby/mcp_proxy/services/system.py +72 -0
  144. gobby/mcp_proxy/services/tool_filter.py +231 -0
  145. gobby/mcp_proxy/services/tool_proxy.py +309 -0
  146. gobby/mcp_proxy/stdio.py +565 -0
  147. gobby/mcp_proxy/tools/__init__.py +27 -0
  148. gobby/mcp_proxy/tools/agents.py +1103 -0
  149. gobby/mcp_proxy/tools/artifacts.py +207 -0
  150. gobby/mcp_proxy/tools/hub.py +335 -0
  151. gobby/mcp_proxy/tools/internal.py +337 -0
  152. gobby/mcp_proxy/tools/memory.py +543 -0
  153. gobby/mcp_proxy/tools/merge.py +422 -0
  154. gobby/mcp_proxy/tools/metrics.py +283 -0
  155. gobby/mcp_proxy/tools/orchestration/__init__.py +23 -0
  156. gobby/mcp_proxy/tools/orchestration/cleanup.py +619 -0
  157. gobby/mcp_proxy/tools/orchestration/monitor.py +380 -0
  158. gobby/mcp_proxy/tools/orchestration/orchestrate.py +746 -0
  159. gobby/mcp_proxy/tools/orchestration/review.py +736 -0
  160. gobby/mcp_proxy/tools/orchestration/utils.py +16 -0
  161. gobby/mcp_proxy/tools/session_messages.py +1056 -0
  162. gobby/mcp_proxy/tools/task_dependencies.py +219 -0
  163. gobby/mcp_proxy/tools/task_expansion.py +591 -0
  164. gobby/mcp_proxy/tools/task_github.py +393 -0
  165. gobby/mcp_proxy/tools/task_linear.py +379 -0
  166. gobby/mcp_proxy/tools/task_orchestration.py +77 -0
  167. gobby/mcp_proxy/tools/task_readiness.py +522 -0
  168. gobby/mcp_proxy/tools/task_sync.py +351 -0
  169. gobby/mcp_proxy/tools/task_validation.py +843 -0
  170. gobby/mcp_proxy/tools/tasks/__init__.py +25 -0
  171. gobby/mcp_proxy/tools/tasks/_context.py +112 -0
  172. gobby/mcp_proxy/tools/tasks/_crud.py +516 -0
  173. gobby/mcp_proxy/tools/tasks/_factory.py +176 -0
  174. gobby/mcp_proxy/tools/tasks/_helpers.py +129 -0
  175. gobby/mcp_proxy/tools/tasks/_lifecycle.py +517 -0
  176. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +301 -0
  177. gobby/mcp_proxy/tools/tasks/_resolution.py +55 -0
  178. gobby/mcp_proxy/tools/tasks/_search.py +215 -0
  179. gobby/mcp_proxy/tools/tasks/_session.py +125 -0
  180. gobby/mcp_proxy/tools/workflows.py +973 -0
  181. gobby/mcp_proxy/tools/worktrees.py +1264 -0
  182. gobby/mcp_proxy/transports/__init__.py +0 -0
  183. gobby/mcp_proxy/transports/base.py +95 -0
  184. gobby/mcp_proxy/transports/factory.py +44 -0
  185. gobby/mcp_proxy/transports/http.py +139 -0
  186. gobby/mcp_proxy/transports/stdio.py +213 -0
  187. gobby/mcp_proxy/transports/websocket.py +136 -0
  188. gobby/memory/backends/__init__.py +116 -0
  189. gobby/memory/backends/mem0.py +408 -0
  190. gobby/memory/backends/memu.py +485 -0
  191. gobby/memory/backends/null.py +111 -0
  192. gobby/memory/backends/openmemory.py +537 -0
  193. gobby/memory/backends/sqlite.py +304 -0
  194. gobby/memory/context.py +87 -0
  195. gobby/memory/manager.py +1001 -0
  196. gobby/memory/protocol.py +451 -0
  197. gobby/memory/search/__init__.py +66 -0
  198. gobby/memory/search/text.py +127 -0
  199. gobby/memory/viz.py +258 -0
  200. gobby/prompts/__init__.py +13 -0
  201. gobby/prompts/defaults/expansion/system.md +119 -0
  202. gobby/prompts/defaults/expansion/user.md +48 -0
  203. gobby/prompts/defaults/external_validation/agent.md +72 -0
  204. gobby/prompts/defaults/external_validation/external.md +63 -0
  205. gobby/prompts/defaults/external_validation/spawn.md +83 -0
  206. gobby/prompts/defaults/external_validation/system.md +6 -0
  207. gobby/prompts/defaults/features/import_mcp.md +22 -0
  208. gobby/prompts/defaults/features/import_mcp_github.md +17 -0
  209. gobby/prompts/defaults/features/import_mcp_search.md +16 -0
  210. gobby/prompts/defaults/features/recommend_tools.md +32 -0
  211. gobby/prompts/defaults/features/recommend_tools_hybrid.md +35 -0
  212. gobby/prompts/defaults/features/recommend_tools_llm.md +30 -0
  213. gobby/prompts/defaults/features/server_description.md +20 -0
  214. gobby/prompts/defaults/features/server_description_system.md +6 -0
  215. gobby/prompts/defaults/features/task_description.md +31 -0
  216. gobby/prompts/defaults/features/task_description_system.md +6 -0
  217. gobby/prompts/defaults/features/tool_summary.md +17 -0
  218. gobby/prompts/defaults/features/tool_summary_system.md +6 -0
  219. gobby/prompts/defaults/research/step.md +58 -0
  220. gobby/prompts/defaults/validation/criteria.md +47 -0
  221. gobby/prompts/defaults/validation/validate.md +38 -0
  222. gobby/prompts/loader.py +346 -0
  223. gobby/prompts/models.py +113 -0
  224. gobby/py.typed +0 -0
  225. gobby/runner.py +488 -0
  226. gobby/search/__init__.py +23 -0
  227. gobby/search/protocol.py +104 -0
  228. gobby/search/tfidf.py +232 -0
  229. gobby/servers/__init__.py +7 -0
  230. gobby/servers/http.py +636 -0
  231. gobby/servers/models.py +31 -0
  232. gobby/servers/routes/__init__.py +23 -0
  233. gobby/servers/routes/admin.py +416 -0
  234. gobby/servers/routes/dependencies.py +118 -0
  235. gobby/servers/routes/mcp/__init__.py +24 -0
  236. gobby/servers/routes/mcp/hooks.py +135 -0
  237. gobby/servers/routes/mcp/plugins.py +121 -0
  238. gobby/servers/routes/mcp/tools.py +1337 -0
  239. gobby/servers/routes/mcp/webhooks.py +159 -0
  240. gobby/servers/routes/sessions.py +582 -0
  241. gobby/servers/websocket.py +766 -0
  242. gobby/sessions/__init__.py +13 -0
  243. gobby/sessions/analyzer.py +322 -0
  244. gobby/sessions/lifecycle.py +240 -0
  245. gobby/sessions/manager.py +563 -0
  246. gobby/sessions/processor.py +225 -0
  247. gobby/sessions/summary.py +532 -0
  248. gobby/sessions/transcripts/__init__.py +41 -0
  249. gobby/sessions/transcripts/base.py +125 -0
  250. gobby/sessions/transcripts/claude.py +386 -0
  251. gobby/sessions/transcripts/codex.py +143 -0
  252. gobby/sessions/transcripts/gemini.py +195 -0
  253. gobby/storage/__init__.py +21 -0
  254. gobby/storage/agents.py +409 -0
  255. gobby/storage/artifact_classifier.py +341 -0
  256. gobby/storage/artifacts.py +285 -0
  257. gobby/storage/compaction.py +67 -0
  258. gobby/storage/database.py +357 -0
  259. gobby/storage/inter_session_messages.py +194 -0
  260. gobby/storage/mcp.py +680 -0
  261. gobby/storage/memories.py +562 -0
  262. gobby/storage/merge_resolutions.py +550 -0
  263. gobby/storage/migrations.py +860 -0
  264. gobby/storage/migrations_legacy.py +1359 -0
  265. gobby/storage/projects.py +166 -0
  266. gobby/storage/session_messages.py +251 -0
  267. gobby/storage/session_tasks.py +97 -0
  268. gobby/storage/sessions.py +817 -0
  269. gobby/storage/task_dependencies.py +223 -0
  270. gobby/storage/tasks/__init__.py +42 -0
  271. gobby/storage/tasks/_aggregates.py +180 -0
  272. gobby/storage/tasks/_crud.py +449 -0
  273. gobby/storage/tasks/_id.py +104 -0
  274. gobby/storage/tasks/_lifecycle.py +311 -0
  275. gobby/storage/tasks/_manager.py +889 -0
  276. gobby/storage/tasks/_models.py +300 -0
  277. gobby/storage/tasks/_ordering.py +119 -0
  278. gobby/storage/tasks/_path_cache.py +110 -0
  279. gobby/storage/tasks/_queries.py +343 -0
  280. gobby/storage/tasks/_search.py +143 -0
  281. gobby/storage/workflow_audit.py +393 -0
  282. gobby/storage/worktrees.py +547 -0
  283. gobby/sync/__init__.py +29 -0
  284. gobby/sync/github.py +333 -0
  285. gobby/sync/linear.py +304 -0
  286. gobby/sync/memories.py +284 -0
  287. gobby/sync/tasks.py +641 -0
  288. gobby/tasks/__init__.py +8 -0
  289. gobby/tasks/build_verification.py +193 -0
  290. gobby/tasks/commits.py +633 -0
  291. gobby/tasks/context.py +747 -0
  292. gobby/tasks/criteria.py +342 -0
  293. gobby/tasks/enhanced_validator.py +226 -0
  294. gobby/tasks/escalation.py +263 -0
  295. gobby/tasks/expansion.py +626 -0
  296. gobby/tasks/external_validator.py +764 -0
  297. gobby/tasks/issue_extraction.py +171 -0
  298. gobby/tasks/prompts/expand.py +327 -0
  299. gobby/tasks/research.py +421 -0
  300. gobby/tasks/tdd.py +352 -0
  301. gobby/tasks/tree_builder.py +263 -0
  302. gobby/tasks/validation.py +712 -0
  303. gobby/tasks/validation_history.py +357 -0
  304. gobby/tasks/validation_models.py +89 -0
  305. gobby/tools/__init__.py +0 -0
  306. gobby/tools/summarizer.py +170 -0
  307. gobby/tui/__init__.py +5 -0
  308. gobby/tui/api_client.py +281 -0
  309. gobby/tui/app.py +327 -0
  310. gobby/tui/screens/__init__.py +25 -0
  311. gobby/tui/screens/agents.py +333 -0
  312. gobby/tui/screens/chat.py +450 -0
  313. gobby/tui/screens/dashboard.py +377 -0
  314. gobby/tui/screens/memory.py +305 -0
  315. gobby/tui/screens/metrics.py +231 -0
  316. gobby/tui/screens/orchestrator.py +904 -0
  317. gobby/tui/screens/sessions.py +412 -0
  318. gobby/tui/screens/tasks.py +442 -0
  319. gobby/tui/screens/workflows.py +289 -0
  320. gobby/tui/screens/worktrees.py +174 -0
  321. gobby/tui/widgets/__init__.py +21 -0
  322. gobby/tui/widgets/chat.py +210 -0
  323. gobby/tui/widgets/conductor.py +104 -0
  324. gobby/tui/widgets/menu.py +132 -0
  325. gobby/tui/widgets/message_panel.py +160 -0
  326. gobby/tui/widgets/review_gate.py +224 -0
  327. gobby/tui/widgets/task_tree.py +99 -0
  328. gobby/tui/widgets/token_budget.py +166 -0
  329. gobby/tui/ws_client.py +258 -0
  330. gobby/utils/__init__.py +3 -0
  331. gobby/utils/daemon_client.py +235 -0
  332. gobby/utils/git.py +222 -0
  333. gobby/utils/id.py +38 -0
  334. gobby/utils/json_helpers.py +161 -0
  335. gobby/utils/logging.py +376 -0
  336. gobby/utils/machine_id.py +135 -0
  337. gobby/utils/metrics.py +589 -0
  338. gobby/utils/project_context.py +182 -0
  339. gobby/utils/project_init.py +263 -0
  340. gobby/utils/status.py +256 -0
  341. gobby/utils/validation.py +80 -0
  342. gobby/utils/version.py +23 -0
  343. gobby/workflows/__init__.py +4 -0
  344. gobby/workflows/actions.py +1310 -0
  345. gobby/workflows/approval_flow.py +138 -0
  346. gobby/workflows/artifact_actions.py +103 -0
  347. gobby/workflows/audit_helpers.py +110 -0
  348. gobby/workflows/autonomous_actions.py +286 -0
  349. gobby/workflows/context_actions.py +394 -0
  350. gobby/workflows/definitions.py +130 -0
  351. gobby/workflows/detection_helpers.py +208 -0
  352. gobby/workflows/engine.py +485 -0
  353. gobby/workflows/evaluator.py +669 -0
  354. gobby/workflows/git_utils.py +96 -0
  355. gobby/workflows/hooks.py +169 -0
  356. gobby/workflows/lifecycle_evaluator.py +613 -0
  357. gobby/workflows/llm_actions.py +70 -0
  358. gobby/workflows/loader.py +333 -0
  359. gobby/workflows/mcp_actions.py +60 -0
  360. gobby/workflows/memory_actions.py +272 -0
  361. gobby/workflows/premature_stop.py +164 -0
  362. gobby/workflows/session_actions.py +139 -0
  363. gobby/workflows/state_actions.py +123 -0
  364. gobby/workflows/state_manager.py +104 -0
  365. gobby/workflows/stop_signal_actions.py +163 -0
  366. gobby/workflows/summary_actions.py +344 -0
  367. gobby/workflows/task_actions.py +249 -0
  368. gobby/workflows/task_enforcement_actions.py +901 -0
  369. gobby/workflows/templates.py +52 -0
  370. gobby/workflows/todo_actions.py +84 -0
  371. gobby/workflows/webhook.py +223 -0
  372. gobby/workflows/webhook_executor.py +399 -0
  373. gobby/worktrees/__init__.py +5 -0
  374. gobby/worktrees/git.py +690 -0
  375. gobby/worktrees/merge/__init__.py +20 -0
  376. gobby/worktrees/merge/conflict_parser.py +177 -0
  377. gobby/worktrees/merge/resolver.py +485 -0
  378. gobby-0.2.5.dist-info/METADATA +351 -0
  379. gobby-0.2.5.dist-info/RECORD +383 -0
  380. gobby-0.2.5.dist-info/WHEEL +5 -0
  381. gobby-0.2.5.dist-info/entry_points.txt +2 -0
  382. gobby-0.2.5.dist-info/licenses/LICENSE.md +193 -0
  383. 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
+ )