blob: 355fe14ca8e3b2f83a0a5a8050820ffcb346b1dd [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() {
Josh Bleecher Snydere01ea0e2025-05-06 02:03:26 +000059 for i := range experiments {
60 e := &experiments[i]
61 byName[e.Name] = e
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000062 }
63}
64
65func (e Experiment) String() string {
66 return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
67}
68
69// Fprint writes a list of all available experiments to w.
70func Fprint(w io.Writer) {
71 mu.Lock()
72 defer mu.Unlock()
73
74 fmt.Fprintln(w, "Available experiments:")
75 for _, e := range experiments {
76 fmt.Fprintln(w, e)
77 }
78}
79
80// Flag is a custom flag type that allows for comma-separated
81// values and can be used multiple times.
82type Flag struct {
83 Value string
84}
85
86// String returns the string representation of the flag value.
87func (f *Flag) String() string {
88 return f.Value
89}
90
91// Set adds a value to the flag.
92func (f *Flag) Set(value string) error {
Josh Bleecher Snyder085f74c2025-05-06 01:58:25 +000093 if f.Value == "" {
94 f.Value = value
95 } else {
96 f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
97 }
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000098 return nil
99}
100
101// Get returns the flag values.
102func (f *Flag) Get() any {
103 return f.Value
104}
105
106// Process handles all flag values, enabling the appropriate experiments.
107func (f *Flag) Process() error {
108 mu.Lock()
109 defer mu.Unlock()
110
111 for name := range strings.SplitSeq(f.Value, ",") {
112 name = strings.TrimSpace(name)
113 if name == "" {
114 continue
115 }
116 e, ok := byName[name]
117 if !ok {
118 return fmt.Errorf("unknown experiment: %q", name)
119 }
120 e.Enabled = true
121 }
122 if byName["all"].Enabled {
Josh Bleecher Snyderd42577f2025-05-05 17:59:00 -0700123 for i := range experiments {
124 e := &experiments[i]
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +0000125 if e.Name == "list" {
126 continue
127 }
128 e.Enabled = true
129 }
130 }
131 return nil
132}