Skip to content

Defining Flows

This section covers the YAML flow format in detail — vertices, edges, data bindings, effects, and atomic groups.

Flow Structure

Every flow file has this top-level structure:

flow:
  flow_name:
    vertex_a:
      # vertex definition
    vertex_b:
      # vertex definition

atomic_groups:      # optional
  group_name:
    vertices: [vertex_a, vertex_b]
    on_failure: rollback

The flow key contains one or more named flows. Each flow contains vertices as nested keys. The atomic_groups key is optional and declares transactional boundaries.

Vertex Fields

Each vertex supports these fields:

Field Required Description
handler Yes Dotted path to the Python function
effect Recommended pure or side_effect
version Recommended Version string (e.g., "1", "2.0")
inputs No Input declarations and data bindings
outputs No Output type declarations
next No List of vertices that execute after this one

Handler

The handler field is a dotted Python import path:

handler: vertices.users.normalize
# Resolves to: from vertices.users import normalize

At runtime, HBIA imports the module and resolves the function. You can also provide handler functions directly when using the Python API.

Effect

The effect field declares whether a vertex has side effects:

  • pure — No side effects. The function always returns the same output for the same input. Pure vertices can be cached, retried, and run in parallel safely.
  • side_effect — Has side effects (database writes, API calls, file I/O). Side-effect vertices skip caching and are treated with care during atomic execution.
normalize:
  handler: vertices.users.normalize
  effect: pure           # safe to cache and retry

save:
  handler: vertices.users.save
  effect: side_effect    # writes to database

Warning

If you omit effect, the linter will warn you (W001). Always declare effects — it's critical information for both the execution engine and AI agents.

Version

Version tracking for the rewrite-over-patch philosophy:

normalize:
  handler: vertices.users.normalize_v2
  version: "2"

When an AI agent modifies a vertex, it creates a new handler function and bumps the version number rather than patching the existing code. This makes changes atomic and auditable.

Inputs

Inputs serve two purposes:

  1. Type declarations — a plain type name means "this vertex expects this type":

    inputs:
      username: str
      age: int
    

  2. Data bindings — a dotted reference resolves to another vertex's output at runtime:

    inputs:
      username: normalize.username    # reads normalize's output
      email: normalize.email
    

The rule is simple: if the value contains a ., it's a data binding. Otherwise, it's a type declaration.

At runtime, data bindings are resolved from the DataStore, which holds all outputs from previously executed vertices. If a binding cannot be resolved, HBIA raises a DataResolutionError.

Outputs

Output declarations define what the vertex produces:

outputs:
  user_id: str
  saved: bool
  errors: list

Supported type names: str, int, float, bool, dict, list, tuple, set, bytes, none.

Next

The next field creates edges in the DAG:

normalize:
  next:
    - validate
    - log_input    # fan-out: two vertices depend on normalize

A vertex with no next field is a sink — a terminal node in the graph. A vertex that no other vertex lists in next is a source — an entry point.

Common Patterns

Linear Pipeline

flow:
  process:
    extract:
      handler: vertices.extract
      effect: pure
      version: "1"
      outputs:
        raw_data: dict
      next:
        - transform

    transform:
      handler: vertices.transform
      effect: pure
      version: "1"
      inputs:
        raw_data: extract.raw_data
      outputs:
        clean_data: dict
      next:
        - load

    load:
      handler: vertices.load
      effect: side_effect
      version: "1"
      inputs:
        clean_data: transform.clean_data
      outputs:
        success: bool

Diamond (Fan-Out / Fan-In)

flow:
  analyze:
    fetch:
      handler: vertices.fetch
      effect: side_effect
      version: "1"
      outputs:
        data: dict
      next:
        - analyze_text
        - analyze_images

    analyze_text:
      handler: vertices.analyze_text
      effect: pure
      version: "1"
      inputs:
        data: fetch.data
      outputs:
        text_score: float
      next:
        - combine

    analyze_images:
      handler: vertices.analyze_images
      effect: pure
      version: "1"
      inputs:
        data: fetch.data
      outputs:
        image_score: float
      next:
        - combine

    combine:
      handler: vertices.combine
      effect: pure
      version: "1"
      inputs:
        text_score: analyze_text.text_score
        image_score: analyze_images.image_score
      outputs:
        final_score: float

In this diamond pattern, analyze_text and analyze_images share no dependencies on each other, so they are placed in the same execution stage and can run in parallel automatically when PARALLEL_ENABLED=True.

Multiple Flows in One File

flow:
  create_user:
    validate:
      # ...
      next: [save]
    save:
      # ...

  delete_user:
    check_permissions:
      # ...
      next: [delete]
    delete:
      # ...

Atomic Groups

Atomic groups wrap sets of vertices in all-or-nothing semantics. If any vertex in the group fails, the entire group is rolled back.

flow:
  payment:
    validate_card:
      handler: vertices.payment.validate_card
      effect: pure
      version: "1"
      outputs:
        card_valid: bool
      next:
        - reserve_funds

    reserve_funds:
      handler: vertices.payment.reserve
      effect: side_effect
      version: "1"
      inputs:
        card_valid: validate_card.card_valid
      outputs:
        reservation_id: str
      next:
        - charge

    charge:
      handler: vertices.payment.charge
      effect: side_effect
      version: "1"
      inputs:
        reservation_id: reserve_funds.reservation_id
      outputs:
        transaction_id: str
      next:
        - confirm

    confirm:
      handler: vertices.payment.confirm
      effect: side_effect
      version: "1"
      inputs:
        transaction_id: charge.transaction_id
      outputs:
        confirmed: bool

atomic_groups:
  payment_processing:
    vertices: [reserve_funds, charge, confirm]
    on_failure: rollback
    no_cache: true
    no_parallel: true

Atomic Group Fields

Field Required Description
vertices Yes List of vertex names in the group
on_failure Yes rollback, compensate, or abort
no_cache No true to bypass cache inside the group (default: true)
no_parallel No true to force serial execution (default: true)

Failure Policies

  • rollback — Restore data store, state store, and lineage to pre-group snapshots. The most common choice for database transactions.
  • compensate — Use SAGA-style compensation handlers to undo completed steps. Necessary when rollback is impossible (e.g., external API calls).
  • abort — Stop execution immediately with no cleanup. Use when failure is unrecoverable.

For detailed information on atomicity and SAGAs, see the Atomicity and SAGA Pattern sections.

Validation

Always validate your flows before running them:

# DSL validation (YAML structure, field types, references)
hbia validate flows/payment.yaml

# Graph validation (cycles, connectivity, atomic group integrity)
hbia graph-validate flows/payment.yaml

# Linting (best practices, naming conventions, missing fields)
hbia lint flows/

What's Next?

Running & Inspecting Flows