Skip to content

Repository files navigation

Bricks

CI Python License: MIT

LLMs guess. Bricks computes.

Bricks is a deterministic execution engine for AI agents. Your LLM writes a Python DSL pipeline from pre-tested building blocks. Bricks validates it (AST whitelist), then executes it — same input, same output, every time. No hallucinated math. No format failures. No token burn on repeat runs.


Try it in 30 seconds — bricks playground

pip install -e ".[playground]"
bricks playground
# OK  Bricks Playground running -> http://localhost:8080
#   (browser opened · Ctrl+C to stop)

The Playground is a local web UI that runs your task through Bricks against embedded scenarios (CRM, tickets, orders) or your own CSV/JSON upload. BYOK for Anthropic / OpenAI; local Claude Code and Ollama need no key.

Prove It — 30 Seconds (CLI)

git clone https://github.com/hemipaska-maker/bricks-ai.git
cd bricks-ai
pip install -e ".[ai,playground]"
bricks playground                          # open the web UI at http://localhost:8080
                                           # claude_code is the default — no API key needed
                                           # for anthropic / openai keys, see "Bring Your Own LLM" below

Here's what you'll see — real results tested across three Claude models:

BricksEngine Raw LLM
Correctness 100% (every model, every seed) 0-60% (varies by model)
Haiku active_count: 18, revenue: 3447.50 ❌ Hallucinated: 15, 3848.50
Sonnet ✅ Correct structured output ❌ Wrote an essay instead of JSON
10-run consistency 10/10 pass 6/10 pass (4 hallucinated)
20-run reuse 20/20 pass, 3,327 tokens total 12/20 pass, 75,880 tokens

Bricks composed one blueprint, reused it 20 times at zero cost. The raw LLM called the API 20 times and still got 40% wrong.


Bring Your Own LLM

Bricks works with any model. Blueprints are model-agnostic — compose with one LLM, execute anywhere.

Set the relevant API key for your provider, then pick the model from the playground's model dropdown (bricks playground):

# Anthropic (default)
export ANTHROPIC_API_KEY=sk-ant-...

# OpenAI
export OPENAI_API_KEY=sk-...

# Google Gemini
export GOOGLE_API_KEY=AIza...

# Local with Ollama (free, no API key) — selectable as `ollama/llama3`

# Claude Code Max plan ($0) — selectable as `claudecode`

How It Works

Your task → LLM generates Python DSL → Bricks validates (AST) → Bricks executes deterministically
  1. You describe what you want in plain English
  2. The LLM picks from 100 pre-tested bricks and writes a Python DSL pipeline (validated with an AST whitelist before execution)
  3. Bricks validates the blueprint (types, connections, missing inputs) before anything runs
  4. Bricks executes it deterministically — same blueprint, any data, identical results
  5. The blueprint is saved. Next time, zero LLM calls needed.

The LLM is used once, for planning. Execution is pure Python — no LLM in the loop, no token cost, no hallucination risk.


Architecture: Engine vs AI Layer

The codebase is split into two packages with a strict one-way dependency:

Package What it is LLM involvement Dependencies
bricks The deterministic engine: brick registry, blueprint validation, DAG execution, 100 stdlib bricks, blueprint store None — pure Python pydantic, typer, ruamel.yaml, rich
bricks_ai The AI layer: LLM composer, healing, provider adapters, task orchestrator, playground, MCP server All of it bricks + optional LLM SDKs

bricks_ai imports bricks; the engine never imports the AI layer. The boundary is enforced in CI by import-linter and an engine-only job that installs the base package — with no LLM SDK present — and runs the engine test suites. A plain pip install bricks-ai gives you the deterministic engine; the AI extras opt you in.


Why Bricks?

Correctness. 100% across every model tested. Raw LLMs hallucinate numbers, ignore format instructions, and drift across runs. Bricks doesn't — every brick is typed and tested.

Cost. Compose once, reuse forever. 20 runs cost 3,327 tokens with Bricks vs 75,880 with raw LLM calls. That's a 95.6% reduction. After the first call, every repeat is free.

Determinism. Same blueprint + different data = guaranteed correct output. No randomness, no temperature, no "try again and hope."

Auditability. Blueprints are plain YAML. Every step is named, every input/output is typed. You can inspect, version, share, and review exactly what ran.


Quick Start — Python API

Natural-language task execution lives in the AI layer (bricks_ai):

from bricks_ai import Bricks

# One line setup — auto-discovers all installed brick packs
engine = Bricks.default()  # reads API key from environment

# Describe what you want in plain English
result = engine.execute(
    "filter active customers and count them",
    {"data": customers_list}
)

print(result["outputs"])      # {"active_customers": [...], "count": 42}
print(result["cache_hit"])    # True on second call — zero tokens!
print(result["tokens_used"])  # 0 on cache hit

Already have a blueprint? The deterministic engine runs it with zero LLM involvement (and zero LLM packages installed):

from bricks import run_blueprint

