How BAML works

BAML compiles when you build and runs inside your app as a library. No sidecar, no server.

What happens when you build?

When you build

On your machine, or in CI.

baml_src/

triage.baml · invoices.baml

$ baml generate

baml_sdk/

src/lib.rsyour functions, typed

src/_inlinedbaml.binyour compiled program

src/_runtime.rsstarts the runtime

Cargo.tomla crate of its own

ships with
your app

When it runs

BAML runs inside your Rust program and loads on the first call, or when you call init(). Your code keeps its threads, BAML runs on its own, and the bridge connects them.

your Rust program

your code, on your threads

thread 1thread 2

baml_sdk (generated)

baml_bridge crate · loads the BAML library at run time

A call crosses to BAML’s workers. Its result crosses back.

BAML runtime, one worker thread per CPU core

baml-workerbaml-workerbaml-worker…

What happens at release?

The runtime is a shared library. By default it downloads once per machine and version, on the first call, into $BAML_HOME/bridges, checked against its SHA-256.

  1. 1

    Warm the cache in your build stage.

    baml_bridge::loader::ensure_library_cached()?;
  2. 2

    Copy the cache into the image and turn downloads off.

    COPY --from=build /root/.baml/bridges /opt/baml/bridges
    ENV BAML_HOME=/opt/baml
    ENV BAML_BRIDGE_DISABLE_DOWNLOAD=1

Pin one version

Keep baml.toml’s toolchain version, your SDK package and the generated code on the same version. If they drift, startup fails with a version skew error.

In production

BOUNDARY_API_KEY sends recordings to Boundary Cloud. BAML_TELEMETRY sets how much is recorded: off, low, medium (the default) or high.

What happens at startup?

Your Rust codeBAML
  1. classify_ticket(body).await

    your first call

  2. Downloads the BAML library

    once per machineinto ~/.baml/bridges

  3. Opens it

    once per process

  4. Sets up its sysops

    once per process

  5. Checks the toolchain version

    once per process

  6. Decodes your compiled program

    once per process

This happens on the first call, or when you call init(), once. After that, calls go straight to the runtime.

What happens when you call a BAML function?

Your Rust codeBAML
  1. Your code calls classify_ticket

  2. The bridge hands it to a BAML worker thread

    the first call starts the pool

  3. Builds the prompt, calls the model, parses the reply

  4. Your code gets a typed result back

BAML does the work on its own threads, not yours.

What happens when BAML throws an exception?

Expected

Your BAML function throws an error it declares

You get an Err back.

Panic

Your BAML code panics, like dividing by zero or a failed assert

You get an Err back.

let arr = [1, 2, 3];
// panics with IndexOutOfBounds
let x = arr[5];

Also division by zero, a missing map key, a failed assert, stack overflow, integer overflow, and baml.sys.panic(…). None of these appear in a function’s throws.

Unhandled

A background task throws, and nothing waits for it

Nothing. The Rust SDK doesn’t report these yet.

Bug in BAML

The BAML runtime itself fails

You get your language’s BAML panic, carrying an SdkPanic, and your program keeps running. A crash during garbage collection still aborts.

⚠ This should never happen. BAML should never crash or segfault your process. If you see one, tell us on Discord and we’ll fix it.

What happens at shutdown?

Your Rust codeBAML
  1. The program starts to exit

  2. BAML’s exit hook runs

    atexit

  3. Waits for calls and spawned tasks still running

  4. Finishes writing recordings

  5. The program exits

BAML finishes its work before your program exits, as long as its exit hook runs.

What happens when a function crosses between your code and BAML?

Your Rust codeBAML
  1. You pass a function to make_widget

    |name| name.to_uppercase()

  2. It crosses as a handle

    a reference to your function, not a copy

  3. BAML calls it, and waits for the answer

  4. Your function runs on your side

    on the bridge’s dispatch pool

  5. The result crosses back as a value

    checked against the type BAML declared

  6. make_widget returns a Widget

    data copied, functions as handles you can call later

Data crosses as a copy. Functions cross as handles, freed when the side holding one lets go.

Why don’t you have docs?

Docs for humans are coming by the end of 2026. Until then, we’re more than happy to make time with you directly.

Meanwhile, we’ve worked hard to ensure agents can understand and use the toolchain well. Seriously, ask Claude: “Install BAML and be ambitious with it.”

Vaibhav Gupta

I know how big a bet a new programming language is. We’re engineers too, and we’re happy to talk it through with you. Email me directly.

Vaibhav Gupta, co-founder and CEO

Meet the team →