vidbyte-cli 0.1.0__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 (269) hide show
  1. vidbyte_cli/__init__.py +11 -0
  2. vidbyte_cli/__main__.py +6 -0
  3. vidbyte_cli/cli.py +18 -0
  4. vidbyte_cli/commands/__init__.py +86 -0
  5. vidbyte_cli/commands/agent_options.py +53 -0
  6. vidbyte_cli/commands/agents/__init__.py +31 -0
  7. vidbyte_cli/commands/agents/suggestion/__init__.py +38 -0
  8. vidbyte_cli/commands/agents/suggestion/prompts/__init__.py +3 -0
  9. vidbyte_cli/commands/agents/suggestion/prompts/all_categories.md +5 -0
  10. vidbyte_cli/commands/agents/suggestion/prompts/approaches.md +100 -0
  11. vidbyte_cli/commands/agents/suggestion/prompts/avoid.md +100 -0
  12. vidbyte_cli/commands/agents/suggestion/prompts/blockers.md +100 -0
  13. vidbyte_cli/commands/agents/suggestion/prompts/capability.md +100 -0
  14. vidbyte_cli/commands/agents/suggestion/prompts/categories.md +5 -0
  15. vidbyte_cli/commands/agents/suggestion/prompts/category.md +5 -0
  16. vidbyte_cli/commands/agents/suggestion/prompts/completed.md +100 -0
  17. vidbyte_cli/commands/agents/suggestion/prompts/constraint.md +100 -0
  18. vidbyte_cli/commands/agents/suggestion/prompts/context.md +100 -0
  19. vidbyte_cli/commands/agents/suggestion/prompts/count.md +5 -0
  20. vidbyte_cli/commands/agents/suggestion/prompts/critic_model.md +100 -0
  21. vidbyte_cli/commands/agents/suggestion/prompts/decision.md +100 -0
  22. vidbyte_cli/commands/agents/suggestion/prompts/dry_run.md +5 -0
  23. vidbyte_cli/commands/agents/suggestion/prompts/extra_compute.md +5 -0
  24. vidbyte_cli/commands/agents/suggestion/prompts/feedback_accept.md +7 -0
  25. vidbyte_cli/commands/agents/suggestion/prompts/feedback_group.md +7 -0
  26. vidbyte_cli/commands/agents/suggestion/prompts/feedback_project.md +100 -0
  27. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reason.md +98 -0
  28. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reject.md +7 -0
  29. vidbyte_cli/commands/agents/suggestion/prompts/feedback_suggestion.md +101 -0
  30. vidbyte_cli/commands/agents/suggestion/prompts/files.md +100 -0
  31. vidbyte_cli/commands/agents/suggestion/prompts/forbidden.md +100 -0
  32. vidbyte_cli/commands/agents/suggestion/prompts/goal.md +100 -0
  33. vidbyte_cli/commands/agents/suggestion/prompts/handoff.md +7 -0
  34. vidbyte_cli/commands/agents/suggestion/prompts/horizon.md +5 -0
  35. vidbyte_cli/commands/agents/suggestion/prompts/hypotheses.md +100 -0
  36. vidbyte_cli/commands/agents/suggestion/prompts/idea.md +100 -0
  37. vidbyte_cli/commands/agents/suggestion/prompts/in_progress.md +100 -0
  38. vidbyte_cli/commands/agents/suggestion/prompts/input.md +100 -0
  39. vidbyte_cli/commands/agents/suggestion/prompts/library.py +29 -0
  40. vidbyte_cli/commands/agents/suggestion/prompts/max_messages.md +5 -0
  41. vidbyte_cli/commands/agents/suggestion/prompts/max_output_tokens.md +5 -0
  42. vidbyte_cli/commands/agents/suggestion/prompts/max_total_tokens.md +5 -0
  43. vidbyte_cli/commands/agents/suggestion/prompts/mistakes.md +100 -0
  44. vidbyte_cli/commands/agents/suggestion/prompts/outcomes.md +100 -0
  45. vidbyte_cli/commands/agents/suggestion/prompts/project.md +100 -0
  46. vidbyte_cli/commands/agents/suggestion/prompts/project_create.md +7 -0
  47. vidbyte_cli/commands/agents/suggestion/prompts/project_description.md +98 -0
  48. vidbyte_cli/commands/agents/suggestion/prompts/project_group.md +7 -0
  49. vidbyte_cli/commands/agents/suggestion/prompts/project_key.md +96 -0
  50. vidbyte_cli/commands/agents/suggestion/prompts/project_list.md +7 -0
  51. vidbyte_cli/commands/agents/suggestion/prompts/project_title.md +99 -0
  52. vidbyte_cli/commands/agents/suggestion/prompts/provider.md +5 -0
  53. vidbyte_cli/commands/agents/suggestion/prompts/question.md +100 -0
  54. vidbyte_cli/commands/agents/suggestion/prompts/risks.md +100 -0
  55. vidbyte_cli/commands/agents/suggestion/prompts/rounds.md +5 -0
  56. vidbyte_cli/commands/agents/suggestion/prompts/run.md +5 -0
  57. vidbyte_cli/commands/agents/suggestion/prompts/success.md +100 -0
  58. vidbyte_cli/commands/agents/suggestion/prompts/timeout.md +5 -0
  59. vidbyte_cli/commands/agents/suggestion/prompts/trajectory.md +100 -0
  60. vidbyte_cli/commands/agents/suggestion/prompts/view.md +5 -0
  61. vidbyte_cli/commands/agents/suggestion/prompts/view_all.md +5 -0
  62. vidbyte_cli/commands/agents/suggestion/render.py +77 -0
  63. vidbyte_cli/commands/agents/suggestion/request_builder.py +240 -0
  64. vidbyte_cli/commands/agents/suggestion/suggest.py +154 -0
  65. vidbyte_cli/commands/agents/suggestion/suggestion_categories.py +77 -0
  66. vidbyte_cli/commands/agents/suggestion/suggestion_feedback.py +85 -0
  67. vidbyte_cli/commands/agents/suggestion/suggestion_handoff.py +67 -0
  68. vidbyte_cli/commands/agents/suggestion/suggestion_projects.py +95 -0
  69. vidbyte_cli/commands/auth/__init__.py +0 -0
  70. vidbyte_cli/commands/auth/login.py +100 -0
  71. vidbyte_cli/commands/auth/logout.py +41 -0
  72. vidbyte_cli/commands/auth/whoami.py +51 -0
  73. vidbyte_cli/commands/billing/__init__.py +20 -0
  74. vidbyte_cli/commands/billing/prompts/__init__.py +1 -0
  75. vidbyte_cli/commands/billing/prompts/confirm.md +16 -0
  76. vidbyte_cli/commands/billing/prompts/group.md +13 -0
  77. vidbyte_cli/commands/billing/prompts/idempotency_key.md +16 -0
  78. vidbyte_cli/commands/billing/prompts/library.py +18 -0
  79. vidbyte_cli/commands/billing/prompts/top_up.md +16 -0
  80. vidbyte_cli/commands/billing/top_up.py +65 -0
  81. vidbyte_cli/commands/config/__init__.py +0 -0
  82. vidbyte_cli/commands/config/get.py +45 -0
  83. vidbyte_cli/commands/config/set.py +39 -0
  84. vidbyte_cli/commands/provider/__init__.py +9 -0
  85. vidbyte_cli/commands/provider/login.py +102 -0
  86. vidbyte_cli/commands/provider/logout.py +45 -0
  87. vidbyte_cli/commands/provider/whoami.py +49 -0
  88. vidbyte_cli/commands/research/__init__.py +5 -0
  89. vidbyte_cli/commands/research/add.py +41 -0
  90. vidbyte_cli/commands/research/options.py +134 -0
  91. vidbyte_cli/commands/research/render.py +157 -0
  92. vidbyte_cli/commands/research/resume.py +37 -0
  93. vidbyte_cli/commands/research/start.py +37 -0
  94. vidbyte_cli/commands/research/status.py +32 -0
  95. vidbyte_cli/commands/research/thread.py +30 -0
  96. vidbyte_cli/commands/research/threads.py +35 -0
  97. vidbyte_cli/commands/research/watch.py +93 -0
  98. vidbyte_cli/commands/runtime/__init__.py +23 -0
  99. vidbyte_cli/commands/runtime/adversarial_team.py +41 -0
  100. vidbyte_cli/commands/runtime/doctor.py +44 -0
  101. vidbyte_cli/commands/runtime/list.py +47 -0
  102. vidbyte_cli/commands/runtime/persistence.py +141 -0
  103. vidbyte_cli/commands/runtime/same_host_ensemble.py +221 -0
  104. vidbyte_cli/commands/runtime/stages.py +553 -0
  105. vidbyte_cli/commands/runtime/task_board.py +1342 -0
  106. vidbyte_cli/commands/setup/__init__.py +0 -0
  107. vidbyte_cli/commands/setup/doctor.py +64 -0
  108. vidbyte_cli/commands/setup/prompts/__init__.py +1 -0
  109. vidbyte_cli/commands/setup/prompts/browser.md +7 -0
  110. vidbyte_cli/commands/setup/prompts/library.py +18 -0
  111. vidbyte_cli/commands/setup/prompts/star.md +13 -0
  112. vidbyte_cli/commands/setup/star.py +132 -0
  113. vidbyte_cli/lib/__init__.py +0 -0
  114. vidbyte_cli/lib/api/__init__.py +0 -0
  115. vidbyte_cli/lib/api/client.py +236 -0
  116. vidbyte_cli/lib/api/endpoints/__init__.py +0 -0
  117. vidbyte_cli/lib/api/endpoints/auth.py +24 -0
  118. vidbyte_cli/lib/api/endpoints/billing.py +36 -0
  119. vidbyte_cli/lib/api/endpoints/research.py +103 -0
  120. vidbyte_cli/lib/api/endpoints/runtime.py +113 -0
  121. vidbyte_cli/lib/api/problem.py +86 -0
  122. vidbyte_cli/lib/api/response.py +101 -0
  123. vidbyte_cli/lib/api/retry.py +116 -0
  124. vidbyte_cli/lib/api/runtime_payment.py +89 -0
  125. vidbyte_cli/lib/auth/__init__.py +16 -0
  126. vidbyte_cli/lib/auth/credentials.py +223 -0
  127. vidbyte_cli/lib/auth/input.py +59 -0
  128. vidbyte_cli/lib/auth/keyring_store.py +126 -0
  129. vidbyte_cli/lib/auth/provider_credentials.py +157 -0
  130. vidbyte_cli/lib/auth/provider_input.py +54 -0
  131. vidbyte_cli/lib/auth/provider_resolver.py +89 -0
  132. vidbyte_cli/lib/auth/provider_store.py +149 -0
  133. vidbyte_cli/lib/auth/provider_verifier.py +219 -0
  134. vidbyte_cli/lib/auth/resolver.py +63 -0
  135. vidbyte_cli/lib/auth/verifier.py +38 -0
  136. vidbyte_cli/lib/config/__init__.py +25 -0
  137. vidbyte_cli/lib/config/atomic.py +47 -0
  138. vidbyte_cli/lib/config/config.py +141 -0
  139. vidbyte_cli/lib/config/migration.py +81 -0
  140. vidbyte_cli/lib/config/models.py +145 -0
  141. vidbyte_cli/lib/config/paths.py +66 -0
  142. vidbyte_cli/lib/config/resolver.py +137 -0
  143. vidbyte_cli/lib/constants/__init__.py +1 -0
  144. vidbyte_cli/lib/constants/runtime.py +186 -0
  145. vidbyte_cli/lib/errors/__init__.py +12 -0
  146. vidbyte_cli/lib/errors/cli_error.py +86 -0
  147. vidbyte_cli/lib/errors/codes.py +43 -0
  148. vidbyte_cli/lib/errors/failures.py +2910 -0
  149. vidbyte_cli/lib/errors/handler.py +72 -0
  150. vidbyte_cli/lib/files/__init__.py +6 -0
  151. vidbyte_cli/lib/files/documents.py +94 -0
  152. vidbyte_cli/lib/files/store.py +156 -0
  153. vidbyte_cli/lib/io/__init__.py +16 -0
  154. vidbyte_cli/lib/io/attachments.py +129 -0
  155. vidbyte_cli/lib/io/codex_attachments.py +47 -0
  156. vidbyte_cli/lib/io/prompt.py +71 -0
  157. vidbyte_cli/lib/io/streams.py +37 -0
  158. vidbyte_cli/lib/io/terminal.py +66 -0
  159. vidbyte_cli/lib/output/__init__.py +7 -0
  160. vidbyte_cli/lib/output/formats.py +29 -0
  161. vidbyte_cli/lib/output/manager.py +95 -0
  162. vidbyte_cli/lib/output/models.py +49 -0
  163. vidbyte_cli/lib/runtime/__init__.py +9 -0
  164. vidbyte_cli/lib/runtime/application.py +134 -0
  165. vidbyte_cli/lib/runtime/context.py +250 -0
  166. vidbyte_cli/lib/runtime/options.py +159 -0
  167. vidbyte_cli/lib/runtime/version.py +30 -0
  168. vidbyte_cli/lib/runtime_primitives/__init__.py +28 -0
  169. vidbyte_cli/lib/runtime_primitives/executor.py +54 -0
  170. vidbyte_cli/lib/runtime_primitives/gate.py +105 -0
  171. vidbyte_cli/lib/runtime_primitives/hosts.py +45 -0
  172. vidbyte_cli/lib/runtime_primitives/planner.py +79 -0
  173. vidbyte_cli/lib/runtime_primitives/stages.py +338 -0
  174. vidbyte_cli/lib/runtime_primitives/task_board.py +982 -0
  175. vidbyte_cli/lib/runtime_primitives/task_board_checkpoints.py +473 -0
  176. vidbyte_cli/lib/runtime_primitives/task_board_system.md +33 -0
  177. vidbyte_cli/lib/runtime_primitives/task_board_tasks.py +111 -0
  178. vidbyte_cli/lib/runtime_primitives/verification.py +68 -0
  179. vidbyte_cli/services/__init__.py +5 -0
  180. vidbyte_cli/services/ensemble/__init__.py +7 -0
  181. vidbyte_cli/services/ensemble/prompts/__init__.py +6 -0
  182. vidbyte_cli/services/ensemble/prompts/implementer_system.md +546 -0
  183. vidbyte_cli/services/ensemble/prompts/implementer_turn.md +49 -0
  184. vidbyte_cli/services/ensemble/prompts/library.py +188 -0
  185. vidbyte_cli/services/ensemble/prompts/planner_system.md +728 -0
  186. vidbyte_cli/services/ensemble/prompts/planner_turn.md +6 -0
  187. vidbyte_cli/services/ensemble/prompts/role_system.md +104 -0
  188. vidbyte_cli/services/ensemble/prompts/role_turn.md +19 -0
  189. vidbyte_cli/services/ensemble/prompts/selector_final_turn.md +19 -0
  190. vidbyte_cli/services/ensemble/prompts/selector_round_turn.md +17 -0
  191. vidbyte_cli/services/ensemble/prompts/selector_system.md +89 -0
  192. vidbyte_cli/services/ensemble/runner.py +30 -0
  193. vidbyte_cli/services/ensemble/sdk.py +134 -0
  194. vidbyte_cli/services/ensemble/service.py +233 -0
  195. vidbyte_cli/services/ensemble/settings.py +60 -0
  196. vidbyte_cli/services/persistence/__init__.py +6 -0
  197. vidbyte_cli/services/persistence/prompts/__init__.py +6 -0
  198. vidbyte_cli/services/persistence/prompts/library.py +46 -0
  199. vidbyte_cli/services/persistence/prompts/persistence_system.md +1 -0
  200. vidbyte_cli/services/persistence/prompts/persistence_turn.md +3 -0
  201. vidbyte_cli/services/persistence/runner.py +35 -0
  202. vidbyte_cli/services/persistence/session.py +116 -0
  203. vidbyte_cli/services/suggestions/__init__.py +7 -0
  204. vidbyte_cli/services/suggestions/categories.py +270 -0
  205. vidbyte_cli/services/suggestions/context.py +222 -0
  206. vidbyte_cli/services/suggestions/extra_compute.py +53 -0
  207. vidbyte_cli/services/suggestions/handoff.py +173 -0
  208. vidbyte_cli/services/suggestions/message_tools.py +47 -0
  209. vidbyte_cli/services/suggestions/project.py +253 -0
  210. vidbyte_cli/services/suggestions/prompts/__init__.py +1 -0
  211. vidbyte_cli/services/suggestions/prompts/categories/__init__.py +3 -0
  212. vidbyte_cli/services/suggestions/prompts/categories/adjacent_opportunity.md +37 -0
  213. vidbyte_cli/services/suggestions/prompts/categories/alternative.md +38 -0
  214. vidbyte_cli/services/suggestions/prompts/categories/big_bets.md +37 -0
  215. vidbyte_cli/services/suggestions/prompts/categories/bottleneck.md +38 -0
  216. vidbyte_cli/services/suggestions/prompts/categories/business_growth.md +38 -0
  217. vidbyte_cli/services/suggestions/prompts/categories/completion.md +37 -0
  218. vidbyte_cli/services/suggestions/prompts/categories/continuation.md +37 -0
  219. vidbyte_cli/services/suggestions/prompts/categories/coordination.md +37 -0
  220. vidbyte_cli/services/suggestions/prompts/categories/creative_exploration.md +37 -0
  221. vidbyte_cli/services/suggestions/prompts/categories/cross_domain.md +37 -0
  222. vidbyte_cli/services/suggestions/prompts/categories/delegation.md +37 -0
  223. vidbyte_cli/services/suggestions/prompts/categories/experiment.md +37 -0
  224. vidbyte_cli/services/suggestions/prompts/categories/feedback.md +37 -0
  225. vidbyte_cli/services/suggestions/prompts/categories/goal_clarification.md +37 -0
  226. vidbyte_cli/services/suggestions/prompts/categories/immediate_next_steps.md +37 -0
  227. vidbyte_cli/services/suggestions/prompts/categories/investigation.md +37 -0
  228. vidbyte_cli/services/suggestions/prompts/categories/learning.md +37 -0
  229. vidbyte_cli/services/suggestions/prompts/categories/leverage.md +37 -0
  230. vidbyte_cli/services/suggestions/prompts/categories/long_term_directions.md +37 -0
  231. vidbyte_cli/services/suggestions/prompts/categories/preparation.md +37 -0
  232. vidbyte_cli/services/suggestions/prompts/categories/prerequisite.md +37 -0
  233. vidbyte_cli/services/suggestions/prompts/categories/prioritization.md +37 -0
  234. vidbyte_cli/services/suggestions/prompts/categories/product_experience.md +37 -0
  235. vidbyte_cli/services/suggestions/prompts/categories/quick_wins.md +37 -0
  236. vidbyte_cli/services/suggestions/prompts/categories/reframing.md +37 -0
  237. vidbyte_cli/services/suggestions/prompts/categories/risk_prevention.md +37 -0
  238. vidbyte_cli/services/suggestions/prompts/categories/simplification.md +37 -0
  239. vidbyte_cli/services/suggestions/prompts/categories/stop_or_defer.md +37 -0
  240. vidbyte_cli/services/suggestions/prompts/categories/strategy.md +37 -0
  241. vidbyte_cli/services/suggestions/prompts/categories/technical_possibilities.md +37 -0
  242. vidbyte_cli/services/suggestions/prompts/categories/verification.md +37 -0
  243. vidbyte_cli/services/suggestions/prompts/critic.md +209 -0
  244. vidbyte_cli/services/suggestions/prompts/critic_message.md +8 -0
  245. vidbyte_cli/services/suggestions/prompts/generator.md +64 -0
  246. vidbyte_cli/services/suggestions/prompts/library.py +72 -0
  247. vidbyte_cli/services/suggestions/prompts/message_generator_argument.md +1 -0
  248. vidbyte_cli/services/suggestions/prompts/message_generator_tool.md +1 -0
  249. vidbyte_cli/services/suggestions/prompts/message_parent_argument.md +1 -0
  250. vidbyte_cli/services/suggestions/prompts/message_parent_tool.md +1 -0
  251. vidbyte_cli/services/suggestions/prompts/message_receipt.md +1 -0
  252. vidbyte_cli/services/suggestions/prompts/revision.md +8 -0
  253. vidbyte_cli/services/suggestions/sdk.py +228 -0
  254. vidbyte_cli/services/suggestions/selection.py +78 -0
  255. vidbyte_cli/services/suggestions/service.py +540 -0
  256. vidbyte_cli/types/__init__.py +0 -0
  257. vidbyte_cli/types/api.py +101 -0
  258. vidbyte_cli/types/attachments.py +79 -0
  259. vidbyte_cli/types/ensemble.py +189 -0
  260. vidbyte_cli/types/provider.py +62 -0
  261. vidbyte_cli/types/research.py +260 -0
  262. vidbyte_cli/types/runtime.py +1098 -0
  263. vidbyte_cli/types/suggestions.py +518 -0
  264. vidbyte_cli-0.1.0.dist-info/METADATA +335 -0
  265. vidbyte_cli-0.1.0.dist-info/RECORD +269 -0
  266. vidbyte_cli-0.1.0.dist-info/WHEEL +5 -0
  267. vidbyte_cli-0.1.0.dist-info/entry_points.txt +2 -0
  268. vidbyte_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  269. vidbyte_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,338 @@
