inspect: drop per-inspector model selection from guided setup · Entire

inspect: drop per-inspector model selection from guided setup

66ed712→main·

dipree·1mo ago·1 file·+21 added/-33 removed

Inspectors run on the agent's default model, so the interactive crew flow no longer asks for or edits a per-inspector model:

The judge keeps its model picker (model matters for the consolidating call). Scripted/runtime model handling is unchanged.

Sessions

cc2e5e5a3dccView transcript

?\ Checkout the hand off doc that I just added.Pi·Opus 4.8·1 step

Changes

1

366 unmodified lines

367
368
369
370
371
372
370
371
372
373
374
375
376
377
378
379
380
381
382
383
378
379
380
7 unmodified lines

388
389
390
397
398
391
392
393
394
395
396
397
398
404
405
406
407
408
399
400
401
411
412
413
414
402
403
404
405
406
407
2 unmodified lines

410
411
412
423
424
425
426
427
413
414
415
416
23 unmodified lines

440
441
442
457
458
459
443
444
445
446
447
461
448
449
450
451
15 unmodified lines

467
468
469
483
484
485
470
471
472
473
474
487
475
476
477
478

366 unmodified lines

if convErr != nil || idx < 0 || idx >= len(slots) {
                continue
            }
            // The agent identifies the slot, so editing defaults to the model
            // (the common case). Swapping the agent is offered as a distinct action,
            // and only when there is more than one candidate to switch to.
            // Inspectors are identified by their agent and run on the agent's
            // default model, so editing only swaps the agent (when more than one is
            // available) or removes the slot — there is no per-inspector model.
            action, err := promptSlotAction(ctx, slots[idx], len(candidates) > 1)
            if err != nil {
                return nil, err
            }
            switch action {
            case "model":
                model, err := promptCrewModel(ctx, slots[idx].agent, slots[idx].model)
                if err != nil {
                    return nil, err
                }
                slots[idx].model = model
            case "agent":
                slot, err := promptChangeAgent(ctx, candidates, slots[idx])
                if err != nil {
                    return crewSlot{}, err
                }

}
}

// promptCrewSlot prompts for one slot: an agent then a model. seed pre-selects
// the current agent/model when editing an existing slot (zero value when adding).
// promptCrewSlot prompts for one inspector slot: just the agent. Inspectors run
// on the agent's default model, so no model is asked here. seed pre-selects the
// current agent when editing (zero value when adding).
func promptCrewSlot(ctx context.Context, launchable []string, seed crewSlot) (crewSlot, error) {
    agentName, err := promptCrewAgent(ctx, launchable, seed.agent, false)
    if err != nil {
        return crewSlot{}, err
    }
    model, err := promptCrewModel(ctx, agentName, seed.model)
    if err != nil {
        return crewSlot{}, err
    }
    return crewSlot{agent: agentName, model: model}, nil
    return crewSlot{agent: agentName}, nil
}

// promptChangeAgent swaps the agent on an existing slot. Because advertised
// models are agent-specific, picking a different agent re-prompts for the model
// (seeded with the current one, so a still-valid/custom value is preserved).
// Keeping the same agent leaves the slot untouched.
// promptChangeAgent swaps the agent on an existing inspector slot. Keeping the
// same agent leaves the slot untouched (preserving any model set via scripted
// config); choosing a different agent resets to that agent's default model.
func promptChangeAgent(ctx context.Context, candidates []string, seed crewSlot) (crewSlot, error) {
    agentName, err := promptCrewAgent(ctx, candidates, seed.agent, true)
    if err != nil {
        return crewSlot{}, err
    }

if agentName == seed.agent {
        return seed, nil
    }
    model, err := promptCrewModel(ctx, agentName, seed.model)
    if err != nil {
        return crewSlot{}, err
    }
    return crewSlot{agent: agentName, model: model}, nil
    return crewSlot{agent: agentName}, nil
}

// buildCrewProfile turns an ordered slot list into a profile. Each slot becomes
23 unmodified lines

return profile
}

// promptSlotAction asks what to do with an existing slot row. The model is the
// primary edit (the agent identifies the slot); "Change agent" is only offered
// when allowAgentChange is set (more than one candidate agent exists).
// promptSlotAction asks what to do with an existing inspector slot row.
// Inspectors run on the agent's default model, so the only edits are swapping
// the agent (offered when allowAgentChange is set — more than one candidate
// exists) or removing the slot.
func promptSlotAction(ctx context.Context, slot crewSlot, allowAgentChange bool) (string, error) {
    options := []huh.Option[string]{huh.NewOption("Change model", "model")}
    options := make([]huh.Option[string], 0, 3)
    if allowAgentChange {
        options = append(options, huh.NewOption("Change agent", "agent"))
    }
15 unmodified lines

}

func slotLabel(s crewSlot) string {
    model := strings.TrimSpace(s.model)
    if model == "" {
        model = "default model"
    // Inspectors normally run on the agent's default model; only surface a model
    // when one was set explicitly (e.g. via scripted --set-slot).
    if model := strings.TrimSpace(s.model); model != "" {
        return labelForSimpleAgent(s.agent) + " · " + model
    }
    return labelForSimpleAgent(s.agent) + " · " + model
    return labelForSimpleAgent(s.agent)
}

// promptCrewAgent picks the agent for a slot. Auto-selects when only one