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.
- vidbyte_cli/__init__.py +11 -0
- vidbyte_cli/__main__.py +6 -0
- vidbyte_cli/cli.py +18 -0
- vidbyte_cli/commands/__init__.py +86 -0
- vidbyte_cli/commands/agent_options.py +53 -0
- vidbyte_cli/commands/agents/__init__.py +31 -0
- vidbyte_cli/commands/agents/suggestion/__init__.py +38 -0
- vidbyte_cli/commands/agents/suggestion/prompts/__init__.py +3 -0
- vidbyte_cli/commands/agents/suggestion/prompts/all_categories.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/approaches.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/avoid.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/blockers.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/capability.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/categories.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/category.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/completed.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/constraint.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/context.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/count.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/critic_model.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/decision.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/dry_run.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/extra_compute.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_accept.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_group.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_project.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_reason.md +98 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_reject.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_suggestion.md +101 -0
- vidbyte_cli/commands/agents/suggestion/prompts/files.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/forbidden.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/goal.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/handoff.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/horizon.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/hypotheses.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/idea.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/in_progress.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/input.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/library.py +29 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_messages.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_output_tokens.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_total_tokens.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/mistakes.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/outcomes.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_create.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_description.md +98 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_group.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_key.md +96 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_list.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_title.md +99 -0
- vidbyte_cli/commands/agents/suggestion/prompts/provider.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/question.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/risks.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/rounds.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/run.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/success.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/timeout.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/trajectory.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/view.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/view_all.md +5 -0
- vidbyte_cli/commands/agents/suggestion/render.py +77 -0
- vidbyte_cli/commands/agents/suggestion/request_builder.py +240 -0
- vidbyte_cli/commands/agents/suggestion/suggest.py +154 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_categories.py +77 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_feedback.py +85 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_handoff.py +67 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_projects.py +95 -0
- vidbyte_cli/commands/auth/__init__.py +0 -0
- vidbyte_cli/commands/auth/login.py +100 -0
- vidbyte_cli/commands/auth/logout.py +41 -0
- vidbyte_cli/commands/auth/whoami.py +51 -0
- vidbyte_cli/commands/billing/__init__.py +20 -0
- vidbyte_cli/commands/billing/prompts/__init__.py +1 -0
- vidbyte_cli/commands/billing/prompts/confirm.md +16 -0
- vidbyte_cli/commands/billing/prompts/group.md +13 -0
- vidbyte_cli/commands/billing/prompts/idempotency_key.md +16 -0
- vidbyte_cli/commands/billing/prompts/library.py +18 -0
- vidbyte_cli/commands/billing/prompts/top_up.md +16 -0
- vidbyte_cli/commands/billing/top_up.py +65 -0
- vidbyte_cli/commands/config/__init__.py +0 -0
- vidbyte_cli/commands/config/get.py +45 -0
- vidbyte_cli/commands/config/set.py +39 -0
- vidbyte_cli/commands/provider/__init__.py +9 -0
- vidbyte_cli/commands/provider/login.py +102 -0
- vidbyte_cli/commands/provider/logout.py +45 -0
- vidbyte_cli/commands/provider/whoami.py +49 -0
- vidbyte_cli/commands/research/__init__.py +5 -0
- vidbyte_cli/commands/research/add.py +41 -0
- vidbyte_cli/commands/research/options.py +134 -0
- vidbyte_cli/commands/research/render.py +157 -0
- vidbyte_cli/commands/research/resume.py +37 -0
- vidbyte_cli/commands/research/start.py +37 -0
- vidbyte_cli/commands/research/status.py +32 -0
- vidbyte_cli/commands/research/thread.py +30 -0
- vidbyte_cli/commands/research/threads.py +35 -0
- vidbyte_cli/commands/research/watch.py +93 -0
- vidbyte_cli/commands/runtime/__init__.py +23 -0
- vidbyte_cli/commands/runtime/adversarial_team.py +41 -0
- vidbyte_cli/commands/runtime/doctor.py +44 -0
- vidbyte_cli/commands/runtime/list.py +47 -0
- vidbyte_cli/commands/runtime/persistence.py +141 -0
- vidbyte_cli/commands/runtime/same_host_ensemble.py +221 -0
- vidbyte_cli/commands/runtime/stages.py +553 -0
- vidbyte_cli/commands/runtime/task_board.py +1342 -0
- vidbyte_cli/commands/setup/__init__.py +0 -0
- vidbyte_cli/commands/setup/doctor.py +64 -0
- vidbyte_cli/commands/setup/prompts/__init__.py +1 -0
- vidbyte_cli/commands/setup/prompts/browser.md +7 -0
- vidbyte_cli/commands/setup/prompts/library.py +18 -0
- vidbyte_cli/commands/setup/prompts/star.md +13 -0
- vidbyte_cli/commands/setup/star.py +132 -0
- vidbyte_cli/lib/__init__.py +0 -0
- vidbyte_cli/lib/api/__init__.py +0 -0
- vidbyte_cli/lib/api/client.py +236 -0
- vidbyte_cli/lib/api/endpoints/__init__.py +0 -0
- vidbyte_cli/lib/api/endpoints/auth.py +24 -0
- vidbyte_cli/lib/api/endpoints/billing.py +36 -0
- vidbyte_cli/lib/api/endpoints/research.py +103 -0
- vidbyte_cli/lib/api/endpoints/runtime.py +113 -0
- vidbyte_cli/lib/api/problem.py +86 -0
- vidbyte_cli/lib/api/response.py +101 -0
- vidbyte_cli/lib/api/retry.py +116 -0
- vidbyte_cli/lib/api/runtime_payment.py +89 -0
- vidbyte_cli/lib/auth/__init__.py +16 -0
- vidbyte_cli/lib/auth/credentials.py +223 -0
- vidbyte_cli/lib/auth/input.py +59 -0
- vidbyte_cli/lib/auth/keyring_store.py +126 -0
- vidbyte_cli/lib/auth/provider_credentials.py +157 -0
- vidbyte_cli/lib/auth/provider_input.py +54 -0
- vidbyte_cli/lib/auth/provider_resolver.py +89 -0
- vidbyte_cli/lib/auth/provider_store.py +149 -0
- vidbyte_cli/lib/auth/provider_verifier.py +219 -0
- vidbyte_cli/lib/auth/resolver.py +63 -0
- vidbyte_cli/lib/auth/verifier.py +38 -0
- vidbyte_cli/lib/config/__init__.py +25 -0
- vidbyte_cli/lib/config/atomic.py +47 -0
- vidbyte_cli/lib/config/config.py +141 -0
- vidbyte_cli/lib/config/migration.py +81 -0
- vidbyte_cli/lib/config/models.py +145 -0
- vidbyte_cli/lib/config/paths.py +66 -0
- vidbyte_cli/lib/config/resolver.py +137 -0
- vidbyte_cli/lib/constants/__init__.py +1 -0
- vidbyte_cli/lib/constants/runtime.py +186 -0
- vidbyte_cli/lib/errors/__init__.py +12 -0
- vidbyte_cli/lib/errors/cli_error.py +86 -0
- vidbyte_cli/lib/errors/codes.py +43 -0
- vidbyte_cli/lib/errors/failures.py +2910 -0
- vidbyte_cli/lib/errors/handler.py +72 -0
- vidbyte_cli/lib/files/__init__.py +6 -0
- vidbyte_cli/lib/files/documents.py +94 -0
- vidbyte_cli/lib/files/store.py +156 -0
- vidbyte_cli/lib/io/__init__.py +16 -0
- vidbyte_cli/lib/io/attachments.py +129 -0
- vidbyte_cli/lib/io/codex_attachments.py +47 -0
- vidbyte_cli/lib/io/prompt.py +71 -0
- vidbyte_cli/lib/io/streams.py +37 -0
- vidbyte_cli/lib/io/terminal.py +66 -0
- vidbyte_cli/lib/output/__init__.py +7 -0
- vidbyte_cli/lib/output/formats.py +29 -0
- vidbyte_cli/lib/output/manager.py +95 -0
- vidbyte_cli/lib/output/models.py +49 -0
- vidbyte_cli/lib/runtime/__init__.py +9 -0
- vidbyte_cli/lib/runtime/application.py +134 -0
- vidbyte_cli/lib/runtime/context.py +250 -0
- vidbyte_cli/lib/runtime/options.py +159 -0
- vidbyte_cli/lib/runtime/version.py +30 -0
- vidbyte_cli/lib/runtime_primitives/__init__.py +28 -0
- vidbyte_cli/lib/runtime_primitives/executor.py +54 -0
- vidbyte_cli/lib/runtime_primitives/gate.py +105 -0
- vidbyte_cli/lib/runtime_primitives/hosts.py +45 -0
- vidbyte_cli/lib/runtime_primitives/planner.py +79 -0
- vidbyte_cli/lib/runtime_primitives/stages.py +338 -0
- vidbyte_cli/lib/runtime_primitives/task_board.py +982 -0
- vidbyte_cli/lib/runtime_primitives/task_board_checkpoints.py +473 -0
- vidbyte_cli/lib/runtime_primitives/task_board_system.md +33 -0
- vidbyte_cli/lib/runtime_primitives/task_board_tasks.py +111 -0
- vidbyte_cli/lib/runtime_primitives/verification.py +68 -0
- vidbyte_cli/services/__init__.py +5 -0
- vidbyte_cli/services/ensemble/__init__.py +7 -0
- vidbyte_cli/services/ensemble/prompts/__init__.py +6 -0
- vidbyte_cli/services/ensemble/prompts/implementer_system.md +546 -0
- vidbyte_cli/services/ensemble/prompts/implementer_turn.md +49 -0
- vidbyte_cli/services/ensemble/prompts/library.py +188 -0
- vidbyte_cli/services/ensemble/prompts/planner_system.md +728 -0
- vidbyte_cli/services/ensemble/prompts/planner_turn.md +6 -0
- vidbyte_cli/services/ensemble/prompts/role_system.md +104 -0
- vidbyte_cli/services/ensemble/prompts/role_turn.md +19 -0
- vidbyte_cli/services/ensemble/prompts/selector_final_turn.md +19 -0
- vidbyte_cli/services/ensemble/prompts/selector_round_turn.md +17 -0
- vidbyte_cli/services/ensemble/prompts/selector_system.md +89 -0
- vidbyte_cli/services/ensemble/runner.py +30 -0
- vidbyte_cli/services/ensemble/sdk.py +134 -0
- vidbyte_cli/services/ensemble/service.py +233 -0
- vidbyte_cli/services/ensemble/settings.py +60 -0
- vidbyte_cli/services/persistence/__init__.py +6 -0
- vidbyte_cli/services/persistence/prompts/__init__.py +6 -0
- vidbyte_cli/services/persistence/prompts/library.py +46 -0
- vidbyte_cli/services/persistence/prompts/persistence_system.md +1 -0
- vidbyte_cli/services/persistence/prompts/persistence_turn.md +3 -0
- vidbyte_cli/services/persistence/runner.py +35 -0
- vidbyte_cli/services/persistence/session.py +116 -0
- vidbyte_cli/services/suggestions/__init__.py +7 -0
- vidbyte_cli/services/suggestions/categories.py +270 -0
- vidbyte_cli/services/suggestions/context.py +222 -0
- vidbyte_cli/services/suggestions/extra_compute.py +53 -0
- vidbyte_cli/services/suggestions/handoff.py +173 -0
- vidbyte_cli/services/suggestions/message_tools.py +47 -0
- vidbyte_cli/services/suggestions/project.py +253 -0
- vidbyte_cli/services/suggestions/prompts/__init__.py +1 -0
- vidbyte_cli/services/suggestions/prompts/categories/__init__.py +3 -0
- vidbyte_cli/services/suggestions/prompts/categories/adjacent_opportunity.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/alternative.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/big_bets.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/bottleneck.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/business_growth.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/completion.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/continuation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/coordination.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/creative_exploration.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/cross_domain.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/delegation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/experiment.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/feedback.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/goal_clarification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/immediate_next_steps.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/investigation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/learning.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/leverage.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/long_term_directions.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/preparation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/prerequisite.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/prioritization.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/product_experience.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/quick_wins.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/reframing.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/risk_prevention.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/simplification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/stop_or_defer.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/strategy.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/technical_possibilities.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/verification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/critic.md +209 -0
- vidbyte_cli/services/suggestions/prompts/critic_message.md +8 -0
- vidbyte_cli/services/suggestions/prompts/generator.md +64 -0
- vidbyte_cli/services/suggestions/prompts/library.py +72 -0
- vidbyte_cli/services/suggestions/prompts/message_generator_argument.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_generator_tool.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_parent_argument.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_parent_tool.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_receipt.md +1 -0
- vidbyte_cli/services/suggestions/prompts/revision.md +8 -0
- vidbyte_cli/services/suggestions/sdk.py +228 -0
- vidbyte_cli/services/suggestions/selection.py +78 -0
- vidbyte_cli/services/suggestions/service.py +540 -0
- vidbyte_cli/types/__init__.py +0 -0
- vidbyte_cli/types/api.py +101 -0
- vidbyte_cli/types/attachments.py +79 -0
- vidbyte_cli/types/ensemble.py +189 -0
- vidbyte_cli/types/provider.py +62 -0
- vidbyte_cli/types/research.py +260 -0
- vidbyte_cli/types/runtime.py +1098 -0
- vidbyte_cli/types/suggestions.py +518 -0
- vidbyte_cli-0.1.0.dist-info/METADATA +335 -0
- vidbyte_cli-0.1.0.dist-info/RECORD +269 -0
- vidbyte_cli-0.1.0.dist-info/WHEEL +5 -0
- vidbyte_cli-0.1.0.dist-info/entry_points.txt +2 -0
- vidbyte_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- vidbyte_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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"
|