result = run_blueprint("blueprints/revenue.yaml", inputs={"values": [1, 2, 3]})
print(result.outputs)

Verified output (a deterministic stdlib brick, regenerated on every commit):

>>> from bricks.core.registry import BrickRegistry
>>> reg = BrickRegistry.from_stdlib()
>>> fn, _ = reg.get("count_words_chars")
>>> fn(text="hello world")
{'result': {'words': 2, 'chars': 11}}

Python DSL

Write pipelines as Python instead of YAML. The @flow decorator traces the function once and builds a DAG:

from bricks import step, for_each, branch, flow

# 1. Simple step chain
@flow
def clean_pipeline(data):
    cleaned = step.clean(text=data)
    return step.summarize(text=cleaned)

blueprint = clean_pipeline.to_blueprint()  # → BlueprintDefinition
yaml_str  = clean_pipeline.to_yaml()       # → YAML string

# 2. for_each — map a brick over every item in a list
@flow
def batch_clean(items):
    return for_each(items, do=lambda x: step.clean(text=x), on_error="collect")

# 3. branch — conditional routing
@flow
def route_record(record):
    return branch(
        condition="is_valid",
        if_true=lambda:  step.enrich(data=record),
        if_false=lambda: step.log_invalid(data=record),
    )

The LLM composer now generates Python DSL code instead of YAML. Generated DSL is validated with an AST whitelist before execution.


MCP Setup

Use Bricks as an MCP server in Claude Desktop or any MCP-compatible host:

{
  "mcpServers": {
    "bricks": {
      "command": "bricks",
      "args": ["serve"],
      "env": {
        "ANTHROPIC_API_KEY": "sk-ant-..."
      }
    }
  }
}

Save this to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows).

The MCP server exposes:

  • Tool: execute_task — run any task through Bricks with optional verbose mode for step-by-step tracing
  • Resources: bricks://catalog (all available bricks) and bricks://blueprints (cached blueprints)
  • Prompts: templates for common tasks
  • Persistent store: blueprints cache to ~/.bricks/blueprints by default, surviving server restarts

Install

# From source (recommended for now)
git clone https://github.com/hemipaska-maker/bricks-ai.git
cd bricks-ai
pip install -e ".[ai,playground]"

# Base install = the deterministic engine only (no LLM SDKs):
#   100 stdlib bricks (data, string, math, validation, encoding),
#   blueprint validation/execution, the store, and the engine CLI commands.
# ai: LiteLLM — enables `bricks_ai` composition (compose/demo/serve).
# playground: FastAPI web UI + provider SDKs + the `bricks playground run` CLI.

Windows Setup

On Windows, pip install bricks-ai[ai] can fail with a long-path error because litellm installs files whose paths exceed the Windows default 260-character limit (MAX_PATH).

Symptom:

ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory: '...\...\...'

Fix (recommended): Enable long paths via the registry or Group Policy:

  1. Open regedit
  2. Navigate to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem
  3. Set LongPathsEnabled to 1
  4. Restart your terminal

Alternative: Install in a short path (e.g. C:\bricks\) to stay under the 260-char limit.

Verify your setup:

bricks check-env

This command checks your Python version, litellm installation, and (on Windows) whether long paths are enabled.


CLI Reference

Usage: bricks [OPTIONS] COMMAND [ARGS]...

