@@ -32,6 +32,8 @@ var respondFn = dun.Respond
3232var installRepo = dun .InstallRepo
3333var callHarnessFn = callHarnessImpl
3434var callHarnessStreamingFn = callHarnessStreamingImpl
35+ var harnessModel string
36+ var harnessModelOverrides map [string ]string
3537
3638func main () {
3739 code := run (os .Args [1 :], os .Stdout , os .Stderr )
@@ -99,6 +101,8 @@ REVIEW MODE:
99101 --principles Path to principles document (default docs/helix/01-frame/principles.md)
100102 --harnesses Comma-separated list of review harnesses (default: codex,claude,gemini)
101103 --synth-harness Harness to synthesize final review (default: first harness)
104+ --model Model override for selected harness(es)
105+ --models Per-harness model overrides (e.g., codex:o3,claude:sonnet)
102106 --automation Mode: manual, plan, auto, yolo (default: auto)
103107 --dry-run Print prompt without calling harnesses
104108 --verbose Print individual harness reviews
@@ -121,6 +125,8 @@ LOOP MODE:
121125
122126 Options:
123127 --harness Agent to use: codex, claude, gemini, opencode (default: from config)
128+ --model Model override for selected harness(es)
129+ --models Per-harness model overrides (e.g., codex:o3,claude:sonnet)
124130 --automation Mode: manual, plan, auto, yolo (default: auto)
125131 --max-iterations Safety limit (default: 100)
126132 --dry-run Show prompt without calling agent
@@ -501,6 +507,8 @@ func runLoop(args []string, stdout io.Writer, stderr io.Writer) int {
501507 fs .SetOutput (stderr )
502508 configPath := fs .String ("config" , explicitConfig , "path to config file" )
503509 harness := fs .String ("harness" , "" , "agent harness (codex|claude|gemini|opencode); default from config" )
510+ model := fs .String ("model" , opts .AgentModel , "model override for selected harness(es)" )
511+ models := fs .String ("models" , "" , "per-harness model overrides (e.g., codex:o3,claude:sonnet)" )
504512 automation := fs .String ("automation" , opts .AutomationMode , "automation mode (manual|plan|auto|yolo)" )
505513 maxIterations := fs .Int ("max-iterations" , 100 , "maximum iterations before stopping" )
506514 dryRun := fs .Bool ("dry-run" , false , "print prompt without calling harness" )
@@ -529,6 +537,30 @@ func runLoop(args []string, stdout io.Writer, stderr io.Writer) int {
529537 }
530538 }
531539
540+ modelOverrides := make (map [string ]string )
541+ for harnessName , modelName := range opts .AgentModels {
542+ if modelName == "" {
543+ continue
544+ }
545+ modelOverrides [harnessName ] = modelName
546+ }
547+ if * models != "" {
548+ parsed , parseErr := parseHarnessModelOverrides (* models )
549+ if parseErr != nil {
550+ fmt .Fprintf (stderr , "dun loop failed: invalid models: %v\n " , parseErr )
551+ return dun .ExitUsageError
552+ }
553+ for harnessName , modelName := range parsed {
554+ modelOverrides [harnessName ] = modelName
555+ }
556+ }
557+ harnessModel = strings .TrimSpace (* model )
558+ if len (modelOverrides ) == 0 {
559+ harnessModelOverrides = nil
560+ } else {
561+ harnessModelOverrides = modelOverrides
562+ }
563+
532564 // Parse quorum configuration if specified
533565 var quorumCfg dun.QuorumConfig
534566 if * quorumFlag != "" || * harnessesFlag != "" {
@@ -737,6 +769,40 @@ func callHarnessStreaming(harness, prompt, automation string, stdout, stderr io.
737769 return callHarnessStreamingFn (harness , prompt , automation , stdout , stderr )
738770}
739771
772+ func resolveHarnessModel (harness string ) string {
773+ if len (harnessModelOverrides ) > 0 {
774+ if model , ok := harnessModelOverrides [harness ]; ok && model != "" {
775+ return model
776+ }
777+ }
778+ return harnessModel
779+ }
780+
781+ func parseHarnessModelOverrides (raw string ) (map [string ]string , error ) {
782+ if raw == "" {
783+ return nil , nil
784+ }
785+ overrides := make (map [string ]string )
786+ parts := strings .Split (raw , "," )
787+ for _ , part := range parts {
788+ item := strings .TrimSpace (part )
789+ if item == "" {
790+ continue
791+ }
792+ fields := strings .SplitN (item , ":" , 2 )
793+ if len (fields ) != 2 {
794+ return nil , fmt .Errorf ("invalid model override %q (expected harness:model)" , item )
795+ }
796+ harness := strings .TrimSpace (fields [0 ])
797+ model := strings .TrimSpace (fields [1 ])
798+ if harness == "" || model == "" {
799+ return nil , fmt .Errorf ("invalid model override %q (expected harness:model)" , item )
800+ }
801+ overrides [harness ] = model
802+ }
803+ return overrides , nil
804+ }
805+
740806func callHarnessImpl (harnessName , prompt , automation string ) (string , error ) {
741807 // Convert automation string to AutomationMode
742808 var mode dun.AutomationMode
@@ -757,7 +823,8 @@ func callHarnessImpl(harnessName, prompt, automation string) (string, error) {
757823 ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Minute )
758824 defer cancel ()
759825
760- result , err := dun .ExecuteHarness (ctx , harnessName , prompt , mode , "." )
826+ model := resolveHarnessModel (harnessName )
827+ result , err := dun .ExecuteHarness (ctx , harnessName , prompt , mode , "." , model )
761828 if err != nil {
762829 return "" , err
763830 }
@@ -784,7 +851,8 @@ func callHarnessStreamingImpl(harnessName, prompt, automation string, stdout, st
784851 ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Minute )
785852 defer cancel ()
786853
787- result , err := dun .ExecuteHarnessWithOutput (ctx , harnessName , prompt , mode , "." , stdout , stderr )
854+ model := resolveHarnessModel (harnessName )
855+ result , err := dun .ExecuteHarnessWithOutput (ctx , harnessName , prompt , mode , "." , model , stdout , stderr )
788856 if err != nil {
789857 return "" , err
790858 }
0 commit comments