gobby 0.2.5__py3-none-any.whl → 0.2.7__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 (244) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +13 -4
  4. gobby/adapters/codex_impl/__init__.py +28 -0
  5. gobby/adapters/codex_impl/adapter.py +722 -0
  6. gobby/adapters/codex_impl/client.py +679 -0
  7. gobby/adapters/codex_impl/protocol.py +20 -0
  8. gobby/adapters/codex_impl/types.py +68 -0
  9. gobby/agents/definitions.py +11 -1
  10. gobby/agents/isolation.py +395 -0
  11. gobby/agents/runner.py +8 -0
  12. gobby/agents/sandbox.py +261 -0
  13. gobby/agents/spawn.py +42 -287
  14. gobby/agents/spawn_executor.py +385 -0
  15. gobby/agents/spawners/__init__.py +24 -0
  16. gobby/agents/spawners/command_builder.py +189 -0
  17. gobby/agents/spawners/embedded.py +21 -2
  18. gobby/agents/spawners/headless.py +21 -2
  19. gobby/agents/spawners/prompt_manager.py +125 -0
  20. gobby/cli/__init__.py +6 -0
  21. gobby/cli/clones.py +419 -0
  22. gobby/cli/conductor.py +266 -0
  23. gobby/cli/install.py +4 -4
  24. gobby/cli/installers/antigravity.py +3 -9
  25. gobby/cli/installers/claude.py +15 -9
  26. gobby/cli/installers/codex.py +2 -8
  27. gobby/cli/installers/gemini.py +8 -8
  28. gobby/cli/installers/shared.py +175 -13
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/skills.py +858 -0
  31. gobby/cli/tasks/ai.py +0 -440
  32. gobby/cli/tasks/crud.py +44 -6
  33. gobby/cli/tasks/main.py +0 -4
  34. gobby/cli/tui.py +2 -2
  35. gobby/cli/utils.py +12 -5
  36. gobby/clones/__init__.py +13 -0
  37. gobby/clones/git.py +547 -0
  38. gobby/conductor/__init__.py +16 -0
  39. gobby/conductor/alerts.py +135 -0
  40. gobby/conductor/loop.py +164 -0
  41. gobby/conductor/monitors/__init__.py +11 -0
  42. gobby/conductor/monitors/agents.py +116 -0
  43. gobby/conductor/monitors/tasks.py +155 -0
  44. gobby/conductor/pricing.py +234 -0
  45. gobby/conductor/token_tracker.py +160 -0
  46. gobby/config/__init__.py +12 -97
  47. gobby/config/app.py +69 -91
  48. gobby/config/extensions.py +2 -2
  49. gobby/config/features.py +7 -130
  50. gobby/config/search.py +110 -0
  51. gobby/config/servers.py +1 -1
  52. gobby/config/skills.py +43 -0
  53. gobby/config/tasks.py +9 -41
  54. gobby/hooks/__init__.py +0 -13
  55. gobby/hooks/event_handlers.py +188 -2
  56. gobby/hooks/hook_manager.py +50 -4
  57. gobby/hooks/plugins.py +1 -1
  58. gobby/hooks/skill_manager.py +130 -0
  59. gobby/hooks/webhooks.py +1 -1
  60. gobby/install/claude/hooks/hook_dispatcher.py +4 -4
  61. gobby/install/codex/hooks/hook_dispatcher.py +1 -1
  62. gobby/install/gemini/hooks/hook_dispatcher.py +87 -12
  63. gobby/llm/claude.py +22 -34
  64. gobby/llm/claude_executor.py +46 -256
  65. gobby/llm/codex_executor.py +59 -291
  66. gobby/llm/executor.py +21 -0
  67. gobby/llm/gemini.py +134 -110
  68. gobby/llm/litellm_executor.py +143 -6
  69. gobby/llm/resolver.py +98 -35
  70. gobby/mcp_proxy/importer.py +62 -4
  71. gobby/mcp_proxy/instructions.py +56 -0
  72. gobby/mcp_proxy/models.py +15 -0
  73. gobby/mcp_proxy/registries.py +68 -8
  74. gobby/mcp_proxy/server.py +33 -3
  75. gobby/mcp_proxy/services/recommendation.py +43 -11
  76. gobby/mcp_proxy/services/tool_proxy.py +81 -1
  77. gobby/mcp_proxy/stdio.py +2 -1
  78. gobby/mcp_proxy/tools/__init__.py +0 -2
  79. gobby/mcp_proxy/tools/agent_messaging.py +317 -0
  80. gobby/mcp_proxy/tools/agents.py +31 -731
  81. gobby/mcp_proxy/tools/clones.py +518 -0
  82. gobby/mcp_proxy/tools/memory.py +3 -26
  83. gobby/mcp_proxy/tools/metrics.py +65 -1
  84. gobby/mcp_proxy/tools/orchestration/__init__.py +3 -0
  85. gobby/mcp_proxy/tools/orchestration/cleanup.py +151 -0
  86. gobby/mcp_proxy/tools/orchestration/wait.py +467 -0
  87. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  88. gobby/mcp_proxy/tools/sessions/_commits.py +232 -0
  89. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  90. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  91. gobby/mcp_proxy/tools/sessions/_handoff.py +499 -0
  92. gobby/mcp_proxy/tools/sessions/_messages.py +138 -0
  93. gobby/mcp_proxy/tools/skills/__init__.py +616 -0
  94. gobby/mcp_proxy/tools/spawn_agent.py +417 -0
  95. gobby/mcp_proxy/tools/task_orchestration.py +7 -0
  96. gobby/mcp_proxy/tools/task_readiness.py +14 -0
  97. gobby/mcp_proxy/tools/task_sync.py +1 -1
  98. gobby/mcp_proxy/tools/tasks/_context.py +0 -20
  99. gobby/mcp_proxy/tools/tasks/_crud.py +91 -4
  100. gobby/mcp_proxy/tools/tasks/_expansion.py +348 -0
  101. gobby/mcp_proxy/tools/tasks/_factory.py +6 -16
  102. gobby/mcp_proxy/tools/tasks/_lifecycle.py +110 -45
  103. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +18 -29
  104. gobby/mcp_proxy/tools/workflows.py +1 -1
  105. gobby/mcp_proxy/tools/worktrees.py +0 -338
  106. gobby/memory/backends/__init__.py +6 -1
  107. gobby/memory/backends/mem0.py +6 -1
  108. gobby/memory/extractor.py +477 -0
  109. gobby/memory/ingestion/__init__.py +5 -0
  110. gobby/memory/ingestion/multimodal.py +221 -0
  111. gobby/memory/manager.py +73 -285
  112. gobby/memory/search/__init__.py +10 -0
  113. gobby/memory/search/coordinator.py +248 -0
  114. gobby/memory/services/__init__.py +5 -0
  115. gobby/memory/services/crossref.py +142 -0
  116. gobby/prompts/loader.py +5 -2
  117. gobby/runner.py +37 -16
  118. gobby/search/__init__.py +48 -6
  119. gobby/search/backends/__init__.py +159 -0
  120. gobby/search/backends/embedding.py +225 -0
  121. gobby/search/embeddings.py +238 -0
  122. gobby/search/models.py +148 -0
  123. gobby/search/unified.py +496 -0
  124. gobby/servers/http.py +24 -12
  125. gobby/servers/routes/admin.py +294 -0
  126. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  127. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  128. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  129. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  130. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  131. gobby/servers/routes/mcp/hooks.py +1 -1
  132. gobby/servers/routes/mcp/tools.py +48 -1317
  133. gobby/servers/websocket.py +2 -2
  134. gobby/sessions/analyzer.py +2 -0
  135. gobby/sessions/lifecycle.py +1 -1
  136. gobby/sessions/processor.py +10 -0
  137. gobby/sessions/transcripts/base.py +2 -0
  138. gobby/sessions/transcripts/claude.py +79 -10
  139. gobby/skills/__init__.py +91 -0
  140. gobby/skills/loader.py +685 -0
  141. gobby/skills/manager.py +384 -0
  142. gobby/skills/parser.py +286 -0
  143. gobby/skills/search.py +463 -0
  144. gobby/skills/sync.py +119 -0
  145. gobby/skills/updater.py +385 -0
  146. gobby/skills/validator.py +368 -0
  147. gobby/storage/clones.py +378 -0
  148. gobby/storage/database.py +1 -1
  149. gobby/storage/memories.py +43 -13
  150. gobby/storage/migrations.py +162 -201
  151. gobby/storage/sessions.py +116 -7
  152. gobby/storage/skills.py +782 -0
  153. gobby/storage/tasks/_crud.py +4 -4
  154. gobby/storage/tasks/_lifecycle.py +57 -7
  155. gobby/storage/tasks/_manager.py +14 -5
  156. gobby/storage/tasks/_models.py +8 -3
  157. gobby/sync/memories.py +40 -5
  158. gobby/sync/tasks.py +83 -6
  159. gobby/tasks/__init__.py +1 -2
  160. gobby/tasks/external_validator.py +1 -1
  161. gobby/tasks/validation.py +46 -35
  162. gobby/tools/summarizer.py +91 -10
  163. gobby/tui/api_client.py +4 -7
  164. gobby/tui/app.py +5 -3
  165. gobby/tui/screens/orchestrator.py +1 -2
  166. gobby/tui/screens/tasks.py +2 -4
  167. gobby/tui/ws_client.py +1 -1
  168. gobby/utils/daemon_client.py +2 -2
  169. gobby/utils/project_context.py +2 -3
  170. gobby/utils/status.py +13 -0
  171. gobby/workflows/actions.py +221 -1135
  172. gobby/workflows/artifact_actions.py +31 -0
  173. gobby/workflows/autonomous_actions.py +11 -0
  174. gobby/workflows/context_actions.py +93 -1
  175. gobby/workflows/detection_helpers.py +115 -31
  176. gobby/workflows/enforcement/__init__.py +47 -0
  177. gobby/workflows/enforcement/blocking.py +269 -0
  178. gobby/workflows/enforcement/commit_policy.py +283 -0
  179. gobby/workflows/enforcement/handlers.py +269 -0
  180. gobby/workflows/{task_enforcement_actions.py → enforcement/task_policy.py} +29 -388
  181. gobby/workflows/engine.py +13 -2
  182. gobby/workflows/git_utils.py +106 -0
  183. gobby/workflows/lifecycle_evaluator.py +29 -1
  184. gobby/workflows/llm_actions.py +30 -0
  185. gobby/workflows/loader.py +19 -6
  186. gobby/workflows/mcp_actions.py +20 -1
  187. gobby/workflows/memory_actions.py +154 -0
  188. gobby/workflows/safe_evaluator.py +183 -0
  189. gobby/workflows/session_actions.py +44 -0
  190. gobby/workflows/state_actions.py +60 -1
  191. gobby/workflows/stop_signal_actions.py +55 -0
  192. gobby/workflows/summary_actions.py +111 -1
  193. gobby/workflows/task_sync_actions.py +347 -0
  194. gobby/workflows/todo_actions.py +34 -1
  195. gobby/workflows/webhook_actions.py +185 -0
  196. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/METADATA +87 -21
  197. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/RECORD +201 -172
  198. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/WHEEL +1 -1
  199. gobby/adapters/codex.py +0 -1292
  200. gobby/install/claude/commands/gobby/bug.md +0 -51
  201. gobby/install/claude/commands/gobby/chore.md +0 -51
  202. gobby/install/claude/commands/gobby/epic.md +0 -52
  203. gobby/install/claude/commands/gobby/eval.md +0 -235
  204. gobby/install/claude/commands/gobby/feat.md +0 -49
  205. gobby/install/claude/commands/gobby/nit.md +0 -52
  206. gobby/install/claude/commands/gobby/ref.md +0 -52
  207. gobby/install/codex/prompts/forget.md +0 -7
  208. gobby/install/codex/prompts/memories.md +0 -7
  209. gobby/install/codex/prompts/recall.md +0 -7
  210. gobby/install/codex/prompts/remember.md +0 -13
  211. gobby/llm/gemini_executor.py +0 -339
  212. gobby/mcp_proxy/tools/session_messages.py +0 -1056
  213. gobby/mcp_proxy/tools/task_expansion.py +0 -591
  214. gobby/prompts/defaults/expansion/system.md +0 -119
  215. gobby/prompts/defaults/expansion/user.md +0 -48
  216. gobby/prompts/defaults/external_validation/agent.md +0 -72
  217. gobby/prompts/defaults/external_validation/external.md +0 -63
  218. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  219. gobby/prompts/defaults/external_validation/system.md +0 -6
  220. gobby/prompts/defaults/features/import_mcp.md +0 -22
  221. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  222. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  223. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  224. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  225. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  226. gobby/prompts/defaults/features/server_description.md +0 -20
  227. gobby/prompts/defaults/features/server_description_system.md +0 -6
  228. gobby/prompts/defaults/features/task_description.md +0 -31
  229. gobby/prompts/defaults/features/task_description_system.md +0 -6
  230. gobby/prompts/defaults/features/tool_summary.md +0 -17
  231. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  232. gobby/prompts/defaults/research/step.md +0 -58
  233. gobby/prompts/defaults/validation/criteria.md +0 -47
  234. gobby/prompts/defaults/validation/validate.md +0 -38
  235. gobby/storage/migrations_legacy.py +0 -1359
  236. gobby/tasks/context.py +0 -747
  237. gobby/tasks/criteria.py +0 -342
  238. gobby/tasks/expansion.py +0 -626
  239. gobby/tasks/prompts/expand.py +0 -327
  240. gobby/tasks/research.py +0 -421
  241. gobby/tasks/tdd.py +0 -352
  242. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/entry_points.txt +0 -0
  243. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/licenses/LICENSE.md +0 -0
  244. {gobby-0.2.5.dist-info → gobby-0.2.7.dist-info}/top_level.txt +0 -0
