gobby 0.2.6__py3-none-any.whl → 0.2.8__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 (198) hide show
  1. gobby/__init__.py +1 -1
  2. gobby/adapters/__init__.py +2 -1
  3. gobby/adapters/claude_code.py +96 -35
  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/adapters/gemini.py +140 -38
  10. gobby/agents/definitions.py +11 -1
  11. gobby/agents/isolation.py +525 -0
  12. gobby/agents/registry.py +11 -0
  13. gobby/agents/sandbox.py +261 -0
  14. gobby/agents/session.py +1 -0
  15. gobby/agents/spawn.py +42 -287
  16. gobby/agents/spawn_executor.py +415 -0
  17. gobby/agents/spawners/__init__.py +24 -0
  18. gobby/agents/spawners/command_builder.py +189 -0
  19. gobby/agents/spawners/embedded.py +21 -2
  20. gobby/agents/spawners/headless.py +21 -2
  21. gobby/agents/spawners/macos.py +26 -1
  22. gobby/agents/spawners/prompt_manager.py +125 -0
  23. gobby/cli/__init__.py +0 -2
  24. gobby/cli/install.py +4 -4
  25. gobby/cli/installers/claude.py +6 -0
  26. gobby/cli/installers/gemini.py +6 -0
  27. gobby/cli/installers/shared.py +103 -4
  28. gobby/cli/memory.py +185 -0
  29. gobby/cli/sessions.py +1 -1
  30. gobby/cli/utils.py +9 -2
  31. gobby/clones/git.py +177 -0
  32. gobby/config/__init__.py +12 -97
  33. gobby/config/app.py +10 -94
  34. gobby/config/extensions.py +2 -2
  35. gobby/config/features.py +7 -130
  36. gobby/config/skills.py +31 -0
  37. gobby/config/tasks.py +4 -28
  38. gobby/hooks/__init__.py +0 -13
  39. gobby/hooks/event_handlers.py +150 -8
  40. gobby/hooks/hook_manager.py +21 -3
  41. gobby/hooks/plugins.py +1 -1
  42. gobby/hooks/webhooks.py +1 -1
  43. gobby/install/gemini/hooks/hook_dispatcher.py +74 -15
  44. gobby/llm/resolver.py +3 -2
  45. gobby/mcp_proxy/importer.py +62 -4
  46. gobby/mcp_proxy/instructions.py +4 -2
  47. gobby/mcp_proxy/registries.py +22 -8
  48. gobby/mcp_proxy/services/recommendation.py +43 -11
  49. gobby/mcp_proxy/tools/agent_messaging.py +93 -44
  50. gobby/mcp_proxy/tools/agents.py +76 -740
  51. gobby/mcp_proxy/tools/artifacts.py +43 -9
  52. gobby/mcp_proxy/tools/clones.py +0 -385
  53. gobby/mcp_proxy/tools/memory.py +2 -2
  54. gobby/mcp_proxy/tools/sessions/__init__.py +14 -0
  55. gobby/mcp_proxy/tools/sessions/_commits.py +239 -0
  56. gobby/mcp_proxy/tools/sessions/_crud.py +253 -0
  57. gobby/mcp_proxy/tools/sessions/_factory.py +63 -0
  58. gobby/mcp_proxy/tools/sessions/_handoff.py +503 -0
  59. gobby/mcp_proxy/tools/sessions/_messages.py +166 -0
  60. gobby/mcp_proxy/tools/skills/__init__.py +14 -29
  61. gobby/mcp_proxy/tools/spawn_agent.py +455 -0
  62. gobby/mcp_proxy/tools/tasks/_context.py +18 -0
  63. gobby/mcp_proxy/tools/tasks/_crud.py +13 -6
  64. gobby/mcp_proxy/tools/tasks/_lifecycle.py +79 -30
  65. gobby/mcp_proxy/tools/tasks/_lifecycle_validation.py +1 -1
  66. gobby/mcp_proxy/tools/tasks/_session.py +22 -7
  67. gobby/mcp_proxy/tools/workflows.py +84 -34
  68. gobby/mcp_proxy/tools/worktrees.py +32 -350
  69. gobby/memory/extractor.py +15 -1
  70. gobby/memory/ingestion/__init__.py +5 -0
  71. gobby/memory/ingestion/multimodal.py +221 -0
  72. gobby/memory/manager.py +62 -283
  73. gobby/memory/search/__init__.py +10 -0
  74. gobby/memory/search/coordinator.py +248 -0
  75. gobby/memory/services/__init__.py +5 -0
  76. gobby/memory/services/crossref.py +142 -0
  77. gobby/prompts/loader.py +5 -2
  78. gobby/runner.py +13 -0
  79. gobby/servers/http.py +1 -4
  80. gobby/servers/routes/admin.py +14 -0
  81. gobby/servers/routes/mcp/endpoints/__init__.py +61 -0
  82. gobby/servers/routes/mcp/endpoints/discovery.py +405 -0
  83. gobby/servers/routes/mcp/endpoints/execution.py +568 -0
  84. gobby/servers/routes/mcp/endpoints/registry.py +378 -0
  85. gobby/servers/routes/mcp/endpoints/server.py +304 -0
  86. gobby/servers/routes/mcp/hooks.py +51 -4
  87. gobby/servers/routes/mcp/tools.py +48 -1506
  88. gobby/servers/websocket.py +57 -1
  89. gobby/sessions/analyzer.py +2 -2
  90. gobby/sessions/lifecycle.py +1 -1
  91. gobby/sessions/manager.py +9 -0
  92. gobby/sessions/processor.py +10 -0
  93. gobby/sessions/transcripts/base.py +1 -0
  94. gobby/sessions/transcripts/claude.py +15 -5
  95. gobby/sessions/transcripts/gemini.py +100 -34
  96. gobby/skills/parser.py +30 -2
  97. gobby/storage/database.py +9 -2
  98. gobby/storage/memories.py +32 -21
  99. gobby/storage/migrations.py +174 -368
  100. gobby/storage/sessions.py +45 -7
  101. gobby/storage/skills.py +80 -7
  102. gobby/storage/tasks/_lifecycle.py +18 -3
  103. gobby/sync/memories.py +1 -1
  104. gobby/tasks/external_validator.py +1 -1
  105. gobby/tasks/validation.py +22 -20
  106. gobby/tools/summarizer.py +91 -10
  107. gobby/utils/project_context.py +2 -3
  108. gobby/utils/status.py +13 -0
  109. gobby/workflows/actions.py +221 -1217
  110. gobby/workflows/artifact_actions.py +31 -0
  111. gobby/workflows/autonomous_actions.py +11 -0
  112. gobby/workflows/context_actions.py +50 -1
  113. gobby/workflows/detection_helpers.py +38 -24
  114. gobby/workflows/enforcement/__init__.py +47 -0
  115. gobby/workflows/enforcement/blocking.py +281 -0
  116. gobby/workflows/enforcement/commit_policy.py +283 -0
  117. gobby/workflows/enforcement/handlers.py +269 -0
  118. gobby/workflows/enforcement/task_policy.py +542 -0
  119. gobby/workflows/engine.py +93 -0
  120. gobby/workflows/evaluator.py +110 -0
  121. gobby/workflows/git_utils.py +106 -0
  122. gobby/workflows/hooks.py +41 -0
  123. gobby/workflows/llm_actions.py +30 -0
  124. gobby/workflows/mcp_actions.py +20 -1
  125. gobby/workflows/memory_actions.py +91 -0
  126. gobby/workflows/safe_evaluator.py +191 -0
  127. gobby/workflows/session_actions.py +44 -0
  128. gobby/workflows/state_actions.py +60 -1
  129. gobby/workflows/stop_signal_actions.py +55 -0
  130. gobby/workflows/summary_actions.py +217 -51
  131. gobby/workflows/task_sync_actions.py +347 -0
  132. gobby/workflows/todo_actions.py +34 -1
  133. gobby/workflows/webhook_actions.py +185 -0
  134. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/METADATA +6 -1
  135. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/RECORD +139 -163
  136. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/WHEEL +1 -1
  137. gobby/adapters/codex.py +0 -1332
  138. gobby/cli/tui.py +0 -34
  139. gobby/install/claude/commands/gobby/bug.md +0 -51
  140. gobby/install/claude/commands/gobby/chore.md +0 -51
  141. gobby/install/claude/commands/gobby/epic.md +0 -52
  142. gobby/install/claude/commands/gobby/eval.md +0 -235
  143. gobby/install/claude/commands/gobby/feat.md +0 -49
  144. gobby/install/claude/commands/gobby/nit.md +0 -52
  145. gobby/install/claude/commands/gobby/ref.md +0 -52
  146. gobby/mcp_proxy/tools/session_messages.py +0 -1055
  147. gobby/prompts/defaults/expansion/system.md +0 -119
  148. gobby/prompts/defaults/expansion/user.md +0 -48
  149. gobby/prompts/defaults/external_validation/agent.md +0 -72
  150. gobby/prompts/defaults/external_validation/external.md +0 -63
  151. gobby/prompts/defaults/external_validation/spawn.md +0 -83
  152. gobby/prompts/defaults/external_validation/system.md +0 -6
  153. gobby/prompts/defaults/features/import_mcp.md +0 -22
  154. gobby/prompts/defaults/features/import_mcp_github.md +0 -17
  155. gobby/prompts/defaults/features/import_mcp_search.md +0 -16
  156. gobby/prompts/defaults/features/recommend_tools.md +0 -32
  157. gobby/prompts/defaults/features/recommend_tools_hybrid.md +0 -35
  158. gobby/prompts/defaults/features/recommend_tools_llm.md +0 -30
  159. gobby/prompts/defaults/features/server_description.md +0 -20
  160. gobby/prompts/defaults/features/server_description_system.md +0 -6
  161. gobby/prompts/defaults/features/task_description.md +0 -31
  162. gobby/prompts/defaults/features/task_description_system.md +0 -6
  163. gobby/prompts/defaults/features/tool_summary.md +0 -17
  164. gobby/prompts/defaults/features/tool_summary_system.md +0 -6
  165. gobby/prompts/defaults/handoff/compact.md +0 -63
  166. gobby/prompts/defaults/handoff/session_end.md +0 -57
  167. gobby/prompts/defaults/memory/extract.md +0 -61
  168. gobby/prompts/defaults/research/step.md +0 -58
  169. gobby/prompts/defaults/validation/criteria.md +0 -47
  170. gobby/prompts/defaults/validation/validate.md +0 -38
  171. gobby/storage/migrations_legacy.py +0 -1359
  172. gobby/tui/__init__.py +0 -5
  173. gobby/tui/api_client.py +0 -278
  174. gobby/tui/app.py +0 -329
  175. gobby/tui/screens/__init__.py +0 -25
  176. gobby/tui/screens/agents.py +0 -333
  177. gobby/tui/screens/chat.py +0 -450
  178. gobby/tui/screens/dashboard.py +0 -377
  179. gobby/tui/screens/memory.py +0 -305
  180. gobby/tui/screens/metrics.py +0 -231
  181. gobby/tui/screens/orchestrator.py +0 -903
  182. gobby/tui/screens/sessions.py +0 -412
  183. gobby/tui/screens/tasks.py +0 -440
  184. gobby/tui/screens/workflows.py +0 -289
  185. gobby/tui/screens/worktrees.py +0 -174
  186. gobby/tui/widgets/__init__.py +0 -21
  187. gobby/tui/widgets/chat.py +0 -210
  188. gobby/tui/widgets/conductor.py +0 -104
  189. gobby/tui/widgets/menu.py +0 -132
  190. gobby/tui/widgets/message_panel.py +0 -160
  191. gobby/tui/widgets/review_gate.py +0 -224
  192. gobby/tui/widgets/task_tree.py +0 -99
  193. gobby/tui/widgets/token_budget.py +0 -166
  194. gobby/tui/ws_client.py +0 -258
  195. gobby/workflows/task_enforcement_actions.py +0 -1343
  196. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/entry_points.txt +0 -0
  197. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/licenses/LICENSE.md +0 -0
  198. {gobby-0.2.6.dist-info → gobby-0.2.8.dist-info}/top_level.txt +0 -0
