Architecture

BOA organizes software into five distinct layers, each with a clear purpose. This layered model keeps blocks small, isolated, and composable — whether they run on the server or in the browser.


The Five-Layer Model (Backend)

Every backend block lives in exactly one of five layers. Higher layers depend on lower layers, never the reverse. This strict hierarchy prevents circular dependencies and keeps each block’s context small enough for both humans and AI to reason about.

5 Interface Entry points (REST, WebSocket, CLI)
4 Workflow Orchestration chains
3 Domain Business rules
2 Capability I/O operations
1 Primitive Pure functions

Layer 1 — Primitives

The smallest, purest functions in the system. Math operations, string utilities, data validation, formatting helpers. Primitives have zero business logic and zero side effects. They never change once written, making them safe to reuse across every project in a monorepo.

Examples

  • Add@1.0.0 — adds two numbers
  • Multiply@1.0.0 — multiplies two numbers
  • FormatString@1.0.0 — template interpolation

Characteristics

  • Pure functions — same input always produces same output
  • No network calls, no database access, no file I/O
  • Highly reusable across projects
  • Ideal candidates for memoization

Layer 2 — Capabilities

Capabilities wrap I/O operations behind a clean interface. API clients, database queries, file uploads, email senders, message queue publishers — anything that talks to the outside world. They isolate side effects so that higher layers never deal with raw I/O.

Examples

  • REST client — wraps HTTP requests
  • Email sender — sends transactional email
  • File uploader — stores files in cloud storage
  • Database query — runs a parameterized SQL query

Characteristics

  • Encapsulates side effects (network, disk, etc.)
  • Provides a stable contract for higher layers
  • Can be mocked for testing domain blocks
  • Never contains business rules

Layer 3 — Domain Blocks

Domain blocks encode narrow, specific business rules. Each one answers a single business question: “What is the order total?”, “How much tax applies?”, “Is this email address valid?”. They are always version-pinned so that business logic is traceable and auditable.

Examples

  • CalculateOrderValue@1.0.0
  • ComputeTax@1.0.0
  • ValidateEmail@1.0.0
  • ApplyDiscount@1.0.0

Characteristics

  • One business rule per block
  • May depend on Primitives and Capabilities
  • Version-pinned in workflows
  • Self-testing via FIXTURE declarations

Layer 4 — Workflows

Workflows are declarative chains of blocks. They contain no logic — only orchestration and data mapping. A workflow declares which blocks to call, in what order, and how to pass data between them. The workflow engine handles execution, error propagation, and context management.

workflow.boa
WORKFLOW OrderProcessing 1.0.0
DESC Calculate value and apply tax for an order.

STEP calcValue = CalculateOrderValue@1.0.0
  MAP items <- _initial.items

STEP calcTax = ComputeTax@1.0.0
  MAP subtotal <- calcValue.subtotal

Layer 5 — Interfaces

Interfaces are entry points that call workflows. REST controllers, React pages, CLI commands, WebSocket handlers — they accept external input, invoke the appropriate workflow, and return the result. Interfaces never contain business logic; they only translate between the outside world and the workflow layer.

Why five layers? Each layer has a single type of responsibility. This means AI only needs context about one layer at a time — a primitive never needs to know about the database, and a workflow never needs to know about HTTP headers.

Universal Runtime Protocol (URP)

All backend blocks communicate through the Universal Runtime Protocol. URP is radically simple: a block reads a JSON envelope from STDIN, performs its work, and writes a JSON result to STDOUT. This is what makes BOA polyglot — any language that can read stdin and write stdout can be a block.

Input Envelope

The runtime sends this JSON object to the block’s standard input:

{
  "block": "CalculateOrderValue",
  "version": "1.0.0",
  "input": {
    "items": [
      { "price": 29.99, "quantity": 2 },
      { "price": 9.99, "quantity": 1 }
    ]
  }
}

Success Response

When the block completes successfully, it writes this to standard output:

{
  "success": true,
  "output": {
    "subtotal": 69.97
  }
}

Error Response

When the block encounters an error, it writes this instead:

{
  "success": false,
  "error": {
    "type": "ValidationError",
    "message": "Items array cannot be empty."
  }
}
URP makes BOA polyglot. Any language that reads stdin and writes stdout can be a block. Write primitives in Rust for performance, domain blocks in TypeScript for productivity, capabilities in Python for ML — they all compose seamlessly through workflows.

The Three Guarantees

URP enforces three guarantees that make blocks safe to compose:

Guarantee What It Means
Isolation Each block runs in its own process. No shared memory, no shared state. One block cannot corrupt another.
Contract Inputs and outputs are declared in block.boa. The runtime validates them before and after execution.
Determinism Given the same input envelope, a block must produce the same output. Side effects are confined to Capability blocks.

UI Architecture (Browser)

The backend uses URP (stdin/stdout) because blocks run as separate processes. In the browser, that model doesn’t apply — everything runs in a single JavaScript thread. So the UI framework uses a different, simpler protocol: plain ES module functions.

The UI layer is split into two block types:

🧠

UI Blocks

ui-block

Pure logic — state derivation, formatting, calculations, data transformations. No DOM manipulation, no fetch(), no browser APIs. Because they are pure, they are memoizable and trivially testable.

🔌

UI Capability Blocks

ui-capability

Side effects — fetch(), localStorage, clipboard access, geolocation, WebSocket connections. They wrap browser APIs behind a stable interface so that UI blocks remain pure.

