Skip to content

AI Agent Context

The content below is the **complete AGENTS.md** file that HBIA generates
via `hbia context --write` (or `hbia init`).  This is the document that
AI coding assistants read automatically when working inside an HBIA project.

It is reproduced here verbatim so that humans can inspect the full set of
rules, conventions, and reference material that an AI agent receives.

To regenerate this file in your own project, run:

```bash
hbia context --write          # backend only (default)
hbia context --write --front  # frontend only
hbia context --write --mono   # full-stack monorepo
```

Honey Badgeria 0.1.0b1 — AGENTS.md

Read this file first. It gives an AI coding assistant everything it needs to write, debug, and maintain code in a Honey Badgeria (HBIA) project. Auto-generated by hbia context --write or hbia init. Do not edit by hand — regenerate after upgrading the library.


0. Why Honey Badgeria Exists

Most codebases become opaque to AI after a few hundred files. HBIA solves this by making YAML definitions the single source of truth.

Core idea: An AI agent should be able to understand the entire application by reading the YAML graph definitions first, then drilling into implementation files only when needed.

The HBIA Equation

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

An AI reads YAML first. Humans benefit too, but the primary consumer of the structural layer is an AI system.

Universal Principles (Both Platforms)

Principle Reason
Explicit structure AI reasoning improves when relationships are explicit
Graph representation Graphs are easier for reasoning engines
Typed interfaces Prevents hallucination and ambiguity
Deterministic flows Enables reproducible reasoning
Semantic contracts Ensures machine verifiability
Minimal syntax Reduces token cost

Backend — The HBIA Workflow