@@ -216,52 +216,111 @@ def main() -> int:
216
216
  # This captures the terminal/process info for session correlation
217
217
  if hook_type == "SessionStart":
218
218
  input_data["terminal_context"] = get_terminal_context()
219
+ # Note: gobby_context (parent_session_id, workflow, etc.) is no longer
220
+ # injected from env vars. For spawned agents, the session is pre-created
221
+ # with all linkage via preflight+resume pattern, so the daemon already
222
+ # has the context when SessionStart fires.
219
223
 
220
224
  # Log what Gemini CLI sends us (for debugging hook data issues)
221
- logger.info(f"[{hook_type}] Received input keys: {list(input_data.keys())}")
225
+ # Extract common context fields for structured logging
226
+ session_id = input_data.get("session_id")
227
+ task_id = input_data.get("task_id")
228
+ project_id = input_data.get("project_id")
229
+ base_context = {
230
+ "hook_type": hook_type,
231
+ "session_id": session_id,
232
+ "task_id": task_id,
233
+ "project_id": project_id,
234
+ }
235
+
236
+ logger.info(
237
+ "[%s] Received input keys: %s",
238
+ hook_type,
239
+ list(input_data.keys()),
240
+ extra=base_context,
241
+ )
222
242
 
223
243
  # Log hook-specific critical fields
