experiment: add simple way to toggle experimental features
diff --git a/cmd/sketch/main.go b/cmd/sketch/main.go
index d66b995..86f57da 100644
--- a/cmd/sketch/main.go
+++ b/cmd/sketch/main.go
@@ -15,6 +15,7 @@
 	"strings"
 	"time"
 
+	"sketch.dev/experiment"
 	"sketch.dev/llm"
 	"sketch.dev/llm/gem"
 	"sketch.dev/llm/oai"
@@ -74,6 +75,15 @@
 		return fmt.Errorf("only -model=claude is supported in safe mode right now, use -unsafe -skaband-addr=''")
 	}
 
+	if err := flagArgs.experimentFlag.Process(); err != nil {
+		fmt.Fprintf(os.Stderr, "error parsing experimental flags: %v\n", err)
+		os.Exit(1)
+	}
+	if experiment.Enabled("list") {
+		experiment.Fprint(os.Stdout)
+		os.Exit(0)
+	}
+
 	// Add a global "session_id" to all logs using this context.
 	// A "session" is a single full run of the agent.
 	ctx := skribe.ContextWithAttr(context.Background(), slog.String("session_id", flagArgs.sessionID))
@@ -146,6 +156,7 @@
 	initialCommit     string
 	gitUsername       string
 	gitEmail          string
+	experimentFlag    experiment.Flag
 	sessionID         string
 	record            bool
 	noCleanup         bool
@@ -192,6 +203,7 @@
 	flag.StringVar(&flags.outsideOS, "outside-os", "", "(internal) OS on the outside system")
 	flag.StringVar(&flags.outsideWorkingDir, "outside-working-dir", "", "(internal) working dir on the outside system")
 	flag.StringVar(&flags.sketchBinaryLinux, "sketch-binary-linux", "", "(development) path to a pre-built sketch binary for linux")
+	flag.Var(&flags.experimentFlag, "x", "enable experimental features (comma-separated list or repeat flag; use 'list' to show all)")
 
 	flag.Parse()
 	return flags
diff --git a/experiment/experiment.go b/experiment/experiment.go
new file mode 100644
index 0000000..1e57f8d
--- /dev/null
+++ b/experiment/experiment.go
@@ -0,0 +1,108 @@
+// Package experiment provides support for experimental features.
+package experiment
+
+import (
+	"fmt"
+	"io"
+	"strings"
+	"sync"
+)
+
+// Experiment represents an experimental feature.
+// Experiments are global.
+type Experiment struct {
+	Name        string // The name of the experiment used in -x flag
+	Description string // A short description of what the experiment does
+	Enabled     bool   // Whether the experiment is enabled
+}
+
+var (
+	mu          sync.Mutex
+	experiments = []Experiment{
+		{
+			Name:        "list",
+			Description: "List all available experiments and exit",
+		},
+		{
+			Name:        "all",
+			Description: "Enable all experiments",
+		},
+	}
+	byName = map[string]*Experiment{}
+)
+
+func Enabled(name string) bool {
+	mu.Lock()
+	defer mu.Unlock()
+	return byName[name].Enabled
+}
+
+func init() {
+	for _, e := range experiments {
+		byName[e.Name] = &e
+	}
+}
+
+func (e Experiment) String() string {
+	return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
+}
+
+// Fprint writes a list of all available experiments to w.
+func Fprint(w io.Writer) {
+	mu.Lock()
+	defer mu.Unlock()
+
+	fmt.Fprintln(w, "Available experiments:")
+	for _, e := range experiments {
+		fmt.Fprintln(w, e)
+	}
+}
+
+// Flag is a custom flag type that allows for comma-separated
+// values and can be used multiple times.
+type Flag struct {
+	Value string
+}
+
+// String returns the string representation of the flag value.
+func (f *Flag) String() string {
+	return f.Value
+}
+
+// Set adds a value to the flag.
+func (f *Flag) Set(value string) error {
+	f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
+	return nil
+}
+
+// Get returns the flag values.
+func (f *Flag) Get() any {
+	return f.Value
+}
+
+// Process handles all flag values, enabling the appropriate experiments.
+func (f *Flag) Process() error {
+	mu.Lock()
+	defer mu.Unlock()
+
+	for name := range strings.SplitSeq(f.Value, ",") {
+		name = strings.TrimSpace(name)
+		if name == "" {
+			continue
+		}
+		e, ok := byName[name]
+		if !ok {
+			return fmt.Errorf("unknown experiment: %q", name)
+		}
+		e.Enabled = true
+	}
+	if byName["all"].Enabled {
+		for _, e := range experiments {
+			if e.Name == "list" {
+				continue
+			}
+			e.Enabled = true
+		}
+	}
+	return nil
+}