package main import ( "bufio" "context" "flag" "fmt" "os" "os/signal" "path/filepath" "sort" "strings" "time" ) const dataDir = ".crows" func main() { if len(os.Args) < 2 { usage() } switch os.Args[1] { case "run": cmdRun(os.Args[2:]) case "trust": cmdTrust() case "log": cmdLog(os.Args[2:]) default: usage() } } func usage() { fmt.Fprintln(os.Stderr, `So Many Crows crows run [-as name] hand the flock a todo list crows trust show trust ledger crows log [-n 20] show recent flight log entries`) os.Exit(2) } type itemResult struct { Item, Mode, TaskType, Answer, Verdict, Skipped string Crows []string } func cmdRun(args []string) { fs := flag.NewFlagSet("run", flag.ExitOnError) as := fs.String("as", defaultRequester(), "requester id") var pos []string for fs.Parse(args); fs.NArg() > 0; fs.Parse(args) { pos = append(pos, fs.Arg(0)) args = fs.Args()[1:] } if len(pos) != 1 { usage() } todoPath := pos[0] g := DefaultGuardrails() ledger, err := LoadLedger(filepath.Join(dataDir, "ledger.json")) must(err) flog, err := OpenFlightLog(filepath.Join(dataDir, "flight.jsonl")) must(err) defer flog.Close() save := func() { if err := ledger.Save(); err != nil { fmt.Fprintln(os.Stderr, "could not save ledger:", err) } } if ok, msg := ledger.Admit(*as, time.Now(), g); !ok { fmt.Println(msg) flog.Write(Event{Kind: "denied", Requester: *as, Note: msg}) save() os.Exit(1) } items, err := readTodo(todoPath, g) must(err) if len(items) > g.MaxItemsPerList { fmt.Printf("That list has %d items. The flock takes at most %d at a time.\n", len(items), g.MaxItemsPerList) save() os.Exit(1) } brain := NewBrain() flock := NewFlock(brain, g, flog) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() in := bufio.NewReader(os.Stdin) listBudget := &Budget{name: "list", limit: g.TokenBudgetPerList} fmt.Printf("Brain: %s · requester: %s · %d items\n", brain.Name(), *as, len(items)) flog.Write(Event{Kind: "list", Requester: *as, Note: fmt.Sprintf("%s (%d items)", todoPath, len(items))}) var results []itemResult for i, item := range items { fmt.Printf("\n[%d/%d] %s\n", i+1, len(items), item) r := itemResult{Item: item} itemBudget := &Budget{name: "item", limit: g.TokenBudgetPerItem} t, err := flock.Triage(ctx, item, itemBudget, listBudget) if err != nil { r.Skipped = "triage failed: " + err.Error() fmt.Println(" " + r.Skipped) results = append(results, r) continue } r.Mode, r.TaskType = t.Mode, t.TaskType fmt.Printf(" Sentry: %s · %s · %s\n", t.Mode, t.TaskType, t.Reason) for _, s := range t.Subtasks { fmt.Printf(" - %s\n", s) } flog.Write(Event{Kind: "triage", Requester: *as, Item: item, Mode: t.Mode, TaskType: t.TaskType, Note: t.Reason}) // The Sentry earns autonomy on triage separately from the crows' work. triageType := "triage/" + t.TaskType if ledger.Level("Sentry", triageType) < Trusted { ok := confirm(in, " Approve triage?") ledger.Judge([]string{"Sentry"}, triageType, ok, g.PromoteAfter) flog.Write(Event{Kind: "verdict", Item: item, Crow: "Sentry", TaskType: triageType, Verdict: verdict(ok)}) if !ok { r.Skipped = "triage rejected" results = append(results, r) continue } } out, err := flock.Execute(ctx, item, t, itemBudget, listBudget) if err != nil { r.Skipped = "flight failed: " + err.Error() fmt.Println(" " + r.Skipped) results = append(results, r) continue } r.Answer, r.Crows = out.Answer, out.Crows fmt.Printf(" Crows: %s\n%s\n", strings.Join(out.Crows, ", "), indent(out.Answer, " > ")) // The least-trusted crow involved sets the bar. lowest := Trusted for _, c := range out.Crows { lowest = min(lowest, ledger.Level(c, t.TaskType)) } if lowest < Regular { ok := confirm(in, " Accept this answer?") ledger.Judge(out.Crows, t.TaskType, ok, g.PromoteAfter) r.Verdict = verdict(ok) } else if lowest < Trusted { r.Verdict = "accepted, unreviewed" } else { r.Verdict = "accepted, trusted" } flog.Write(Event{Kind: "verdict", Item: item, Crow: strings.Join(out.Crows, ","), TaskType: t.TaskType, Verdict: r.Verdict}) results = append(results, r) } save() resultPath := strings.TrimSuffix(todoPath, filepath.Ext(todoPath)) + ".crows.md" must(writeResults(resultPath, todoPath, results)) fmt.Printf("\nResults: %s · tokens used: %d/%d\n", resultPath, listBudget.spent, listBudget.limit) } func readTodo(path string, g Guardrails) ([]string, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var items []string for _, line := range strings.Split(string(data), "\n") { line = strings.TrimSpace(line) for _, p := range []string{"- [ ]", "- [x]", "-", "*"} { line = strings.TrimSpace(strings.TrimPrefix(line, p)) } if line == "" || strings.HasPrefix(line, "#") { continue } if len(line) > g.MaxItemChars { return nil, fmt.Errorf("an item is %d characters; the cap is %d", len(line), g.MaxItemChars) } items = append(items, line) } return items, nil } // confirm fails safe: anything but y/yes, including no input at all, is a no. func confirm(in *bufio.Reader, prompt string) bool { fmt.Print(prompt + " [y/N] ") line, _ := in.ReadString('\n') a := strings.ToLower(strings.TrimSpace(line)) if line == "" { fmt.Println() } return a == "y" || a == "yes" } func verdict(ok bool) string { if ok { return "approved" } return "rejected" } func writeResults(path, source string, results []itemResult) error { var sb strings.Builder fmt.Fprintf(&sb, "# So Many Crows: %s\n\n_%s_\n\n", filepath.Base(source), time.Now().Format("2006-01-02 15:04")) for _, r := range results { if r.Skipped != "" { fmt.Fprintf(&sb, "- [ ] %s\n - skipped: %s\n\n", r.Item, r.Skipped) continue } box := "x" if r.Verdict == "rejected" { box = " " } fmt.Fprintf(&sb, "- [%s] %s\n - %s · %s · %s · %s\n%s\n\n", box, r.Item, r.Mode, r.TaskType, strings.Join(r.Crows, ", "), r.Verdict, indent(r.Answer, " > ")) } return os.WriteFile(path, []byte(sb.String()), 0o644) } func cmdTrust() { l, err := LoadLedger(filepath.Join(dataDir, "ledger.json")) must(err) names := []string{"stranger", "regular", "trusted"} show := func(title string, m map[string]*Record) { fmt.Println(title) keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) if len(keys) == 0 { fmt.Println(" (none yet)") } for _, k := range keys { r := m[k] fmt.Printf(" %-28s %-9s +%d -%d streak %d\n", k, names[r.Level], r.Approved, r.Rejected, r.Streak) } } show("Crows", l.Crows) show("\nFlock", l.Flock) fmt.Println("\nRequesters") for id, r := range l.Requesters { cd := "none" if time.Now().Before(r.CooldownUntil) { cd = time.Until(r.CooldownUntil).Round(time.Second).String() } fmt.Printf(" %-20s strikes %d · cooldown %s\n", id, r.Strikes, cd) } } func cmdLog(args []string) { fs := flag.NewFlagSet("log", flag.ExitOnError) n := fs.Int("n", 20, "entries to show") fs.Parse(args) data, err := os.ReadFile(filepath.Join(dataDir, "flight.jsonl")) must(err) lines := strings.Split(strings.TrimSpace(string(data)), "\n") if len(lines) > *n { lines = lines[len(lines)-*n:] } fmt.Println(strings.Join(lines, "\n")) } func indent(s, prefix string) string { lines := strings.Split(strings.TrimSpace(s), "\n") for i := range lines { lines[i] = prefix + lines[i] } return strings.Join(lines, "\n") } func defaultRequester() string { if u := os.Getenv("USER"); u != "" { return u } return "local" } func must(err error) { if err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } }