blob: 6f126ab4d96e1ee9c048120eaebe08697a1de69e [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"
7 "strings"
8 "sync"
9)
10
11// Experiment represents an experimental feature.
12// Experiments are global.
13type Experiment struct {
14 Name string // The name of the experiment used in -x flag
15 Description string // A short description of what the experiment does
16 Enabled bool // Whether the experiment is enabled
17}
18
19var (
20 mu sync.Mutex
21 experiments = []Experiment{
22 {
23 Name: "list",
24 Description: "List all available experiments and exit",
25 },
26 {
27 Name: "all",
28 Description: "Enable all experiments",
29 },
Josh Bleecher Snyder503b5e32025-05-05 13:30:55 -070030 {
31 Name: "not_done",
32 Description: "Let the model backtrack halfway through a done tool call",
33 },
Josh Bleecher Snydere2518e52025-04-29 11:13:40 -070034 {
35 Name: "llm_review",
36 Description: "Add an LLM step to the codereview tool",
37 },
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000038 }
39 byName = map[string]*Experiment{}
40)
41
42func Enabled(name string) bool {
43 mu.Lock()
44 defer mu.Unlock()
45 return byName[name].Enabled
46}
47
48func init() {
49 for _, e := range experiments {
50 byName[e.Name] = &e
51 }
52}
53
54func (e Experiment) String() string {
55 return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
56}
57
58// Fprint writes a list of all available experiments to w.
59func Fprint(w io.Writer) {
60 mu.Lock()
61 defer mu.Unlock()
62
63 fmt.Fprintln(w, "Available experiments:")
64 for _, e := range experiments {
65 fmt.Fprintln(w, e)
66 }
67}
68
69// Flag is a custom flag type that allows for comma-separated
70// values and can be used multiple times.
71type Flag struct {
72 Value string
73}
74
75// String returns the string representation of the flag value.
76func (f *Flag) String() string {
77 return f.Value
78}
79
80// Set adds a value to the flag.
81func (f *Flag) Set(value string) error {
82 f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
83 return nil
84}
85
86// Get returns the flag values.
87func (f *Flag) Get() any {
88 return f.Value
89}
90
91// Process handles all flag values, enabling the appropriate experiments.
92func (f *Flag) Process() error {
93 mu.Lock()
94 defer mu.Unlock()
95
96 for name := range strings.SplitSeq(f.Value, ",") {
97 name = strings.TrimSpace(name)
98 if name == "" {
99 continue
100 }
101 e, ok := byName[name]
102 if !ok {
103 return fmt.Errorf("unknown experiment: %q", name)
104 }
105 e.Enabled = true
106 }
107 if byName["all"].Enabled {
108 for _, e := range experiments {
109 if e.Name == "list" {
110 continue
111 }
112 e.Enabled = true
113 }
114 }
115 return nil
116}