blob: e7c798ab812fa244401f18b4dc365bba8b0e5e2f [file] [log] [blame]
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +00001// Package experiment provides support for experimental features.
2package experiment
3
4import (
5 "fmt"
6 "io"
Josh Bleecher Snyder6f446fa2025-05-05 14:51:49 -07007 "log/slog"
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +00008 "strings"
9 "sync"
10)
11
12// Experiment represents an experimental feature.
13// Experiments are global.
14type Experiment struct {
15 Name string // The name of the experiment used in -x flag
16 Description string // A short description of what the experiment does
17 Enabled bool // Whether the experiment is enabled
18}
19
20var (
21 mu sync.Mutex
22 experiments = []Experiment{
23 {
24 Name: "list",
25 Description: "List all available experiments and exit",
26 },
27 {
28 Name: "all",
29 Description: "Enable all experiments",
30 },
Josh Bleecher Snyder503b5e32025-05-05 13:30:55 -070031 {
32 Name: "not_done",
33 Description: "Let the model backtrack halfway through a done tool call",
34 },
Josh Bleecher Snydere2518e52025-04-29 11:13:40 -070035 {
36 Name: "llm_review",
37 Description: "Add an LLM step to the codereview tool",
38 },
Josh Bleecher Snyderd7970e62025-05-01 01:56:28 +000039 {
40 Name: "precommit",
41 Description: "Changes title tool to a precommit tool that provides commit message style guidance",
42 },
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000043 }
44 byName = map[string]*Experiment{}
45)
46
47func Enabled(name string) bool {
48 mu.Lock()
49 defer mu.Unlock()
Josh Bleecher Snyder6f446fa2025-05-05 14:51:49 -070050 e, ok := byName[name]
51 if !ok {
52 slog.Error("unknown experiment", "name", name)
53 return false
54 }
55 return e.Enabled
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000056}
57
58func init() {
59 for _, e := range experiments {
60 byName[e.Name] = &e
61 }
62}
63
64func (e Experiment) String() string {
65 return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
66}
67
68// Fprint writes a list of all available experiments to w.
69func Fprint(w io.Writer) {
70 mu.Lock()
71 defer mu.Unlock()
72
73 fmt.Fprintln(w, "Available experiments:")
74 for _, e := range experiments {
75 fmt.Fprintln(w, e)
76 }
77}
78
79// Flag is a custom flag type that allows for comma-separated
80// values and can be used multiple times.
81type Flag struct {
82 Value string
83}
84
85// String returns the string representation of the flag value.
86func (f *Flag) String() string {
87 return f.Value
88}
89
90// Set adds a value to the flag.
91func (f *Flag) Set(value string) error {
92 f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
93 return nil
94}
95
96// Get returns the flag values.
97func (f *Flag) Get() any {
98 return f.Value
99}
100
101// Process handles all flag values, enabling the appropriate experiments.
102func (f *Flag) Process() error {
103 mu.Lock()
104 defer mu.Unlock()
105
106 for name := range strings.SplitSeq(f.Value, ",") {
107 name = strings.TrimSpace(name)
108 if name == "" {
109 continue
110 }
111 e, ok := byName[name]
112 if !ok {
113 return fmt.Errorf("unknown experiment: %q", name)
114 }
115 e.Enabled = true
116 }
117 if byName["all"].Enabled {
118 for _, e := range experiments {
119 if e.Name == "list" {
120 continue
121 }
122 e.Enabled = true
123 }
124 }
125 return nil
126}