Stage 3: scout guided flow — single-screen crew + standalone master step · Entire
Stage 3: scout guided flow — single-screen crew + standalone master step
a9ea5bf→main·
dipree·1mo ago·1 file·+136 added/-41 removed
- Slots: single-screen list seeded with ALL launchable agents (default model). Rows are selectable to Edit/Remove; "+ Add slot" adds one; "Done · N" finishes. Replaces the linear add-another loop.
- Master: separate step that now picks a STANDALONE master (its own agent + model via MasterAgent/MasterModel), chosen from installed text-generation-capable agents — not constrained to the worker slots. Only prompted when ≥2 slots.
- Task stays uniform across the whole crew (guided default); per-slot skills remain available via
entire scout --edit.
Removes promptForSimpleReviewMaster + promptAddAnotherSlot; adds promptForStandaloneMaster, promptCrewSlot, promptSlotAction.
Sessions
bca7cbf63255View transcript
?\
List me all the potential command combinations for review.Pi·Opus 4.8·1 step
Changes
1
cmd/entire/cli/review
- Mpicker.go+136/-41
11 unmodified lines
12
13
14
15
16
17
18
102 unmodified lines
121
122
123
123
124
125
126
127
127
128
129
130
131
132
133
45 unmodified lines
179
180
181
179
180
181
182
182
183
184
185
186
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
191
192
193
203
204
205
206
195
196
197
207
208
209
210
199
200
201
202
203
204
205
211
212
213
214
207
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
15 unmodified lines
293
294
295
234
235
236
237
296
297
298
299
239
240
241
242
243
300
301
302
303
304
305
306
307
308
309
246
310
311
248
312
313
314
315
117 unmodified lines
433
434
435
372
373
374
375
436
437
438
439
440
441
442
443
444
445
377
378
446
447
448
380
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
11 unmodified lines
"io"
"log/slog"
"sort"
"strconv"
"strings"
"charm.land/huh/v2"
102 unmodified lines
return "", settings.ReviewProfileConfig{}, err
}
if len(profile.Agents) > 1 {
master, err := promptForSimpleReviewMaster(ctx, profile)
masterAgent, masterModel, err := promptForStandaloneMaster(ctx, out, launchable)
if err != nil {
return "", settings.ReviewProfileConfig{}, err
}
profile.Master = master
profile.MasterAgent = masterAgent
profile.MasterModel = masterModel
profile.Master = ""
}
fmt.Fprintf(out, "Saved %q review profile with %s.\n", profileName, strings.Join(sortedProfileAgentNames(profile), ", "))
fmt.Fprintln(out)
45 unmodified lines
model string
}
// promptForReviewCrew builds the review crew one worker at a time. It always
// configures a first slot (agent + model), then keeps offering "Add another"
// until the user is done — so the crew is never empty and the same agent can be
// added repeatedly, on different or identical models.
// promptForReviewCrew builds the review crew on a single screen. It seeds one
// slot per launchable agent (the guided default is "all agents"), then lets the
// user add, edit, or remove slots from a list until Done. Duplicate slots (same
// agent and model) are allowed; each becomes its own worker.
func promptForReviewCrew(ctx context.Context, out io.Writer, profileName string, launchable []string) (settings.ReviewProfileConfig, error) {
fmt.Fprintln(out, "Build the review crew")
fmt.Fprintln(out, "Add one worker at a time — each is an agent + model. You can add the same")
fmt.Fprintln(out, "agent more than once, on different or identical models.")
fmt.Fprintln(out, "Add review slots")
fmt.Fprintln(out, "Each slot is an agent + model. The whole crew runs the same task;")
fmt.Fprintln(out, "set per-slot skills later with `entire scout --edit`.\n")
slots := make([]crewSlot, 0, len(launchable))
for _, name := range launchable {
slots = append(slots, crewSlot{agent: name})
}
const (
actAdd = "add"
actDone = "done"
slotPrefix = "slot:"
)
for {
agentName, err := promptCrewAgent(ctx, launchable)
if err != nil {
return settings.ReviewProfileConfig{}, err
}
options := make([]huh.Option[string], 0, len(slots)+2)
for i, s := range slots {
options = append(options, huh.NewOption(fmt.Sprintf("%d %s", i+1, slotLabel(s)), slotPrefix+strconv.Itoa(i)))
}
model, err := promptCrewModel(ctx, agentName)
if err != nil {
return settings.ReviewProfileConfig{}, err
}
options = append(options, huh.NewOption("+ Add slot", actAdd))
if len(slots) > 0 {
options = append(options, huh.NewOption(fmt.Sprintf("Done · %d slot(s)", len(slots)), actDone))
}
slot := crewSlot{agent: agentName, model: model}
slots = append(slots, slot)
fmt.Fprintf(out, "Added %s (%d in crew)\n\n", slotLabel(slot), len(slots))
more, err := promptAddAnotherSlot(ctx)
if err != nil {
return settings.ReviewProfileConfig{}, err
}
picked := actDone
if len(slots) == 0 {
picked = actAdd
}
if !more {
form := newAccessibleForm(huh.NewGroup(
huh.NewSelect[string]().
Title("Review crew").
Description("Select a slot to edit or remove it; + Add slot to add one.").
Options(options...).
Height(reviewPickerHeight(len(options))).
Value(&picked),
))
if err := form.RunWithContext(ctx); err != nil {
return settings.ReviewProfileConfig{}, fmt.Errorf("review crew picker: %w", err)
}
switch {
case picked == actAdd:
slot, err := promptCrewSlot(ctx, launchable)
if err != nil {
return settings.ReviewProfileConfig{}, err
}
slots = append(slots, slot)
case picked == actDone:
if len(slots) == 0 {
continue
}
return buildCrewProfile(ctx, profileName, slots), nil
case strings.HasPrefix(picked, slotPrefix):
idx, convErr := strconv.Atoi(strings.TrimPrefix(picked, slotPrefix))
if convErr != nil || idx < 0 || idx >= len(slots) {
continue
}
action, err := promptSlotAction(ctx, slots[idx])
if err != nil {
return settings.ReviewProfileConfig{}, err
}
switch action {
case "edit":
slot, err := promptCrewSlot(ctx, launchable)
if err != nil {
return settings.ReviewProfileConfig{}, err
}
slots[idx] = slot
case "remove":
slots = append(slots[:idx], slots[idx+1:]...)
}
}
}
}
// promptCrewSlot prompts for one slot: an agent then a model.
func promptCrewSlot(ctx context.Context, launchable []string) (crewSlot, error) {
agentName, err := promptCrewAgent(ctx, launchable)
if err != nil {
return crewSlot{}, err
}
model, err := promptCrewModel(ctx, agentName)
if err != nil {
return crewSlot{}, err
}
return crewSlot{agent: agentName, model: model}, nil
}
// buildCrewProfile turns an ordered slot list into a profile. Each slot becomes
// a worker keyed by workerIDForAgentModel, which disambiguates duplicates
// (claude-code, claude-code-2, claude-code:opus, …).
return profile
}
// promptAddAnotherSlot asks whether to add another worker. Defaults to Done, so
// pressing enter finishes the crew.
func promptAddAnotherSlot(ctx context.Context) (bool, error) {
add := false
// promptSlotAction asks what to do with an existing slot row.
func promptSlotAction(ctx context.Context, slot crewSlot) (string, error) {
picked := "cancel"
form := newAccessibleForm(huh.NewGroup(
huh.NewConfirm().
Title("Add another worker?").
Affirmative("Add another").
Negative("Done").
Value(&add),
huh.NewSelect[string]().
Title(slotLabel(slot)).
Options(
huh.NewOption("Edit", "edit"),
huh.NewOption("Remove", "remove"),
huh.NewOption("Cancel", "cancel"),
).
Value(&picked),
))
if err := form.RunWithContext(ctx); err != nil {
return false, fmt.Errorf("review crew add-another: %w", err)
}
return add, nil
}
return picked, nil
}
func slotLabel(s crewSlot) string {
return false
}
func promptForSimpleReviewMaster(ctx context.Context, profile settings.ReviewProfileConfig) (string, error) {
choices := reviewMasterAgentChoices(profile.Agents)
if len(choices) == 0 {
return "", errors.New("no selected review agent can write the final report")
// promptForStandaloneMaster picks the standalone master (judge) as its own
// agent + model, independent of the worker slots. Candidates are launchable
// agents that can write text. Returns (agentName, model).
func promptForStandaloneMaster(ctx context.Context, out io.Writer, launchable []string) (string, string, error) {
candidates := make([]string, 0, len(launchable))
for _, name := range launchable {
if agentSupportsTextGeneration(ctx, name) {
candidates = append(candidates, name)
}
}
if len(choices) == 1 {
return choices[0].Name, nil
}
if len(candidates) == 0 {
return "", "", errors.New("no installed agent can write the final report")
}
return promptForReviewMasterAgent(ctx, choices, profile.Master)
fmt.Fprintln(out, "Choose the review master")
fmt.Fprintln(out, "It evaluates the workers' reports and writes the final verdict.")
fmt.Fprintln(out, "")
agentName := candidates[0]
if len(candidates) > 1 {
options := make([]huh.Option[string], 0, len(candidates))
for _, name := range candidates {
options = append(options, huh.NewOption(labelForSimpleAgent(name), name))
}
form := newAccessibleForm(huh.NewGroup(
huh.NewSelect[string]().
Title("Master agent").
Options(options...).
Height(reviewPickerHeight(len(options))).
Value(&agentName),
))
if err := form.RunWithContext(ctx); err != nil {
return "", "", fmt.Errorf("master agent picker: %w", err)
}
}
model, err := promptCrewModel(ctx, agentName)
if err != nil {
return "", "", fmt.Errorf("master model picker: %w", err)
}
return agentName, model, nil
}
func ConfirmRunReviewNow(ctx context.Context, out io.Writer) (bool, error) {