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,247 @@
1
+ """
2
+ Persistence configuration module.
3
+
4
+ Contains storage and sync-related Pydantic config models:
5
+ - MemoryConfig: Memory system settings (injection, decay, search)
6
+ - MemorySyncConfig: Memory file sync settings (debounce, export path)
7
+
8
+ Extracted from app.py using Strangler Fig pattern for code decomposition.
9
+ """
10
+
11
+ from pathlib import Path
12
+
13
+ from pydantic import BaseModel, Field, field_validator
14
+
15
+ __all__ = [
16
+ "MemoryConfig",
17
+ "MemorySyncConfig",
18
+ "Mem0Config",
19
+ "MemUConfig",
20
+ "OpenMemoryConfig",
21
+ ]
22
+
23
+
24
+ class Mem0Config(BaseModel):
25
+ """Mem0 backend configuration.
26
+
27
+ Configure this section when using backend: 'mem0' for cloud-based
28
+ semantic memory storage via the Mem0 AI service (mem0ai package).
29
+
30
+ Requires: pip install mem0ai
31
+ """
32
+
33
+ api_key: str | None = Field(
34
+ default=None,
35
+ description="Mem0 API key for authentication (required when backend='mem0')",
36
+ )
37
+ user_id: str | None = Field(
38
+ default=None,
39
+ description="Default user ID for memories (optional, defaults to 'default')",
40
+ )
41
+ org_id: str | None = Field(
42
+ default=None,
43
+ description="Organization ID for multi-tenant use (optional)",
44
+ )
45
+
46
+
47
+ class MemUConfig(BaseModel):
48
+ """MemU backend configuration.
49
+
50
+ Configure this section when using backend: 'memu' for structured
51
+ memory storage via the MemU SDK (NevaMind-AI/memU via memu-py).
52
+
53
+ Requires: pip install memu-py
54
+ """
55
+
56
+ database_type: str = Field(
57
+ default="inmemory",
58
+ description="Database type: 'inmemory', 'sqlite', or 'postgres'",
59
+ )
60
+ database_url: str | None = Field(
61
+ default=None,
62
+ description="Database connection URL (for sqlite/postgres)",
63
+ )
64
+ llm_api_key: str | None = Field(
65
+ default=None,
66
+ description="LLM API key for embeddings (optional, uses OpenAI by default)",
67
+ )
68
+ llm_base_url: str | None = Field(
69
+ default=None,
70
+ description="LLM API base URL (optional)",
71
+ )
72
+ user_id: str | None = Field(
73
+ default=None,
74
+ description="Default user ID for memories (optional)",
75
+ )
76
+
77
+
78
+ class OpenMemoryConfig(BaseModel):
79
+ """OpenMemory backend configuration.
80
+
81
+ Configure this section when using backend: 'openmemory' for self-hosted
82
+ embedding-based memory storage via the OpenMemory REST API.
83
+
84
+ OpenMemory provides semantic search over memories using local embeddings.
85
+ """
86
+
87
+ base_url: str = Field(
88
+ default="http://localhost:8080",
89
+ description="OpenMemory server base URL (required when backend='openmemory')",
90
+ )
91
+ api_key: str | None = Field(
92
+ default=None,
93
+ description="Optional API key for authentication",
94
+ )
95
+ user_id: str | None = Field(
96
+ default=None,
97
+ description="Default user ID for memories (optional, defaults to 'default')",
98
+ )
99
+
100
+ @field_validator("base_url")
101
+ @classmethod
102
+ def validate_url(cls, v: str) -> str:
103
+ """Validate base_url is a valid URL format."""
104
+ if not v.startswith(("http://", "https://")):
105
+ raise ValueError("base_url must start with http:// or https://")
106
+ # Remove trailing slash for consistency
107
+ return v.rstrip("/")
108
+
109
+
110
+ class MemoryConfig(BaseModel):
111
+ """Memory system configuration."""
112
+
113
+ enabled: bool = Field(
114
+ default=True,
115
+ description="Enable persistent memory system",
116
+ )
117
+ backend: str = Field(
118
+ default="sqlite",
119
+ description=(
120
+ "Storage backend for memories. Options: "
121
+ "'sqlite' (default, local SQLite database), "
122
+ "'mem0' (Mem0 cloud-based semantic memory via mem0ai), "
123
+ "'memu' (MemU structured memory via memu-py), "
124
+ "'openmemory' (self-hosted OpenMemory REST API), "
125
+ "'null' (no persistence, for testing)"
126
+ ),
127
+ )
128
+ mem0: Mem0Config = Field(
129
+ default_factory=Mem0Config,
130
+ description="Mem0 backend configuration (only used when backend='mem0')",
131
+ )
132
+ memu: MemUConfig = Field(
133
+ default_factory=MemUConfig,
134
+ description="MemU backend configuration (only used when backend='memu')",
135
+ )
136
+ openmemory: OpenMemoryConfig = Field(
137
+ default_factory=OpenMemoryConfig,
138
+ description="OpenMemory backend configuration (only used when backend='openmemory')",
139
+ )
140
+ importance_threshold: float = Field(
141
+ default=0.7,
142
+ description="Minimum importance score for memory injection",
143
+ )
144
+ decay_enabled: bool = Field(
145
+ default=True,
146
+ description="Enable memory importance decay over time",
147
+ )
148
+ decay_rate: float = Field(
149
+ default=0.05,
150
+ description="Importance decay rate per month",
151
+ )
152
+ decay_floor: float = Field(
153
+ default=0.1,
154
+ description="Minimum importance score after decay",
155
+ )
156
+ search_backend: str = Field(
157
+ default="tfidf",
158
+ description=(
159
+ "Search backend for memory recall. Options: "
160
+ "'tfidf' (default, zero-dependency local search), "
161
+ "'text' (simple substring matching)"
162
+ ),
163
+ )
164
+ auto_crossref: bool = Field(
165
+ default=False,
166
+ description="Automatically create cross-references between similar memories",
167
+ )
168
+ crossref_threshold: float = Field(
169
+ default=0.3,
170
+ description="Minimum similarity score to create a cross-reference (0.0-1.0)",
171
+ )
172
+ crossref_max_links: int = Field(
173
+ default=5,
174
+ description="Maximum number of cross-references to create per memory",
175
+ )
176
+ access_debounce_seconds: int = Field(
177
+ default=60,
178
+ description="Minimum seconds between access stat updates for the same memory",
179
+ )
180
+
181
+ @field_validator("importance_threshold", "decay_rate", "decay_floor", "crossref_threshold")
182
+ @classmethod
183
+ def validate_probability(cls, v: float) -> float:
184
+ """Validate value is between 0.0 and 1.0."""
185
+ if not (0.0 <= v <= 1.0):
186
+ raise ValueError("Value must be between 0.0 and 1.0")
187
+ return v
188
+
189
+ @field_validator("crossref_max_links")
190
+ @classmethod
191
+ def validate_positive_links(cls, v: int) -> int:
192
+ """Validate crossref_max_links is positive."""
193
+ if v < 1:
194
+ raise ValueError("crossref_max_links must be at least 1")
195
+ return v
196
+
197
+ @field_validator("search_backend")
198
+ @classmethod
199
+ def validate_search_backend(cls, v: str) -> str:
200
+ """Validate search_backend is a supported option."""
201
+ valid_backends = {"tfidf", "text"}
202
+ if v not in valid_backends:
203
+ raise ValueError(
204
+ f"Invalid search_backend '{v}'. Must be one of: {sorted(valid_backends)}"
205
+ )
206
+ return v
207
+
208
+ @field_validator("backend")
209
+ @classmethod
210
+ def validate_backend(cls, v: str) -> str:
211
+ """Validate backend is a supported storage option."""
212
+ valid_backends = {"sqlite", "mem0", "memu", "openmemory", "null"}
213
+ if v not in valid_backends:
214
+ raise ValueError(f"Invalid backend '{v}'. Must be one of: {sorted(valid_backends)}")
215
+ return v
216
+
217
+
218
+ class MemorySyncConfig(BaseModel):
219
+ """Memory backup configuration (filesystem export).
220
+
221
+ Note: This was previously named for "sync" but is actually a backup mechanism.
222
+ Memories are stored in the database via MemoryBackendProtocol; this config
223
+ controls the JSONL backup file export (for disaster recovery/migration).
224
+
225
+ TODO: Consider renaming to MemoryBackupConfig in a future breaking change.
226
+ """
227
+
228
+ enabled: bool = Field(
229
+ default=True,
230
+ description="Enable memory synchronization to filesystem",
231
+ )
232
+ export_debounce: float = Field(
233
+ default=5.0,
234
+ description="Seconds to wait before exporting after a change",
235
+ )
236
+ export_path: Path = Field(
237
+ default=Path(".gobby/memories.jsonl"),
238
+ description="Path to the memories export file (relative to project root or absolute)",
239
+ )
240
+
241
+ @field_validator("export_debounce")
242
+ @classmethod
243
+ def validate_positive(cls, v: float) -> float:
244
+ """Validate value is non-negative."""
245
+ if v < 0:
246
+ raise ValueError("Value must be non-negative")
247
+ return v
@@ -0,0 +1,141 @@
1
+ """
2
+ Server configuration module.
3
+
4
+ Contains server and networking Pydantic config models:
5
+ - WebSocketSettings: WebSocket server settings (port, ping interval)
6
+ - MCPClientProxyConfig: MCP proxy settings (timeouts, embeddings, search mode)
7
+
8
+ Extracted from app.py using Strangler Fig pattern for code decomposition.
9
+ """
10
+
11
+ from typing import Literal
12
+
13
+ from pydantic import BaseModel, Field, field_validator
14
+
15
+ __all__ = ["WebSocketSettings", "MCPClientProxyConfig"]
16
+
17
+
18
+ class WebSocketSettings(BaseModel):
19
+ """WebSocket server configuration."""
20
+
21
+ enabled: bool = Field(
22
+ default=True,
23
+ description="Enable WebSocket server for real-time communication",
24
+ )
25
+ port: int = Field(
26
+ default=8766,
27
+ description="Port for WebSocket server to listen on",
28
+ )
29
+ ping_interval: int = Field(
30
+ default=30,
31
+ description="Ping interval in seconds for keepalive",
32
+ )
33
+ ping_timeout: int = Field(
34
+ default=10,
35
+ description="Pong timeout in seconds before considering connection dead",
36
+ )
37
+
38
+ @field_validator("port")
39
+ @classmethod
40
+ def validate_port(cls, v: int) -> int:
41
+ """Validate port number is in valid range."""
42
+ if not (1024 <= v <= 65535):
43
+ raise ValueError("Port must be between 1024 and 65535")
44
+ return v
45
+
46
+ @field_validator("ping_interval", "ping_timeout")
47
+ @classmethod
48
+ def validate_positive(cls, v: int) -> int:
49
+ """Validate value is positive."""
50
+ if v <= 0:
51
+ raise ValueError("Value must be positive")
52
+ return v
53
+
54
+
55
+ class MCPClientProxyConfig(BaseModel):
56
+ """MCP client proxy configuration for downstream MCP servers."""
57
+
58
+ enabled: bool = Field(
59
+ default=True,
60
+ description="Enable MCP client proxy for downstream MCP servers",
61
+ )
62
+ connect_timeout: float = Field(
63
+ default=30.0,
64
+ description="Timeout in seconds for establishing connections to MCP servers",
65
+ )
66
+ proxy_timeout: int = Field(
67
+ default=30,
68
+ description="Timeout in seconds for proxy calls to downstream MCP servers",
69
+ )
70
+ tool_timeout: int = Field(
71
+ default=30,
72
+ description="Timeout in seconds for tool schema operations",
73
+ )
74
+ tool_timeouts: dict[str, float] = Field(
75
+ default_factory=dict,
76
+ description="Map of tool names to specific timeouts in seconds",
77
+ )
78
+
79
+ # Semantic search and embeddings
80
+ search_mode: Literal["llm", "semantic", "hybrid"] = Field(
81
+ default="llm",
82
+ description="Default search mode for tool recommendations: 'llm' (LLM-based), 'semantic' (embedding similarity), 'hybrid' (both)",
83
+ )
84
+ embedding_provider: str = Field(
85
+ default="openai",
86
+ description="Provider for embedding generation (openai, litellm)",
87
+ )
88
+ embedding_model: str = Field(
89
+ default="text-embedding-3-small",
90
+ description="Model to use for tool embedding generation",
91
+ )
92
+ min_similarity: float = Field(
93
+ default=0.3,
94
+ description="Minimum similarity threshold for semantic search results (0.0-1.0)",
95
+ )
96
+ top_k: int = Field(
97
+ default=10,
98
+ description="Default number of results to return for semantic search",
99
+ )
100
+
101
+ # Refresh settings
102
+ refresh_on_server_add: bool = Field(
103
+ default=True,
104
+ description="Automatically refresh tool embeddings when adding a new MCP server",
105
+ )
106
+ refresh_timeout: float = Field(
107
+ default=300.0,
108
+ description="Timeout in seconds for tool refresh operations (embedding generation)",
109
+ )
110
+
111
+ @field_validator("connect_timeout", "refresh_timeout")
112
+ @classmethod
113
+ def validate_connect_timeout(cls, v: float) -> float:
114
+ """Validate timeout is positive."""
115
+ if v <= 0:
116
+ raise ValueError("Timeout must be positive")
117
+ return v
118
+
119
+ @field_validator("proxy_timeout", "tool_timeout")
120
+ @classmethod
121
+ def validate_timeout(cls, v: int) -> int:
122
+ """Validate timeout is positive."""
123
+ if v <= 0:
124
+ raise ValueError("Timeout must be positive")
125
+ return v
126
+
127
+ @field_validator("min_similarity")
128
+ @classmethod
129
+ def validate_min_similarity(cls, v: float) -> float:
130
+ """Validate min_similarity is between 0 and 1."""
131
+ if not 0.0 <= v <= 1.0:
132
+ raise ValueError("min_similarity must be between 0.0 and 1.0")
133
+ return v
134
+
135
+ @field_validator("top_k")
136
+ @classmethod
137
+ def validate_top_k(cls, v: int) -> int:
138
+ """Validate top_k is positive."""
139
+ if v <= 0:
140
+ raise ValueError("top_k must be positive")
141
+ return v
@@ -0,0 +1,250 @@
1
+ """
2
+ Session configuration module.
3
+
4
+ Contains session-related Pydantic config models:
5
+ - ContextInjectionConfig: Subagent context injection settings
6
+ - SessionSummaryConfig: Session summary generation settings
7
+ - TitleSynthesisConfig: Session title synthesis settings
8
+ - MessageTrackingConfig: Session message tracking settings
9
+ - SessionLifecycleConfig: Session lifecycle management settings
10
+
11
+ Extracted from app.py using Strangler Fig pattern for code decomposition.
12
+ """
13
+
14
+ from pydantic import BaseModel, Field, field_validator
15
+
16
+ __all__ = [
17
+ "ArtifactHandoffConfig",
18
+ "ContextInjectionConfig",
19
+ "SessionSummaryConfig",
20
+ "TitleSynthesisConfig",
21
+ "MessageTrackingConfig",
22
+ "SessionLifecycleConfig",
23
+ ]
24
+
25
+
26
+ class ArtifactHandoffConfig(BaseModel):
27
+ """Configuration for artifact inclusion in session handoffs.
28
+
29
+ Controls how artifacts are collected and formatted when generating
30
+ handoff context for session continuation.
31
+ """
32
+
33
+ max_artifacts_in_handoff: int = Field(
34
+ default=10,
35
+ description="Maximum number of artifacts to include in handoff context",
36
+ )
37
+ max_context_size: int = Field(
38
+ default=50000,
39
+ description="Maximum size in characters for handoff context",
40
+ )
41
+ include_parent_artifacts: bool = Field(
42
+ default=True,
43
+ description="Include artifacts from parent session in handoff",
44
+ )
45
+ max_lineage_depth: int = Field(
46
+ default=3,
47
+ description="Maximum depth to traverse session lineage for artifacts",
48
+ )
49
+
50
+ @field_validator("max_artifacts_in_handoff", "max_context_size", "max_lineage_depth")
51
+ @classmethod
52
+ def validate_positive(cls, v: int) -> int:
53
+ """Validate value is positive."""
54
+ if v <= 0:
55
+ raise ValueError("Value must be positive")
56
+ return v
57
+
58
+
59
+ class ContextInjectionConfig(BaseModel):
60
+ """Context injection configuration for subagent spawning.
61
+
62
+ Controls how context is resolved and injected into subagent prompts.
63
+ """
64
+
65
+ enabled: bool = Field(
66
+ default=True,
67
+ description="Enable context injection for subagents",
68
+ )
69
+ default_source: str = Field(
70
+ default="summary_markdown",
71
+ description="Default context source when not specified. "
72
+ "Options: summary_markdown, compact_markdown, session_id:<id>, "
73
+ "transcript:<n>, file:<path>",
74
+ )
75
+ max_file_size: int = Field(
76
+ default=51200,
77
+ description="Maximum file size in bytes for file: source (default: 50KB)",
78
+ )
79
+ max_content_size: int = Field(
80
+ default=51200,
81
+ description="Maximum content size in bytes for all sources (default: 50KB)",
82
+ )
83
+ max_transcript_messages: int = Field(
84
+ default=100,
85
+ description="Maximum number of messages for transcript: source",
86
+ )
87
+ truncation_suffix: str = Field(
88
+ default="\n\n[truncated: {bytes} bytes remaining]",
89
+ description="Suffix template appended when content is truncated",
90
+ )
91
+ context_template: str | None = Field(
92
+ default=None,
93
+ description="Custom template for context injection. "
94
+ "Use {{ context }} and {{ prompt }} placeholders. "
95
+ "If None, uses the default template.",
96
+ )
97
+
98
+ @field_validator("max_file_size", "max_content_size", "max_transcript_messages")
99
+ @classmethod
100
+ def validate_positive(cls, v: int) -> int:
101
+ """Validate value is positive."""
102
+ if v <= 0:
103
+ raise ValueError("Value must be positive")
104
+ return v
105
+
106
+
107
+ class SessionSummaryConfig(BaseModel):
108
+ """Session summary generation configuration."""
109
+
110
+ enabled: bool = Field(
111
+ default=True,
112
+ description="Enable LLM-based session summary generation",
113
+ )
114
+ provider: str = Field(
115
+ default="claude",
116
+ description="LLM provider to use for session summary",
117
+ )
118
+ model: str = Field(
119
+ default="claude-haiku-4-5",
120
+ description="Model to use for session summary generation",
121
+ )
122
+ prompt: str = Field(
123
+ default="""Generate a concise session summary for handoff to another agent or future session.
124
+
125
+ ## Session Context
126
+ Transcript Summary:
127
+ {transcript_summary}
128
+
129
+ Git Status:
130
+ {git_status}
131
+
132
+ File Changes:
133
+ {file_changes}
134
+
135
+ {todo_list}
136
+
137
+ {session_tasks}
138
+
139
+ ## Instructions
140
+ Create a summary with these sections:
141
+ 1. **What was accomplished** - Key completions in 2-3 bullet points
142
+ 2. **Current state** - What's in progress or pending
143
+ 3. **Next steps** - Clear actionable items for continuation
144
+
145
+ Be concise. Focus on what the next agent needs to know to continue effectively.""",
146
+ description="Prompt template for session summary (use placeholders: {transcript_summary}, {git_status}, {file_changes}, {todo_list}, {session_tasks})",
147
+ )
148
+ summary_file_path: str = Field(
149
+ default="~/.gobby/session_summaries",
150
+ description="Directory path for session summary markdown files",
151
+ )
152
+
153
+
154
+ class TitleSynthesisConfig(BaseModel):
155
+ """Title synthesis configuration."""
156
+
157
+ enabled: bool = Field(
158
+ default=True,
159
+ description="Enable title synthesis for sessions",
160
+ )
161
+ provider: str = Field(
162
+ default="claude",
163
+ description="LLM provider to use for title synthesis",
164
+ )
165
+ model: str = Field(
166
+ default="claude-haiku-4-5",
167
+ description="Model to use for title synthesis",
168
+ )
169
+ prompt: str | None = Field(
170
+ default=None,
171
+ description="Custom prompt template for title synthesis",
172
+ )
173
+
174
+
175
+ class MessageTrackingConfig(BaseModel):
176
+ """Configuration for session message tracking."""
177
+
178
+ enabled: bool = Field(
179
+ default=True,
180
+ description="Enable session message tracking",
181
+ )
182
+ poll_interval: float = Field(
183
+ default=5.0,
184
+ description="Polling interval in seconds for transcript updates",
185
+ )
186
+ debounce_delay: float = Field(
187
+ default=1.0,
188
+ description="Debounce delay in seconds for message processing",
189
+ )
190
+ max_message_length: int = Field(
191
+ default=10000,
192
+ description="Maximum length of a single message content",
193
+ )
194
+ broadcast_enabled: bool = Field(
195
+ default=True,
196
+ description="Enable broadcasting message events",
197
+ )
198
+
199
+ @field_validator("poll_interval", "debounce_delay")
200
+ @classmethod
201
+ def validate_positive(cls, v: float) -> float:
202
+ """Validate value is positive."""
203
+ if v <= 0:
204
+ raise ValueError("Value must be positive")
205
+ return v
206
+
207
+
208
+ class SessionLifecycleConfig(BaseModel):
209
+ """Configuration for session lifecycle management.
210
+
211
+ Handles:
212
+ - Pausing active sessions with no recent activity
213
+ - Expiring stale sessions (active/paused for too long)
214
+ - Background transcript processing for expired sessions
215
+ """
216
+
217
+ active_session_pause_minutes: int = Field(
218
+ default=30,
219
+ description="Minutes of inactivity before active sessions are marked paused",
220
+ )
221
+ stale_session_timeout_hours: int = Field(
222
+ default=24,
223
+ description="Hours after which inactive sessions are marked expired",
224
+ )
225
+ expire_check_interval_minutes: int = Field(
226
+ default=60,
227
+ description="How often to check for stale sessions (minutes)",
228
+ )
229
+ transcript_processing_interval_minutes: int = Field(
230
+ default=5,
231
+ description="How often to process pending transcripts (minutes)",
232
+ )
233
+ transcript_processing_batch_size: int = Field(
234
+ default=10,
235
+ description="Maximum sessions to process per batch",
236
+ )
237
+
238
+ @field_validator(
239
+ "active_session_pause_minutes",
240
+ "stale_session_timeout_hours",
241
+ "expire_check_interval_minutes",
242
+ "transcript_processing_interval_minutes",
243
+ "transcript_processing_batch_size",
244
+ )
245
+ @classmethod
246
+ def validate_positive(cls, v: int) -> int:
247
+ """Validate value is positive."""
248
+ if v <= 0:
249
+ raise ValueError("Value must be positive")
250
+ return v