Fabric

Fabric runs bounded LLM agents and durable control graphs in Gleam on Erlang/OTP. Typed tools and application policy govern effects; recorded outcomes support approval and recovery. Applications provide models, domain operations, authorization and resource supervision.

Use Fabric when an agent or workflow needs typed boundaries, approval, or recovery across process loss. Fabric targets Erlang and requires Gleam 1.18 or later.

Capabilities

Typical uses

Fabric does not provide retrieval or vector storage, agent token streaming, remote agent protocols, distributed Erlang, or general workflow compensation. Saga, Relay, and Hellhound integrations are application recipes; see the module documentation and consumer examples.

Install

Add Fabric from Hex:

gleam add fabric

Commit gleam.toml and manifest.toml to retain the resolved versions. The optional PostgreSQL adapter installs from a Git subdirectory.

Start with an agent

This short-lived summarizer uses an in-memory store. Pass a model made with fabric/llm.model(client, config, model_id) and the application’s started HTTP Gun client, or provide a custom fabric/model.Model.

import fabric
import fabric/agent
import fabric/model
import fabric/policy
import fabric/run
import fabric/store
import fabric/store/backend
import gleam/erlang/process
import gleam/option.{None}
import gleam/result
import gleam/time/duration

pub type Error {
  InvalidAgent(List(agent.ConfigError))
  StoreFailed(backend.StoreError)
  RunFailed(fabric.Error)
}

pub fn summarize(
  model: model.Model,
  text: String,
) -> Result(run.Status(String), Error) {
  use summarizer <- result.try(
    agent.new("summarizer", model, [], policy.always_allow())
    |> agent.with_system_prompt("Summarize the supplied text in one paragraph.")
    |> agent.with_max_turns(2)
    |> agent.with_model_timeout(run.After(duration.seconds(20)))
    |> agent.build
    |> result.map_error(InvalidAgent),
  )
  let runs = store.in_memory(process.new_name("summaries"))
  use Nil <- result.try(store.start(runs) |> result.map_error(StoreFailed))
  let outcome =
    fabric.start(
      runs,
      summarizer,
      id: run.new_id(),
      context: Nil,
      prompt: text,
      correlation: None,
    )
    |> result.try(fabric.await(_, within: duration.seconds(45)))
    |> result.map_error(RunFailed)
  use Nil <- result.try(store.stop(runs) |> result.map_error(StoreFailed))
  outcome
}

This example discards its in-memory records when the store stops. A service that needs recovery supervises a persistent store and keeps each run id. A completed answer also does not prove that every action succeeded; inspect fabric.snapshot before treating an agent’s answer as business success.

Core model

The application builds a checked Agent or Graph definition and starts runs in a Store. The store retains run state and supervises runners; a handle only addresses a run, not its worker. Typed Tool and graph Operation boundaries classify external results, while application Policy and approval verifiers decide who may proceed.

flowchart LR
  app[Application] -->|defines and starts| control[Agent or graph]
  app -->|owns| supervisor[OTP supervisor]
  supervisor --> store[Store]
  control -->|committed transitions| store
  store -->|supervises| runners[Runners]
  store --> backend[Memory, directory, or custom backend]
  runners --> model[Model or provider]
  runners --> effects[Tools and graph operations]

Ownership and recovery

Examples

The usage examples cover bounds, failure classification, waiting, budgets, reconciliation, and recovery. The graph consumer, decision consumer, jobs consumer, and writing consumer show larger applications. The PostgreSQL adapter documents database-backed leases and operations.

Generation settings are fixed on a model instance and supplied again when rebuilding a runtime. Fabric retains its own messages, tools, answer decoding, and correlation. This checked excerpt assumes imports from fabric/agent, fabric/llm, fabric/policy, llm_wire/generation, and json/blueprint/codec; client and config come from your provider setup.

let settings =
  generation.default()
  |> generation.with_max_tokens(128)
  |> generation.with_temperature(0.2)
let assert Ok(agent) =
  agent.new(
    "configured",
    llm.configured_model(client, config, "fixture", settings),
    [],
    policy.always_allow(),
  )
  |> agent.with_answer(codec.int())
  |> agent.build

The design defines supported contracts, limits, and capabilities still unbuilt. Historical live-provider measurements and their limitations are recorded in BENCHMARKS.md; Fabric has no runtime benchmark harness. See AGENTS.md for repository development and validation commands.

✨ Search Document