package agents import ( "context" "fmt" "log/slog" ) const abandonedMergeText = "The Merge turn ended unfinished (interrupted, failed to start, or with no final message), so byre didn't squash or push the task. Merge again to retry." func (m *Manager) poke() { select { case m.pokes <- struct{}{}: default: } } func (m *Manager) coordinate() { started := map[string]bool{} for { select { case <-m.watchStop: return case <-m.pokes: m.coordinatePass(context.Background(), started) } } } func (m *Manager) coordinatePass(ctx context.Context, started map[string]bool) { work, err := m.store.Coordinate(ctx) if err != nil { slog.Error("coordinate", "err", err) return } for _, id := range work.AbandonedMerges { slog.Info("merge turn ended unfinished", "task", id) m.fileError(ctx, id, abandonedMergeText) m.deliverIdle(ctx, id) } for _, c := range work.StartMerges { m.startClaimedMerge(ctx, c) } for _, id := range work.StartDiscards { m.startDiscard(ctx, id) } for _, r := range work.EndReviewers { go m.cleanUpReviewer(r) } current := map[string]bool{} once := func(key string, run func()) { current[key] = true if started[key] { return } started[key] = true go run() } for _, f := range work.FinalizeMerges { once("merge:"+f.Task.Id, func() { m.finalizeMerge(f) }) } for _, id := range work.FinishDiscards { once("discard:"+id, func() { m.finishDiscard(id) }) } for _, r := range work.StartReviews { once(fmt.Sprintf("review:%s:%d", r.TaskID, r.N), func() { m.startReviews(r) }) } for _, s := range work.StartStages { once(fmt.Sprintf("stage:%s:%d:%s", s.TaskID, s.SnapshotN, s.Stage), func() { m.startReviewStage(s) }) } for _, r := range work.SettleReviews { once(fmt.Sprintf("settle:%s:%d", r.TaskID, r.N), func() { m.settleReviews(r) }) } for _, id := range work.AdvanceStages { once("advance:"+id, func() { m.advanceStage(id) }) } for _, d := range work.LeadDeliveries { once(fmt.Sprintf("lead:%s:%d:%d", d.Owner, d.First, d.Last), func() { m.deliverLead(d.Owner) }) } for _, q := range work.QueuedDelivery { once(fmt.Sprintf("queue:%s:%d:%s", q.SessionID, q.Input, q.Response), func() { m.deliverQueuedTurn(q.SessionID) }) } for _, p := range work.ReadyPrompts { once("ready:"+p.TaskID, func() { m.promptReady(p) }) } for key := range started { if !current[key] { delete(started, key) } } }