Nests: team rooms with core crows and borrowing
- Server: nests API, persistence, core-first picking, borrowed flag (built by the backend agent against EVENTS.md; tests pass with -race) - UI: nests in the sidebar, one feed per nest, header shows the core team, New nest dialog, borrowed crows labeled, per-nest waiting count - start.command: one-click local server in controlled mode (mock crows, localhost only, state in .crows/) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UQnSw4RRWp8uqpj3nsnnXF
This commit is contained in:
14 files changed
+782
-62
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -82,22 +83,46 @@ type Flock struct {
|
||||
log *FlightLog
|
||||
sem chan struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
next int
|
||||
mu sync.Mutex
|
||||
next int // rotation through the whole flock (the todo nest)
|
||||
core map[string]int // per nest: rotation within its core
|
||||
borrow map[string]int // per nest: rotation through crows outside its core
|
||||
}
|
||||
|
||||
func NewFlock(brain Brain, g Guardrails, log *FlightLog) *Flock {
|
||||
return &Flock{brain: brain, g: g, log: log, sem: make(chan struct{}, g.MaxCrowsInFlight)}
|
||||
return &Flock{
|
||||
brain: brain, g: g, log: log, sem: make(chan struct{}, g.MaxCrowsInFlight),
|
||||
core: map[string]int{}, borrow: map[string]int{},
|
||||
}
|
||||
}
|
||||
|
||||
// pick rotates through the roster so every crow gets a chance to build a record.
|
||||
func (f *Flock) pick(n int) []string {
|
||||
// pick chooses n crows for a job in a nest. A nest with no core (todo) rotates
|
||||
// through the whole roster so every crow gets a chance to build a record.
|
||||
// Otherwise core crows go first (rotating within the core), and any extra
|
||||
// crows are borrowed from outside the core (rotating).
|
||||
func (f *Flock) pick(nest Nest, n int) []string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
out := make([]string, n)
|
||||
for i := range out {
|
||||
out[i] = roster[f.next%len(roster)]
|
||||
f.next++
|
||||
out := make([]string, 0, n)
|
||||
if len(nest.Crows) == 0 {
|
||||
for range n {
|
||||
out = append(out, roster[f.next%len(roster)])
|
||||
f.next++
|
||||
}
|
||||
return out
|
||||
}
|
||||
core := nest.Crows
|
||||
for range min(n, len(core)) {
|
||||
out = append(out, core[f.core[nest.ID]%len(core)])
|
||||
f.core[nest.ID]++
|
||||
}
|
||||
outside := slices.DeleteFunc(slices.Clone(roster), func(c string) bool { return slices.Contains(core, c) })
|
||||
if len(outside) == 0 {
|
||||
outside = core // the whole flock is the core: nobody left to borrow
|
||||
}
|
||||
for len(out) < n {
|
||||
out = append(out, outside[f.borrow[nest.ID]%len(outside)])
|
||||
f.borrow[nest.ID]++
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -169,11 +194,11 @@ func (f *Flock) Triage(ctx context.Context, w Watcher, item string, budgets ...*
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (f *Flock) Execute(ctx context.Context, w Watcher, item string, t Triage, budgets ...*Budget) (Outcome, error) {
|
||||
func (f *Flock) Execute(ctx context.Context, w Watcher, item string, t Triage, nest Nest, budgets ...*Budget) (Outcome, error) {
|
||||
switch t.Mode {
|
||||
case "split":
|
||||
// Split depth is 1: subtasks go straight to crows, never back to the Sentry.
|
||||
crows := f.pick(len(t.Subtasks))
|
||||
crows := f.pick(nest, len(t.Subtasks))
|
||||
reports, err := f.fanOut(ctx, w, crows, t.Subtasks, budgets...)
|
||||
if err != nil {
|
||||
return Outcome{}, err
|
||||
@@ -181,7 +206,7 @@ func (f *Flock) Execute(ctx context.Context, w Watcher, item string, t Triage, b
|
||||
return f.reconcile(ctx, w, item, crows, t.Subtasks, reports, budgets...)
|
||||
case "murder":
|
||||
n := min(f.g.MurderSize, f.g.MaxCrowsInFlight)
|
||||
crows := f.pick(n)
|
||||
crows := f.pick(nest, n)
|
||||
tasks := make([]string, n)
|
||||
for i := range tasks {
|
||||
tasks[i] = item
|
||||
@@ -192,7 +217,7 @@ func (f *Flock) Execute(ctx context.Context, w Watcher, item string, t Triage, b
|
||||
}
|
||||
return f.reconcile(ctx, w, item, crows, tasks, reports, budgets...)
|
||||
default:
|
||||
c := f.pick(1)[0]
|
||||
c := f.pick(nest, 1)[0]
|
||||
ans, err := f.ask(ctx, w, c, crowSystem, "TASK: "+item, budgets...)
|
||||
return Outcome{Answer: ans, Crows: []string{c}}, err
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user