UI Block Signature

Unlike backend blocks that use URP’s stdin/stdout protocol, UI blocks are plain ES module functions. They receive an input object and return an output object synchronously (or as a Promise for async capability blocks):

ui-block
// Pure UI block — no side effects
export default function formatPrice(input) {
  const { amount, currency } = input;
  return {
    formatted: `${currency} ${amount.toFixed(2)}`
  };
}
ui-capability
// Capability block — wraps fetch() side effect
export default async function fetchCart(input) {
  const res = await fetch(`/api/cart/${input.cartId}`);
  const data = await res.json();
  return { cart: data };
}
Why not URP in the browser? URP uses stdin/stdout because backend blocks run as separate OS processes. In the browser, everything runs in one thread — there’s no stdin. Plain function calls are faster, simpler, and natively supported by every bundler and framework.

Block Anatomy

Every block has a block.boa manifest that declares everything about it: identity, layer, runtime, inputs, outputs, business rules, test fixtures, and dependencies. This manifest is the single source of truth — both the framework and AI read it to understand what the block does.

block.boa
# Identity
BLOCK CalculateOrderValue 1.0.0
LAYER domain
RUNTIME node
ENTRY index.js
DESC Computes the subtotal from a list of order items.

# AI-native metadata
INTENT Calculate the total value of a shopping cart.
RULE subtotal = sum of (price * quantity) for each item.
RULE Round the result to 2 decimal places.
RULE Return 0 if the items array is empty.
TAGS order, cart, pricing, subtotal

# Contract
IN items:array!          # required (!) — list of order items
OUT subtotal:number     # the computed subtotal

# Self-testing fixtures
FIXTURE {"items":[{"price":50,"quantity":2}]} -> {"subtotal":100}
FIXTURE {"items":[]} -> {"subtotal":0}

# Error types this block may throw
ERR ValidationError

# Dependencies on other blocks
DEPS Multiply@1.0.0

Each field serves a specific purpose:

Field Purpose
BLOCK Block name and semantic version. Workflows pin this exact version.
LAYER Which architectural layer: primitive, capability, domain, workflow, or interface.
RUNTIME Execution runtime: node, python, go, rust, etc.
ENTRY The file that the runtime executes (e.g., index.js, main.py).
DESC One-line human-readable description of what the block does.
INTENT The user’s original requirement in their own words. Preserves the why for future AI sessions.
RULE Business rules and constraints, one per line. Structured and parseable by AI.
TAGS Semantic keywords for block discovery and search.
IN Input field with type. ! means required, ? means optional.
OUT Output field with type. Declares the block’s return contract.
FIXTURE Expected input/output pairs for self-testing. Run via boa test or boa validate.
ERR Error types the block may throw (e.g., ValidationError).
DEPS Other blocks this block depends on, with pinned versions.
AI-native by design. The INTENT, RULE, FIXTURE, and TAGS fields exist specifically for AI. They let an AI agent understand what a block does, why it exists, what constraints it must obey, and how to test it — all without reading the implementation code.

Data Flow

Workflows pass data between blocks using a simple referencing system. Each step in a workflow produces output that subsequent steps can reference by step ID. The workflow engine manages an internal WorkflowContext that accumulates all step outputs.

How References Work

When a workflow starts, the initial input is available under the special key _initial. Each step’s output is stored under its step ID. The MAP directive wires data from one step’s output to another step’s input.

workflow.boa
WORKFLOW OrderProcessing 1.0.0
DESC Calculate value, apply tax, and format the result.

# Step 1: reads from the workflow's initial input
STEP calcValue = CalculateOrderValue@1.0.0
  MAP items <- _initial.items        # workflow input

# Step 2: reads from Step 1's output
STEP calcTax = ComputeTax@1.0.0
  MAP subtotal <- calcValue.subtotal  # output of step 1

# Step 3: reads from both initial input and Step 2's output
STEP format = FormatString@1.0.0
  MAP template <- _initial.template   # workflow input
  MAP total <- calcTax.total          # output of step 2

Context Accumulation

As the workflow engine executes each step, the context grows:

After Step Available References
Start _initial.items, _initial.template
calcValue _initial.*, calcValue.subtotal
calcTax _initial.*, calcValue.*, calcTax.total, calcTax.tax
format _initial.*, calcValue.*, calcTax.*, format.result
Forward references only. A step can only reference _initial or the output of a step that appears before it in the workflow. Referencing a later step is a validation error that boa validate will catch.

Visual Data Flow

For the OrderProcessing workflow above, data flows like this:

                 Workflow Input
                 { items, template }
                       |
            +----------+----------+
            |                     |
            v                     |
  +-------------------+           |
  | CalculateOrder    |           |
  | Value@1.0.0       |           |
  |                   |           |
  | items <- _initial |           |
  | -> subtotal       |           |
  +-------------------+           |
            |                     |
            v                     |
  +-------------------+           |
  | ComputeTax@1.0.0  |           |
  |                   |           |
  | subtotal <-       |           |
  |   calcValue       |           |
  | -> total, tax      |           |
  +-------------------+           |
            |                     |
            +----------+----------+
                       |
                       v
            +-------------------+
            | FormatString      |
            | @1.0.0            |
            |                   |
            | template <-      |
            |   _initial        |
            | total <- calcTax |
            | -> result         |
            +-------------------+
                       |
                       v
              Workflow Output
              { result }

Explore Workflows    .boa Format Reference