Skip to content

Philosophy & Thesis

The Core Idea — Up Front

HBIA is not a DAG framework because we want to model workflows. It is a DAG framework because a directed acyclic graph is the structure that large language models understand best.

LLMs reason sequentially — token by token, step by step. A DAG maps directly onto that reasoning style: step A feeds step B, which feeds step C. When the entire application architecture is a flat, parseable graph, the AI agent can read it in one pass, understand every operation, every data flow, and every side effect — without opening a single implementation file.

YAML = Architecture    (what the system does, how parts connect)
Code = Implementation  (how each part works internally)

The YAML definitions are the single source of truth. The AI reads them first; humans benefit too.


Three Ways to Build the Same App

To understand what HBIA proposes, compare three approaches to the same feature: "create a user account" — validate the input, persist the user, and send a welcome email.

1. A Senior Engineer's OOP Architecture

A well-structured codebase by an experienced developer using DDD, clean architecture, dependency injection, interfaces, and abstractions:

graph TD
    Controller["UserController<br/><small>REST layer</small>"]
    Service["UserService<br/><small>orchestration</small>"]
    IValidator["«interface»<br/>IUserValidator"]
    Validator["UserValidator<br/><small>implements IValidator</small>"]
    IRepo["«interface»<br/>IUserRepository"]
    Repo["UserRepository<br/><small>implements IRepo</small>"]
    IMailer["«interface»<br/>IMailer"]
    Mailer["SmtpMailer<br/><small>implements IMailer</small>"]
    UoW["UnitOfWork<br/><small>transaction scope</small>"]
    DTO["CreateUserDTO"]
    Entity["UserEntity<br/><small>domain model</small>"]
    Mapper["UserMapper<br/><small>DTO ↔ Entity</small>"]
    DI["DI Container<br/><small>resolves all above</small>"]

    Controller --> Service
    Service --> IValidator --> Validator
    Service --> IRepo --> Repo
    Service --> IMailer --> Mailer
    Service --> UoW
    Controller --> DTO
    Service --> Mapper
    Mapper --> Entity
    DI -.-> Controller
    DI -.-> Service
    DI -.-> Validator
    DI -.-> Repo
    DI -.-> Mailer

~12 files, multiple abstraction layers, interfaces, a DI container, mappers, DTOs, entities. This is good engineering — but only if a senior human holds the mental model. An AI agent reading this codebase must trace through interfaces, implementations, dependency injection bindings, and inheritance to understand what actually happens when a user is created.

2. What the AI Actually Produces (Without Guardrails)

The same feature after 10 iterations of unsupervised AI coding:

graph TD
    A["create_user.py<br/><small>validates + saves + emails<br/>all in one function</small>"]
    B["user_utils.py<br/><small>duplicate validation<br/>slightly different logic</small>"]
    C["send_email.py<br/><small>standalone, no connection<br/>to anything</small>"]
    D["db_helper.py<br/><small>creates new DB connection<br/>on every call</small>"]
    E["api_endpoint.py<br/><small>calls A directly,<br/>catches all exceptions,<br/>returns 200 always</small>"]
    F["create_user_v2.py<br/><small>copy-pasted from A<br/>with 'fixes'</small>"]

    E --> A
    E --> F
    A --> D
    F --> D
    A -.->|"imports some things"| B
    C -.->|"nobody calls this"| C

    style A fill:#ff9999
    style B fill:#ff9999
    style C fill:#cccccc
    style F fill:#ff9999

Scattered scripts, duplicated logic, invisible side effects, dead code, no traceable data flow. Not because the AI is bad — but because it has no persistent memory, no architectural instinct, and no awareness that the codebase is drifting.

3. The Same App With HBIA

graph LR
    V["validate_input<br/><small>effect: pure</small>"]
    S["save_user<br/><small>effect: side_effect</small>"]
    W["send_welcome<br/><small>effect: side_effect</small>"]

    V -->|"clean_email, clean_username"| S
    S -->|"user_id"| W
flow:
  create_user:
    validate_input:
      handler: vertices.users.validate_input
      effect: pure
      version: "1"
      inputs: { email: str, username: str }
      outputs: { clean_email: str, clean_username: str }
      next: [save_user]

    save_user:
      handler: vertices.users.save_user
      effect: side_effect
      version: "1"
      inputs:
        clean_email: validate_input.clean_email
        clean_username: validate_input.clean_username
      outputs: { user_id: str }
      next: [send_welcome]

    send_welcome:
      handler: vertices.users.send_welcome
      effect: side_effect
      version: "1"
      inputs:
        user_id: save_user.user_id
        email: validate_input.clean_email
      outputs: { sent: bool }

3 vertices, 2 edges, 1 YAML file. No classes, no interfaces, no DI, no inheritance. Every operation, every data binding, every side effect is explicit and visible. An AI agent reads this file and knows exactly what the app does — without chasing abstractions across a dozen files.

Each vertex's implementation is a plain Python function:

def validate_input(email: str, username: str) -> dict:
    return {"clean_email": email.lower().strip(), "clean_username": username.strip()}

No base classes. No decorators required. No framework coupling.


Why This Works for AI

The problem isn't that OOP is bad. The problem is that AI agents can't hold a mental model across sessions. Every time an AI opens a codebase, it starts from scratch with a limited context window. Tracing through interfaces → implementations → DI bindings → inheritance hierarchies consumes enormous context and still produces fragile understanding.

