Serving
h, err := bb.Handler(flow, opts...) // http.Handler for embedding
err := bb.Serve(ctx, flow, // or own the listener + shutdown
bb.Addr(":8080"),
bb.Trace(bb.JSONL(os.Stdout)), // jsonl trace of every flow
bb.Store(bb.FileStore(dir)), // durable checkpointing, survives restarts
bb.DefaultFlowName("jarvis"), // reported id, default "brain"
)Serve/Handler/Run default Store to an in-memory backend (bb.MemStore()) when it's not set — durability and triggers work with zero config, but nothing survives a process restart. Pass bb.FileStore(dir) (or another persistent backend) once that matters.
Serve/Handler validate the whole flow at startup — modelless default agents, unbuildable models, and declared Select exits with no matching member all fail before the port binds. That is the single place wiring errors surface; the other is Ask (schema/transport, at runtime).
bb.DefaultFlowName only labels a flow served without a registry name — the flow passed straight to Serve/Handler, or one added via bb.WithDefaultFlow. It sets what /v1/models and every response's model field report for that flow; it never affects routing. A flow named via bb.WithFlow(f).As("acme/coder") (below) already reports that name and ignores DefaultFlowName.
Endpoints
| Endpoint | Protocol |
|---|---|
POST /v1/chat/completions | OpenAI, streaming |
POST /v1/messages | Anthropic, streaming |
GET /v1/models | Both — lists every served flow id |
GET /v1/diagnostics/trace | Always-on diagnostics ring |
POST /v1/hooks/{endpointID} | A registered bb.Webhook |
Sampling parameters a client sends (model, temperature, max_tokens, …) are accepted, never an error, and reach the flow as request context — not applied automatically. See Flows, agents & turns for how a handler is meant to read and act on them. Caller tools and <think> blocks pass through untouched the same way; honoring them is the brain's choice.
Serving several flows
One brain can serve many flows, chosen by the request's model:
bb.WithFlow(chatFlow) // unnamed → the default flow
bb.WithFlow(codeFlow).As("acme/coder") // named → picked by model id
bb.WithFlow(mathFlow).As("acme/math").
Serve(ctx) // chainable; Serve ends the chainA request naming a registered model routes to that flow; a request naming no or an unknown model gets the default. Which flow is the default is a precedence (highest wins, last-within-rank wins):
bb.Serve(ctx, f)— an explicit default passed toServe.bb.WithDefaultFlow(f)— an explicit default, no name.bb.WithFlow(f)— the last unnamed flow.bb.WithFlow(f).As(name)— a named flow, default only if nothing unnamed exists.
WithFlow(f).As(name) names a flow (calling As twice is a compile error). A RegisterFlow (unnamed) cannot chain another WithFlow — a chain holds one default — but a named flow can. Serve(ctx) with no default is valid when at least one named flow is registered.
This is a routing convenience for one owner publishing several named brains from a single binary — not a tenancy model. Flows share the process, the store, and the trust boundary; memory belongs to whichever flow's handlers write it. See the introduction for why multi-tenancy is explicitly out of scope.
Handler first, runner second
The engine exposes an http.Handler the author can mount anywhere, and a convenience runner (bb.Serve) that owns the listener and graceful shutdown. Author-added routes are served either way — mounting the handler yourself still serves the engine's routes plus yours.