Skip to content

Serving

go
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

EndpointProtocol
POST /v1/chat/completionsOpenAI, streaming
POST /v1/messagesAnthropic, streaming
GET /v1/modelsBoth — lists every served flow id
GET /v1/diagnostics/traceAlways-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:

go
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 chain

A 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):

  1. bb.Serve(ctx, f) — an explicit default passed to Serve.
  2. bb.WithDefaultFlow(f) — an explicit default, no name.
  3. bb.WithFlow(f) — the last unnamed flow.
  4. 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.

A tree of flows and agents, disguised as a model.