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,746 @@
1
+ """Task orchestration tool: orchestrate_ready_tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING, Any, Literal
7
+
8
+ from gobby.mcp_proxy.tools.internal import InternalToolRegistry
9
+ from gobby.mcp_proxy.tools.task_readiness import _get_ready_descendants
10
+ from gobby.storage.tasks import TaskNotFoundError
11
+
12
+ from .utils import get_current_project_id
13
+
14
+ if TYPE_CHECKING:
15
+ from gobby.agents.runner import AgentRunner
16
+ from gobby.storage.tasks import LocalTaskManager
17
+ from gobby.storage.worktrees import LocalWorktreeManager
18
+ from gobby.worktrees.git import WorktreeGitManager
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def register_orchestrator(
24
+ registry: InternalToolRegistry,
25
+ task_manager: LocalTaskManager,
26
+ worktree_storage: LocalWorktreeManager,
27
+ git_manager: WorktreeGitManager | None = None,
28
+ agent_runner: AgentRunner | None = None,
29
+ default_project_id: str | None = None,
30
+ ) -> None:
31
+ """Register orchestrate_ready_tasks tool."""
32
+
33
+ # Lazy import to avoid circular dependency
34
+ from gobby.mcp_proxy.tools.tasks import resolve_task_id_for_mcp
35
+
36
+ async def orchestrate_ready_tasks(
37
+ parent_task_id: str,
38
+ provider: Literal["claude", "gemini", "codex", "antigravity"] = "gemini",
39
+ model: str | None = None,
40
+ terminal: str = "auto",
41
+ mode: str = "terminal",
42
+ workflow: str | None = "auto-task",
43
+ max_concurrent: int = 3,
44
+ parent_session_id: str | None = None,
45
+ project_path: str | None = None,
46
+ coding_provider: Literal["claude", "gemini", "codex", "antigravity"] | None = None,
47
+ coding_model: str | None = None,
48
+ base_branch: str | None = None,
49
+ ) -> dict[str, Any]:
50
+ """
51
+ Orchestrate spawning agents in worktrees for ready subtasks.
52
+
53
+ Gets ready subtasks under a parent task, creates worktrees for each,
54
+ and spawns agents to work on them. Returns list of spawned agent/worktree pairs.
55
+
56
+ Used by the auto-orchestrator workflow to parallelize work.
57
+
58
+ Provider assignment:
59
+ - coding_provider/coding_model: Use these for implementation tasks (preferred)
60
+ - provider/model: Fallback if coding_* not specified
61
+
62
+ Args:
63
+ parent_task_id: Task reference: #N, N (seq_num), path (1.2.3), or UUID
64
+ provider: Fallback LLM provider (default: gemini)
65
+ model: Fallback model override
66
+ terminal: Terminal for terminal mode (default: auto)
67
+ mode: Execution mode (terminal, embedded, headless)
68
+ workflow: Workflow for spawned agents (default: auto-task)
69
+ max_concurrent: Maximum concurrent agents to spawn (default: 3)
70
+ parent_session_id: Parent session ID for context (required)
71
+ project_path: Path to project directory
72
+ coding_provider: LLM provider for implementation tasks (overrides provider)
73
+ coding_model: Model for implementation tasks (overrides model)
74
+ base_branch: Branch to base worktrees on (auto-detected if not provided)
75
+
76
+ Returns:
77
+ Dict with:
78
+ - success: bool
79
+ - spawned: List of {task_id, agent_id, worktree_id, branch_name}
80
+ - skipped: List of {task_id, reason} for tasks not spawned
81
+ - error: Optional error message
82
+ """
83
+ # Validate mode parameter
84
+ valid_modes = {"terminal", "headless", "embedded"}
85
+ if mode not in valid_modes:
86
+ return {
87
+ "success": False,
88
+ "error": f"Invalid mode '{mode}'. Must be one of: {', '.join(sorted(valid_modes))}",
89
+ "spawned": [],
90
+ "skipped": [],
91
+ }
92
+
93
+ # Resolve parent_task_id reference
94
+ try:
95
+ resolved_parent_task_id = resolve_task_id_for_mcp(task_manager, parent_task_id)
96
+ except (TaskNotFoundError, ValueError) as e:
97
+ return {
98
+ "success": False,
99
+ "error": f"Invalid parent_task_id: {e}",
100
+ "spawned": [],
101
+ "skipped": [],
102
+ }
103
+
104
+ if agent_runner is None:
105
+ return {
106
+ "success": False,
107
+ "error": "Agent runner not configured. Cannot orchestrate.",
108
+ "spawned": [],
109
+ "skipped": [],
110
+ }
111
+
112
+ if parent_session_id is None:
113
+ return {
114
+ "success": False,
115
+ "error": "parent_session_id is required for orchestration",
116
+ "spawned": [],
117
+ "skipped": [],
118
+ }
119
+
120
+ # Resolve project ID
121
+ resolved_project_id = default_project_id
122
+ if project_path:
123
+ from pathlib import Path
124
+
125
+ from gobby.utils.project_context import get_project_context
126
+
127
+ ctx = get_project_context(Path(project_path))
128
+ if ctx:
129
+ resolved_project_id = ctx.get("id")
130
+
131
+ if not resolved_project_id:
132
+ resolved_project_id = get_current_project_id()
133
+
134
+ if not resolved_project_id:
135
+ return {
136
+ "success": False,
137
+ "error": "Could not resolve project ID",
138
+ "spawned": [],
139
+ "skipped": [],
140
+ }
141
+
142
+ # Get ready subtasks under the parent task
143
+ ready_tasks = _get_ready_descendants(
144
+ task_manager=task_manager,
145
+ parent_task_id=resolved_parent_task_id,
146
+ project_id=resolved_project_id,
147
+ )
148
+
149
+ if not ready_tasks:
150
+ return {
151
+ "success": True,
152
+ "message": f"No ready subtasks found under {resolved_parent_task_id}",
153
+ "spawned": [],
154
+ "skipped": [],
155
+ }
156
+
157
+ # Check how many agents are currently running for this project
158
+ # Get exact count by not limiting the query - ensures max_concurrent is respected
159
+ from gobby.storage.worktrees import WorktreeStatus
160
+
161
+ active_worktrees = worktree_storage.list_worktrees(
162
+ project_id=resolved_project_id,
163
+ status=WorktreeStatus.ACTIVE.value,
164
+ )
165
+
166
+ # Count worktrees claimed by active sessions (have agent_session_id)
167
+ current_running = sum(1 for wt in active_worktrees if wt.agent_session_id)
168
+ available_slots = max(0, max_concurrent - current_running)
169
+
170
+ if available_slots == 0:
171
+ return {
172
+ "success": True,
173
+ "message": f"Max concurrent limit reached ({max_concurrent} agents running)",
174
+ "spawned": [],
175
+ "skipped": [
176
+ {"task_id": t.id, "reason": "max_concurrent limit reached"} for t in ready_tasks
177
+ ],
178
+ "current_running": current_running,
179
+ }
180
+
181
+ # Limit to available slots
182
+ tasks_to_spawn = ready_tasks[:available_slots]
183
+ tasks_skipped = ready_tasks[available_slots:]
184
+
185
+ # Resolve effective provider and model for implementation tasks
186
+ # Priority: explicit parameter > workflow variable > default
187
+ from gobby.workflows.state_manager import WorkflowStateManager
188
+
189
+ workflow_vars: dict[str, Any] = {}
190
+ if parent_session_id:
191
+ state_manager = WorkflowStateManager(task_manager.db)
192
+ state = state_manager.get_state(parent_session_id)
193
+ if state:
194
+ workflow_vars = state.variables
195
+
196
+ # Provider assignment chain: parameter > workflow var > default
197
+ effective_provider = coding_provider or workflow_vars.get("coding_provider") or provider
198
+ effective_model = coding_model or workflow_vars.get("coding_model") or model
199
+ # Also capture terminal from workflow if not explicitly set
200
+ effective_terminal = (
201
+ terminal if terminal != "auto" else workflow_vars.get("terminal", "auto")
202
+ )
203
+
204
+ spawned: list[dict[str, Any]] = []
205
+ skipped: list[dict[str, Any]] = []
206
+
207
+ # Add skipped due to limit
208
+ for task in tasks_skipped:
209
+ skipped.append(
210
+ {
211
+ "task_id": task.id,
212
+ "title": task.title,
213
+ "reason": "max_concurrent limit reached",
214
+ }
215
+ )
216
+
217
+ # Import worktree tool helpers
218
+ import platform
219
+ import tempfile
220
+ from pathlib import Path
221
+
222
+ from gobby.mcp_proxy.tools.worktrees import (
223
+ _copy_project_json_to_worktree,
224
+ _install_provider_hooks,
225
+ )
226
+ from gobby.workflows.loader import WorkflowLoader
227
+
228
+ def get_worktree_base_dir() -> Path:
229
+ """Get base directory for worktrees."""
230
+ if platform.system() == "Windows":
231
+ base = Path(tempfile.gettempdir()) / "gobby-worktrees"
232
+ else:
233
+ # nosec B108: /tmp is intentional - worktrees are temporary
234
+ base = Path("/tmp").resolve() / "gobby-worktrees" # nosec B108
235
+ base.mkdir(parents=True, exist_ok=True)
236
+ return base
237
+
238
+ # Detect base_branch if not provided
239
+ effective_base_branch = base_branch
240
+ if not effective_base_branch and git_manager is not None:
241
+ effective_base_branch = git_manager.get_default_branch()
242
+ logger.debug(f"Auto-detected base branch: {effective_base_branch}")
243
+ elif not effective_base_branch:
244
+ effective_base_branch = "main" # Fallback when no git_manager
245
+
246
+ for task in tasks_to_spawn:
247
+ try:
248
+ # Generate branch name from task ID
249
+ branch_name = f"task/{task.id}"
250
+ safe_branch = branch_name.replace("/", "-")
251
+
252
+ # Check if worktree already exists for this task
253
+ existing_wt = worktree_storage.get_by_task(task.id)
254
+ if existing_wt and existing_wt.agent_session_id:
255
+ # Worktree exists and has active agent
256
+ skipped.append(
257
+ {
258
+ "task_id": task.id,
259
+ "title": task.title,
260
+ "reason": f"Already has active worktree: {existing_wt.id}",
261
+ }
262
+ )
263
+ continue
264
+
265
+ # Check if branch worktree exists
266
+ existing_branch_wt = worktree_storage.get_by_branch(
267
+ resolved_project_id, branch_name
268
+ )
269
+ if existing_branch_wt and existing_branch_wt.agent_session_id:
270
+ skipped.append(
271
+ {
272
+ "task_id": task.id,
273
+ "title": task.title,
274
+ "reason": f"Branch {branch_name} has active agent",
275
+ }
276
+ )
277
+ continue
278
+
279
+ # Validate workflow early (before creating worktree to avoid cleanup)
280
+ if workflow:
281
+ workflow_loader = WorkflowLoader()
282
+ is_valid, error_msg = workflow_loader.validate_workflow_for_agent(
283
+ workflow, project_path=project_path
284
+ )
285
+ if not is_valid:
286
+ skipped.append(
287
+ {
288
+ "task_id": task.id,
289
+ "title": task.title,
290
+ "reason": f"Invalid workflow: {error_msg}",
291
+ }
292
+ )
293
+ continue
294
+
295
+ # Determine worktree path
296
+ newly_created_worktree = False
297
+ if existing_wt:
298
+ worktree = existing_wt
299
+ elif existing_branch_wt:
300
+ worktree = existing_branch_wt
301
+ # Link task to existing worktree
302
+ worktree_storage.update(worktree.id, task_id=task.id)
303
+ else:
304
+ # Create new worktree
305
+ if git_manager is None:
306
+ skipped.append(
307
+ {
308
+ "task_id": task.id,
309
+ "title": task.title,
310
+ "reason": "No git manager configured",
311
+ }
312
+ )
313
+ continue
314
+
315
+ # Generate path
316
+ project_name = Path(git_manager.repo_path).name
317
+ base_dir = get_worktree_base_dir()
318
+ worktree_path = str(base_dir / project_name / safe_branch)
319
+
320
+ # Create git worktree
321
+ result = git_manager.create_worktree(
322
+ worktree_path=worktree_path,
323
+ branch_name=branch_name,
324
+ base_branch=effective_base_branch,
325
+ create_branch=True,
326
+ )
327
+
328
+ if not result.success:
329
+ skipped.append(
330
+ {
331
+ "task_id": task.id,
332
+ "title": task.title,
333
+ "reason": f"Failed to create worktree: {result.error}",
334
+ }
335
+ )
336
+ continue
337
+
338
+ # Record in database
339
+ worktree = worktree_storage.create(
340
+ project_id=resolved_project_id,
341
+ branch_name=branch_name,
342
+ worktree_path=worktree_path,
343
+ base_branch=effective_base_branch,
344
+ task_id=task.id,
345
+ )
346
+
347
+ # Copy project.json and install hooks (with cleanup on failure)
348
+ try:
349
+ _copy_project_json_to_worktree(
350
+ git_manager.repo_path, worktree.worktree_path
351
+ )
352
+ _install_provider_hooks(effective_provider, worktree.worktree_path)
353
+ except Exception as init_error:
354
+ # Cleanup: delete DB record and git worktree
355
+ worktree_storage.delete(worktree.id)
356
+ git_manager.delete_worktree(
357
+ worktree_path=worktree_path,
358
+ force=True,
359
+ delete_branch=True,
360
+ )
361
+ skipped.append(
362
+ {
363
+ "task_id": task.id,
364
+ "title": task.title,
365
+ "reason": f"Worktree initialization failed: {init_error}",
366
+ }
367
+ )
368
+ continue
369
+ newly_created_worktree = True
370
+
371
+ # Build prompt with task context
372
+ prompt = _build_task_prompt(task)
373
+
374
+ # Check spawn depth
375
+ can_spawn, reason, _depth = agent_runner.can_spawn(parent_session_id)
376
+ if not can_spawn:
377
+ skipped.append(
378
+ {
379
+ "task_id": task.id,
380
+ "title": task.title,
381
+ "reason": reason,
382
+ }
383
+ )
384
+ continue
385
+
386
+ # Prepare agent run
387
+ from gobby.agents.runner import AgentConfig
388
+ from gobby.llm.executor import AgentResult
389
+ from gobby.utils.machine_id import get_machine_id
390
+
391
+ machine_id = get_machine_id()
392
+
393
+ config = AgentConfig(
394
+ prompt=prompt,
395
+ parent_session_id=parent_session_id,
396
+ project_id=resolved_project_id,
397
+ machine_id=machine_id,
398
+ source=effective_provider,
399
+ workflow=workflow,
400
+ task=task.id,
401
+ session_context="summary_markdown",
402
+ mode=mode,
403
+ terminal=effective_terminal,
404
+ worktree_id=worktree.id,
405
+ provider=effective_provider,
406
+ model=effective_model,
407
+ max_turns=50, # Allow substantial work
408
+ timeout=600.0, # 10 minutes
409
+ project_path=worktree.worktree_path,
410
+ )
411
+
412
+ prepare_result = agent_runner.prepare_run(config)
413
+ if isinstance(prepare_result, AgentResult):
414
+ skipped.append(
415
+ {
416
+ "task_id": task.id,
417
+ "title": task.title,
418
+ "reason": prepare_result.error or "Failed to prepare agent run",
419
+ }
420
+ )
421
+ continue
422
+
423
+ context = prepare_result
424
+ if context.session is None or context.run is None:
425
+ skipped.append(
426
+ {
427
+ "task_id": task.id,
428
+ "title": task.title,
429
+ "reason": "Internal error: context missing session or run",
430
+ }
431
+ )
432
+ continue
433
+
434
+ child_session = context.session
435
+ agent_run = context.run
436
+
437
+ # Claim worktree for child session
438
+ worktree_storage.claim(worktree.id, child_session.id)
439
+
440
+ # Note: Task status is updated to in_progress only after successful spawn
441
+
442
+ # Spawn in terminal
443
+ if mode == "terminal":
444
+ from gobby.agents.spawn import TerminalSpawner
445
+
446
+ spawner = TerminalSpawner()
447
+ spawn_result = spawner.spawn_agent(
448
+ cli=effective_provider,
449
+ cwd=worktree.worktree_path,
450
+ session_id=child_session.id,
451
+ parent_session_id=parent_session_id,
452
+ agent_run_id=agent_run.id,
453
+ project_id=resolved_project_id,
454
+ workflow_name=workflow,
455
+ agent_depth=child_session.agent_depth,
456
+ max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
457
+ terminal=effective_terminal,
458
+ prompt=prompt,
459
+ )
460
+
461
+ if not spawn_result.success:
462
+ worktree_storage.release(worktree.id)
463
+ # Clean up newly created worktree on spawn failure
464
+ if newly_created_worktree and git_manager is not None:
465
+ worktree_storage.delete(worktree.id)
466
+ git_manager.delete_worktree(
467
+ worktree_path=worktree.worktree_path,
468
+ force=True,
469
+ delete_branch=True,
470
+ )
471
+ skipped.append(
472
+ {
473
+ "task_id": task.id,
474
+ "title": task.title,
475
+ "reason": spawn_result.error or "Terminal spawn failed",
476
+ }
477
+ )
478
+ continue
479
+
480
+ # Mark task as in_progress only after successful spawn
481
+ task_manager.update_task(task.id, status="in_progress")
482
+
483
+ spawned.append(
484
+ {
485
+ "task_id": task.id,
486
+ "title": task.title,
487
+ "agent_id": agent_run.id,
488
+ "session_id": child_session.id,
489
+ "worktree_id": worktree.id,
490
+ "branch_name": worktree.branch_name,
491
+ "worktree_path": worktree.worktree_path,
492
+ "terminal_type": spawn_result.terminal_type,
493
+ "pid": spawn_result.pid,
494
+ }
495
+ )
496
+
497
+ elif mode == "embedded":
498
+ from gobby.agents.spawn import EmbeddedSpawner
499
+
500
+ embedded_spawner = EmbeddedSpawner()
501
+ embedded_result = embedded_spawner.spawn_agent(
502
+ cli=effective_provider,
503
+ cwd=worktree.worktree_path,
504
+ session_id=child_session.id,
505
+ parent_session_id=parent_session_id,
506
+ agent_run_id=agent_run.id,
507
+ project_id=resolved_project_id,
508
+ workflow_name=workflow,
509
+ agent_depth=child_session.agent_depth,
510
+ max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
511
+ prompt=prompt,
512
+ )
513
+
514
+ if not embedded_result.success:
515
+ worktree_storage.release(worktree.id)
516
+ # Clean up newly created worktree on spawn failure
517
+ if newly_created_worktree and git_manager is not None:
518
+ worktree_storage.delete(worktree.id)
519
+ git_manager.delete_worktree(
520
+ worktree_path=worktree.worktree_path,
521
+ force=True,
522
+ delete_branch=True,
523
+ )
524
+ skipped.append(
525
+ {
526
+ "task_id": task.id,
527
+ "title": task.title,
528
+ "reason": embedded_result.error or "Embedded spawn failed",
529
+ }
530
+ )
531
+ continue
532
+
533
+ # Mark task as in_progress only after successful spawn
534
+ task_manager.update_task(task.id, status="in_progress")
535
+
536
+ spawned.append(
537
+ {
538
+ "task_id": task.id,
539
+ "title": task.title,
540
+ "agent_id": agent_run.id,
541
+ "session_id": child_session.id,
542
+ "worktree_id": worktree.id,
543
+ "branch_name": worktree.branch_name,
544
+ "worktree_path": worktree.worktree_path,
545
+ }
546
+ )
547
+
548
+ else: # headless
549
+ from gobby.agents.spawn import HeadlessSpawner
550
+
551
+ headless_spawner = HeadlessSpawner()
552
+ headless_result = headless_spawner.spawn_agent(
553
+ cli=effective_provider,
554
+ cwd=worktree.worktree_path,
555
+ session_id=child_session.id,
556
+ parent_session_id=parent_session_id,
557
+ agent_run_id=agent_run.id,
558
+ project_id=resolved_project_id,
559
+ workflow_name=workflow,
560
+ agent_depth=child_session.agent_depth,
561
+ max_agent_depth=agent_runner._child_session_manager.max_agent_depth,
562
+ prompt=prompt,
563
+ )
564
+
565
+ if not headless_result.success:
566
+ worktree_storage.release(worktree.id)
567
+ # Clean up newly created worktree on spawn failure
568
+ if newly_created_worktree and git_manager is not None:
569
+ worktree_storage.delete(worktree.id)
570
+ git_manager.delete_worktree(
571
+ worktree_path=worktree.worktree_path,
572
+ force=True,
573
+ delete_branch=True,
574
+ )
575
+ skipped.append(
576
+ {
577
+ "task_id": task.id,
578
+ "title": task.title,
579
+ "reason": headless_result.error or "Headless spawn failed",
580
+ }
581
+ )
582
+ continue
583
+
584
+ # Mark task as in_progress only after successful spawn
585
+ task_manager.update_task(task.id, status="in_progress")
586
+
587
+ spawned.append(
588
+ {
589
+ "task_id": task.id,
590
+ "title": task.title,
591
+ "agent_id": agent_run.id,
592
+ "session_id": child_session.id,
593
+ "worktree_id": worktree.id,
594
+ "branch_name": worktree.branch_name,
595
+ "worktree_path": worktree.worktree_path,
596
+ "pid": headless_result.pid,
597
+ }
598
+ )
599
+
600
+ except Exception as e:
601
+ logger.exception(f"Error orchestrating task {task.id}")
602
+ skipped.append(
603
+ {
604
+ "task_id": task.id,
605
+ "title": task.title,
606
+ "reason": str(e),
607
+ }
608
+ )
609
+
610
+ # Store spawned agents in workflow state for tracking
611
+ if spawned and parent_session_id:
612
+ try:
613
+ state_manager = WorkflowStateManager(task_manager.db)
614
+ state = state_manager.get_state(parent_session_id)
615
+ if state:
616
+ current_spawned = state.variables.get("spawned_agents", [])
617
+ # Append new agents to existing list
618
+ current_spawned.extend(spawned)
619
+ state.variables["spawned_agents"] = current_spawned
620
+ state_manager.save_state(state)
621
+ logger.info(
622
+ f"Updated spawned_agents in workflow state: {len(current_spawned)} total"
623
+ )
624
+ except Exception as e:
625
+ logger.warning(f"Failed to update workflow state: {e}")
626
+
627
+ return {
628
+ "success": True,
629
+ "parent_task_id": resolved_parent_task_id,
630
+ "spawned": spawned,
631
+ "skipped": skipped,
632
+ "spawned_count": len(spawned),
633
+ "skipped_count": len(skipped),
634
+ "current_running": current_running + len(spawned),
635
+ "max_concurrent": max_concurrent,
636
+ }
637
+
638
+ registry.register(
639
+ name="orchestrate_ready_tasks",
640
+ description=(
641
+ "Spawn agents in worktrees for ready subtasks under a parent task. "
642
+ "Used by auto-orchestrator workflow for parallel execution. "
643
+ "Supports role-based provider assignment: explicitly passed params > workflow variables > defaults. "
644
+ "Workflow variables: coding_provider, coding_model, terminal."
645
+ ),
646
+ input_schema={
647
+ "type": "object",
648
+ "properties": {
649
+ "parent_task_id": {
650
+ "type": "string",
651
+ "description": "Task reference: #N, N (seq_num), path (1.2.3), or UUID",
652
+ },
653
+ "provider": {
654
+ "type": "string",
655
+ "description": "Fallback LLM provider (claude, gemini, codex, antigravity)",
656
+ "default": "gemini",
657
+ },
658
+ "model": {
659
+ "type": "string",
660
+ "description": "Fallback model override",
661
+ "default": None,
662
+ },
663
+ "coding_provider": {
664
+ "type": "string",
665
+ "description": (
666
+ "LLM provider for implementation tasks. "
667
+ "Overrides 'provider' for coding work."
668
+ ),
669
+ "default": None,
670
+ },
671
+ "coding_model": {
672
+ "type": "string",
673
+ "description": (
674
+ "Model for implementation tasks. Overrides 'model' for coding work."
675
+ ),
676
+ "default": None,
677
+ },
678
+ "terminal": {
679
+ "type": "string",
680
+ "description": "Terminal for terminal mode (auto, ghostty, iterm2, etc.)",
681
+ "default": "auto",
682
+ },
683
+ "mode": {
684
+ "type": "string",
685
+ "description": "Execution mode (terminal, embedded, headless)",
686
+ "default": "terminal",
687
+ },
688
+ "workflow": {
689
+ "type": "string",
690
+ "description": "Workflow for spawned agents",
691
+ "default": "auto-task",
692
+ },
693
+ "max_concurrent": {
694
+ "type": "integer",
695
+ "description": "Maximum concurrent agents to spawn",
696
+ "default": 3,
697
+ },
698
+ "parent_session_id": {
699
+ "type": "string",
700
+ "description": "Parent session ID for context (required)",
701
+ },
702
+ "project_path": {
703
+ "type": "string",
704
+ "description": "Path to project directory",
705
+ "default": None,
706
+ },
707
+ "base_branch": {
708
+ "type": "string",
709
+ "description": (
710
+ "Branch to base worktrees on (e.g., main, master, develop). "
711
+ "Auto-detected from repository if not provided."
712
+ ),
713
+ "default": None,
714
+ },
715
+ },
716
+ "required": ["parent_task_id", "parent_session_id"],
717
+ },
718
+ func=orchestrate_ready_tasks,
719
+ )
720
+
721
+
722
+ def _build_task_prompt(task: Any) -> str:
723
+ """Build a prompt for a task agent."""
724
+ prompt_parts = [
725
+ f"# Task: {task.title}",
726
+ f"Task ID: {task.id}",
727
+ ]
728
+
729
+ if task.description:
730
+ prompt_parts.append(f"\n## Description\n{task.description}")
731
+
732
+ if task.category:
733
+ prompt_parts.append(f"\n## Category\n{task.category}")
734
+
735
+ if task.validation_criteria:
736
+ prompt_parts.append(f"\n## Validation Criteria\n{task.validation_criteria}")
737
+
738
+ prompt_parts.append(
739
+ "\n## Instructions\n"
740
+ "1. Implement the task as described\n"
741
+ "2. Write tests if applicable\n"
742
+ f"3. Commit your changes with the task ID in the message: [{task.id}]\n"
743
+ "4. Close the task when complete using close_task(commit_sha=...)"
744
+ )
745
+
746
+ return "\n".join(prompt_parts)