224
244
  if hook_type == "SessionStart":
225
- logger.info(f"[SessionStart] session_id={input_data.get('session_id')}")
245
+ logger.info(
246
+ "[SessionStart] session_id=%s",
247
+ session_id,
248
+ extra=base_context,
249
+ )
226
250
  elif hook_type == "SessionEnd":
251
+ reason = input_data.get("reason")
227
252
  logger.info(
228
- f"[SessionEnd] session_id={input_data.get('session_id')}, "
229
- f"reason={input_data.get('reason')}"
253
+ "[SessionEnd] session_id=%s, reason=%s",
254
+ session_id,
255
+ reason,
256
+ extra={**base_context, "reason": reason},
230
257
  )
231
258
  elif hook_type == "BeforeAgent":
232
259
  prompt = input_data.get("prompt", "")
233
260
  prompt_preview = prompt[:100] + "..." if len(prompt) > 100 else prompt
234
261
  logger.info(
235
- f"[BeforeAgent] session_id={input_data.get('session_id')}, prompt={prompt_preview}"
262
+ "[BeforeAgent] session_id=%s, prompt=%s",
263
+ session_id,
264
+ prompt_preview,
265
+ extra={**base_context, "prompt_preview": prompt_preview},
236
266
  )
237
267
  elif hook_type == "BeforeTool":
