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 --writeorhbia 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¶
-
Lint all flows:
-
Validate all flows:
-
Run the full test suite:
-
Check tech debt:
-
List all flows for overview:
-
Review for:
- 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. - Disconnected vertices (no
nextpointing to/from them) - Duplicate logic across flows (candidate for shared vertices)
- Missing
effectorversionon any vertex - Missing tests for new vertices
- Proper
effectannotation (purevsside_effect) -
Correct
versionstrings (bump when logic changes) -
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¶
- Every vertex MUST have
effectandversion— enables caching and AI comprehension. - Use
next:to connect vertices — entry point is auto-detected (the vertex no one points to).nextis the backbone of HBIA. Withoutnextedges, vertices are isolated nodes with no pipeline — the entire graph model is pointless. - Declare
inputsandoutputsexplicitly — enables contract validation and AI understanding of data flow. - Add comments above each vertex explaining its purpose.
- 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¶
Option A: Use the CLI (Recommended)¶
# 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:
Infrastructure Pattern¶
When a feature needs external services (database, auth, caching):
- Add the config to
settings.py— new_env()call - Add the env var to
example.env— with documentation - Create/update a module in
infra/— singleton connection - 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.
Auth — Keycloak (Recommended)¶
HBIA recommends Keycloak as the identity provider for production
applications. The scaffolded infra/auth.py contains the connection
boilerplate. Setup:
- Deploy a Keycloak instance (Docker, k8s, or cloud)
- Create a realm and client
- Fill in
KEYCLOAK_*values in.env - Uncomment
auth_dependencyinapp.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)¶
Async¶
Cache (pure vertices only)¶
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)¶
- A handler is a plain Python function (sync or async).
- It receives inputs as keyword arguments.
- It must return a dict. Each key becomes
vertex_name.key. - Mark
effect: side_effectfor non-deterministic handlers. - Bump
versionwhen handler logic changes. - Never create DB connections inside a handler — use
infra/. - NEVER use
os.environin a handler — import config values fromsettings.py. If you need a new config value, add it tosettings.pyandexample.envfirst, 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¶
- Isolated single-vertex flows (WORST MISTAKE) — Creating one YAML
file per function with no
nextconnections. 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 nonext, you are doing it wrong. Combine them into proper flows chained withnext. - Disconnected vertices — Every flow with multiple vertices must
have
next:fields connecting them. The linter catches this (E002). - Missing effect/version — Every vertex needs both.
- Generic names —
step1,process,handlerare useless. Usevalidate_payment,create_user,send_notification. - Giant flows — Keep flows under 10 vertices. Split large flows into sub-flows by domain.
- Forgetting to lint — Run
hbia lintbefore every commit. - No comments in YAML — Add a comment above each vertex explaining its purpose.
- Instantiating resources in handlers — DB connections, API clients
must come from
infra/, never created inline. - Using
os.environoutside settings.py — Environment variables are read ONLY insettings.py. Handlers and app code import values fromsettings.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¶
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¶
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¶
-
Validate all domains:
-
Lint all domains:
-
Check for circular effect chains: The linter detects these automatically. A circular chain like
EffectA → mutates StateB → EffectB → mutates StateAcauses infinite re-renders and must be fixed immediately. -
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.
-
Check for dead events: Events declared in YAML but not emitted by any UI component are unreachable code.
-
Verify hook safety (CRITICAL):
- Does every UI component's
readslist match what it actually renders? Over-reading causes unnecessary re-renders. - Does every effect
watchthe minimum set of fields it needs? Broad watches cause cascading effects. - Are there effects that
mutatestate they alsowatch? This is a self-triggering loop — always an error. -
Are there effects with
debounce_ms: 0watching rapidly- changing state? This causes render storms. -
Verify ZERO
localStorageusage (MANDATORY): Search the entire codebase forlocalStorage,sessionStorage, andwindow.storage. Any occurrence is a blocking error. The linter catches this automatically, but verify during Pit Stops. -
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 fromerror.userMessage, never raw error objects or tracebacks. -
Verify reactive chain completeness: For every user-facing feature, trace the full chain:
If any link is missing, the feature is broken. -
Regenerate runtime (if YAML changed):
-
Run the test suite:
-
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 +
Statesuffix: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¶
readsusesStateName.fieldName— always dot-notation.flowusesStateName.mutation_name— ordered, sequential.watchusesStateName.fieldName— what triggers the effect.mutatesusesStateName— which state the effect can write.guardsusesStateName.fieldName— boolean fields checked before the event executes.- Every state MUST have
interface— enables TypeScript codegen. - 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>:
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¶
-
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.
-
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. -
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. -
Watch for self-triggering effects. An effect that watches
StateA.xand mutatesStateAwill trigger itself infinitely. The linter catches direct cycles, but indirect ones (A→B→A via multiple effects) require manual review during Pit Stops. -
Debounce rapid-fire state. If state changes on every keystroke (search input, form fields), set
debounce_mson the watching effect. Without debounce, each keystroke triggers the full effect chain. -
Don't mix HBIA state with React state for the same data. If
countlives inCounterState, don't also haveconst [count, setCount] = useState(0). This creates two sources of truth. -
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¶
-
Create primitives (if they don't exist yet):
-
Create the domain directory:
-
Define State first — What data does this feature own?
-
Define Events — What can the user do?
-
Define Effects — What side effects happen?
-
Define UI — What does the user see? Build views using primitives from
primitives/: -
Validate and lint:
-
Regenerate runtime:
-
Implement TypeScript files:
state/<Name>State/interface.ts— TypeScript interfacestate/<Name>State/mutations.ts— mutation functionseffects/<effect>/handler.ts— effect handler-
ui/<Component>/<view>.tsx— React view (compose from primitives!) -
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 |
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).
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¶
- 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 insidecounter/, the architecture is wrong. - Skipping primitives — The
primitives/directory holds reusable atomic UI components (buttons, inputs, cards). Always create primitives first before building domain views. Views ingraph/should compose primitives, never inline raw HTML elements. - Mutating state from a component — Components ONLY emit events.
Events trigger mutations. Never call store methods from a
.tsx. - Putting API calls in components — API calls belong in Effect
handlers, accessed through
services/. - 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.
- Using
useEffectfor data fetching — Use the HBIA Effect graph. React'suseEffectis for DOM synchronization only. - Over-subscribing to state — Don't use
useHBIA()when a typed hook likeuseCounterState()exists. Over-subscribing causes re-renders on unrelated state changes. - Declaring state without an interface — Every state node needs a TypeScript interface for type safety.
- Giant state nodes — Keep state nodes under 10 fields. Split by
concern:
CartItemsState,CartTotalsState,CartUIState. - Missing source on events — Always declare
source:so AI agents can trace where events originate. - Using
localStorage(FORBIDDEN) —localStorageis 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. - Showing raw errors in the UI — NEVER render server error details,
tracebacks, or stack traces. Use the
ServiceResult<T>wrapper fromservices/api.ts. UI showserror.userMessage, never the raw exception. - Forgetting to regenerate — After any YAML change, run
hbia ui-codegen graph/ --output hbia-runtime/. - 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¶
-
Backend defines the API contract first — The backend flow's output types define what the frontend service returns.
-
Frontend State interfaces mirror backend responses — If the backend returns
{user_id, email, name}, the frontend has a State with those same fields. -
One service per backend domain — If the backend has
flows/auth/, the frontend hasservices/auth_service.ts. -
Effects are the API boundary — UI components never import from
services/. Only Effect handlers call services. -
Validate both platforms in Pit Stops:
-
Keep YAML in sync — When a backend flow output changes, update the corresponding frontend State fields and service return types.
-
AI Navigation Order for Monorepos:
- Read root
hbia.yaml - Read
back/flows/— understand backend pipelines - Read
front/graph/— understand frontend reactive architecture - Read
front/services/— understand the bridge - 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_CHECKINGguards.
Rewrite over Patch — Versioning¶
- Backend: Vertices carry a
versionfield. Bump it when logic changes. - Frontend: State interfaces are versioned via TypeScript exports.
Performance Discipline¶
- Explicit loops: Every
forloop 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)¶
- No hardcoding — tuneables live in
settings.py(back) or environment variables via.env.local(front). - NEVER use
os.environdirectly — Backend: ALL environment variables MUST be read insettings.pyusing the centralized_env()helpers. Handlers, vertices, and application code import values fromsettings.py, never fromos.environoros.getenv. This is non-negotiable. Example: - Use
example.envas the contract — Every environment variable the project needs MUST be documented inexample.env. Developers copy it to.envand fill in values. This is the single source of truth for what the project requires. - HTTP endpoints require auth — use
auth_dependency(FastAPI) or guards (frontend events). HBIA recommends Keycloak as the identity provider for production applications (seeinfra/auth.py). - Handler import allow-list — set
ALLOWED_HANDLER_PREFIXES. - Input validation — Backend: typed contracts. Frontend: guards on events.
- Path validation — cache commands verify target dirs.
- Frontend services — Never call backend APIs directly from components
or effects. Always go through
services/. - CORS — Always configure CORS from day one. The
CORS_ORIGINSsetting controls which origins can access the API.
Auto-generated by hbia context. Version: 0.1.0b1