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,6 @@
1
+ <task>
2
+ {{task}}
3
+ </task>
4
+
5
+ Inspect the workspace as needed, then return the roster of exactly {{roles}} roles as the
6
+ single JSON object described in your instructions.
@@ -0,0 +1,104 @@
1
+ <identity>
2
+ {{identity}}
3
+ </identity>
4
+
5
+ <personality>
6
+ {{personality}}
7
+ </personality>
8
+
9
+ <expertise>
10
+ {{expertise}}
11
+ </expertise>
12
+
13
+ <knowledge>
14
+ {{knowledge}}
15
+ </knowledge>
16
+
17
+ <skills>
18
+ {{skills}}
19
+ </skills>
20
+
21
+ <goal>
22
+ {{goal}}
23
+ </goal>
24
+
25
+ <mandate>
26
+ Use this role to generate between {{minimum}} and {{maximum}} genuinely distinct approaches
27
+ to the task in this session. You are producing a slate, not a recommendation, so do not stop
28
+ when you find the first workable answer. Every approach must be complete enough for another
29
+ agent to act on without asking you a follow-up question and different in substance from the
30
+ others. Explain the assumptions that make it viable, the tradeoffs it accepts, and concrete
31
+ checks that could validate or falsify it. Weigh it honestly rather than weakening the cons of
32
+ the approach you personally prefer. A selector will compare your work with many other roles,
33
+ so missing context can eliminate an otherwise strong idea.
34
+ </mandate>
35
+
36
+ <constraints>
37
+ You are running in a read-only sandbox. You cannot write, move, or delete any file, and any
38
+ attempt to do so will fail. Read whatever you need from the workspace, then describe work that
39
+ could be performed; never claim that you already performed it.
40
+ </constraints>
41
+
42
+ <instructions-and-output>
43
+ Here are the instructions to follow. Use the expertise, knowledge, and skills in your assigned
44
+ profile to interpret the task through the particular perspective you were created to supply.
45
+ Your purpose is not to echo the other roles or guess what a generic answer would look like; it
46
+ is to produce a high-quality set of alternatives that this role is unusually qualified to see.
47
+ Inspect evidence before relying on it, separate facts from assumptions, and make uncertainty
48
+ visible. Deliberately create more possibilities than you return so the final slate reflects
49
+ selection rather than the order in which ideas occurred to you. Keep the approaches different
50
+ in mechanism, scope, sequencing, or governing principle. Then encode the result exactly in the
51
+ structured form below.
52
+
53
+ 1. Understand the task from this role's perspective. Read the complete task and the planner
54
+ context, identify the outcome being sought, and name the constraints that matter most to
55
+ your role. Write a concise `task_analysis` that makes your interpretation explicit. Do not
56
+ begin proposing until you can distinguish the requested outcome from a merely familiar
57
+ version of the problem.
58
+ 2. Inspect the available evidence. Open the relevant workspace files, documentation, data, or
59
+ other artifacts that can confirm how the current system actually works. Record important
60
+ unknowns as assumptions instead of silently filling them in. Use your assigned expertise to
61
+ notice evidence another role might overlook.
62
+ 3. Diverge before choosing. Brainstorm at least three times as many candidate approaches as
63
+ the maximum you may return, varying the mechanism and the tradeoff profile rather than just
64
+ the wording. Include conservative, ambitious, incremental, and structurally different
65
+ options when they are genuinely applicable. Describe in `role_strategy` how your role
66
+ created useful variance.
67
+ 4. Evaluate and narrow. Test each candidate against the task, workspace facts, likely failure
68
+ modes, cost, reversibility, and the role's own standards. Remove duplicates, dominated
69
+ variants, and ideas that depend on assumptions you can already disprove. Keep between
70
+ {{minimum}} and {{maximum}} approaches that remain defensible for different reasons.
71
+ 5. Write actionable structured proposals. For every survivor, explain what to do, real pros
72
+ and cons, risks, affected files, assumptions, tradeoffs, and validation steps. Use
73
+ `confidence` to reflect the quality of evidence, not enthusiasm. Re-read the whole slate as
74
+ an implementer and fill any context gap that would otherwise require a follow-up.
75
+
76
+ Return one JSON object and nothing else. It has exactly four top-level keys:
77
+ `role` is "{{role_name}}"; `task_analysis` explains how this role interprets the task;
78
+ `role_strategy` explains how this role created and narrowed its alternatives; and `approaches`
79
+ is an array of {{minimum}} to {{maximum}} objects. Every approach has exactly ten keys:
80
+ `title`, `approach`, `pros`, `cons`, `risks`, `files`, `confidence`, `assumptions`,
81
+ `tradeoffs`, and `validation_steps`. The plural context fields are arrays of strings;
82
+ `pros`, `cons`, `assumptions`, `tradeoffs`, and `validation_steps` each contain at least one
83
+ item, while `risks` and `files` may be empty. `confidence` is exactly `low`, `medium`, or
84
+ `high`. No prose, code fence, missing key, extra key, or out-of-band text is permitted.
85
+ </instructions-and-output>
86
+
87
+ <checklist>
88
+ Before returning, verify every one of these:
89
+ 1. The slate contains between {{minimum}} and {{maximum}} approaches.
90
+ 2. Every approach differs materially from every other approach.
91
+ 3. Every approach is relevant to the actual task and consistent with inspected evidence.
92
+ 4. Every assumption is explicit and every validation step could produce evidence.
93
+ 5. Pros and cons are concrete, balanced, and specific to their approach.
94
+ 6. An implementer could act on each approach without asking what it means.
95
+ 7. The object uses exactly the keys and value types required above.
96
+ </checklist>
97
+
98
+ <things-not-to-do>
99
+ - Do not edit the workspace or describe an edit as already completed.
100
+ - Do not pad the slate with cosmetic variants to reach the minimum count.
101
+ - Do not let a favorite approach receive vague cons while alternatives receive detailed ones.
102
+ - Do not treat an unverified premise as a workspace fact.
103
+ - Do not write a hybrid entry that asks the selector to choose among several hidden variants.
104
+ </things-not-to-do>
@@ -0,0 +1,19 @@
1
+ <task>
2
+ {{task}}
3
+ </task>
4
+
5
+ <planner-context>
6
+ Task analysis:
7
+ {{task_analysis}}
8
+
9
+ Why this roster was assembled:
10
+ {{roster_strategy}}
11
+
12
+ Coverage the roster is intended to provide:
13
+ {{coverage_summary}}
14
+ </planner-context>
15
+
16
+ Investigate the workspace from your assigned perspective, then return your slate of
17
+ {{minimum}} to {{maximum}} approaches as the single JSON object described in your
18
+ instructions. Name every file an approach would change in its `files` array, so the agent that
19
+ compares approaches can see where yours and another member's touch the same place.
@@ -0,0 +1,19 @@
1
+ <task>
2
+ {{task}}
3
+ </task>
4
+
5
+ <round number="{{round}}" surviving="{{surviving}}" keep="1">
6
+ This is the final round. {{surviving}} candidates are still alive and exactly one may survive.
7
+ The candidate you keep is the approach that will actually be implemented. Your `round_summary`,
8
+ `comparison_basis`, `rationale`, `evidence`, `uncertainties`, `implementation_guidance`, and
9
+ `cons` are the brief the implementing agent receives — it cannot see this conversation or the
10
+ other candidates. Say what is to be done and what it must be careful of, concretely enough to
11
+ act on.
12
+ </round>
13
+
14
+ <candidates>
15
+ {{candidates}}
16
+ </candidates>
17
+
18
+ Return the round as the single JSON object described in your instructions, keeping exactly one
19
+ candidate.
@@ -0,0 +1,17 @@
1
+ <task>
2
+ {{task}}
3
+ </task>
4
+
5
+ <round number="{{round}}" surviving="{{surviving}}" keep="{{target}}">
6
+ This is narrowing round {{round}}. {{surviving}} candidates are still alive and exactly
7
+ {{target}} may survive this round. Weigh the pros and cons of every candidate you keep, and
8
+ list every id you drop. State the common comparison basis, summarize the round, and include
9
+ evidence, uncertainty, and implementation guidance for every survivor.
10
+ </round>
11
+
12
+ <candidates>
13
+ {{candidates}}
14
+ </candidates>
15
+
16
+ Return the round as the single JSON object described in your instructions, keeping exactly
17
+ {{target}} candidates.
@@ -0,0 +1,89 @@
1
+ <identity>
2
+ You are an expert decision architect who selects among competing approaches to difficult tasks
3
+ in any domain. You know how to compare technical plans, research strategies, operational
4
+ responses, creative directions, business decisions, and other forms of proposed action without
5
+ pretending they share one universal success metric. Several independent specialists have each
6
+ offered alternatives, none saw the others' work, and none can answer follow-up questions. You
7
+ must end the disagreement transparently and in writing. You distinguish persuasive presentation
8
+ from evidence, confidence from calibration, and popularity from independent confirmation. You
9
+ reason by disciplined elimination and comparative fit rather than enthusiasm. You do not carry
10
+ out the task or invent a new solution; you read, verify, compare, narrow, and choose.
11
+ </identity>
12
+
13
+ <goal>
14
+ Reduce a slate of candidate approaches to the single best one, in a sequence of narrowing
15
+ rounds you will be walked through one round at a time. Your measure of success is that the
16
+ approach you end on would still be the right choice to someone who read every candidate
17
+ carefully — not the one that was described most fluently and not the one the most people
18
+ happened to suggest. In each round you will be given the candidates that are still alive and
19
+ told exactly how many may survive it, and you must weigh the pros and cons of every candidate
20
+ you keep so that the reason it survived is on the record. Popularity is weak evidence, since
21
+ two colleagues sharing an assumption is one assumption counted twice; verifiable fit with the
22
+ available evidence is strong evidence. When you are down to the final round you will be asked
23
+ for exactly one candidate, and what you write about it becomes the brief the implementer
24
+ works from, so it must be specific enough to act on. You may inspect the workspace and any
25
+ available task artifacts to check a claim a candidate depends on, and you should, because a
26
+ claim you can falsify is the fastest way to eliminate a whole group of candidates at once.
27
+ </goal>
28
+
29
+ <instructions-and-output>
30
+ Here are the instructions to follow in every narrowing round. Use the task itself to determine
31
+ what success means instead of applying an engineering-only or domain-independent scorecard.
32
+ Compare every candidate on the same explicit basis while allowing the task to decide which
33
+ criteria deserve the most weight. Check claims against available evidence when doing so is
34
+ practical, and identify uncertainty when it is not. Preserve useful diversity in early rounds
35
+ so the process does not converge prematurely on several versions of one idea. Make every
36
+ elimination traceable to a comparative reason. In the final round, write guidance detailed
37
+ enough for another agent to execute without seeing the discarded slate.
38
+
39
+ 1. Frame the decision. Read the task, every surviving candidate, and the context its proposing
40
+ role supplied before ranking anything. Identify the outcome, constraints, and domain-specific
41
+ standards that distinguish success from a superficially plausible answer. State those
42
+ standards in `comparison_basis` and summarize the round's decision in `round_summary`.
43
+ 2. Verify premises. Separate observed facts, proposer assumptions, and claims that can be
44
+ checked against workspace or task artifacts. Perform cheap decisive checks and cite the
45
+ resulting evidence in each kept verdict. Treat unresolved material questions as
46
+ `uncertainties`, never as facts that happen to support a preferred candidate.
47
+ 3. Remove redundancy and infeasibility. Group candidates that are materially the same despite
48
+ different wording, and retain at most the strongest representative when capacity is scarce.
49
+ Eliminate candidates with false critical premises or fatal constraint violations. Do not
50
+ create a hybrid replacement, because only offered candidates may survive.
51
+ 4. Compare tradeoffs and preserve option value. Weigh fit, benefits, costs, risks, assumptions,
52
+ validation burden, reversibility, and likely quality of execution. In non-final rounds, keep
53
+ distinct mechanisms when their relative value remains genuinely unresolved. Use scores as a
54
+ comparative summary of the analysis rather than as a substitute for it.
55
+ 5. Narrow exactly and hand off clearly. Keep exactly the required number, name every eliminated
56
+ id, and ensure every id came from the current slate. For each survivor, write concrete pros,
57
+ cons, rationale, evidence, uncertainties, and `implementation_guidance`. In the final round,
58
+ make that guidance a complete execution brief for the selected approach.
59
+
60
+ Return one JSON object and nothing else. It has exactly four top-level keys:
61
+ `round_summary` is the round's concise comparative conclusion; `comparison_basis` is an array
62
+ of at least one criterion applied across the slate; `kept` contains exactly the required number
63
+ of verdict objects; and `eliminated` contains every dropped id. Each verdict has exactly eight
64
+ keys: `candidate_id`, `pros`, `cons`, `score`, `rationale`, `evidence`, `uncertainties`, and
65
+ `implementation_guidance`. `pros`, `cons`, and `evidence` are non-empty arrays of strings;
66
+ `uncertainties` is an array that may be empty; `score` is an integer from 1 to 100. Every id
67
+ must have been offered in the current round. Missing keys, extra keys, prose, or code fences
68
+ fail the run.
69
+ </instructions-and-output>
70
+
71
+ <checklist>
72
+ Before returning, verify every one of these:
73
+ 1. Every surviving candidate was read before any was eliminated.
74
+ 2. The comparison basis comes from the task and was applied consistently.
75
+ 3. Duplicates do not occupy multiple survivor slots without a stated material difference.
76
+ 4. Every kept verdict has concrete evidence, uncertainty, pros, cons, and execution guidance.
77
+ 5. Scores are comparable and rationales explain rather than repeat them.
78
+ 6. Exactly the requested number survived and every offered id appears once across the outcome.
79
+ 7. The structured object uses exactly the required keys and value types.
80
+ </checklist>
81
+
82
+ <things-not-to-do>
83
+ - Do not invent a hybrid or rewrite a candidate while evaluating it.
84
+ - Do not treat proposer confidence or repeated popularity as independent evidence.
85
+ - Do not use an engineering convention as a criterion unless the task is engineering work.
86
+ - Do not eliminate a candidate solely because it is unfamiliar or more laborious.
87
+ - Do not defer a decision by carrying redundant candidates into the next round.
88
+ - Do not hide an unresolved material premise behind a confident score.
89
+ </things-not-to-do>
@@ -0,0 +1,30 @@
1
+ """Launches one verified ensemble run: build the stages, then run the algorithm.
2
+
3
+ The command admits and verifies before calling here, so this class never touches the
4
+ network: it receives the loaded SDK and the verified grant, builds the run's stages, and
5
+ hands them to the service. The charged admission travels with the grant into the result.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from ...types.ensemble import EnsembleInputs, EnsembleResult
11
+ from ...types.runtime import RuntimeAdmissionGrant, RuntimeLaunchPlan
12
+ from .sdk import EnsembleSdk
13
+ from .service import EnsembleService
14
+ from .settings import EnsembleStages
15
+
16
+
17
+ class EnsembleRunner:
18
+ """Builds one run's stages from verified inputs and runs the ensemble algorithm."""
19
+
20
+ def run(
21
+ self,
22
+ plan: RuntimeLaunchPlan,
23
+ inputs: EnsembleInputs,
24
+ sdk: EnsembleSdk,
25
+ grant: RuntimeAdmissionGrant,
26
+ ) -> EnsembleResult:
27
+ # No admission here: the command already bought and verified it, so starting the
28
+ # service is the only thing left that can fail.
29
+ stages = EnsembleStages(sdk, inputs, plan.working_directory)
30
+ return EnsembleService(stages).run(grant.charged_cents)
@@ -0,0 +1,134 @@
1
+ """The single lazy binding between this CLI and the Vidbyte SDK's Codex integration.
2
+
3
+ Every SDK symbol is resolved inside `load()`, never at module scope, so `vidbyte-cli --help`
4
+ stays fast and keeps working even where the SDK's Codex integration is missing or too old.
5
+ Callers see only the local Protocol and this class.
6
+
7
+ Resolution happens before paid admission, so a missing dependency costs nothing.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from typing import Any, Protocol
14
+
15
+ from ...lib.errors.failures import EnsembleSdkUnavailable
16
+ from ...types.ensemble import EnsembleInputs
17
+
18
+ # The SDK module path and the exact symbols this service needs from it.
19
+ _CODEX_MODULE = "vidbyte.agents.codex"
20
+ _ERRORS_MODULE = "vidbyte.lib.errors"
21
+ _REQUIRED = (
22
+ "CodexAgentSettings",
23
+ "CodexForkSettings",
24
+ "CodexHarnessAgentSettings",
25
+ "CodexReasoningEffort",
26
+ "CodexRunInput",
27
+ "CodexSandbox",
28
+ "CodexThreadSettings",
29
+ "CodexTurnSettings",
30
+ )
31
+
32
+
33
+ # The two SDK exception classes the service must tell apart: host fault, schema violation.
34
+ ErrorTypes = tuple[type[Exception], type[Exception]]
35
+
36
+
37
+ class EnsembleAgent(Protocol):
38
+ """The subset of the SDK agent surface this service actually drives."""
39
+
40
+ thread_id: str
41
+
42
+ async def arun(self, request: Any) -> Any: ...
43
+
44
+ async def afork(self, settings: Any) -> EnsembleAgent: ...
45
+
46
+
47
+ class EnsembleSdk:
48
+ """Holds the resolved SDK symbols and builds every provider value from them."""
49
+
50
+ def __init__(self, symbols: dict[str, Any], agent: Any, errors: ErrorTypes) -> None:
51
+ # Storing the resolved symbols is what keeps every SDK name confined to this module.
52
+ self._symbols = symbols
53
+ self._agent_type = agent
54
+ self._error_type, self._schema_error_type = errors
55
+
56
+ @classmethod
57
+ def load(cls) -> EnsembleSdk:
58
+ # Imports at call time; an SDK predating the Codex integration fails the same way.
59
+ try:
60
+ codex = __import__(_CODEX_MODULE, fromlist=["*"])
61
+ errors = __import__(_ERRORS_MODULE, fromlist=["*"])
62
+ symbols = {name: getattr(codex, name) for name in _REQUIRED}
63
+ agent_type = codex.CodexHarnessAgent
64
+ resolved = (errors.CodexAgentError, errors.OutputSchemaViolationError)
65
+ except (ImportError, AttributeError) as error:
66
+ raise EnsembleSdkUnavailable(error) from error
67
+ return cls(symbols, agent_type, resolved)
68
+
69
+ def agent(self, settings: Any) -> EnsembleAgent:
70
+ # Constructs a root agent; forks are produced by the agent itself, never here.
71
+ created: EnsembleAgent = self._agent_type(settings)
72
+ return created
73
+
74
+ def run_input(self, prompt: str) -> Any:
75
+ # One text turn, which is the only input modality this primitive uses.
76
+ return self._symbols["CodexRunInput"].text(prompt)
77
+
78
+ def is_provider_error(self, error: Exception) -> bool:
79
+ # Lets the service classify a host failure without importing the SDK's error class.
80
+ return isinstance(error, self._error_type)
81
+
82
+ def is_schema_error(self, error: Exception) -> bool:
83
+ # A schema violation is a sibling of CodexAgentError, not a subclass: the host worked,
84
+ # the model just produced output that did not validate. The two need different errors.
85
+ return isinstance(error, self._schema_error_type)
86
+
87
+ def root_settings(self, prompt: str, schema: type, codex: Any) -> Any:
88
+ # The planner reads the workspace to design roles, so it is read-only like the roles.
89
+ return self._symbols["CodexHarnessAgentSettings"](
90
+ name="ensemble-planner",
91
+ system_prompt=prompt,
92
+ codex=codex,
93
+ output_schema=schema,
94
+ )
95
+
96
+ def fork_settings(self, name: str, prompt: str, schema: type | None, codex: Any) -> Any:
97
+ # A fork inherits everything it does not override; the schema decides its output shape.
98
+ return self._symbols["CodexForkSettings"](
99
+ name=name,
100
+ system_prompt=prompt,
101
+ codex=codex,
102
+ output_schema=schema,
103
+ clear_output_schema=schema is None,
104
+ )
105
+
106
+ def codex_settings(self, inputs: EnsembleInputs, cwd: Path, *, write: bool) -> Any:
107
+ # Sandbox is set on thread and turn because an unset value inherits the user's config.
108
+ sandbox = self._sandbox(write=write)
109
+ return self._symbols["CodexAgentSettings"](
110
+ thread=self._symbols["CodexThreadSettings"](
111
+ cwd=str(cwd),
112
+ model=inputs.model or "",
113
+ sandbox=sandbox,
114
+ ephemeral=False,
115
+ ),
116
+ turn=self._symbols["CodexTurnSettings"](
117
+ cwd=str(cwd),
118
+ effort=self._effort(inputs),
119
+ model=inputs.model or "",
120
+ sandbox=sandbox,
121
+ ),
122
+ )
123
+
124
+ def _sandbox(self, *, write: bool) -> Any:
125
+ # Read-only is never the provider default, so both modes are named explicitly.
126
+ sandbox = self._symbols["CodexSandbox"]
127
+ return sandbox.WORKSPACE_WRITE if write else sandbox.READ_ONLY
128
+
129
+ def _effort(self, inputs: EnsembleInputs) -> Any:
130
+ # An unset effort maps to the provider-default sentinel the SDK strips before sending.
131
+ effort = self._symbols["CodexReasoningEffort"]
132
+ if inputs.reasoning_effort is None:
133
+ return effort.PROVIDER_DEFAULT
134
+ return effort(inputs.reasoning_effort.value)
@@ -0,0 +1,233 @@
1
+ """The four-stage algorithm: plan roles, propose in parallel, select down to one, implement.
2
+
3
+ Roles branch from the root thread rather than from each other, so their errors stay
4
+ decorrelated — that independence is the whole reason an ensemble beats one agent. The
5
+ selector and the implementer also branch from the root, so neither inherits a single role's
6
+ framing; the selector then runs several turns on its own thread, which is what lets a later
7
+ narrowing round build on the reasoning of the earlier ones.
8
+
9
+ A role that fails is recorded and stepped over; only an empty slate aborts the run.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import asyncio
15
+ from math import ceil
16
+ from typing import Any, Literal
17
+
18
+ from ...lib.errors.failures import (
19
+ EnsembleAllRolesFailed,
20
+ EnsembleHostFailed,
21
+ EnsembleImplementerFailed,
22
+ EnsembleProposalUnusable,
23
+ EnsembleRolePlanInvalid,
24
+ EnsembleSelectionInvalid,
25
+ )
26
+ from ...types.ensemble import (
27
+ ApproachCandidate,
28
+ EnsembleResult,
29
+ EnsembleRoleFailure,
30
+ GeneratedRole,
31
+ RolePlan,
32
+ RoleProposal,
33
+ SelectedApproach,
34
+ SelectionRound,
35
+ )
36
+ from .sdk import EnsembleAgent
37
+ from .settings import EnsembleStages
38
+
39
+ RoleFailureReason = Literal["timeout", "host_error", "invalid_output"]
40
+ Proposals = tuple[RoleProposal, ...]
41
+ Candidates = tuple[ApproachCandidate, ...]
42
+
43
+ # Each round keeps a fifth of what it was given, so a thousand candidates reach one in five
44
+ # rounds while fifteen reach one in two. Narrowing this fast is what keeps the selector's
45
+ # context bounded; the ladder is computed here rather than chosen by the agent so a run's
46
+ # shape depends only on how many candidates exist.
47
+ _NARROWING_DIVISOR = 5
48
+
49
+
50
+ class EnsembleService:
51
+ """Runs one same-host ensemble from a validated plan to a normalized result."""
52
+
53
+ def __init__(self, stages: EnsembleStages) -> None:
54
+ # One service instance per run; the stages object carries every shared collaborator.
55
+ self._stages = stages
56
+
57
+ def run(self, cents: int) -> EnsembleResult:
58
+ # The single synchronous boundary; everything below it shares one event loop.
59
+ return asyncio.run(self._orchestrate(cents))
60
+
61
+ async def _orchestrate(self, cents: int) -> EnsembleResult:
62
+ # Plan, fan out concurrently, narrow to one, implement; the root is the only ancestor.
63
+ inputs = self._stages.inputs
64
+ root = self._stages.sdk.agent(self._stages.root())
65
+ plan = await self._plan_roles(root)
66
+ outcomes = await asyncio.gather(
67
+ *(self._propose(root, role, plan) for role in plan.roles),
68
+ return_exceptions=True,
69
+ )
70
+ proposals, failures = self._partition(plan.roles, list(outcomes))
71
+ if not proposals:
72
+ raise EnsembleAllRolesFailed(len(failures))
73
+ candidates = self._candidates(proposals)
74
+ rounds, selected, selector_thread = await self._select(root, candidates)
75
+ implementation, thread_id = await self._implement(root, selected, len(candidates))
76
+ return EnsembleResult(
77
+ task=inputs.task,
78
+ host=inputs.host,
79
+ roles=plan.roles,
80
+ proposals=proposals,
81
+ failures=failures,
82
+ candidates=len(candidates),
83
+ rounds=rounds,
84
+ selected=selected,
85
+ implementation=implementation,
86
+ root_thread_id=root.thread_id,
87
+ selector_thread_id=selector_thread,
88
+ implementer_thread_id=thread_id,
89
+ charged_cents=cents,
90
+ )
91
+
92
+ async def _plan_roles(self, root: EnsembleAgent) -> RolePlan:
93
+ # One turn, which both mints the thread id forking requires and designs the roster.
94
+ stages = self._stages
95
+ prompt = stages.prompts.planner_turn_prompt(stages.inputs.task, stages.inputs.roles)
96
+ try:
97
+ reply = await root.arun(stages.sdk.run_input(prompt))
98
+ # CancelledError is a BaseException, so Ctrl-C passes through this handler untouched.
99
+ except Exception as error:
100
+ if stages.sdk.is_schema_error(error):
101
+ raise EnsembleRolePlanInvalid(stages.inputs.roles) from error
102
+ raise EnsembleHostFailed("planner", error) from error
103
+ plan = getattr(reply, "structured", None)
104
+ if not isinstance(plan, RolePlan) or len(plan.roles) != stages.inputs.roles:
105
+ raise EnsembleRolePlanInvalid(stages.inputs.roles)
106
+ # Names label every candidate the selector sees, so duplicates would make ids ambiguous.
107
+ if len({role.name for role in plan.roles}) != len(plan.roles):
108
+ raise EnsembleRolePlanInvalid(stages.inputs.roles)
109
+ return plan
110
+
111
+ async def _propose(
112
+ self, root: EnsembleAgent, role: GeneratedRole, plan: RolePlan
113
+ ) -> RoleProposal:
114
+ # Bounded so one hung host cannot hold the fan-in open for every other role.
115
+ stages = self._stages
116
+ prompt = stages.prompts.role_turn_prompt(stages.inputs.task, plan)
117
+ async with asyncio.timeout(stages.inputs.role_timeout_seconds):
118
+ agent = await root.afork(stages.proposal(role))
119
+ reply = await agent.arun(stages.sdk.run_input(prompt))
120
+ proposal = getattr(reply, "structured", None)
121
+ if not isinstance(proposal, RoleProposal):
122
+ raise EnsembleProposalUnusable(role.name)
123
+ return proposal
124
+
125
+ async def _select(
126
+ self, root: EnsembleAgent, candidates: Candidates
127
+ ) -> tuple[tuple[SelectionRound, ...], SelectedApproach, str]:
128
+ # One selector thread across every round, so a later round sees its own earlier reasoning.
129
+ stages = self._stages
130
+ try:
131
+ agent = await root.afork(stages.selector())
132
+ except Exception as error:
133
+ raise EnsembleHostFailed("selector", error) from error
134
+ rounds: list[SelectionRound] = []
135
+ alive = candidates
136
+ while True:
137
+ number, target = len(rounds) + 1, self._target(len(alive))
138
+ final = target == 1
139
+ outcome = await self._round(agent, self._round_prompt(alive, number, target), target)
140
+ rounds.append(outcome)
141
+ alive = self._survivors(alive, outcome)
142
+ if final:
143
+ # The winner is joined back to its full proposal, which no agent resends.
144
+ chosen = SelectedApproach(
145
+ candidate=alive[0],
146
+ verdict=outcome.kept[0],
147
+ round_summary=outcome.round_summary,
148
+ comparison_basis=outcome.comparison_basis,
149
+ )
150
+ return tuple(rounds), chosen, agent.thread_id
151
+
152
+ def _round_prompt(self, alive: Candidates, number: int, target: int) -> str:
153
+ # The last round is authored separately: what it writes becomes the implementer brief.
154
+ prompts, task = self._stages.prompts, self._stages.inputs.task
155
+ if target == 1:
156
+ return prompts.selector_final_prompt(task, alive, number)
157
+ return prompts.selector_round_prompt(task, alive, number, target)
158
+
159
+ async def _round(self, agent: EnsembleAgent, prompt: str, target: int) -> SelectionRound:
160
+ # A round the host cannot complete ends the run: there is no partial way to narrow.
161
+ stages = self._stages
162
+ try:
163
+ reply = await agent.arun(stages.sdk.run_input(prompt))
164
+ except Exception as error:
165
+ if stages.sdk.is_schema_error(error):
166
+ raise EnsembleSelectionInvalid(target) from error
167
+ raise EnsembleHostFailed("selector", error) from error
168
+ outcome = getattr(reply, "structured", None)
169
+ if not isinstance(outcome, SelectionRound) or len(outcome.kept) != target:
170
+ raise EnsembleSelectionInvalid(target)
171
+ return outcome
172
+
173
+ def _survivors(self, alive: Candidates, outcome: SelectionRound) -> Candidates:
174
+ # Kept ids are matched against the slate actually offered, never trusted as given.
175
+ offered = {candidate.candidate_id: candidate for candidate in alive}
176
+ kept = tuple(dict.fromkeys(verdict.candidate_id for verdict in outcome.kept))
177
+ if len(kept) != len(outcome.kept) or any(item not in offered for item in kept):
178
+ raise EnsembleSelectionInvalid(len(outcome.kept))
179
+ return tuple(offered[item] for item in kept)
180
+
181
+ def _target(self, alive: int) -> int:
182
+ # Always narrows: a slate of five or fewer goes straight to the final round.
183
+ return max(ceil(alive / _NARROWING_DIVISOR), 1)
184
+
185
+ def _candidates(self, proposals: Proposals) -> Candidates:
186
+ # Ids are assigned here so they are stable, compact, and checkable on the way back.
187
+ return tuple(
188
+ ApproachCandidate(
189
+ candidate_id=f"{role_index}.{approach_index}",
190
+ role=proposal.role,
191
+ task_analysis=proposal.task_analysis,
192
+ role_strategy=proposal.role_strategy,
193
+ approach=approach,
194
+ )
195
+ for role_index, proposal in enumerate(proposals, 1)
196
+ for approach_index, approach in enumerate(proposal.approaches, 1)
197
+ )
198
+
199
+ async def _implement(
200
+ self, root: EnsembleAgent, selected: SelectedApproach, candidates: int
201
+ ) -> tuple[str, str]:
202
+ # Forks the root, not the selector, so it inherits the task but not the deliberation.
203
+ stages = self._stages
204
+ prompt = stages.prompts.implementer_turn_prompt(
205
+ stages.inputs.task, selected, candidates, stages.inputs.roles
206
+ )
207
+ try:
208
+ agent = await root.afork(stages.implementer())
209
+ reply = await agent.arun(stages.sdk.run_input(prompt))
210
+ except Exception as error:
211
+ raise EnsembleImplementerFailed(error) from error
212
+ return str(getattr(reply, "content", "")), agent.thread_id
213
+
214
+ def _partition(self, roles: tuple[GeneratedRole, ...], outcomes: list[Any]) -> tuple[Any, Any]:
215
+ # Survivors carry the run; a failed role becomes a record, never a raised error.
216
+ proposals: list[RoleProposal] = []
217
+ failures: list[EnsembleRoleFailure] = []
218
+ for role, outcome in zip(roles, outcomes, strict=True):
219
+ if isinstance(outcome, RoleProposal):
220
+ proposals.append(outcome)
221
+ elif isinstance(outcome, BaseException):
222
+ failures.append(EnsembleRoleFailure(role=role.name, reason=self._reason(outcome)))
223
+ return tuple(proposals), tuple(failures)
224
+
225
+ def _reason(self, error: BaseException) -> RoleFailureReason:
226
+ # Cancellation is re-raised rather than classified, so Ctrl-C still reaches the caller.
227
+ if isinstance(error, asyncio.CancelledError):
228
+ raise error
229
+ if isinstance(error, TimeoutError):
230
+ return "timeout"
231
+ if isinstance(error, Exception) and self._stages.sdk.is_provider_error(error):
232
+ return "host_error"
233
+ return "invalid_output"