238
268
  tool_name = input_data.get("tool_name") or input_data.get("function_name", "unknown")
239
269
  logger.info(
240
- f"[BeforeTool] tool_name={tool_name}, session_id={input_data.get('session_id')}"
270
+ "[BeforeTool] tool_name=%s, session_id=%s",
271
+ tool_name,
272
+ session_id,
273
+ extra={**base_context, "tool_name": tool_name},
241
274
  )
242
275
  elif hook_type == "AfterTool":
243
276
  tool_name = input_data.get("tool_name") or input_data.get("function_name", "unknown")
277
+ error = input_data.get("error")
244
278
  logger.info(
245
- f"[AfterTool] tool_name={tool_name}, session_id={input_data.get('session_id')}"
279
+ "[AfterTool] tool_name=%s, session_id=%s",
280
+ tool_name,
281
+ session_id,
282
+ extra={**base_context, "tool_name": tool_name, "error": error},
246
283
  )
247
284
  elif hook_type == "BeforeToolSelection":
248
- logger.info(f"[BeforeToolSelection] session_id={input_data.get('session_id')}")
285
+ logger.info(
286
+ "[BeforeToolSelection] session_id=%s",
287
+ session_id,
288
+ extra=base_context,
289
+ )
249
290
  elif hook_type == "BeforeModel":
