Concurrency
A flow with several agents runs them concurrently; they can coordinate with a checkpoint:
cp := bb.NewCheckpoint()
recognizer := bb.NewAgent().OnMessage(func(ctx context.Context, t bb.Turn, _ bb.ModelChat) error {
t.Reply(classify(t.Last().Content)); bb.Reached(cp); return nil
})
guard := bb.NewAgent().OnMessage(func(ctx context.Context, t bb.Turn, chat bb.ModelChat) error {
if err := bb.Wait(ctx, cp); err != nil { return err } // wait for recognizer
// ...
return nil
})
flow := bb.NewFlow().WithAgent(recognizer, guard)Group strategies over member flows
bb.All(a, b, …)— run all, merge every reply, end when all end.bb.One(a, b, …)— first to finish wins, the rest are cancelled.bb.Group(a, b, …)— run all over one live shared chat: a member's reply is immediately visible to the others (a member's nextAsk, orturn.Last(), sees it). Order members withCheckpoint/Waitwhen one must see another's contribution first.
Two agents Selecting different next-flows concurrently is a loud error, never a silent last-writer race — the same id from both is fine. The same tracing applies to parallel work: see Telemetry for how token spend is (and isn't) attributed across overlapping flows.
Streaming inside a concurrent group
No member of Select/One/All/Group may claim the client's live stream directly — racing concurrent members for one stream would let whichever called turn.Stream() first win, regardless of whose content ends up in the answer. Their replies still reach the client, buffered and delivered by the next bb.Respond. Full detail in Streaming.
Triggers reached inside a group
A trigger (bb.Every/bb.Once) reached inside bb.One(a, b, …) only commits once that branch is the actual winner — a losing member's trigger is discarded, not scheduled anyway. All/Group need no such gating: every member's contribution is kept, so committing a member's trigger the moment it's reached is already correct there. See Triggers & initiative.