AILA (AI Lab Assistant) is a modular AI security platform: Python 3.11+ backend (FastAPI, SQLModel/Alembic over PostgreSQL, ARQ/Redis), a Typer CLI, and a React
- Vite + TypeScript frontend. The platform owns infrastructure (routing, runtime, services, contracts, tools); modules own domain logic.
This guide is for engineers contributing to the platform or shipping a new module. It assumes familiarity with Python, FastAPI, and async I/O.
git clone <repo-url> AILA
cd AILAFull setup steps (Postgres, Redis, env vars, migrations, first run) live in
./QUICKSTART.md. The minimum to start hacking on Python
code is:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"Frontend work uses the pnpm workspace at the repo root:
corepack enable && pnpm installBranch off main for every change. Naming convention is in section 3.
git checkout main
git pull
git checkout -b feat/<module>/<short-description>A module is a self-contained domain unit under src/aila/modules/<module_id>/.
The platform discovers and wires modules at boot -- adding one requires no
platform edits beyond registration.
Copy the template and rename:
cp -r src/aila/modules/_template src/aila/modules/my_moduleRename Template / TEMPLATE symbols to your module name. The template's
README.md lists every required
substitution.
Every module must provide:
| File | Purpose |
|---|---|
module.py |
ModuleProtocol implementation + create_module() factory |
runtime.py |
ModuleRuntime.handle() request handler |
capabilities.py |
MODULE_DESCRIPTION, MODULE_TOOLS, MODULE_EXAMPLES |
tool_keys.py |
Tool key constants, prefixed with <module_id>. |
workflow.py (or workflow/) |
Explicit state machine |
contracts/ |
Pydantic boundary models |
tools/ |
Tool subclass implementations |
services/ |
Domain service layer |
reporting/ |
Report generation |
Optional: api_router.py, db_models/, frontend/. The full contract is
docs/MODULE_STANDARD.md (v2.1).
src/aila/modules/hello_world/ is the
canonical minimal-but-complete module. It exercises every required surface --
runtime, capabilities, tool keys, workflow, contracts, tools, services,
reporting, and api_router.py -- and is the recommended reference when shaping
a new module.
Append your module to src/aila/platform/modules/builtin.py. The platform's
discovery loop picks it up on next boot; mismatches against
MODULE_STANDARD.md fail fast at startup.
- A module must never import from another module.
- A module may import only from
aila.platform.*and its own subpackages. api_router.pymust defer platform imports insideroute_specs()to avoid import cycles at module load.- Tool keys must be prefixed with the module id (e.g.
hello_world.greet).
feat/<module>/<description> # new functionality in a module
fix/<module>/<description> # bug fix in a module
docs/<description> # documentation-only change
<module> is the module id (forensics, hello_world, vr,
vulnerability) or platform for platform-wide work. <description> is
kebab-case and short.
Examples:
feat/vulnerability/epss-scoring
fix/forensics/empty-evidence-list
docs/env-vars-reference
Imperative mood, subject line at most 72 characters, scoped prefix.
<type>(<scope>): <subject>
<type>:feat,fix,docs,refactor,test,chore,perf.<scope>: module id,platform,frontend,cli, or a doc area.<subject>: imperative, no trailing period.- Body (optional, blank line above) explains why, not what. Reference issues by number where applicable.
Examples:
feat(vulnerability): add EPSS scoring
fix(forensics): handle empty evidence list
docs: update ENV_VARS reference
refactor(platform): consolidate UoW session lifecycle
Every PR must pass all four gates. Run them locally before pushing.
| # | Gate | Command |
|---|---|---|
| 1 | Unit tests | python -m pytest tests/ --ignore=tests/test_e2e.py --ignore=tests/test_e2e_live.py |
| 2 | Honesty audit | python -m aila.tools.honesty_audit src/aila --whitelist honesty_whitelist.py |
| 3 | Lint | python -m ruff check src/aila/ |
| 4 | Frontend typecheck (if frontend changed) | pnpm -r run type-check (workspace-wide) |
The honesty audit must report zero findings. Whitelist entries in
honesty_whitelist.py require an inline comment
justifying the pattern; unjustified entries are rejected. See
docs/HONESTY_AUDIT.md for the rule catalogue.
Or run all gates at once:
make checkThe full standard is in docs/GOLDEN_RULES.md. The points
that block PRs:
- PEP 8. Enforced by
ruff. - Type annotations on every public function.
-> dictis not a type. Use Pydantic models orTypedDictfor structured returns. - Google-style docstrings on every public class and function. Explain why, not what the signature already shows.
__all__on every public module. No exceptions.- No bare
except Exception. Catch what you expect, let the rest propagate. Log at minimum if you must catch broadly. - No
**kwargspassthrough without validating keys against a known set. - No TODO / FIXME in committed code. File an issue.
- No dead code. If it is not called, delete it. No "moved to X" comments, no re-exports kept "for now".
- No legacy preservation. Refactors cut over fully -- no shims, no parallel APIs, no compatibility wrappers unless explicitly requested.
Test stack and conventions are in docs/TEST_GUIDE.md.
Summary:
- Runner:
pytestwithpytest-asynciofor async tests. - HTTP:
httpx.AsyncClientwithASGITransport. Do not use Starlette'sTestClient-- it deadlocks on SSE and async routes. - Layout: mirror the source tree under
tests/. API tests undertests/api/, module tests undertests/modules/<module_id>/or prefixed astests/test_<module_id>_*.py(andtests/api/test_<module_id>_*.pyfor API tests). Thevrmodule uses the prefixed layout. - Naming:
test_<behavior>.py::test_<case>. The filename names the behavior under test; the function name names the case. - Fixtures over inline setup. Reuse
tests/conftest.py,tests/api/conftest.py, and module-level conftests rather than reconstructing state per test. - Unit tests stay infrastructure-free. No live Postgres, Redis, LLM, or
network. Use the seeded
test_dbfixture and async clients. Anything that needs real infrastructure belongs intests/test_e2e.pyortests/test_e2e_live.py, both of which are excluded from the unit-test gate. - Mocks at the boundary only. Mock external services; do not mock the code under test.
Run a focused subset while iterating:
python -m pytest tests/modules/<module_id> -x -q- Confirm all four quality gates pass locally (
make check). - Push the branch and open a PR against
main. - PR description must state:
- What changed. The user-visible or system-visible behavior.
- Why. The motivation -- the bug, the requirement, the design force.
- Scope. Modules, platform subsystems, or surfaces touched.
- Issue link.
Closes #<n>orRefs #<n>where applicable.
- CI re-runs every gate. Failures block merge -- fix them, do not retry.
- Address review comments by amending commits where it keeps history clean, or by adding follow-up commits where the review trail matters. Squash on merge unless the history is intentionally preserved.
A PR is mergeable when gates pass, review is resolved, and the change is a coherent unit -- not a partial migration that leaves the design contradicting itself.
Many contributors use Claude Code, Cursor, Copilot, or other LLM-powered editors. The repository is set up to support this.
.claude/CLAUDE.md is auto-discovered by Claude Code at session start. It
contains the repository layout, build commands, module authoring steps, 5
non-negotiable rules, 8 common mistakes, and a verification checklist. Other
LLM editors can be pointed to this file manually.
These are the patterns that consistently require human correction:
- Top-level
api_routerimports inmodule.py. MODULE_STANDARD requires deferred import insideroute_specs(). The honesty audit catches this, but every LLM defaults to top-of-file imports. - Bare
except Exception. LLMs default to broad catches. Use specific types:(OSError, TimeoutError, RuntimeError)for infrastructure paths. - Missing
__all__. LLMs forget this on every new file. Every__init__.pyand public module needs it. - Cross-module imports. LLMs will import from
aila.modules.vulnerabilityinsideaila.modules.forensicswithout hesitation. The honesty audit flags this, but catch it during review. os.getenvinstead of ConfigRegistry. Module-scoped config must useConfigRegistry.get(), which resolves env var -> DB -> schema default.- Schema changes without Alembic. LLMs will use
metadata.create_all()or rawCREATE TABLE. All DDL goes throughsrc/aila/alembic/versions/.
- Let the LLM generate. Do not trust the output.
- Run
make checkbefore committing -- this catches 90% of LLM mistakes. - Review every import the LLM added. Check it doesn't cross module boundaries.
- Review every
exceptblock. Check it catches specific types. - Verify
__all__exists on every new file. - If the LLM suggests a "compatibility wrapper" or "backwards-compatible shim" -- delete it. This codebase does full cutover, not gradual migration.
The frontend is React + Vite + TypeScript with Tailwind CSS v4.
Use the platform design system everywhere:
- Backgrounds:
bg-base,bg-surface,bg-elevated - Text:
text-text,text-text-muted,text-accent - Borders:
border-border - CSS variables:
var(--color-*)for dynamic theming - Components:
AilaCard,AilaBadge,EmptyState, shadcn components
- No custom CSS files. Tailwind utilities + CSS variables only.
- No hardcoded hex colors. Use CSS variables or Tailwind tokens.
- No Tailwind arbitrary values like
h-[720px]orbg-[#131313]. Tailwind v4 does not generate them. Use inlinestyle={{ height: 720 }}. - No
position: fixedoverlay layouts. Pages render inside AppShell's content area. - CSS variables in SVG: Recharts
fillattributes do not resolvevar(--color-*). Use theuseThemeChartColors()hook to resolve hex values viagetComputedStyle.
Modules contribute UI via ModuleFrontendSpec in frontend/spec.ts:
export const frontendSpec: ModuleFrontendSpec = {
moduleId: "my_module",
nav: [{ id: "my_module.home", slot: "sidebar.main", label: "My Module",
to: "/my_module", order: 100 }],
routes: [{ id: "my_module.home", path: "/my_module", title: "My Module",
nav: true, slot: "page.full", page: MyModulePage }],
};See src/aila/modules/hello_world/frontend/ for a working example.
| Script | Platform | Usage |
|---|---|---|
start.sh |
Windows (Git Bash) | bash start.sh / bash start.sh stop |
start-linux.sh |
Linux / macOS | ./start-linux.sh / ./start-linux.sh stop |
make dev |
Any (prints instructions) | make backend, make frontend, make worker in separate terminals |
The startup scripts load .env, start audit-mcp, the FastAPI backend, 4 ARQ
workers (default, vr, vulnerability, forensics), and the Vite frontend.
Logs go to .run/<slug>.log (e.g. .run/backend.log, .run/worker-vr.log).
Open an issue. There is no chat channel.