Flow-First Search (BACK RULE #1)

When you need to find anything — a feature, a bug, a data path — start in flows/. The YAML files are a compact map of the whole backend:

flows/           ← read these FIRST to understand the backend
├── auth/
│   └── login.yaml       login flow
├── payments/
│   └── charge.yaml      payment flow
└── orders/
    └── create.yaml      order creation flow

Each YAML defines the complete pipeline — vertices, their handlers, their connections (via next), and data bindings. You can read the entire backend structure without opening a single Python file.

A flow without next connections is not a flow — it is a dead node. Every non-trivial operation (API endpoint, business process, data transformation) should be a multi-step pipeline where vertices are chained via next. If you find yourself creating one YAML file per function with no next, you are misusing the framework.

Only after you understand the graph should you open the Python handler files. This saves enormous context and avoids getting lost in implementation details.

Trust the CLI (BACK RULE #2)

HBIA has a rich CLI. Use it aggressively instead of manually reading files or guessing structure:

hbia inspect flows/payments.yaml         # topology, stages, vertex list
hbia inspect flows/payments.yaml --json  # machine-readable for parsing
hbia explain flows/payments.yaml         # human-readable explanation
hbia validate flows/payments.yaml        # find YAML errors fast
hbia graph-validate flows/payments.yaml  # full structural validation
hbia lint flows/                         # AI best-practice checks
hbia viz flows/payments.yaml             # visualize the DAG
hbia viz flows/ --ascii                  # visualize ALL flows at once
hbia health                              # tech debt: large files, cycles, dupes
hbia doctor                              # environment check
hbia flow-list                           # list all flows across all files

Before writing new code, always run hbia validate and hbia lint on the affected flows. After writing code, run hbia lint, hbia health, and the test suite.


Backend Pit Stop (BACK RULE #3)

Every 3-5 vertices (or after any significant change), stop and perform a Pit Stop — a conscious, thorough review of the entire backend:

Backend Pit Stop Checklist

  1. Lint all flows:

    hbia lint flows/
    hbia lint flows/ --strict    # treat warnings as errors
    

  2. Validate all flows:

    hbia validate flows/*.yaml
    hbia graph-validate flows/*.yaml
    

  3. Run the full test suite:

    python -m pytest tests/ -x -q
    

  4. Check tech debt:

    hbia health
    

  5. List all flows for overview:

    hbia flow-list
    

  6. Review for:

  7. Isolated single-vertex flows (CRITICAL) — If you see 4+ flows each with one vertex and no next, the architecture is broken. Combine them into proper multi-step pipelines.
  8. Disconnected vertices (no next pointing to/from them)
  9. Duplicate logic across flows (candidate for shared vertices)
  10. Missing effect or version on any vertex
  11. Missing tests for new vertices
  12. Proper effect annotation (pure vs side_effect)
  13. Correct version strings (bump when logic changes)

  14. Document the Pit Stop: Leave a brief note (commit message, PR comment, or docs/pit_stops.md) summarizing what was checked and found.

A Pit Stop is not optional. It is the difference between a codebase that stays healthy and one that accumulates invisible debt.


Backend Naming Conventions (MANDATORY)

Consistent naming is critical for AI comprehension.

Vertex Names

  • Always snake_case: create_user, validate_payment, send_email
  • Pattern: <action>_<noun> or <domain>_<action>: fetch_users, auth_login, parse_csv
  • Never camelCase, PascalCase, or kebab-case
  • Never generic names like step1, process, handle

Flow Names

  • Always snake_case: user_registration, payment_processing
  • One clear responsibility per flow

Handler Files

  • One handler per file, named same as the vertex: create_user.py
  • Group by domain in subfolders: vertices/auth/login.py
  • Handler function named same as file: vertices.auth.login.login

Flow Files

  • One flow per file (keep files small and focused)
  • Group by domain: flows/auth/login.yaml, flows/payments/charge.yaml
  • Add comments explaining the pipeline purpose

Backend YAML DSL — Flow Format (REQUIRED)

ALWAYS use the flow format when creating new YAML files.

Basic Structure

# Flow: <name>
# <Brief description of what this flow does>
# Pipeline: step_a → step_b → step_c

flow:
  <flow_name>:
    <vertex_name>:
      handler: <dotted.handler.path>
      effect: <pure|side_effect>        # REQUIRED
      version: "<number>"               # REQUIRED
      inputs:                           # data bindings (optional)
        <param>: <source_vertex.output_key>
      outputs:                          # output type declarations
        <key>: <type>
      next:                             # edges to next vertex(es)
        - <next_vertex_name>

Complete Example

# Flow: user_registration
# Validates user input, creates the account, and sends a welcome email.
# Pipeline: validate_input → create_user → send_welcome

flow:
  user_registration:
    validate_input:
      handler: vertices.users.validate_input.validate_input
      effect: pure
      version: "1"
      inputs:
        email: str
        username: str
      outputs:
        is_valid: bool
        clean_email: str
        clean_username: str
      next:
        - create_user

    create_user:
      handler: vertices.users.create_user.create_user
      effect: side_effect
      version: "1"
      inputs:
        clean_email: validate_input.clean_email
        clean_username: validate_input.clean_username
      outputs:
        user_id: str
        created: bool
      next:
        - send_welcome

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

Key Rules

  1. Every vertex MUST have effect and version — enables caching and AI comprehension.
  2. Use next: to connect vertices — entry point is auto-detected (the vertex no one points to). next is the backbone of HBIA. Without next edges, vertices are isolated nodes with no pipeline — the entire graph model is pointless.
  3. Declare inputs and outputs explicitly — enables contract validation and AI understanding of data flow.
  4. Add comments above each vertex explaining its purpose.
  5. One flow per file for clarity.

CRITICAL: Flows MUST Chain Vertices via next (BACK RULE #4)

The most common mistake when generating HBIA YAML is creating isolated single-vertex flows — one file per function, no next connections. This defeats the entire purpose of HBIA.

WRONG — DO NOT DO THIS:

# ✘ BAD: isolated single-vertex flow (no pipeline, no `next`)
# file: flows/comments/add_comment.yaml
flow:
  add_comment:
    add_comment:
      handler: vertices.comments.add_comment.add_comment
      effect: side_effect
      version: "1"
      inputs:
        user_id: str
        text: str
      outputs:
        comment_id: str
# ✘ BAD: another isolated single-vertex flow
# file: flows/users/get_user_by_token.yaml
flow:
  get_user_by_token:
    get_user_by_token:
      handler: vertices.users.get_user_by_token.get_user_by_token
      effect: pure
      version: "1"
      inputs:
        token: str
      outputs:
        user_id: str

The above is wrong because: - Each flow has one vertex and no next — there is no pipeline - The operation "add a comment" actually requires: authenticate → validate input → persist → return result - Creating one file per function with no connections is just a fancy function registry, not a graph

RIGHT — DO THIS INSTEAD:

# ✔ CORRECT: multi-step pipeline with `next` chaining
# file: flows/comments/add_comment.yaml
flow:
  add_comment:
    authenticate:
      handler: vertices.auth.verify_token.verify_token
      effect: pure
      version: "1"
      inputs:
        token: str
      outputs:
        user_id: str
        is_valid: bool
      next:
        - validate_input

    validate_input:
      handler: vertices.comments.validate_comment.validate_comment
      effect: pure
      version: "1"
      inputs:
        text: authenticate.user_id
        project_id: str
      outputs:
        clean_text: str
        is_valid: bool
      next:
        - persist_comment

    persist_comment:
      handler: vertices.comments.add_comment.add_comment
      effect: side_effect
      version: "1"
      inputs:
        user_id: authenticate.user_id
        text: validate_input.clean_text
      outputs:
        comment_id: str
        created_at: str

The rule of thumb: If you find yourself creating 4+ YAML files where each has a single vertex and no next, stop immediately — you are doing it wrong. Combine related operations into multi-step flows.

Atomic Groups

atomic_groups:
  payment_txn:
    vertices: [reserve_inventory, charge_card, confirm_order]
    on_failure: rollback
    no_cache: true
    no_parallel: true

Backend — How to Create a New Feature

# 1. Scaffold flow + vertex stubs in one command
hbia flow-create user_registration \
  --vertices validate_input,create_user,send_welcome \
  --domain users

# 2. Implement each handler
#    Edit vertices/users/validate_input.py
#    Edit vertices/users/create_user.py
#    Edit vertices/users/send_welcome.py

# 3. Update the YAML with proper inputs/outputs/effects
#    Edit flows/users/user_registration.yaml

# 4. Lint and validate
hbia lint flows/users/user_registration.yaml
hbia validate flows/users/user_registration.yaml

# 5. Write tests → Run tests → Pit Stop!
python -m pytest tests/ -x -q
hbia lint flows/ --strict
hbia health

Scaffolding Commands

hbia flow-create payment \
  --vertices validate_payment,process_charge,send_receipt \
  --domain payments \
  --effect side_effect

hbia vertex-create login --domain auth --inputs email,password --outputs token

hbia init my_app                          # plain project
hbia init my_api --framework fastapi      # FastAPI + HBIA (CORS, auth, manage.py)
hbia init my_app --framework monorepo     # Full-stack monorepo (FastAPI backend)

After hbia init, the project is immediately runnable:

cd my_api
python manage.py        # starts the FastAPI server on port 8000

Infrastructure Pattern

When a feature needs external services (database, auth, caching):

  1. Add the config to settings.py — new _env() call
  2. Add the env var to example.env — with documentation
  3. Create/update a module in infra/ — singleton connection
  4. Import from infra/ in handlers — never instantiate inline

For authentication specifically, use the Keycloak boilerplate in infra/auth.py — it is pre-configured by hbia init.


Backend Linting (AI Best-Practice Enforcement)

Run hbia lint on every flow file. The linter checks:

Code Severity What it checks
E001 error Vertex in next not defined in flow
E002 error Flow has multiple vertices but zero edges
W001 warning Missing effect field
W002 warning Missing version field
W003 warning Missing handler field
W005 warning Missing outputs declaration
W006 warning Vertex name not in snake_case
W007 warning Single-vertex flow (likely incomplete pipeline)
W008 warning Project has 4+ isolated flows — architecture red flag
hbia lint flows/                    # lint all flows
hbia lint flows/ --strict           # warnings = errors
hbia lint flows/ --json             # machine-readable output

Always run lint before committing. Zero errors is required. Zero warnings is strongly recommended.


Backend Settings

All features disabled by default for safe deployments.

Centralized Settings (BACK RULE #5 — CRITICAL)

All configuration lives in settings.py. This is the single source of truth. Environment variables are read ONLY in settings.py using the _env() helpers. NEVER use os.environ directly anywhere else.

The pattern:

# settings.py — the ONLY file that reads os.environ
from dotenv import load_dotenv
load_dotenv()

def _env(key, default=""): return os.environ.get(key, default)
def _env_bool(key, default=False): ...

SETTINGS = {
    "GRAPH_FILE": _env("HBIA_GRAPH_FILE", "flows/example_flow.yaml"),
    ...
}
HOST = _env("HOST", "0.0.0.0")
CORS_ORIGINS = [o.strip() for o in _env("CORS_ORIGINS", "http://localhost:3000").split(",")]
KEYCLOAK_URL = _env("KEYCLOAK_URL", "")
# In handlers, app code, infra — ALWAYS import from settings
from settings import SETTINGS, HOST, CORS_ORIGINS, KEYCLOAK_URL

Settings Reference

Setting Default Env Var
GRAPH_FILE "flows/example_flow.yaml" HBIA_GRAPH_FILE
HANDLER_MODULES ["vertices"] HBIA_HANDLER_MODULES
CACHE_ENABLED False HBIA_CACHE_ENABLED
PARALLEL_ENABLED False HBIA_PARALLEL_ENABLED
ASYNC_ENABLED False HBIA_ASYNC_ENABLED
MAX_WORKERS 4 HBIA_MAX_WORKERS
HOST "0.0.0.0" HOST
PORT 8000 PORT
DEBUG True DEBUG
CORS_ORIGINS ["http://localhost:3000"] CORS_ORIGINS

Environment Files

File Purpose
example.env Contract — every env var the project needs, with docs
.env Local values — copy from example.env, fill in secrets

Every new env var MUST be added to example.env first. The .env file is in .gitignore. example.env is committed.

HBIA recommends Keycloak as the identity provider for production applications. The scaffolded infra/auth.py contains the connection boilerplate. Setup:

  1. Deploy a Keycloak instance (Docker, k8s, or cloud)
  2. Create a realm and client
  3. Fill in KEYCLOAK_* values in .env
  4. Uncomment auth_dependency in app.py
Setting Env Var
Keycloak URL KEYCLOAK_URL
Realm KEYCLOAK_REALM
Client ID KEYCLOAK_CLIENT_ID
Client Secret KEYCLOAK_CLIENT_SECRET
JWT Algorithm JWT_ALGORITHM (default: RS256)

Backend Execution Modes

Serial (default)

from honey_badgeria.back.runtime.runner import run_flow
result = run_flow(graph, handlers={"h": fn})

Parallel (thread pool)

result = run_flow(graph, parallel_enabled=True, max_workers=4)

Async

from honey_badgeria.back.runtime.runner import run_flow_async
result = await run_flow_async(graph)

Cache (pure vertices only)

result = run_flow(graph, cache_enabled=True)

Atomic Groups

from honey_badgeria.back.atomicity import AtomicGroup, FailurePolicy
from honey_badgeria.back.atomicity.backends import InMemoryBackend
result = run_flow(graph, transaction_backend=InMemoryBackend())

Backend Testing Utilities

from honey_badgeria.testing import GraphFactory, FlowTester, VertexMock

# Build a test graph
graph = GraphFactory.create(
    vertices=[
        {"name": "a", "handler": fn_a, "effect": "pure", "version": "1"},
        {"name": "b", "handler": fn_b, "effect": "pure", "version": "1",
         "inputs": {"x": "a.x"}},
    ],
    edges=[("a", "b")],
)

# Run and assert
tester = FlowTester(graph)
result = tester.run()

Backend Handler Rules (IMPORTANT)

  1. A handler is a plain Python function (sync or async).
  2. It receives inputs as keyword arguments.
  3. It must return a dict. Each key becomes vertex_name.key.
  4. Mark effect: side_effect for non-deterministic handlers.
  5. Bump version when handler logic changes.
  6. Never create DB connections inside a handler — use infra/.
  7. NEVER use os.environ in a handler — import config values from settings.py. If you need a new config value, add it to settings.py and example.env first, then import it.

Backend Python Decorators

from honey_badgeria.decorators import vertex, flow

@vertex
def hello(name="World"):
    return {"greeting": f"Hello, {name}!"}

@vertex(name="custom_name")
def handler():
    return {"result": 42}

@flow("my_flow")
def my_flow():
    ...

Backend CLI Commands Reference

Command Description
hbia version Show library version
hbia init NAME Scaffold project
hbia doctor Check environment
hbia health Detect tech debt
hbia lint [FILES] AI best-practice linting
hbia run FILE Execute a graph
hbia validate FILE Validate YAML
hbia inspect FILE Show topology
hbia explain FILE Human/AI explanation
hbia graph FILE Quick graph summary
hbia graph-validate FILE Full validation
hbia graph-sync MODULE Generate YAML from decorators
hbia flow-build MODULE Auto-discover and build graph
hbia flow-create NAME Scaffold flow + vertex stubs
hbia flow-list List all flows
hbia viz FILE Visualize graph
hbia vertex-create NAME Scaffold vertex handler
hbia cache-info Cache statistics
hbia cache-prune Prune old cache
hbia clear-cache Delete all cache
hbia context AI context document

Backend Project Structure

my_project/
├── AGENTS.md               # ← This file (AI context)
├── settings.py             # ★ ALL config lives here — NEVER os.environ elsewhere
├── example.env             # ★ Env var contract — committed to repo
├── .env                    # Local secrets — in .gitignore
├── manage.py               # Entry point (starts server or runs flows)
├── app.py                  # FastAPI application (if using FastAPI)
├── hbia.yaml               # Project manifest
├── flows/                  # ★ SOURCE OF TRUTH — read these first
│   ├── auth/
│   │   └── login.yaml
│   └── payments/
│       └── charge.yaml
├── vertices/               # Handlers grouped by domain
│   ├── auth/
│   │   └── login.py
│   └── payments/
│       └── charge.py
├── infra/                  # Shared infrastructure (singletons)
│   ├── auth.py             # Keycloak / JWT integration
│   └── database.py
└── tests/
    ├── test_auth.py
    └── test_payments.py

Backend Module Map (Quick Reference)

Module Purpose
honey_badgeria.conf.settings Settings, configure, get_settings
honey_badgeria.decorators @vertex, @flow
honey_badgeria.back.graph Graph, Vertex, Edge
honey_badgeria.back.topology GraphTopology
honey_badgeria.back.lint AILinter
honey_badgeria.back.loader GraphBuilder
honey_badgeria.back.runtime.runner run_flow, run_flow_async
honey_badgeria.back.dsl.loader load_graph_from_yaml
honey_badgeria.back.dsl.validator DSLValidator
honey_badgeria.testing GraphFactory, FlowTester, VertexMock
honey_badgeria.back.explain GraphExplainer
honey_badgeria.logging AILogger, EventType

Backend Exception Hierarchy

HoneyBadgeriaError
├── GraphError
│   ├── VertexAlreadyExistsError
│   ├── VertexNotFoundError
│   ├── EdgeInvalidError
│   └── GraphCycleError
├── GraphValidationError
├── DSLValidationError
├── HandlerNotFoundError
├── HandlerResolutionError
├── FlowNotFoundError
├── DataResolutionError
├── ContractError
│   ├── ContractInputError
│   └── ContractOutputError
├── ExecutionError
│   ├── VertexExecutionError
│   └── StageExecutionError
├── AtomicError / SagaError
├── CacheError
└── ConfigurationError

Backend — Common Mistakes to Avoid

  1. Isolated single-vertex flows (WORST MISTAKE) — Creating one YAML file per function with no next connections. This defeats the entire purpose of HBIA. The linter flags this as W007 (single-vertex flow) and W008 (too many isolated flows at project level). Every real business operation is a multi-step pipeline. If you have 4+ YAML files each with one vertex and no next, you are doing it wrong. Combine them into proper flows chained with next.
  2. Disconnected vertices — Every flow with multiple vertices must have next: fields connecting them. The linter catches this (E002).
  3. Missing effect/version — Every vertex needs both.
  4. Generic namesstep1, process, handler are useless. Use validate_payment, create_user, send_notification.
  5. Giant flows — Keep flows under 10 vertices. Split large flows into sub-flows by domain.
  6. Forgetting to lint — Run hbia lint before every commit.
  7. No comments in YAML — Add a comment above each vertex explaining its purpose.
  8. Instantiating resources in handlers — DB connections, API clients must come from infra/, never created inline.
  9. Using os.environ outside settings.py — Environment variables are read ONLY in settings.py. Handlers and app code import values from settings.py. This is a hard rule.

Backend — CRUD Pattern (Standardized)

HBIA provides a standardized CRUD router that maps RESTful endpoints to HBIA flows and wraps all responses in the HBIA Response Envelope.

HBIA Response Envelope

Every HBIA endpoint returns this shape:

// Success
{"ok": true, "data": {...}, "meta": {...}}

// Error
{"ok": false, "error": {"code": "NOT_FOUND", "message": "..."}}

// Paginated
{"ok": true, "data": [...], "meta": {"total": 100, "page": 1, "page_size": 20, "pages": 5}}

Helpers (honey_badgeria.integrations.fastapi.response):

Function Purpose
hbia_success(data, meta) Wrap data in success envelope
hbia_error(code, message) Wrap error in error envelope
hbia_paginated(items, total, page, page_size) Paginated success

CRUD Router Builder

from honey_badgeria.integrations.fastapi.crud_router import build_crud_router

crud = build_crud_router(
    hbia_app,
    resource="items",
    flows={
        "list": "list_items",      # GET    /api/v1/items
        "get": "get_item",         # GET    /api/v1/items/{id}
        "create": "create_item",   # POST   /api/v1/items
        "update": "update_item",   # PUT    /api/v1/items/{id}
        "delete": "delete_item",   # DELETE /api/v1/items/{id}
    },
)
app.include_router(crud)

Only provide the flow keys you need — partial CRUD is fine.

Scaffold a Complete CRUD Resource

hbia crud-create items \
  --fields "name:str,price:float,active:bool" \
  --domain inventory

This generates: - Backend: flows, vertices, tests - Frontend: state, events, effects, UI, service - Both: fully connected and ready to run


Frontend — The HBIA UI Workflow

The frontend uses four reactive graphs instead of execution DAGs. The graphs are interconnected but each has a single responsibility:

Graph Defines Analogous to (Backend)
UI Graph Component hierarchy, reads, events Flow topology
State Graph State ownership, typed fields, mutations Vertex contracts
Effect Graph Side effects triggered by state changes side_effect vertices
Event Graph User action → mutation sequences Flow execution order

The Fundamental Rule

Events mutate State.
State triggers Effects.
State drives UI.

This is the only direction data flows. UI never mutates state directly. Effects never emit events. Violations of this rule create untraceable bugs.

Graph-First Search (FRONT RULE #1)

When you need to find anything in the frontend — a feature, a bug, a data path — start in graph/.

graph/               ← read these FIRST to understand the frontend
├── counter/         ← EXAMPLE ONLY — do NOT put real code here
│   ├── ui/          component hierarchy
│   ├── state/       state ownership
│   ├── effects/     side effect declarations
│   └── events/      user event flows
├── auth/            ← real domain (create your own!)
│   ├── ui/
│   ├── state/
│   ├── effects/
│   └── events/
└── dashboard/       ← real domain (create your own!)
    └── ...

Each domain directory contains YAML files that define the complete reactive architecture of that feature. You can read the entire frontend structure without opening a single TypeScript file.

Only after you understand the graph should you open the .tsx view files or .ts handlers.

The counter/ Domain Is an EXAMPLE (FRONT RULE #1b — CRITICAL)

The graph/counter/ domain is auto-generated by hbia init as a working example. It exists solely to demonstrate the four-graph pattern. NEVER put real application code in the counter domain.

When building a new application: 1. Create your own domain directories under graph/ (e.g. graph/auth/, graph/comments/, graph/dashboard/). 2. Do NOT modify counter/ to repurpose it for your feature. 3. You may delete counter/ once you understand the pattern.

If the AI agent puts all new code into graph/counter/, that is a mistake. Every feature gets its own domain directory.

Trust the Frontend CLI (FRONT RULE #2)

hbia ui-inspect graph/counter/           # inspect domain structure
hbia ui-inspect graph/counter/ --json    # machine-readable output
hbia ui-graph graph/counter/             # ASCII graph visualization
hbia ui-graph graph/counter/ --type state  # visualize a specific graph
hbia ui-validate graph/                  # validate all YAML definitions
hbia ui-lint graph/                      # design quality checks
hbia ui-codegen graph/ --output hbia-runtime/  # generate TypeScript runtime

Before writing new UI code, always run hbia ui-validate and hbia ui-lint on the affected domains. After writing code, run the lint again and the test suite.


Frontend Pit Stop (FRONT RULE #3)

Every 3-5 graph changes (new components, new state, new events), stop and perform a Frontend Pit Stop.

Frontend Pit Stop Checklist

  1. Validate all domains:

    hbia ui-validate graph/
    

  2. Lint all domains:

    hbia ui-lint graph/
    

  3. Check for circular effect chains: The linter detects these automatically. A circular chain like EffectA → mutates StateB → EffectB → mutates StateA causes infinite re-renders and must be fixed immediately.

  4. Check for unused state: State nodes that are declared but never read by any UI component or effect are dead weight. The linter flags these as warnings.

  5. Check for dead events: Events declared in YAML but not emitted by any UI component are unreachable code.

  6. Verify hook safety (CRITICAL):

  7. Does every UI component's reads list match what it actually renders? Over-reading causes unnecessary re-renders.
  8. Does every effect watch the minimum set of fields it needs? Broad watches cause cascading effects.
  9. Are there effects that mutate state they also watch? This is a self-triggering loop — always an error.
  10. Are there effects with debounce_ms: 0 watching rapidly- changing state? This causes render storms.

  11. Verify ZERO localStorage usage (MANDATORY): Search the entire codebase for localStorage, sessionStorage, and window.storage. Any occurrence is a blocking error. The linter catches this automatically, but verify during Pit Stops.

  12. Verify no raw errors in the UI: Check that all API calls use ServiceResult<T> from services. Confirm that error messages shown to users come from error.userMessage, never raw error objects or tracebacks.

  13. Verify reactive chain completeness: For every user-facing feature, trace the full chain:

    UI event → Event YAML → mutation steps → State change →
    Effect triggers → Side effects → UI re-render
    
    If any link is missing, the feature is broken.

  14. Regenerate runtime (if YAML changed):

    hbia ui-codegen graph/ --output hbia-runtime/
    

  15. Run the test suite:

    npm test
    

  16. Document the Pit Stop.


Frontend Naming Conventions (MANDATORY)

UI Components

  • PascalCase: CounterPage, DashboardChart, AuthLoginForm
  • Pattern: <Domain><Purpose>: PaymentForm, UserProfile
  • View files: snake_case.tsx: counter_page_view.tsx
  • One component per YAML node

State Nodes

  • PascalCase + State suffix: CounterState, AuthState, CartState
  • Interface: <StateName>Data: CounterStateData, AuthStateData
  • Fields: camelCase: itemCount, isAuthenticated, dateRange
  • Mutations: snake_case: set_count, increment_count, reset

Effects

  • snake_case: fetch_chart_data, log_count, sync_to_local_storage
  • Pattern: <action>_<target>: fetch_users, validate_form
  • Handler files: same name: fetch_chart_data_handler.ts

Events

  • snake_case: increment, apply_filters, submit_form
  • Pattern: <action> or <action>_<noun>: login, add_to_cart

Domains

  • snake_case directory: counter/, auth/, dashboard/
  • One feature per domain

Frontend YAML DSL — The Four Graph Types

UI Node (ui/<ComponentName>/<name>_node.yaml)

component: CounterPage           # PascalCase component name
view: counter_page_view          # TypeScript view file (snake_case)
reads:                           # State fields this component renders
  - CounterState.count
events:                          # Events this component can emit
  on_increment: increment        # <ui_action>: <event_name>
  on_decrement: decrement
children:                        # Child components (optional)
  - CounterDisplay
props:                           # External props (optional)
  title: string

State Node (state/<StateName>/state.yaml)

state: CounterState              # PascalCase + State suffix
interface: CounterStateData      # TypeScript interface name
fields:                          # Typed state fields
  count: number
  label: string
mutations:                       # Allowed mutation function names
  - increment_count
  - decrement_count
  - set_label
triggers:                        # Effects to invoke on state change
  - log_count
initial:                         # Default values (optional)
  count: "0"
  label: '"Counter"'

Effect Node (effects/<effect_name>/effect.yaml)

effect: log_count                # snake_case effect name
watch:                           # State fields that trigger this effect
  - CounterState.count
handler: log_count_handler       # TypeScript handler file
effect_type: side_effect         # side_effect | derived
mutates:                         # State nodes this effect can write to (optional)
  - AnalyticsState
debounce_ms: 300                 # Debounce milliseconds (optional)

Event Node (events/<event_name>.yaml)

event: increment                 # snake_case event name
flow:                            # Ordered mutation sequence
  - CounterState.increment_count
source: CounterPage              # UI component that emits this (optional)
guards:                          # Conditions that must be true (optional)
  - AuthState.is_authenticated

Key DSL Rules

  1. reads uses StateName.fieldName — always dot-notation.
  2. flow uses StateName.mutation_name — ordered, sequential.
  3. watch uses StateName.fieldName — what triggers the effect.
  4. mutates uses StateName — which state the effect can write.
  5. guards uses StateName.fieldName — boolean fields checked before the event executes.
  6. Every state MUST have interface — enables TypeScript codegen.
  7. Every effect MUST have handler — the implementation file.

Frontend Reactive Rules (CRITICAL)

These rules define how the four graphs interact. Violating them creates bugs that are invisible to traditional debugging.

Rule 1: Unidirectional Flow Only

User Action → Event → Mutation → State Change → Effect → UI Update
     ↑                                                        |
     └────────────────── (via new event) ─────────────────────┘

The cycle only restarts via a new user action. Effects MUST NOT emit events. State MUST NOT directly trigger other state mutations.

Rule 2: Components Are Pure Renderers

UI components: - ✅ Read state (via reads) - ✅ Emit events (via events) - ✅ Render children (via children) - ❌ NEVER mutate state directly - ❌ NEVER call services/APIs - ❌ NEVER hold local state for data that should be in the graph - ❌ NEVER use useEffect for data fetching (use Effect graph)

Rule 3: State Owns All Data

  • All application data lives in State nodes.
  • Local component state (useState) is only for ephemeral UI things: tooltip visibility, input focus, animation state.
  • If two components need the same data → it belongs in a State node.
  • If data survives a route change → it belongs in a State node.

Rule 4: Effects Are the Only Side-Effect Channel

  • API calls → Effect (via services/)
  • Analytics tracking → Effect
  • WebSocket subscriptions → Effect
  • Timers/intervals → Effect

Never put side effects in event flows, mutations, or components.

localStorage is STRICTLY FORBIDDEN — see Rule 7 below.

Rule 5: Events Are Atomic Transactions

An event's flow list is an ordered sequence of mutations that execute as a unit. If flow: [CartState.add_item, CartState.recalculate], both mutations run before any effect or re-render. This prevents intermediate invalid states from being visible.

Rule 6: Mutations Are the Only State Writers

State fields change only through declared mutations. No direct property assignment. The generated store enforces this.

Rule 7: localStorage Is FORBIDDEN (ABSOLUTE — NO EXCEPTIONS)

NEVER use localStorage, sessionStorage, or window.storage. This is not a suggestion — it is a hard prohibition.

localStorage violates every principle HBIA is built on: - It creates invisible, untyped, untracked state outside the graph - It makes data flow untraceable for AI agents - It bypasses every validation, mutation, and effect chain - It causes hydration mismatches in SSR frameworks - It is a security liability (XSS can read everything) - It makes testing impossible (hidden global side effects)

The ONLY scenario where localStorage is acceptable is when the user explicitly requests it with full knowledge of the tradeoffs. The AI agent must NEVER suggest, recommend, or use localStorage on its own initiative.

What to use instead: - Persistent state → Use the HBIA State graph with an Effect that syncs to the backend via services/. - Session data → Use React Context or the HBIA State graph. - Auth tokens → Use HTTP-only cookies managed by the backend (Keycloak handles this automatically). - User preferences → Store in the backend database, load via Effect.

The frontend linter flags any localStorage usage as an error. The Pit Stop checklist requires confirming zero localStorage usage.

Rule 8: NEVER Expose Raw Errors to the UI

Error tracebacks, server error details, and stack traces must NEVER be rendered in the UI. This is a security and UX violation.

The services/api.ts module wraps all API calls in ServiceResult<T>:

type ServiceResult<T> =
  | { ok: true; data: T }
  | { ok: false; error: HBIAServiceError }

Effect handlers must use this pattern:

async function fetch_data_handler(state, services) {
  const result = await apiService.get<MyData>("/api/v1/data")
  if (result.ok) {
    return { MyState: { data: result.data, error: null } }
  }
  // Show user-friendly message, NEVER the raw error
  return { MyState: { error: result.error.userMessage } }
}

UI components render error from State — never catch exceptions:

// ✔ CORRECT — display a friendly message from state
{error && <p className="text-red-500">{error}</p>}

// ✘ WRONG — never render raw error objects or tracebacks
{error && <pre>{error.stack}</pre>}

Raw errors are logged to the console in development mode only. In production, users see friendly messages like "Something went wrong."


Frontend Hook Safety (CRITICAL — Read Carefully)

The generated HBIA runtime produces React hooks from the YAML definitions. These hooks have specific safety properties that must be maintained.

Generated Hooks

Hook Source Purpose
use<StateName>() State node Subscribe to state changes
use<EventName>() Event node Get event dispatcher function
useHBIA() Runtime Access full store (avoid when possible)

Hook Safety Checklist

  1. No hook calls inside conditions or loops. React hooks must be called at the top level of the component, in the same order every render. This is standard React rules, but especially important because HBIA hooks subscribe to the store.

  2. Subscribe to the narrowest state possible.const state = useHBIA() — subscribes to ALL state. ✅ const counter = useCounterState() — subscribes to one slice. Over-subscribing causes unnecessary re-renders.

  3. Never call a mutation hook conditionally. The dispatcher returned by use<EventName>() is stable (does not change between renders), so it's safe to memoize. But the hook call itself must be unconditional.

  4. Watch for self-triggering effects. An effect that watches StateA.x and mutates StateA will trigger itself infinitely. The linter catches direct cycles, but indirect ones (A→B→A via multiple effects) require manual review during Pit Stops.

  5. Debounce rapid-fire state. If state changes on every keystroke (search input, form fields), set debounce_ms on the watching effect. Without debounce, each keystroke triggers the full effect chain.

  6. Don't mix HBIA state with React state for the same data. If count lives in CounterState, don't also have const [count, setCount] = useState(0). This creates two sources of truth.

  7. Effects must be idempotent or properly guarded. An effect may fire multiple times (e.g., during React strict mode double-render). Design handlers to be safe for re-execution.

Hook Dependency Audit

During each Frontend Pit Stop, verify:

Check How
Over-subscription List all useHBIA() calls → replace with typed hooks
Stale closures Verify event dispatchers don't capture outdated state
Effect storms Check effects with debounce_ms: 0 on frequently-changing fields
Memory leaks Ensure effects with subscriptions/intervals have cleanup
Circular chains Run hbia ui-lint → check for circular_effects errors

Frontend — How to Create a New Feature

Primitives First (FRONT RULE #5 — START HERE)

Before creating any domain graph, build your UI primitives in the primitives/ directory. Primitives are the atomic building blocks: buttons, inputs, cards, badges, modals, etc.

primitives/
├── button.tsx           # <Button variant="primary" />
├── input.tsx            # <Input label="Email" />
├── card.tsx             # <Card title="..." />
├── badge.tsx            # <Badge color="green" />
├── modal.tsx            # <Modal open={true} />
└── icon.tsx             # <Icon name="check" />

Rules for primitives: - Zero state, zero hooks, zero side effects. Primitives are pure presentational components that accept props and render HTML. - Every UI view (.tsx) in graph/ should compose primitives. Never write raw <button> or <input> in graph views — use the primitive instead. - Create primitives BEFORE building domain views. This ensures consistency and reusability across the entire application. - Keep them small — one component per file, under 50 lines.

If the AI agent creates domain views without first creating or using primitives, the architecture will accumulate inconsistency and duplication. Primitives are the foundation layer.

Step-by-Step

  1. Create primitives (if they don't exist yet):

    primitives/button.tsx
    primitives/input.tsx
    primitives/card.tsx
    

  2. Create the domain directory:

    graph/<domain>/ui/
    graph/<domain>/state/
    graph/<domain>/effects/
    graph/<domain>/events/
    

  3. Define State first — What data does this feature own?

    # graph/<domain>/state/<Name>State/state.yaml
    state: CartState
    interface: CartStateData
    fields:
      items: CartItem[]
      total: number
    mutations:
      - add_item
      - remove_item
      - recalculate_total
    triggers:
      - persist_cart
    

  4. Define Events — What can the user do?

    # graph/<domain>/events/add_to_cart.yaml
    event: add_to_cart
    flow:
      - CartState.add_item
      - CartState.recalculate_total
    source: ProductCard
    

  5. Define Effects — What side effects happen?

    # graph/<domain>/effects/persist_cart/effect.yaml
    effect: persist_cart
    watch:
      - CartState.items
    handler: persist_cart_handler
    effect_type: side_effect
    

  6. Define UI — What does the user see? Build views using primitives from primitives/:

    # graph/<domain>/ui/ProductCard/product_card_node.yaml
    component: ProductCard
    view: product_card_view
    reads:
      - CartState.items
    events:
      on_add: add_to_cart
    

  7. Validate and lint:

    hbia ui-validate graph/<domain>/
    hbia ui-lint graph/<domain>/
    

  8. Regenerate runtime:

    hbia ui-codegen graph/ --output hbia-runtime/
    

  9. Implement TypeScript files:

  10. state/<Name>State/interface.ts — TypeScript interface
  11. state/<Name>State/mutations.ts — mutation functions
  12. effects/<effect>/handler.ts — effect handler
  13. ui/<Component>/<view>.tsx — React view (compose from primitives!)

  14. Pit Stop!


Frontend Linting (AI Design-Quality Checks)

Run hbia ui-lint on every domain. The linter checks:

Category Severity What it checks
localStorage_forbidden error Any use of localStorage/sessionStorage (BANNED)
raw_error_exposure error Error tracebacks exposed in UI components
unused_state warning State declared but never read by UI or effects
dead_event warning Event declared but not emitted by any UI component
orphaned_effect info Effect not listed as trigger in any state
broad_state warning State with > 10 fields (split recommended)
missing_interface warning State without TypeScript interface
circular_effects error Effect chain that creates infinite loop
naming info Naming convention violations
hbia ui-lint graph/                 # lint all domains
hbia ui-lint graph/counter/         # lint one domain

circular_effects errors are blockers. They cause infinite re-renders and must be resolved before proceeding.


Frontend Code Generation

The codegen produces a self-contained TypeScript runtime from YAML definitions. No external state library required (no Redux, no Zustand, no MobX).

hbia ui-codegen graph/ --output hbia-runtime/

Generated Files

File Contents
types.ts TypeScript interfaces from state field declarations
store.ts Central store with pub/sub reactivity and typed mutations
effects.ts Effect watcher system with debounce support
events.ts Event dispatchers with guard evaluation
react.tsx React hooks (useSyncExternalStore) — use<State>, use<Event>
index.ts Barrel exports

When to Regenerate

Regenerate the runtime any time a YAML definition changes: - New state fields or mutations - New effects or events - Changed interface names - New domains

The generated files are meant to be committed to version control. They are deterministic — same YAML input always produces the same output.

What Is NOT Generated

The codegen does not generate: - View components (.tsx files in ui/) — you write these - Mutation logic (.ts files in state/) — you write these - Effect handlers (.ts files in effects/) — you write these - Services (services/*.ts) — you write these

The YAML defines the contract. You implement the logic.


Frontend CLI Commands Reference

Command Description
hbia ui-inspect PATH Inspect domain graph structure
hbia ui-graph PATH ASCII visualization of graphs
hbia ui-validate PATH Validate YAML definitions
hbia ui-lint PATH Design quality checks
hbia ui-codegen PATH Generate TypeScript runtime

Frontend Project Structure

front/
├── hbia.yaml               # Frontend project manifest
├── package.json             # Node.js dependencies
├── tsconfig.json            # TypeScript config
├── next.config.ts           # Next.js config
├── tailwind.config.ts       # Tailwind config
├── app/                     # Next.js App Router
│   ├── layout.tsx
│   ├── page.tsx
│   └── globals.css
├── graph/                   # ★ SOURCE OF TRUTH — read these first
│   ├── counter/
│   │   ├── ui/
│   │   │   └── CounterPage/
│   │   │       ├── counter_page_node.yaml    # UI definition
│   │   │       └── counter_page_view.tsx     # React view
│   │   ├── state/
│   │   │   └── CounterState/
│   │   │       ├── state.yaml                # State definition
│   │   │       ├── interface.ts              # TypeScript interface
│   │   │       └── mutations.ts              # Mutation functions
│   │   ├── effects/
│   │   │   └── log_count/
│   │   │       ├── effect.yaml               # Effect definition
│   │   │       └── handler.ts                # Effect handler
│   │   └── events/
│   │       ├── increment.yaml                # Event definition
│   │       └── decrement.yaml
│   └── auth/
│       └── ...
├── primitives/              # Shared UI primitives (Button, Input, etc.)
│   └── button.tsx
├── services/                # Backend API communication
│   └── api.ts
└── hbia-runtime/            # ★ Generated — do not edit manually
    ├── types.ts
    ├── store.ts
    ├── effects.ts
    ├── events.ts
    ├── react.tsx
    └── index.ts

Frontend Module Map (Quick Reference)

Module Purpose
honey_badgeria.front.dsl.schemas UINodeSchema, StateNodeSchema, EffectNodeSchema, EventFlowSchema
honey_badgeria.front.dsl.parser YAML parsing for all node types
honey_badgeria.front.dsl.validator FrontendDSLValidator (cross-reference checks)
honey_badgeria.front.dsl.loader load_frontend_domain, load_all_frontend_domains
honey_badgeria.front.graph.domain FrontendDomain (aggregates 4 graphs)
honey_badgeria.front.graph.ui_graph UIGraph, UINode
honey_badgeria.front.graph.state_graph StateGraph, StateNode
honey_badgeria.front.graph.effect_graph EffectGraph, EffectNode
honey_badgeria.front.graph.event_graph EventGraph, EventNode
honey_badgeria.front.codegen.runtime_gen FrontendCodegen
honey_badgeria.front.explain FrontendExplainer
honey_badgeria.front.lint FrontendLinter, FrontendLintIssue

Frontend — Common Mistakes to Avoid

  1. Putting code in the counter example domain (WORST MISTAKE)graph/counter/ is an auto-generated demo. NEVER repurpose it for real application code. Create a new domain directory for each feature: graph/auth/, graph/comments/, graph/dashboard/, etc. If all your code lives inside counter/, the architecture is wrong.
  2. Skipping primitives — The primitives/ directory holds reusable atomic UI components (buttons, inputs, cards). Always create primitives first before building domain views. Views in graph/ should compose primitives, never inline raw HTML elements.
  3. Mutating state from a component — Components ONLY emit events. Events trigger mutations. Never call store methods from a .tsx.
  4. Putting API calls in components — API calls belong in Effect handlers, accessed through services/.
  5. Self-triggering effects — An effect that watches and mutates the same state creates an infinite loop. The linter catches direct cycles but not all indirect ones.
  6. Using useEffect for data fetching — Use the HBIA Effect graph. React's useEffect is for DOM synchronization only.
  7. Over-subscribing to state — Don't use useHBIA() when a typed hook like useCounterState() exists. Over-subscribing causes re-renders on unrelated state changes.
  8. Declaring state without an interface — Every state node needs a TypeScript interface for type safety.
  9. Giant state nodes — Keep state nodes under 10 fields. Split by concern: CartItemsState, CartTotalsState, CartUIState.
  10. Missing source on events — Always declare source: so AI agents can trace where events originate.
  11. Using localStorage (FORBIDDEN)localStorage is banned in HBIA projects. It creates invisible untracked state outside the graph. The linter flags this as an error. Use the HBIA State graph for persistence, React Context for ephemeral session data, and HTTP-only cookies for auth tokens.
  12. Showing raw errors in the UI — NEVER render server error details, tracebacks, or stack traces. Use the ServiceResult<T> wrapper from services/api.ts. UI shows error.userMessage, never the raw exception.
  13. Forgetting to regenerate — After any YAML change, run hbia ui-codegen graph/ --output hbia-runtime/.
  14. Mixing React state with HBIA state — If data lives in a State node, don't duplicate it in useState. One source of truth.

Frontend — CRUD Pattern (Standardized)

CRUD Service Factory

The services/api.ts module provides a createCrudService<T>() factory that generates typed CRUD methods matching the backend HBIA envelope:

// services/items_service.ts
import { createCrudService } from "./api"

interface Item {
  id: string
  name: string
  price: number
}

export const itemsService = createCrudService<Item>("items")

CRUD in Effect Handlers

Effect handlers call the service and branch on ok:

// graph/items/effects/fetch_items/handler.ts
import { itemsService } from "@/services/items_service"
import { store } from "@/hbia-runtime"

export async function fetch_items_handler(
  state: HBIAState,
  _services: Record<string, unknown>,
): Promise<void> {
  store.set_loading({ loading: true })

  const result = await itemsService.list()
  if (result.ok) {
    store.set_items({ items: result.data, error: null })
  } else {
    store.set_items({ error: result.error.userMessage })
  }

  store.set_loading({ loading: false })
}

Event-Triggered Effects (on_event)

Effects can be triggered directly by events using on_event instead of watch. This is the recommended pattern for CRUD operations:

# Effect: fetch_items
# Triggered when the load_items event fires (not by state changes).
effect: fetch_items
on_event:
  - load_items
handler: fetch_items_handler
effect_type: side_effect
mutates:
  - ItemsState

The on_event field accepts a list of event names. When any of those events fire, the effect handler runs. An effect may have both watch and on_event — it will trigger on either.

Complete CRUD Domain Example

graph/items/
├── state/ItemsState/
│   ├── state.yaml          # items, loading, error fields
│   ├── interface.ts        # TypeScript interface
│   └── mutations.ts        # set_items, set_loading, set_error
├── events/
│   ├── load_items.yaml     # on page load
│   ├── create_item.yaml    # form submit
│   ├── update_item.yaml    # inline edit
│   └── delete_item.yaml    # delete button
├── effects/
│   ├── fetch_items/        # on_event: [load_items]
│   ├── create_item_api/    # on_event: [create_item]
│   ├── update_item_api/    # on_event: [update_item]
│   └── delete_item_api/    # on_event: [delete_item]
├── ui/ItemsList/
│   ├── items_list_node.yaml
│   └── items_list_view.tsx
└── ui/ItemForm/
    ├── item_form_node.yaml
    └── item_form_view.tsx

Monorepo — Back + Front Symbiosis

When backend and frontend coexist in the same repository, they share the HBIA philosophy but communicate through well-defined boundaries.

Architecture

my_app/
├── AGENTS.md          # This file — covers BOTH platforms
├── hbia.yaml          # Root manifest (points to back/ and front/)
├── back/              # Python backend (HBIA DAG engine)
│   ├── flows/         # ★ Backend source of truth
│   ├── vertices/
│   ├── infra/
│   └── tests/
└── front/             # Next.js frontend (HBIA reactive graphs)
    ├── graph/         # ★ Frontend source of truth
    ├── services/      # ← THE BRIDGE between front and back
    ├── hbia-runtime/
    └── app/

The Bridge: services/

The front/services/ directory is the only point of contact between frontend and backend. It contains typed API clients that call backend endpoints.

front/services/
├── api.ts              # Base HTTP client (fetch wrapper)
├── auth_service.ts     # Calls back/flows/auth/* endpoints
├── payment_service.ts  # Calls back/flows/payments/* endpoints
└── user_service.ts     # Calls back/flows/users/* endpoints

Rules for services: - One service file per backend domain - Service functions are called only from Effect handlers - Service functions return typed responses matching State interfaces - Service file names mirror backend flow domains

Data Flow Across the Stack

                    FRONTEND                          BACKEND
                    ────────                          ───────
User clicks   →  Event (YAML)
              Mutations (flow)
              State changes
              Effect triggers
              Effect handler  →  service.ts  →  HTTP  →  FastAPI endpoint
                                                         run_flow()
                                                         HBIA DAG execution
                                                         JSON response
              ←  Effect handler  ←  service.ts  ←  HTTP  ←  ┘
              Effect mutates State
              UI re-renders

Mapping Conventions

Backend Frontend Connection
flows/auth/login.yaml graph/auth/ domain services/auth_service.ts
Vertex create_user output UserState.currentUser field service maps response → mutation
Flow execution result Effect handler return service returns typed DTO

Symbiotic Development Rules

  1. Backend defines the API contract first — The backend flow's output types define what the frontend service returns.

  2. Frontend State interfaces mirror backend responses — If the backend returns {user_id, email, name}, the frontend has a State with those same fields.

  3. One service per backend domain — If the backend has flows/auth/, the frontend has services/auth_service.ts.

  4. Effects are the API boundary — UI components never import from services/. Only Effect handlers call services.

  5. Validate both platforms in Pit Stops:

    # Backend
    cd back && hbia lint flows/ --strict && hbia health
    
    # Frontend
    cd front && hbia ui-validate graph/ && hbia ui-lint graph/
    

  6. Keep YAML in sync — When a backend flow output changes, update the corresponding frontend State fields and service return types.

  7. AI Navigation Order for Monorepos:

  8. Read root hbia.yaml
  9. Read back/flows/ — understand backend pipelines
  10. Read front/graph/ — understand frontend reactive architecture
  11. Read front/services/ — understand the bridge
  12. Open implementation files only when needed

Foundational Principles (Both Platforms)

Code as Self-Explanatory for AI

  • Every module, class, and function has a docstring.
  • Variable names are descriptive.
  • Complex logic gets inline comments explaining why.

Small, Isolated, Declarative Modules

  • Each file does one thing. Target ≤ 300 lines per module.
  • No circular imports. Use TYPE_CHECKING guards.

Rewrite over Patch — Versioning

  • Backend: Vertices carry a version field. Bump it when logic changes.
  • Frontend: State interfaces are versioned via TypeScript exports.

Performance Discipline

  • Explicit loops: Every for loop needs a clear O(n) justification. Nested loops are O(n²) — find a better structure.
  • Prefer optimal patterns: hash maps for O(1) lookups, binary search, memoization (HBIA's cache is built for this), generators over lists.
  • Infinite loop prevention: Backend — HBIA's topological sort prevents cycles. Frontend — the linter detects circular effect chains.

Infrastructure as Singleton

  • Backend: Centralize DB connections and API clients in infra/.
  • Frontend: Centralize API communication in services/.
  • Never instantiate shared resources inside handlers or hooks.

Security Rules (Mandatory — Both Platforms)

  1. No hardcoding — tuneables live in settings.py (back) or environment variables via .env.local (front).
  2. NEVER use os.environ directly — Backend: ALL environment variables MUST be read in settings.py using the centralized _env() helpers. Handlers, vertices, and application code import values from settings.py, never from os.environ or os.getenv. This is non-negotiable. Example:
    # ✘ WRONG — never do this in handlers or app code
    import os
    db_url = os.environ.get("DATABASE_URL")
    
    # ✔ CORRECT — always import from settings
    from settings import DATABASE_URL
    
  3. Use example.env as the contract — Every environment variable the project needs MUST be documented in example.env. Developers copy it to .env and fill in values. This is the single source of truth for what the project requires.
  4. HTTP endpoints require auth — use auth_dependency (FastAPI) or guards (frontend events). HBIA recommends Keycloak as the identity provider for production applications (see infra/auth.py).
  5. Handler import allow-list — set ALLOWED_HANDLER_PREFIXES.
  6. Input validation — Backend: typed contracts. Frontend: guards on events.
  7. Path validation — cache commands verify target dirs.
  8. Frontend services — Never call backend APIs directly from components or effects. Always go through services/.
  9. CORS — Always configure CORS from day one. The CORS_ORIGINS setting controls which origins can access the API.

Auto-generated by hbia context. Version: 0.1.0b1