291
+ model = input_data.get("model", "unknown")
250
292
  logger.info(
251
- f"[BeforeModel] session_id={input_data.get('session_id')}, "
252
- f"model={input_data.get('model', 'unknown')}"
293
+ "[BeforeModel] session_id=%s, model=%s",
294
+ session_id,
295
+ model,
296
+ extra={**base_context, "model": model},
253
297
  )
254
298
  elif hook_type == "AfterModel":
255
- logger.info(f"[AfterModel] session_id={input_data.get('session_id')}")
299
+ logger.info(
300
+ "[AfterModel] session_id=%s",
301
+ session_id,
302
+ extra=base_context,
303
+ )
256
304
  elif hook_type == "PreCompress":
257
- logger.info(f"[PreCompress] session_id={input_data.get('session_id')}")
305
+ logger.info(
306
+ "[PreCompress] session_id=%s",
307
+ session_id,
308
+ extra=base_context,
309
+ )
258
310
  elif hook_type == "Notification":
311
+ message = input_data.get("message")
259
312
  logger.info(
260
- f"[Notification] session_id={input_data.get('session_id')}, "
261
- f"message={input_data.get('message')}"
313
+ "[Notification] session_id=%s, message=%s",
314
+ session_id,
315
+ message,
316
+ extra={**base_context, "notification_message": message},
262
317
  )
263
318
  elif hook_type == "AfterAgent":
264
- logger.info(f"[AfterAgent] session_id={input_data.get('session_id')}")
319
+ logger.info(
320
+ "[AfterAgent] session_id=%s",
321
+ session_id,
322
+ extra=base_context,
323
+ )
265
324
 
266
325
  if debug_mode:
267
326
  logger.debug(f"Input data: {input_data}")
gobby/llm/resolver.py CHANGED
@@ -18,7 +18,8 @@ from typing import TYPE_CHECKING, Literal
18
18
  from gobby.llm.executor import AgentExecutor
19
19
 
20
20
  if TYPE_CHECKING:
21
- from gobby.config.app import DaemonConfig, LLMProvidersConfig
21
+ from gobby.config.app import DaemonConfig
22
+ from gobby.config.llm_providers import LLMProvidersConfig
22
23
  from gobby.workflows.definitions import WorkflowDefinition
23
24
 
24
25
  logger = logging.getLogger(__name__)