@@ -22,13 +22,9 @@ from typing import TYPE_CHECKING, Any, Literal, cast
22
22
 
23
23
  from gobby.mcp_proxy.tools.internal import InternalToolRegistry
24
24
  from gobby.utils.project_context import get_project_context
25
- from gobby.workflows.definitions import WorkflowState
26
- from gobby.workflows.loader import WorkflowLoader
27
- from gobby.workflows.state_manager import WorkflowStateManager
28
25
  from gobby.worktrees.git import WorktreeGitManager
29
26
 
30
27
  if TYPE_CHECKING:
31
- from gobby.agents.runner import AgentRunner
32
28
  from gobby.storage.worktrees import LocalWorktreeManager
33
29
  from gobby.worktrees.git import WorktreeGitManager
34
30
 
@@ -291,7 +287,6 @@ def create_worktrees_registry(
291
287
  worktree_storage: LocalWorktreeManager,
292
288
  git_manager: WorktreeGitManager | None = None,
293
289
  project_id: str | None = None,
294
- agent_runner: AgentRunner | None = None,
295
290
  ) -> InternalToolRegistry:
296
291
  """
297
292
  Create a worktree tool registry with all worktree-related tools.
@@ -300,7 +295,6 @@ def create_worktrees_registry(
300
295
  worktree_storage: LocalWorktreeManager for database operations.
301
296
  git_manager: WorktreeGitManager for git operations.
302
297
  project_id: Default project ID for operations.
303
- agent_runner: AgentRunner for spawning agents in worktrees.
304
298
 
305
299
  Returns:
306
300
  InternalToolRegistry with all worktree tools registered.
@@ -929,336 +923,4 @@ def create_worktrees_registry(
929
923
 
930
924
  return {}
931
925
 
932
- @registry.tool(
933
- name="spawn_agent_in_worktree",
934
- description="Create a worktree and spawn an agent in it.",
935
- )
936
- async def spawn_agent_in_worktree(
937
- prompt: str,
938
- branch_name: str,
939
- base_branch: str = "main",
940
- task_id: str | None = None,
941
- parent_session_id: str | None = None,
942
- mode: str = "terminal", # Note: in_process mode is not supported
943
- terminal: str = "auto",
944
- provider: Literal["claude", "gemini", "codex", "antigravity"] = "claude",
945
- model: str | None = None,
946
- workflow: str | None = None,
947
- timeout: float = 120.0,
948
- max_turns: int = 10,
949
- project_path: str | None = None,
950
- ) -> dict[str, Any]:
951
- """
952
- Create a worktree and spawn an agent to work in it.
953
-
954
- This combines worktree creation with agent spawning for isolated development.
955
-
956
- Args:
957
- prompt: The task/prompt for the agent.
958
- branch_name: Name for the new branch/worktree.
959
- base_branch: Branch to base the worktree on (default: main).
960
- task_id: Optional task ID to link to this worktree.
961
- parent_session_id: Parent session ID for context.
962
- mode: Execution mode (terminal, embedded, headless). Note: in_process is not supported.
963
- terminal: Terminal for terminal/embedded modes (auto, ghostty, etc.).
964
- provider: LLM provider (claude, gemini, etc.).
965
- model: Optional model override.
966
- workflow: Workflow name to execute.
967
- timeout: Execution timeout in seconds (default: 120).
968
- max_turns: Maximum turns (default: 10).
969
- project_path: Path to project directory (pass cwd from CLI).
970
-
971
- Returns:
972
- Dict with worktree_id, run_id, and status.
973
- """
974
- if agent_runner is None:
975
- return {
976
- "success": False,
977
- "error": "Agent runner not configured. Cannot spawn agent.",
978
- }
979
-
980
- # Resolve project context
981
- resolved_git_mgr, resolved_project_id, error = _resolve_project_context(
982
- project_path, git_manager, project_id
983
- )
984
- if error:
985
- return {"success": False, "error": error}
986
-
987
- # Type narrowing: if no error, these are guaranteed non-None
988
- if resolved_git_mgr is None or resolved_project_id is None:
989
- raise RuntimeError("Git manager or project ID unexpectedly None")
990
-
991
- if parent_session_id is None:
992
- return {
993
- "success": False,
994
- "error": "parent_session_id is required for agent spawning.",
995
- }
996
-
997
- # Handle mode aliases and validation
998
- # "interactive" is an alias for "terminal" mode
999
- if mode == "interactive":
1000
- mode = "terminal"
1001
-
1002
- valid_modes = ["terminal", "embedded", "headless"]
1003
- if mode not in valid_modes:
1004
- return {
1005
- "success": False,
1006
- "error": (
1007
- f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)} (or 'interactive' as alias for 'terminal'). "
1008
- f"Note: 'in_process' mode is not supported for spawn_agent_in_worktree."
1009
- ),
1010
- }
1011
-
1012
- # Normalize terminal parameter to lowercase for enum compatibility
1013
- # (TerminalType enum values are lowercase, e.g., "terminal.app" not "Terminal.app")
1014
- if isinstance(terminal, str):
1015
- terminal = terminal.lower()
1016
-
1017
- # Validate workflow (reject lifecycle workflows)
1018
- if workflow:
1019
- workflow_loader = WorkflowLoader()
1020
- is_valid, error_msg = workflow_loader.validate_workflow_for_agent(
1021
- workflow, project_path=project_path
1022
- )
1023
- if not is_valid:
1024
- return {
1025
- "success": False,
1026
- "error": error_msg,
1027
- }
1028
-
1029
- # Check if worktree already exists for this branch
1030
- existing = worktree_storage.get_by_branch(resolved_project_id, branch_name)
1031
- if existing:
1032
- # Use existing worktree
1033
- worktree = existing
1034
- logger.info(f"Using existing worktree for branch '{branch_name}'")
1035
- else:
1036
- # Generate worktree path in temp directory
1037
- project_name = Path(resolved_git_mgr.repo_path).name
1038
- worktree_path = _generate_worktree_path(branch_name, project_name)
1039
-
1040
- # Create git worktree
1041
- result = resolved_git_mgr.create_worktree(
1042
- worktree_path=worktree_path,
1043
- branch_name=branch_name,
1044
- base_branch=base_branch,
1045
- create_branch=True,
1046
- )
1047
-
1048
- if not result.success:
1049
- return {
1050
- "success": False,
1051
- "error": result.error or "Failed to create git worktree",
1052
- }
1053
-
1054
- # Record in database
1055
- worktree = worktree_storage.create(
1056
- project_id=resolved_project_id,
1057
- branch_name=branch_name,
1058
- worktree_path=worktree_path,
1059
- base_branch=base_branch,
1060
- task_id=task_id,
1061
- )
1062
-
1063
- # Copy project.json and install provider hooks
1064
- _copy_project_json_to_worktree(resolved_git_mgr.repo_path, worktree.worktree_path)
1065
- _install_provider_hooks(provider, worktree.worktree_path)
1066
-
1067
- # Check spawn depth limit
1068
- can_spawn, reason, _depth = agent_runner.can_spawn(parent_session_id)
1069
- if not can_spawn:
1070
- return {
1071
- "success": False,
1072
- "error": reason,
1073
- "worktree_id": worktree.id,
1074
- }
1075
-
1076
- # Import AgentConfig and get machine_id
1077
- from gobby.agents.runner import AgentConfig
1078
- from gobby.utils.machine_id import get_machine_id
1079
-
1080
- # Auto-detect machine_id if not provided
1081
- machine_id = get_machine_id()
1082
-
1083
- # Create agent config with worktree
1084
- config = AgentConfig(
1085
- prompt=prompt,
1086
- parent_session_id=parent_session_id,
1087
- project_id=resolved_project_id,
1088
- machine_id=machine_id,
1089
- source=provider,
1090
- workflow=workflow,
1091
- task=task_id,
1092
- session_context="summary_markdown",
1093
- mode=mode,
1094
- terminal=terminal,
1095
- worktree_id=worktree.id,
1096
- provider=provider,
1097
- model=model,
1098
- max_turns=max_turns,
1099
- timeout=timeout,
1100
- project_path=worktree.worktree_path,
1101
- )
1102
-
1103
- # For terminal/embedded/headless modes, use prepare_run + spawner
1104
- # (runner.run() is only for in_process mode)
1105
- from gobby.llm.executor import AgentResult
1106
-
1107
- prepare_result = agent_runner.prepare_run(config)
1108
- if isinstance(prepare_result, AgentResult):
1109
- # prepare_run returns AgentResult on error
1110
- return {
1111
- "success": False,
1112
- "worktree_id": worktree.id,
1113
- "worktree_path": worktree.worktree_path,
1114
- "branch_name": worktree.branch_name,
1115
- "error": prepare_result.error,
1116
- }
1117
-
1118
- # Successfully prepared - we have context with session and run
1119
- context = prepare_result
1120
-
1121
- if context.session is None or context.run is None:
1122
- return {
1123
- "success": False,
1124
- "worktree_id": worktree.id,
1125
- "error": "Internal error: context missing session or run after prepare_run",
1126
- }
1127
-
1128
- child_session = context.session
1129
- agent_run = context.run
1130
-
1131
- # Claim worktree for the child session
1132
- worktree_storage.claim(worktree.id, child_session.id)
1133
-
1134
- # Pre-save workflow state with session_task if task_id is provided
1135
- # This ensures suggest_next_task() will scope to this task's subtasks
1136
- if task_id and workflow:
1137
- try:
1138
- workflow_state_manager = WorkflowStateManager(worktree_storage.db)
1139
- initial_state = WorkflowState(
1140
- session_id=child_session.id,
1141
- workflow_name=workflow,
1142
- step="", # Will be set when workflow actually starts
1143
- variables={"session_task": task_id},
1144
- )
1145
- workflow_state_manager.save_state(initial_state)
1146
- logger.debug(
1147
- f"Pre-saved workflow state for session {child_session.id} "
1148
- f"with session_task={task_id}"
1149
- )
1150
- except Exception as e:
1151
- logger.warning(f"Failed to pre-save workflow state: {e}")
1152
- # Continue anyway - this is an optimization, not a requirement
1153
-
1154
- # Build enhanced prompt with worktree context
1155
- # This helps the agent understand it's in an isolated worktree, not the main repo
1156
- enhanced_prompt = _build_worktree_context_prompt(
1157
- original_prompt=prompt,
1158
- worktree_path=worktree.worktree_path,
1159
- branch_name=worktree.branch_name,
1160
- task_id=task_id,
1161
- main_repo_path=str(resolved_git_mgr.repo_path),
1162
- )
1163
-
1164
- # Spawn in terminal using TerminalSpawner
1165
- if mode == "terminal":
1166
- from gobby.agents.spawn import TerminalSpawner
1167
-
1168
- terminal_spawner = TerminalSpawner()
1169
- terminal_result = terminal_spawner.spawn_agent(
1170
- cli=provider, # claude, gemini, codex
1171
- cwd=worktree.worktree_path,
1172
- session_id=child_session.id,
1173
- parent_session_id=parent_session_id,
1174
- agent_run_id=agent_run.id,
1175
- project_id=resolved_project_id,
1176
- workflow_name=workflow,
1177
- agent_depth=child_session.agent_depth,
1178
- max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
1179
- terminal=terminal,
1180
- prompt=enhanced_prompt,
1181
- )
1182
-
1183
- if not terminal_result.success:
1184
- return {
1185
- "success": False,
1186
- "worktree_id": worktree.id,
1187
- "worktree_path": worktree.worktree_path,
1188
- "branch_name": worktree.branch_name,
1189
- "run_id": agent_run.id,
1190
- "child_session_id": child_session.id,
1191
- "error": terminal_result.error or terminal_result.message,
1192
- }
1193
-
1194
- return {
1195
- "success": True,
1196
- "worktree_id": worktree.id,
1197
- "worktree_path": worktree.worktree_path,
1198
- "branch_name": worktree.branch_name,
1199
- "run_id": agent_run.id,
1200
- "child_session_id": child_session.id,
1201
- "status": "pending",
1202
- "message": f"Agent spawned in {terminal_result.terminal_type} (PID: {terminal_result.pid})",
1203
- "terminal_type": terminal_result.terminal_type,
1204
- "pid": terminal_result.pid,
1205
- }
1206
-
1207
- elif mode == "embedded":
1208
- from gobby.agents.spawn import EmbeddedSpawner
1209
-
1210
- embedded_spawner = EmbeddedSpawner()
1211
- embedded_result = embedded_spawner.spawn_agent(
1212
- cli=provider,
1213
- cwd=worktree.worktree_path,
1214
- session_id=child_session.id,
1215
- parent_session_id=parent_session_id,
1216
- agent_run_id=agent_run.id,
1217
- project_id=resolved_project_id,
1218
- workflow_name=workflow,
1219
- agent_depth=child_session.agent_depth,
1220
- max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
1221
- prompt=enhanced_prompt,
1222
- )
1223
-
1224
- return {
1225
- "success": embedded_result.success,
1226
- "worktree_id": worktree.id,
1227
- "worktree_path": worktree.worktree_path,
1228
- "branch_name": worktree.branch_name,
1229
- "run_id": agent_run.id,
1230
- "child_session_id": child_session.id,
1231
- "status": "pending" if embedded_result.success else "error",
1232
- "error": embedded_result.error if not embedded_result.success else None,
1233
- }
1234
-
1235
- else: # headless
1236
- from gobby.agents.spawn import HeadlessSpawner
1237
-
1238
- headless_spawner = HeadlessSpawner()
1239
- headless_result = headless_spawner.spawn_agent(
1240
- cli=provider,
1241
- cwd=worktree.worktree_path,
1242
- session_id=child_session.id,
1243
- parent_session_id=parent_session_id,
1244
- agent_run_id=agent_run.id,
1245
- project_id=resolved_project_id,
1246
- workflow_name=workflow,
1247
- agent_depth=child_session.agent_depth,
1248
- max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
1249
- prompt=enhanced_prompt,
1250
- )
1251
-
1252
- return {
1253
- "success": headless_result.success,
1254
- "worktree_id": worktree.id,
1255
- "worktree_path": worktree.worktree_path,
1256
- "branch_name": worktree.branch_name,
1257
- "run_id": agent_run.id,
1258
- "child_session_id": child_session.id,
1259
- "status": "pending" if headless_result.success else "error",
1260
- "pid": headless_result.pid if headless_result.success else None,
1261
- "error": headless_result.error if not headless_result.success else None,
1262
- }
1263
-
1264
926
  return registry
@@ -76,7 +76,12 @@ def get_backend(backend_type: str, **kwargs: Any) -> MemoryBackendProtocol:
76
76
  return NullBackend()
77
77
 
78
78
  elif backend_type == "mem0":
79
- from gobby.memory.backends.mem0 import Mem0Backend
79
+ try:
80
+ from gobby.memory.backends.mem0 import Mem0Backend
81
+ except ImportError as e:
82
+ raise ImportError(
83
+ "mem0ai is not installed. Install with: pip install gobby[mem0]"
84
+ ) from e
80
85
 
81
86
  api_key: str | None = kwargs.get("api_key")
82
87
  if api_key is None:
@@ -60,7 +60,12 @@ class Mem0Backend:
60
60
  **kwargs: Additional MemoryClient configuration
61
61
  """
62
62
  # Lazy import to avoid requiring mem0ai when not used
63
- from mem0 import MemoryClient
63
+ try:
64
+ from mem0 import MemoryClient
65
+ except ImportError as e:
66
+ raise ImportError(
67
+ "Mem0 backend requires 'mem0ai' package. Install it with: pip install gobby[mem0]"
68
+ ) from e
64
69
 
65
70
  self._client: MemoryClient = MemoryClient(api_key=api_key, **kwargs)
66
71
  self._default_user_id = user_id