A DAG eliminates this entirely:

  • Sequential reasoning. LLMs process tokens left to right. A DAG is step A → step B → step C. The AI is already wired to think this way.
  • Flat architecture. No nesting, no abstract hierarchies. The entire application structure fits in a single YAML file that an LLM can parse in one pass.
  • Explicit data flow. Every input and output is declared. No guessing about what functions return or what side effects lurk inside a method call.
  • Deterministic execution. Topological sort gives a unique execution order. The AI doesn't need to reason about when things happen — the graph defines it.
  • Parallelism for free. Vertices that share no dependencies run concurrently. No thread management, no race conditions to reason about.

AI Comprehension in Practice

When an AI agent reads a flow YAML, it immediately gets:

  1. The complete operation list — every vertex, handler, inputs, outputs, effect type.
  2. The complete data flow — every binding between vertices, resolved by name.
  3. The complete execution order — the topological sort of the DAG.
  4. The complete failure semantics — atomic groups, rollback policies, compensations.

No code reading required. No call-chain tracing. The entire architecture is visible in a single, parseable file.


The HBIA Semantic Layer

HBIA implements the YAML = Architecture equation through a semantic layer — YAML definitions that describe the architecture explicitly:

  • What each operation does (handler)
  • What data it receives and produces (typed inputs/outputs)
  • Where data flows between operations (explicit bindings)
  • Whether it has side effects (pure vs side_effect)
  • How operations connect (DAG edges)
  • What must execute atomically (atomic groups)
  • What to do when things fail (rollback, compensate, abort)

This layer is not documentation — it is the architecture itself.


Backend: The DAG Execution Engine

The backend models applications as directed acyclic graphs:

  • Vertices are operations (Python functions) with declared inputs, outputs, types, and effect annotations.
  • Edges define data flow between vertices.
  • Flows are named entry points into the graph.
  • Atomic groups wrap vertex sets in all-or-nothing semantics.
  • SAGAs handle compensating transactions when rollback is impossible.

The engine handles topological ordering, parallel execution of independent stages, async coroutine support, SHA-256 cache for pure vertices, and full data lineage tracking.

Frontend: The Reactive Graph System

The frontend decomposes UIs into four interconnected reactive graphs:

  1. UI Graph — Pure render components. No hooks, no state, no effects.
  2. State Graph — Typed fields, mutations, and effect triggers.
  3. Effect Graph — Side effects triggered by state changes or events.
  4. Event Graph — User interactions mapped to ordered mutation sequences.

The fundamental rule: Events → mutate State → trigger Effects → State drives UI.

The ui-codegen command generates a self-contained TypeScript runtime from these YAML definitions — no Redux, no Zustand, no external state library.


Design Principles

# Principle What It Means
1 Explicit over implicit Every connection, data flow, and side effect is declared in YAML.
2 Flat over nested No deep nesting or abstract hierarchies. One-pass readability.
3 Rewrite over patch Vertices are versioned; changes are rewrites, not patches.
4 Contracts over trust Typed input/output contracts validated at runtime.
5 Minimal core YAML, CLI, viz, FastAPI — all optional extras.
6 AI-first, human-friendly Designed for agents; readable and inspectable by humans.

Operational Discipline

HBIA encodes a set of rules into the generated AGENTS.md context file (via hbia context --write). These keep AI-generated code from drifting into chaos. The full set lives in the AI Agent Context page; here is what they solve:

Preventing Architectural Drift

What the rules enforce Why it matters
YAML-first navigation — always start in flows/ (backend) or graph/ (frontend) before opening code. Prevents the AI from getting lost in implementation details and losing the big picture.
CLI validation loop — run hbia lint, hbia validate, hbia graph-validate before and after every change. Catches structural errors that code review misses: disconnected vertices, missing effects, broken bindings.
Pit Stops every 3–5 changes — stop, lint everything, run tests, scan for drift. (Full checklist) The single most important habit. Prevents invisible technical debt from compounding.
Flows must chain vertices — no single-vertex flows with no next. The most common AI mistake. Isolated vertices defeat the entire graph model.
Centralized settings — all config in settings.py, never os.environ in handlers. Prevents scattered config that AI agents forget about across sessions.

Preventing Frontend Chaos

What the rules enforce Why it matters
Unidirectional data flow — Events → State → Effects → UI, no shortcuts. Makes the entire reactive chain traceable from YAML.
Components are pure renderers — no useState for app data, no useEffect for fetching. Eliminates the scattered state and hidden effects that make React codebases opaque to AI.
Effects are the only side-effect channel — API calls, analytics, timers go through Effect nodes only. All side effects are declared in YAML and traceable.
localStorage is forbidden — linter enforces this as a hard error. Prevents invisible untracked state outside the graph.
Events are atomic — a mutation sequence executes as a unit before re-renders. Prevents intermediate invalid states from reaching the UI.

For the complete rule set, naming conventions, DSL reference, and project structure — see the AI Agent Context page, which contains the full auto-generated AGENTS.md document that AI coding assistants read.


The Pit Stop

Every 3–5 changes, stop everything and review. Not a quick glance — a thorough, checklist-driven review of the entire system. Lint everything. Validate everything. Run the tests. Look for drift.

The Pit Stop is what separates a codebase that stays healthy from one that silently rots. For the full explanation and checklists, see the dedicated Pit Stop page.