1
+ """Runs caller-defined stages with one fresh Codex agent per stage attempt.
2
+
3
+ The SDK owns transport and thread identity. This adapter owns stage fan-out, sequential
4
+ handoff through the previous reply, and rejection of incomplete results. Every stage
5
+ setting arrives here already validated as a CLI enum or a settings-checked value, so this
6
+ module converts rather than parses: the closed value sets are enforced at the command
7
+ layer where Click can reject a bad word before the wallet is ever touched.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+ import json
14
+ import random
15
+ import time
16
+ from collections.abc import Callable, Mapping
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ from ...types.runtime import RuntimeLaunchPlan as Plan
20
+ from ...types.runtime import (
21
+ StageSpec,
22
+ StagesResult,
23
+ StagesSettings,
24
+ StageStepResult,
25
+ )
26
+ from ..constants.runtime import StagesCodexConfig as CodexConfig
27
+ from ..constants.runtime import StagesLimit, StagesProgress
28
+ from ..errors.failures import StagesHostFailed, StagesSettingsInvalid
29
+
30
+ if TYPE_CHECKING:
31
+ from vidbyte.agents.codex import CodexHarnessAgent
32
+ from vidbyte.agents.types import AgentMessage
33
+ from vidbyte.lib.dataclasses.codex import CodexHarnessAgentSettings
34
+
35
+
36
+ class StagesCodexSession:
37
+ """One invocation's environment and lazy per-stage SDK agents."""
38
+
39
+ def __init__(self, environment: Mapping[str, str], progress: Callable[[str], None]) -> None:
40
+ # Construction stays free of SDK imports so help never needs Codex.
41
+ self._environment = dict(environment)
42
+ self._progress = progress
43
+ self._executable = ""
44
+ self._working_directory = ""
45
+ self._prepared = False
46
+
47
+ def prepare(self, plan: Plan, settings: StagesSettings) -> None:
48
+ # Records launch paths without starting any model turn, so every failure the
49
+ # caller can cause is still ahead of payment. Agents are built lazily per
50
+ # attempt because a failed attempt may hold broken transport state.
51
+ from vidbyte.agents.codex import CodexHarnessAgent # noqa: F401
52
+
53
+ _ = settings
54
+ self._executable = str(plan.executable)
55
+ self._working_directory = str(plan.working_directory)
56
+ self._prepared = True
57
+
58
+ def run(self, plan: Plan, settings: StagesSettings, admission_id: str) -> StagesResult:
59
+ # Dispatches to the chosen topology behind one guarded entry.
60
+ from vidbyte.lib.errors import CodexAgentError
61
+
62
+ if not self._prepared:
63
+ raise StagesHostFailed()
64
+ try:
65
+ return asyncio.run(self._run(plan, settings, admission_id))
66
+ except StagesHostFailed:
67
+ raise
68
+ except StagesSettingsInvalid:
69
+ raise
70
+ except (CodexAgentError, TimeoutError) as error:
71
+ raise StagesHostFailed() from error
72
+
73
+ async def _run(self, plan: Plan, settings: StagesSettings, admission_id: str) -> StagesResult:
74
+ # Sequential threads prior output; parallel fans out independently.
75
+ if settings.parallel:
76
+ return await self._run_parallel(plan, settings, admission_id)
77
+ return await self._run_sequential(plan, settings, admission_id)
78
+
79
+ async def _run_sequential(
80
+ self, plan: Plan, settings: StagesSettings, admission_id: str
81
+ ) -> StagesResult:
82
+ # Awaits each stage before starting the next one, keeping the completed prefix
83
+ # when stop_on_error halts the run at the first exhausted stage.
84
+ texts: list[str] = []
85
+ steps: list[StageStepResult] = []
86
+ completed = 0
87
+ failed = 0
88
+ previous = plan.task
89
+ self._progress(StagesProgress.STAGE_STARTING)
90
+ for index, spec in enumerate(settings.stages):
91
+ prompt = spec.prompt.replace("{{previous}}", previous)
92
+ outcome = await self._run_stage(plan, spec, index, prompt, settings)
93
+ if outcome is None:
94
+ failed += 1
95
+ steps.append(self._failed_step(spec, index, settings))
96
+ texts.append(f"Stage {index + 1} failed.")
97
+ if settings.stop_on_error:
98
+ break
99
+ continue
100
+ completed += 1
101
+ texts.append(outcome[0])
102
+ steps.append(self._completed_step(spec, index, outcome[1], outcome[2], outcome[3]))
103
+ previous = outcome[0]
104
+ self._progress(StagesProgress.STAGE_COMPLETE)
105
+ self._progress(StagesProgress.COMPLETE)
106
+ return self._result(admission_id, completed, failed, steps, texts)
107
+
108
+ async def _run_parallel(
109
+ self, plan: Plan, settings: StagesSettings, admission_id: str
110
+ ) -> StagesResult:
111
+ # Starts every stage at once; stages must not depend on each other. Each stage
112
+ # still retries on its own, and failures are recorded with placeholders so
113
+ # indices stay aligned with the requested order.
114
+ prompts = [spec.prompt.replace("{{previous}}", plan.task) for spec in settings.stages]
115
+ outcomes = await asyncio.gather(
116
+ *[
117
+ self._run_stage(plan, spec, index, prompt, settings)
118
+ for index, (spec, prompt) in enumerate(zip(settings.stages, prompts, strict=True))
119
+ ]
120
+ )
121
+ texts: list[str] = []
122
+ steps: list[StageStepResult] = []
123
+ completed = 0
124
+ failed = 0
125
+ for index, (spec, outcome) in enumerate(zip(settings.stages, outcomes, strict=True)):
126
+ if outcome is None:
127
+ failed += 1
128
+ steps.append(self._failed_step(spec, index, settings))
129
+ texts.append(f"Stage {index + 1} failed.")
130
+ continue
131
+ completed += 1
132
+ texts.append(outcome[0])
133
+ steps.append(self._completed_step(spec, index, outcome[1], outcome[2], outcome[3]))
134
+ self._progress(StagesProgress.COMPLETE)
135
+ return self._result(admission_id, completed, failed, steps, texts)
136
+
137
+ async def _run_stage(
138
+ self, plan: Plan, spec: StageSpec, index: int, prompt: str, settings: StagesSettings
139
+ ) -> tuple[str, str, int, int] | None:
140
+ # One stage start to finish. Every attempt builds its own agent and sends the
141
+ # same prompt, so a retry recovers from a dead host rather than from a stage
142
+ # the agent understood but could not do. Returns text, thread id, attempts,
143
+ # and duration, or None when every attempt is exhausted.
144
+ from vidbyte.lib.errors import CodexAgentError
145
+
146
+ _ = index
147
+ started = time.perf_counter()
148
+ attempts = 0
149
+ for attempt in range(settings.max_retries_per_stage + 1):
150
+ attempts = attempt + 1
151
+ if attempt > 0:
152
+ self._progress(StagesProgress.STAGE_RETRYING)
153
+ await asyncio.sleep(self._backoff_seconds(attempt - 1))
154
+ try:
155
+ agent = self._build_agent(plan, spec)
156
+ reply = await self._turn(agent, spec, prompt)
157
+ thread = self._thread_id(reply)
158
+ text = self._completed_text(reply)
159
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
160
+ return (text, thread, attempts, elapsed_ms)
161
+ except (asyncio.CancelledError, KeyboardInterrupt):
162
+ # Cancellation is never a host failure and never retries.
163
+ raise
164
+ except (StagesSettingsInvalid, ValueError):
165
+ # Fatal caller content fails fast instead of burning retries.
166
+ raise
167
+ except (TimeoutError, ConnectionError, StagesHostFailed, CodexAgentError):
168
+ # Transport and host failures retry with backoff at the loop top.
169
+ continue
170
+ return None
171
+
172
+ def _backoff_seconds(self, retry_index: int) -> float:
173
+ # Gives a recovering host room: 2s, 8s, 32s plus up to 1s of jitter so
174
+ # concurrent stages do not retry at the same instant.
175
+ return 2.0 * (4.0**retry_index) + random.uniform(0.0, 1.0)
176
+
177
+ def _build_agent(self, plan: Plan, spec: StageSpec) -> CodexHarnessAgent:
178
+ # Constructs a fresh agent so threads never leak across stages or attempts.
179
+ from vidbyte.agents.codex import CodexHarnessAgent
180
+
181
+ return CodexHarnessAgent(self._to_settings(plan, spec))
182
+
183
+ async def _turn(self, agent: CodexHarnessAgent, spec: StageSpec, prompt: str) -> AgentMessage:
184
+ # Timeout cancels arun so the SDK client unwinds before continuing.
185
+ from vidbyte.lib.dataclasses.codex import (
186
+ CodexMentionInput,
187
+ CodexRunInput,
188
+ CodexSkillInput,
189
+ CodexTextInput,
190
+ )
191
+
192
+ items: list[Any] = [CodexTextInput(prompt)]
193
+ if spec.image.strip():
194
+ items.append(self._image_item(spec.image.strip()))
195
+ if spec.skill.strip():
196
+ name, _, path = spec.skill.partition("=")
197
+ items.append(CodexSkillInput(name.strip(), path.strip()))
198
+ if spec.mention.strip():
199
+ name, _, path = spec.mention.partition("=")
200
+ items.append(CodexMentionInput(name.strip(), path.strip()))
201
+ run_input = CodexRunInput(items=tuple(items))
202
+ async with asyncio.timeout(StagesLimit.TURN_TIMEOUT_SECONDS):
203
+ return await agent.arun(run_input)
204
+
205
+ def _image_item(self, image: str) -> Any:
206
+ # Remote URLs and local paths are different SDK items with no shared shape.
207
+ from vidbyte.lib.dataclasses.codex import CodexImageInput, CodexLocalImageInput
208
+
209
+ if image.lower().startswith(("https://", "http://", "data:")):
210
+ return CodexImageInput(image)
211
+ return CodexLocalImageInput(image)
212
+
213
+ def _completed_text(self, reply: AgentMessage) -> str:
214
+ # Rejects incomplete replies without exposing model content in errors.
215
+ data = reply.codex
216
+ if data is None or data.status != "completed" or not data.final_response:
217
+ raise StagesHostFailed()
218
+ return str(reply.content)
219
+
220
+ def _thread_id(self, reply: AgentMessage) -> str:
221
+ # Requires a stable thread identity for per-step accounting.
222
+ data = reply.codex
223
+ thread = str(data.thread_id) if data is not None else ""
224
+ if not thread.strip():
225
+ raise StagesHostFailed()
226
+ return thread
227
+
228
+ def _completed_step(
229
+ self, spec: StageSpec, index: int, thread: str, attempts: int, duration_ms: int
230
+ ) -> StageStepResult:
231
+ # Records one successful stage with its identity and attempt accounting.
232
+ return StageStepResult(
233
+ index=index,
234
+ name=spec.name,
235
+ status="completed",
236
+ thread_id=thread,
237
+ attempts=attempts,
238
+ duration_ms=duration_ms,
239
+ )
240
+
241
+ def _failed_step(
242
+ self, spec: StageSpec, index: int, settings: StagesSettings
243
+ ) -> StageStepResult:
244
+ # Records one exhausted stage with a static placeholder thread identity.
245
+ return StageStepResult(
246
+ index=index,
247
+ name=spec.name,
248
+ status="failed",
249
+ thread_id=f"stage-{index + 1}-failed",
250
+ attempts=settings.max_retries_per_stage + 1,
251
+ duration_ms=0,
252
+ )
253
+
254
+ def _result(
255
+ self,
256
+ admission_id: str,
257
+ completed: int,
258
+ failed: int,
259
+ steps: list[StageStepResult],
260
+ texts: list[str],
261
+ ) -> StagesResult:
262
+ # Joins per-stage texts into the run-level text, keeping indices aligned even
263
+ # across failures so a later stage never reads the wrong predecessor.
264
+ text = texts[-1] if texts else ""
265
+ if any(step.status == "failed" for step in steps):
266
+ text = "\n\n".join(texts)
267
+ return StagesResult(
268
+ admission_id=admission_id,
269
+ completed=completed,
270
+ failed=failed,
271
+ steps=tuple(steps),
272
+ stage_texts=tuple(texts),
273
+ text=text,
274
+ )
275
+
276
+ def _to_settings(self, plan: Plan, spec: StageSpec) -> CodexHarnessAgentSettings:
277
+ # Translates one stage spec to validated harness agent settings. Every closed-set value
278
+ # is already a CLI enum whose members are a subset of the SDK's, so each one converts by
279
+ # value with no lookup table and no alias handling: review of PR #36 asked for the legal
280
+ # words to be defined and enforced as enums in the command layer rather than re-parsed
281
+ # from free strings here. A ValueError can still surface if the pinned SDK drops a
282
+ # member, and it is reported as invalid settings rather than as a host failure.
283
+ from vidbyte.lib.dataclasses.codex import (
284
+ CodexAgentSettings,
285
+ CodexClientSettings,
286
+ CodexHarnessAgentSettings,
287
+ CodexThreadSettings,
288
+ CodexTurnSettings,
289
+ )
290
+ from vidbyte.lib.enums.codex import (
291
+ CodexApprovalMode,
292
+ CodexPersonality,
293
+ CodexReasoningEffort,
294
+ CodexReasoningSummary,
295
+ CodexSandbox,
296
+ )
297
+
298
+ try:
299
+ sandbox = CodexSandbox(spec.sandbox.value)
300
+ approval = CodexApprovalMode(spec.approval.value)
301
+ personality = CodexPersonality(spec.personality.value)
302
+ client = CodexClientSettings(
303
+ codex_bin=str(plan.executable),
304
+ cwd=str(plan.working_directory),
305
+ env=self._environment,
306
+ config_overrides=tuple(setting.value for setting in CodexConfig),
307
+ )
308
+ thread = CodexThreadSettings(
309
+ model=spec.model,
310
+ sandbox=sandbox,
311
+ approval_mode=approval,
312
+ personality=personality,
313
+ )
314
+ turn = CodexTurnSettings(
315
+ model=spec.model,
316
+ effort=CodexReasoningEffort(spec.effort.value),
317
+ summary=CodexReasoningSummary(spec.summary.value),
318
+ sandbox=sandbox,
319
+ approval_mode=approval,
320
+ personality=personality,
321
+ )
322
+ schema: dict[str, Any] | None = None
323
+ if spec.output_schema.strip():
324
+ parsed: Any = json.loads(spec.output_schema)
325
+ if not isinstance(parsed, dict):
326
+ raise StagesSettingsInvalid()
327
+ schema = parsed
328
+ return CodexHarnessAgentSettings(
329
+ name=spec.name,
330
+ system_prompt=spec.system_prompt,
331
+ additional_context=spec.additional_context,
332
+ output_schema=schema,
333
+ codex=CodexAgentSettings(client=client, thread=thread, turn=turn),
334
+ )
335
+ except StagesSettingsInvalid:
336
+ raise
337
+ except ValueError as error:
338
+ raise StagesSettingsInvalid() from error