@@ -479,7 +480,7 @@ def _create_codex_executor(
479
480
 
480
481
  # Re-export for TYPE_CHECKING
481
482
  if TYPE_CHECKING:
482
- from gobby.config.app import LLMProviderConfig
483
+ from gobby.config.llm_providers import LLMProviderConfig
483
484
 
484
485
 
485
486
  class ExecutorRegistry:
@@ -5,6 +5,8 @@ import re
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from gobby.config.app import DaemonConfig
8
+ from gobby.config.features import DEFAULT_IMPORT_MCP_SERVER_PROMPT
9
+ from gobby.prompts import PromptLoader
8
10
  from gobby.storage.database import DatabaseProtocol
9
11
  from gobby.storage.mcp import LocalMCPManager
10
12
  from gobby.storage.projects import LocalProjectManager
@@ -18,6 +20,21 @@ logger = logging.getLogger(__name__)
18
20
  # Pattern to detect placeholder secrets like <YOUR_API_KEY>
19
21
  SECRET_PLACEHOLDER_PATTERN = re.compile(r"<YOUR_[A-Z0-9_]+>")
20
22
 
23
+ DEFAULT_GITHUB_FETCH_PROMPT = """Fetch the README from this GitHub repository and extract MCP server configuration:
24
+
25
+ {github_url}
26
+
27
+ If the URL doesn't point directly to a README, try to find and fetch the README.md file.
28
+
29
+ After reading the documentation, extract the MCP server configuration as a JSON object."""
30
+
31
+ DEFAULT_SEARCH_FETCH_PROMPT = """Search for MCP server: {search_query}
32
+
33
+ Find the official documentation or GitHub repository for this MCP server.
34
+ Then fetch and read the README or installation docs.
35
+
36
+ After reading the documentation, extract the MCP server configuration as a JSON object."""
37
+
21
38
 
22
39
  class MCPServerImporter:
23
40
  """Handles importing MCP servers from various sources."""
@@ -46,6 +63,21 @@ class MCPServerImporter:
46
63
  self.mcp_client_manager = mcp_client_manager
47
64
  self.import_config = config.get_import_mcp_server_config()
48
65
 
66
+ # Initialize prompt loader
67
+ project_path = None
68
+ if current_project_id:
69
+ if project := self.project_manager.get(current_project_id):
70
+ project_path = project.repo_path
71
+
72
+ from pathlib import Path
73
+
74
+ self._loader = PromptLoader(project_dir=Path(project_path) if project_path else None)
75
+
76
+ # Register fallbacks
77
+ self._loader.register_fallback("import/github_fetch", lambda: DEFAULT_GITHUB_FETCH_PROMPT)
78
+ self._loader.register_fallback("import/search_fetch", lambda: DEFAULT_SEARCH_FETCH_PROMPT)
79
+ self._loader.register_fallback("import/system", lambda: DEFAULT_IMPORT_MCP_SERVER_PROMPT)
80
+
49
81
  async def import_from_project(
50
82
  self,
51
83
  source_project: str,
@@ -171,10 +203,23 @@ class MCPServerImporter:
171
203
  from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query
172
204
 
173
205
  # Build prompt to fetch and extract config
174
- prompt = self.import_config.github_fetch_prompt.format(github_url=github_url)
206
+ prompt_path = self.import_config.github_fetch_prompt_path or "import/github_fetch"
207
+ try:
208
+ prompt = self._loader.render(prompt_path, {"github_url": github_url})
209
+ except Exception as e:
210
+ logger.warning(f"Failed to load Github fetch prompt: {e}")
211
+ prompt = DEFAULT_GITHUB_FETCH_PROMPT.format(github_url=github_url)
212
+
213
+ # Get system prompt
214
+ sys_prompt_path = self.import_config.prompt_path or "import/system"
215
+ try:
216
+ system_prompt = self._loader.render(sys_prompt_path, {})
217
+ except Exception as e:
218
+ logger.warning(f"Failed to load import system prompt: {e}")
219
+ system_prompt = DEFAULT_IMPORT_MCP_SERVER_PROMPT
175
220
 
176
221
  options = ClaudeAgentOptions(
177
- system_prompt=self.import_config.prompt,
222
+ system_prompt=system_prompt,
178
223
  max_turns=3,
179
224
  model=self.import_config.model,
180
225
  allowed_tools=["WebFetch"],
@@ -222,10 +267,23 @@ class MCPServerImporter:
222
267
  from claude_agent_sdk import AssistantMessage, ClaudeAgentOptions, TextBlock, query
223
268
 
224
269
  # Build prompt to search and extract config
225
- prompt = self.import_config.search_fetch_prompt.format(search_query=search_query)
270
+ prompt_path = self.import_config.search_fetch_prompt_path or "import/search_fetch"
271
+ try:
272
+ prompt = self._loader.render(prompt_path, {"search_query": search_query})
273
+ except Exception as e:
274
+ logger.warning(f"Failed to load search fetch prompt: {e}")
275
+ prompt = DEFAULT_SEARCH_FETCH_PROMPT.format(search_query=search_query)
276
+
277
+ # Get system prompt
278
+ sys_prompt_path = self.import_config.prompt_path or "import/system"
279
+ try:
280
+ system_prompt = self._loader.render(sys_prompt_path, {})
281
+ except Exception as e:
282
+ logger.warning(f"Failed to load import system prompt: {e}")
283
+ system_prompt = DEFAULT_IMPORT_MCP_SERVER_PROMPT
226
284
 
227
285
  options = ClaudeAgentOptions(
228
- system_prompt=self.import_config.prompt,
286
+ system_prompt=system_prompt,
229
287
  max_turns=5, # More turns for search + fetch
230
288
  model=self.import_config.model,
231
289
  allowed_tools=["WebSearch", "WebFetch"],
@@ -26,9 +26,11 @@ def build_gobby_instructions() -> str:
26
26
  At the start of EVERY session:
27
27
  1. `list_mcp_servers()` — Discover available servers
28
28
  2. `list_skills()` — Discover available skills
29
- 3. Session ID: Look for `session_id: <uuid>` in your context.
29
+ 3. Session ID: Look for `Gobby Session Ref:` or `Gobby Session ID:` in your context.
30
30
  If missing, call:
31
- `call_tool("gobby-sessions", "get_current", {"external_id": "<your-session-id>", "source": "claude"})`
31
+ `call_tool("gobby-sessions", "get_current_session", {"external_id": "<your-session-id>", "source": "<cli-name>"})`
32
+
33
+ Session and task references use `#N` format (e.g., `#1`, `#42`) which is project-scoped.
32
34
  </startup>
33
35
 
34
36
  <tool_discovery>
@@ -114,7 +114,7 @@ def setup_internal_registries(
114
114
  # Initialize sessions registry (messages + session CRUD)
115
115
  # Register if either message_manager or local_session_manager is available
116
116
  if message_manager is not None or local_session_manager is not None:
117
- from gobby.mcp_proxy.tools.session_messages import create_session_messages_registry
117
+ from gobby.mcp_proxy.tools.sessions import create_session_messages_registry
118
118
 
119
119
  session_messages_registry = create_session_messages_registry(
120
120
  message_manager=message_manager,
@@ -168,22 +168,38 @@ def setup_internal_registries(
168
168
 
169
169
  # Initialize agents registry if agent_runner is available
170
170
  if agent_runner is not None:
171
- from gobby.agents.registry import get_running_agent_registry
171
+ from gobby.agents.definitions import AgentDefinitionLoader
172
172
  from gobby.mcp_proxy.tools.agents import create_agents_registry
173
173
 
174
+ # Create clone git manager if we have a git manager
175
+ clone_git_manager = None
176
+ if git_manager is not None:
177
+ try:
178
+ from gobby.clones.git import CloneGitManager
179
+
180
+ clone_git_manager = CloneGitManager(git_manager.repo_path)
181
+ except Exception as e:
182
+ logger.debug(f"CloneGitManager not available for spawn_agent: {e}")
183
+
174
184
  agents_registry = create_agents_registry(
175
185
  runner=agent_runner,
176
- tool_proxy_getter=tool_proxy_getter,
186
+ agent_loader=AgentDefinitionLoader(),
187
+ session_manager=local_session_manager,
188
+ task_manager=task_manager,
189
+ worktree_storage=worktree_storage,
190
+ git_manager=git_manager,
191
+ clone_storage=clone_storage,
192
+ clone_manager=clone_git_manager,
177
193
  )
178
194
 
179
- # Add inter-agent messaging tools if message manager is available
180
- if inter_session_message_manager is not None:
195
+ # Add inter-agent messaging tools if message manager and session manager are available
196
+ if inter_session_message_manager is not None and local_session_manager is not None:
181
197
  from gobby.mcp_proxy.tools.agent_messaging import add_messaging_tools
182
198
 
183
199
  add_messaging_tools(
184
200
  registry=agents_registry,
185
201
  message_manager=inter_session_message_manager,
186
- agent_registry=get_running_agent_registry(),
202
+ session_manager=local_session_manager,
187
203
  )
188
204
  logger.debug("Agent messaging tools added to agents registry")
189
205
 
@@ -198,7 +214,6 @@ def setup_internal_registries(
198
214
  worktree_storage=worktree_storage,
199
215
  git_manager=git_manager,
200
216
  project_id=project_id,
201
- agent_runner=agent_runner,
202
217
  )
203
218
  manager.add_registry(worktrees_registry)
204
219
  logger.debug("Worktrees registry initialized")
@@ -222,7 +237,6 @@ def setup_internal_registries(
222
237
  clone_storage=clone_storage,
223
238
  git_manager=clone_git_manager,
224
239
  project_id=project_id or "",
225
- agent_runner=agent_runner,
226
240
  )
227
241
  manager.add_registry(clones_registry)
228
242
  logger.debug("Clones registry initialized")
@@ -7,13 +7,30 @@ import logging
7
7
  from typing import TYPE_CHECKING, Any, Literal
8
8
 
9
9
  if TYPE_CHECKING:
10
- from gobby.config.app import RecommendToolsConfig
10
+ from gobby.config.features import RecommendToolsConfig
11
+ from gobby.prompts import PromptLoader
11
12
 
12
13
  logger = logging.getLogger("gobby.mcp.server")
13
14
 
14
15
  # Search mode type
15
16
  SearchMode = Literal["llm", "semantic", "hybrid"]
16
17
 
18
+ DEFAULT_HYBRID_RERANK_PROMPT = """Re-rank the following tools for the task: "{task_description}"
19
+
20
+ Candidates:
21
+ {candidate_list}
22
+
23
+ Select the best {top_k} tools. Return JSON:
24
+ {{"recommendations": [{{"server": "...", "tool": "...", "reason": "..."}}]}}"""
25
+
26
+ DEFAULT_LLM_PROMPT = """Recommend tools for the task: "{task_description}"
27
+
28
+ Available Servers:
29
+ {available_servers}
30
+
31
+ Return JSON:
32
+ {{"recommendations": [{{"server": "...", "tool": "...", "reason": "..."}}]}}"""
33
+
17
34
 
18
35
  class RecommendationService:
19
36
  """Service for recommending tools."""
@@ -31,12 +48,17 @@ class RecommendationService:
31
48
  self._semantic_search = semantic_search
32
49
  self._project_id = project_id
33
50
  self._config = config
51
+ self._loader = PromptLoader()
52
+ self._loader.register_fallback(
53
+ "features/recommend_hybrid", lambda: DEFAULT_HYBRID_RERANK_PROMPT
54
+ )
55
+ self._loader.register_fallback("features/recommend_llm", lambda: DEFAULT_LLM_PROMPT)
34
56
 
35
57
  def _get_config(self) -> RecommendToolsConfig:
36
58
  """Get config with fallback to defaults."""
37
59
  if self._config is not None:
38
60
  return self._config
39
- from gobby.config.app import RecommendToolsConfig
61
+ from gobby.config.features import RecommendToolsConfig
40
62
 
41
63
  return RecommendToolsConfig()
42
64
 
@@ -153,11 +175,16 @@ class RecommendationService:
153
175
  for c in candidates
154
176
  )
155
177
 
156
- prompt = config.hybrid_rerank_prompt.format(
157
- task_description=task_description,
158
- candidate_list=candidate_list,
159
- top_k=top_k,
160
- )
178
+ prompt_path = config.hybrid_rerank_prompt_path or "features/recommend_hybrid"
179
+ context = {
180
+ "task_description": task_description,
181
+ "candidate_list": candidate_list,
182
+ "top_k": top_k,
183
+ }
184
+ try:
185
+ prompt = self._loader.render(prompt_path, context)
186
+ except Exception:
187
+ prompt = DEFAULT_HYBRID_RERANK_PROMPT.format(**context)
161
188
 
162
189
  provider = self._llm_service.get_default_provider()
163
190
  response = await provider.generate_text(prompt)
@@ -191,10 +218,15 @@ class RecommendationService:
191
218
  config = self._get_config()
192
219
  available_servers = self._mcp_manager.get_available_servers()
193
220
 
194
- prompt = config.llm_prompt.format(
195
- task_description=task_description,
196
- available_servers=", ".join(available_servers),
197
- )
221
+ prompt_path = config.llm_prompt_path or "features/recommend_llm"
222
+ context = {
223
+ "task_description": task_description,
224
+ "available_servers": ", ".join(available_servers),
225
+ }
226
+ try:
227
+ prompt = self._loader.render(prompt_path, context)
228
+ except Exception:
229
+ prompt = DEFAULT_LLM_PROMPT.format(**context)
198
230
 
199
231
  provider = self._llm_service.get_default_provider()
200
232
  response = await provider.generate_text(prompt)