Commands:
  check       Validate a blueprint YAML file (lint...
  check-env   Diagnose the local environment (Python...
  compose     AI-compose a blueprint from a natural...
  demo        Interactive 3-act demo: simplicity ->...
  dry-run     Validate a blueprint without executing...
  init        Scaffold a new Bricks project in the...
  list        List all available Bricks in the registry.
  new         Scaffold new Bricks components.
  playground  Bricks Playground — web UI and headless...
  run         Execute a blueprint.
  serve       Start the Bricks MCP server on stdio...
  store       Blueprint store management.

Run bricks <command> --help for per-command flags.


Community Packs

Create a bricks-{name} package and publish it to PyPI. Users install it and it auto-registers:

# your-package/pyproject.toml
[project.entry-points."bricks.packs"]
mypack = "bricks_mypack"
# bricks_mypack/__init__.py
from bricks.core.brick import brick
from bricks.core.registry import BrickRegistry

@brick(description="Fetch from my API")
def fetch_my_api(endpoint: str) -> dict:
    ...

def register(registry: BrickRegistry) -> None:
    registry.register("fetch_my_api", fetch_my_api, fetch_my_api.__brick_meta__)

After pip install bricks-mypack, Bricks.default() discovers and loads it automatically.

See examples/agent.yaml for a full configuration reference and examples/skill.md for how to describe a skill.


Stdlib Bricks Snapshot

Brick Tags Description
absolute_value math, arithmetic Return the absolute value of a number. Returns {result: absolute}.
add_days date, arithmetic Add a number of days to an ISO 8601 date. Returns {result: new_date}.
add_hours date, arithmetic Add hours to an ISO 8601 datetime string. Returns {result: new_datetime}.
base64_decode encoding, base64 Decode a base64 string to UTF-8. Returns {result: decoded}.
base64_encode encoding, base64 Encode a UTF-8 string to base64. Returns {result: encoded}.
calculate_aggregates data, aggregate, math Aggregate a numeric field across a list of dicts. Returns {result: aggregated_value}.
cast_data_types data, casting, types Cast dict values to specified types. Returns {result: cast_dict}.
ceil_value math, rounding Round value up to nearest integer. Returns {result: ceiling}.
chunk_list list, chunk, split Split a list into chunks of a given size. Returns {result: chunks}.
clamp_value math, range Clamp value to [minimum, maximum]. Returns {result: clamped}.
clean_whitespace string, cleaning Strip leading/trailing whitespace and collapse internal runs. Returns {result: cleaned}.
compare_values validation, comparison Compare two values using an operator. Returns {result: bool}.
compute_hash security, hash, digest Compute a hash digest of a string. Returns {result: hex_digest}.
concatenate_strings string, join Join a list of strings with a separator. Returns {result: joined}.
convert_case string, case, transform Convert string case. Returns {result: converted}.

Showing 15 of 100 stdlib bricks. Full list: docs/BRICK_CATALOG.md.


Benchmark: What's Actually Happening?

The CRM-pipeline benchmark generates 50 fake customer records and asks: "How many are active? What's their total revenue? What's the average?"

The task prompt (what both engines receive):

Parse the JSON string, filter for status='active', count the active customers, sum their monthly_revenue, and compute the average revenue. Return: active_count, total_active_revenue, avg_active_revenue.

The data (50 records, looks like this):

{
  "customers": [
    {"id": 1, "name": "Bob Jones", "email": "bob.jones0@example.com",
     "status": "active", "plan": "pro", "monthly_revenue": 109.0,
     "signup_date": "2020-07-07"},
    {"id": 2, "name": "Carol Smith", "email": "carol.smith1@example.com",
     "status": "inactive", "plan": "enterprise", "monthly_revenue": 514.5,
     "signup_date": "2021-02-02"},
    ...48 more records...
  ]
}

What BricksEngine does:

  1. LLM reads the task and the brick catalog (not the data) → composes a YAML blueprint: extract_json_from_str → filter_dict_list(status=active) → count_dict_list → calculate_aggregates(sum, avg)
  2. Bricks validates the blueprint (types, connections, missing inputs)
  3. Bricks executes it deterministically with the 50 records
  4. Returns: {"active_count": 18, "total_active_revenue": 3447.50, "avg_active_revenue": 191.53}

What RawLLMEngine does:

  1. LLM receives the task AND the full 50-record JSON
  2. LLM tries to count, sum, and average in its head
  3. Returns... it depends on the model:
    • Haiku: {"active_count": 15, "total_active_revenue": 3848.50, ...} ❌ (hallucinated numbers)
    • Sonnet: "Let me analyze this step by step. First, I'll identify..." ❌ (essay instead of JSON)
    • ClaudeCode: sometimes correct, sometimes wrong — 60% pass rate over 10 runs

The key insight: Bricks uses the LLM only for planning (which bricks to chain). The actual math runs in deterministic Python. The raw LLM has to do everything — read 50 records, count, sum, divide — in one shot. That's where hallucination happens.


Try It Instantly — Interactive Demo

No API key needed. See Bricks in action right in your terminal:

bricks demo

Three acts: compose a blueprint, execute it on CRM data, compare Bricks vs raw LLM. Run bricks demo --act 1 for just the first act.


Benchmark Scenarios

The playground ships preset scenarios (CRM, support tickets, dataset join, custom) selectable from the web UI or runnable headlessly via the CLI:

bricks playground                                    # web UI — pick a scenario from the dropdown
bricks playground run crm_pipeline                   # headless: input data, blueprint, outputs
bricks playground run crm_pipeline --compare-raw     # add a side-by-side raw-LLM run
bricks playground run path/to/your_scenario.yaml     # your own scenario, same shape as the bundled YAMLs

Each preset runs BricksEngine on the bundled data, prints the composed blueprint, and (optionally) compares against a raw LLM call.

Preset What it proves Bricks Raw LLM
crm_pipeline Determinism beats reasoning ✅ Correct ❌ Wrong (hallucination or format failure)
cross_dataset_join Multi-table join + grouped aggregation total_completed: 18, basic_revenue: 3758.70, … ❌ Often misses the join shape
ticket_pipeline Generalises to a different domain (support tickets) ✅ Correct ❌ Struggles with PII + filtering
custom_example Inline / BYO data — no dataset file required available_count: 3, total_value: 2859.75 ✅ Trivial enough that raw usually gets it

Tested with ClaudeCode, Claude Haiku, and Claude Sonnet.

About

Deterministic execution engine for AI agents. LLMs guess. Bricks computes.

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages