Modular AI security platform with pluggable analysis modules: a Python core exposing a Typer CLI and a FastAPI REST API, backed by PostgreSQL with pgvector and an ARQ/Redis task queue, paired with a React + Vite + TypeScript frontend.
+-------------------------------------------------------------+
| Frontend (frontend/) |
| React 19 + Vite + TypeScript shell |
| Module UIs mounted from modules/<id>/frontend/ |
+----------------------------+--------------------------------+
| HTTP / SSE / JWT
+----------------------------v--------------------------------+
| API (src/aila/api/) |
| FastAPI app with 29 platform routers + module-contributed |
| routers, JWT auth, RBAC, |
| SSE event streams, OpenAPI at /docs |
+----------------------------+--------------------------------+
|
+--------------------+--------------------+
| |
+-------v-------------+ +--------v-----------+
| Platform | | Modules |
| src/aila/platform/ | | src/aila/modules/ |
| | | |
| routing/ | | forensics/ |
| runtime/ | <-- ModuleProto. | hello_world/ |
| services/ | contracts -> | vr/ |
| contracts/ | | vulnerability/ |
| tools/ | | |
| llm/ | | |
| tasks/ (ARQ) | | Each module owns |
| workflows/ | | its own runtime, |
| sse/ | | tools, workflow, |
| events/ | | contracts, API |
| automation/ | | router, frontend, |
| config.py, uow.py | | and DB models. |
| | | |
| | | See docs/vr/ for |
| | | the VR engine + |
| | | MCP architecture. |
| | | |
+----+-------------+--+ +---------+----------+
| | |
+----v---+ +-----v------+ +-------v---------+
| Redis | | PostgreSQL | | Per-module ARQ |
| ARQ | | SQLModel + | | queue tracks: |
| queues | | Alembic + | | default, vr, |
| | | pgvector | | vulnerability, |
+--------+ +------------+ | forensics |
src/aila/storage/ | |
+-----------------+
Layer responsibilities
src/aila/platform/) -- routing, runtime construction,
shared services, module/tool contracts, LLM client and pipelines, ARQ task
registration, workflow engine, SSE bus. Never imports from a feature module.src/aila/modules/) -- domain logic. Each module is a
self-contained package implementing ModuleProtocol. One module never
imports from another. Layout is fixed by docs/MODULE_STANDARD.md.
Current modules: vulnerability (CVE/CWE scanning + intel),
forensics (DFIR investigations), vr (vulnerability research --
graph-aware audit, fuzz campaign proposals, enterprise PDF reports,
exploit/PoC writer agent), malware (sample-centric reverse
engineering over ida-headless-mcp-exp), and the hello_world
reference module.src/aila/api/) -- FastAPI application (aila.api.app:app).
Modules contribute additional routers via api_router.py.frontend/) -- top-level Vite + React + TypeScript shell.
Module UIs live under src/aila/modules/<id>/frontend/ and are mounted by
the shell through the frontend module spec. Managed as a pnpm
workspace at the repo root.src/aila/storage/) -- SQLModel models, Alembic migrations,
config registry, secret store. Vector search uses pgvector with
384-dimensional embeddings.For deeper detail see docs/ARCHITECTURE.md.
Every reasoning module in AILA (vr, malware, anything future that
holds a multi-turn LLM conversation against an MCP backend) runs the
same four-phase loop driven by the platform's reasoning engine
(src/aila/platform/services/reasoning.py). Personas differ, tool
surfaces differ, terminal outcome kinds differ -- the loop does not.
+----------------------------------+
| case_state.observables |
| prior tool readings + directives|
| + agent scratchpad (capped) |
+-----------------+----------------+
|
v
+---------+ +------+-------+ +--------+
| THINK |-------->| HYPOTHESIZE |------->| ACT |
| persona | reads | produce or | emits | tool_ |
| prompt | state | refine list | one | run |
| + state | | with claim, | action| OR |
| | | why_plaus., | | submit |
| | | kill_crit. | | OR text|
+---------+ +--------------+ +----+---+
^ |
| |
+-------------- OBSERVE ---------------------+
tool result -> observables_delta
merged into case_state
Think. Each turn, one persona (researcher / critic / implementer)
receives the system prompt + persona prompt + the current
case_state projection. The case_state carries every prior tool
reading the platform decided was worth keeping in attention (tool
observables capped at the last 80, agent scratchpad capped at 15),
plus directives the auto-steering pipeline injected.
Hypothesize. The persona's output is a structured envelope
carrying a hypothesis list. Each hypothesis is a triple of
{claim, why_plausible, kill_criterion} -- the kill_criterion is the
specific evidence that would refute it. Hypotheses persist across
turns and accumulate state (live / rejected / resolved); the
rejection reason and resolution note travel with them. The frontend
renders the live + rejected + resolved aggregate per investigation
so the operator sees the deliberation drift in real time.
Act. The same envelope emits exactly one action: a
tool_run (dispatch one MCP tool call), a submit of a terminal
outcome (AnalysisReport, FindingDraft, TriageVerdict, ...), or a
text-only deliberation turn that updates hypotheses without
external action. The executor dispatches tool_runs through the
module's MCP bridge layer (which adds defenses: auto-poll on
pending, kwarg coercion, alias maps, address-format coercion --
see VR / Malware sections below).
Observe. The tool response flows through a per-adapter pass
that shapes the response into an observables_delta (key-value
pairs the next turn will see) plus a durable observation row in
the module's observation table (cross-investigation memory, kind +
polarity tagged). The case_state merges the delta; the renderer
decides which observables make it back into the next prompt under
the attention cap.
Multi-persona adversarial discipline. The reasoning module's
branch manager spawns multiple personas in parallel against the
same investigation. Each branch holds its own case_state. The
platform's deliberation broker surfaces rejection signal across
branches (a hypothesis rejected by 2+ siblings becomes a
sibling_consensus_rejection directive on the still-live branch).
Claim verifier promotes evidence reaching a confidence floor;
synth assembles the final outcome from the converged claims.
The pattern is what lets two very different modules (source-level vulnerability hunting vs binary malware reverse engineering) share the same engine + the same operator UI + the same observation memory abstraction. Adding a third reasoning module is mostly writing prompts + an MCP bridge + an outcome contract; the loop is already there.
The vulnerability research module (vr) drives a multi-MCP backend with
graph-aware code intelligence, semantic search, and binary analysis. Three
MCP servers run alongside AILA, exposed over HTTP and orchestrated by the
platform's task queue.
+-------------------------------------------------------------+
| AILA backend (Python + ARQ) |
| agent loop -> tool_executor -> bridge tools -> MCP servers |
+----+--------------------+--------------------+--------------+
| | |
+----v-----+ +-------v--------+ +------v---------+
| audit- | | ida-headless- | | semble |
| mcp | | mcp | | (embedded in |
| 18822 | | 18821 | | audit-mcp) |
+----------+ +----------------+ +----------------+
trailmark Hex-Rays + miasm Model2Vec + BM25
graph engine binary engine chunk retrieval
+ GPU CSR + 81 tools
+ 58 tools
/tools for catalog, /tools/<name> for invocation)index_codebase. Per-index cached on disk via DurableIndexStore, recovered automatically on restart.from_trailmark() constructs a GPU CSR adjacency matrix when CUDA is present; powers attack_surface, fuzzing_targets, unreachable_from_entrypoints on monorepo-scale graphs.~/.audit-mcp/semble-cache/<index_id>.pkl after first build -- subsequent restarts load in ~9s instead of rebuildingsemantic_search(query, top_k, alpha, rerank, filter_*), find_related(file, line, top_k), semble_stats(index_id)read_function queries the semble chunk index first (matches by name + definition pattern); falls back to a process-cached TypeResolver instead of rebuilding it per call (was the source of 15-minute hangs on firefox-scale).AUDIT_MCP_WORKERS env / --workers CLI flag. Each worker holds its own engine + semble + TypeResolver caches; AILA's bridge pre-warms all workers on the first call to a new index_id (Linux/macOS only -- Windows uvicorn multi-worker is broken)./tools catalog)ida_headless/poll_mutation so concurrent operator + agent edits don't raceassess_exploitability, prove_overflow, prove_bounds_sufficient)The VR module runs adversarial 3-persona deliberation (researcher / critic / synthesizer) over the MCP tool surface. Each tool call goes through AuditMcpBridgeTool (or its IDA equivalent) which provides:
fuzzing_targets(threshold=...)) before the HTTP round-trip and returns a structured "did you mean" error so the next turn self-correctstop_n -> limit, cutoff -> min_complexity, etc.) per tool's actual signature(server, tool, args) AND (server, tool, error_class) so the agent can't burn turns varying the value of a bad kwarg name; injects a hard pivot directive after 3 consecutive failuresattack_surface, complexity_hotspots, fuzzing_targets, ...) without a source read, forces the agent into read_function / taint_paths_to / callers_of or a finding submissiondead_code and unreachable_from_entrypoints from agents running against C++/Java/Kotlin/C#/Swift/Objective-C/Scala targets (static call graphs are blind to vtable + template dispatch on those languages)fuzzing_targets on firefox return status=pending + task_id; the bridge polls poll_task for up to ~15 min so AILA's 900s HTTP timeout doesn't kill long graph queriesindex_id fires 16 parallel summary + semble_stats requests so every uvicorn worker warms its caches before the agent's real query landsTarget ingestion is split into three independently-tracked stages with per-stage status, attempts counter, and reaper:
INGESTION -- audit_mcp index_codebase clone + parse (timeout 14400s)CAPABILITY_PROFILE -- D-51 capability rule evaluation (timeout 1800s)FUNCTION_RANKING -- fuzzing_targets ranking with GPU CSR (timeout 1800s)Operator can resume a stuck target via POST /vr/targets/{id}/resume-analysis; the endpoint fans out per non-DONE stage. Reaper runs every minute via ARQ cron, flips RUNNING stages past their timeout to FAILED with "reaper: RUNNING for Xs > Ys timeout".
For day-to-day MCP operations and the full VR agent design see docs/vr/.
The malware module ports the VR adversarial-deliberation pattern
onto sample-centric reverse engineering. Same multi-persona loop
(halvar / noor researchers, maddie / yuki critics, renzo / wei
implementers), same SSE-streamed timeline, same observation memory --
but every tool dispatch is constrained to ida-headless-mcp-exp
only. audit_mcp and android_mcp are explicitly hidden from the
agent-facing catalog and rejected at the executor's allowlist
(backend services like the claim verifier still construct their own
bridge instances).
sample upload
|
v
target_analysis stages (Alembic-tracked, per-stage durable)
open_binary -> auto_analysis -> string_classification
|
v
investigation kinds:
triage | full_analysis | unpack_only | config_extract
| yara_generate | family_attribute
|
v
multi-persona reasoning loop:
investigation_setup -> investigation_loop -> investigation_emit
|
v
outcomes:
ANALYSIS_REPORT | TRIAGE_VERDICT | UNPACK_ARTIFACT
| CONFIG_BLOB | YARA_RULE_DRAFT | FAMILY_ATTRIBUTION
| FINDING | OUTCOME_REVIEW
The agent dispatches exclusively against ida-headless-mcp-exp
running on port 18821. The bridge layer
(platform/mcp/bridges/ida_headless.py) adds nine defenses on top
of the raw catalog so the agent's habits don't cost turns:
status: pending -- bridge sleeps + re-POSTs the
same call (2s -> 3s -> 4.5s -> 8s capped) until the per-call
async work lands ready or the 240s budget runs out. poll_analysis
itself is the only excluded action.status=pending +
worker_phase in {exiting_idle, crashed, ""} +
heartbeat_age_s >= 600), the bridge skips the 240s poll and
returns a structured dead_worker_diagnostic error naming the SHA,
heartbeat age, queue depth, and the exact operator action
(restart ida-headless, clear crash_counts.json, re-upload to
force fresh analysis). Threshold override:
IDA_HEADLESS_DEAD_WORKER_HEARTBEAT_S.(action, sorted_kwargs) within
IDA_HEADLESS_DEDUP_TTL_S (default 300s) replays the cached
ready payload. Scoped to 23 read-only actions (xrefs_to/from,
decompile, find_api_call_sites, callers_of, build_call_tree,
list_strings/functions, capa_scan, detect_crypto_primitives,
etc.). State mutators (open_binary, upload, patch_assemble) and
freshness-sensitive (poll_analysis) are excluded. Cached hits
stamp _ida_bridge_dedup: hit so the executor can distinguish
replay from fresh. Per-call bypass: pass _ida_bridge_no_dedup=True.sub_<hex> / loc_<hex> /
unk_<hex> to 0x<hex> before dispatch. avoid_addresses
(list) gets per-element rewriting.encoding value alias -- the string-family tools
(list_strings, get_string_at) accept utf16 / utf-16 /
utf-16le / utf16-le as aliases for the canonical utf16le.
Closes the round-trip with the same-named label the server emits
under by_encoding; an agent passing back the value it just read
from count_only no longer falls into a total=0 false negative.offset / limit / cursor /
top_k get silently stripped when the target tool doesn't declare
them, so snapshot tools no longer TypeError on agent habits.search_pattern alias map -- rewrites pattern / pattern_str /
query to the canonical pattern_type enum kwarg.status=None payload normalization -- tools like
binary_metadata that omit a top-level status field get
status='ready' injected so the downstream executor's whitelist
doesn't synthesize a spurious empty error._ALWAYS_SUPPRESS enforcement -- a curated set of tools the
agent must never reach is checked BEFORE the specialized-adapter
lookup AND subtracted from _effective_tools (which would
otherwise re-introduce them via the runtime bridge catalog).
Current suppress set: ida_headless.classify_strings (regex
categorizer that returns 19 buckets on a 10,254-string binary;
agents derail by treating its empty output as evidence) and
audit_mcp.search_source (deprecated). Suppressed tools also
drop from registered_tools() + specialized_tools() so the
diagnostic listings stay consistent.The XREF adapter additionally surfaces a pagination_hint block
on payload + a one-line directive in the observable when
total > MAX_LIST_PREVIEW=20. The directive names the suppressed
row count + the exact follow-up call shape
(offset=20, limit=20) + the call_id for direct payload
access. The full xref array is preserved verbatim in payload.xrefs
regardless of length; only the per-turn observable preview is trimmed.
The system prompt teaches a four-stage extraction workflow instead of pattern-matching on hostname-shaped substrings:
find_api_call_sites('FindResourceW') for embedded-resource
patterns, decryptor xrefs for crypto blobs, or
RegQueryValueExW for registry-stored configs.read_memory(VA, size) the encrypted
blob, decrypt with binary-sourced constants, parse with the
known offsets, identify the C2 field.WinHttpConnect / InternetOpenUrlA / connect.
If the path doesn't close, it isn't the C2 -- keep going.ida-headless-mcp-exp now eager-initializes _Frontend in main()
/ main_http() before binding the transport, so recover_all()
populates _binaries from cache/<sha>/state.json BEFORE the
server accepts the first HTTP request. AILA workers carrying stale
binary_id values in mcp_handles_json survive restarts without
seeing Unknown binary_id. Defensive _sha fallback handles
out-of-band registrations (e.g. a stdio MCP transport writing
state.json that the HTTP server's already-completed sweep missed).
POST /malware/investigations/{id}/reset wipes
messages + observations + outcomes + forked branches +
workflow_state_cursor archive in one transaction, flips status
back to CREATED. Refuses while RUNNING (operator must pause
first); PAUSED is fine because the cursor wipe handles the
archive that /resume would otherwise rely on.RUNNING and the
reaper missed a stall (engine reports in-flight but no task
heartbeat). Calls the same /re-enqueue endpoint the reaper
would.Synthesis & Narrative card under the primary outcome).
POST /malware/investigations/{id}/synthesize accepts
{ force, tone, length, enumerate_every_suspicious, operator_focus }.
Default force=True; tones operator | executive | technical | analyst | forensic; lengths brief | standard | exhaustive.
enumerate_every_suspicious=True is the "don't drop anything"
mode that walks every persona's answer + reasoning and surfaces
every distinct suspicious item (string, address, function, IOC,
persistence artifact, decoded blob). Runs against an already-
COMPLETED investigation without flipping its status; the
structured panel_summary is overwritten plus every promoted
field (family_attribution, capabilities, iocs,
attribution_rationale, headline_verdict, detection_guidance,
next_actions, panel_dissent, inconclusive_areas,
inconclusive_capabilities) is re-derived.payload.investigation_narrative on the canonical outcome,
alongside (not replacing) panel_summary. Endpoint:
POST /malware/investigations/{id}/narrative with
{ force, tone, length, operator_focus }. Tones blog | incident_report | thriller | academic | casual; lengths
short (~1.5-2.5K words), standard (~3.5-5.5K words), long
(~8-15K words). Schema enforces body.min_length=4000 so the
LLM cannot bail with a 200-char intro stub. UI button opens the
rendered markdown in a side modal with table of contents,
copy-to-clipboard, and the title / tone / length / word count
metadata strip.edit_outcome action.
Counterpart to the deferred request_edit vote (which only
suggests edits the synthesis agent picks up on the next pass);
edit_outcome merges patches immediately. Refused on non-draft
outcomes; protected workflow-owned keys (panel_contributions,
panel_summary, verifier_report, applied_by_synthesis) are
dropped from the merge. Every applied edit writes an audit row.reject vote no longer kills an outcome. A second sibling must
concur (VETO_K=2 in services/outcome_review.py). Single rejects
still record on the outcome and surface in the proposing branch's
prompt so it can react; the state flip waits on the chorus.
Approve quorum (approve_count >= quorum_k) still ships the
outcome; veto is evaluated BEFORE approve so a tied chorus
resolves to rejected.Prerequisites
Required:
corepack enable to activate pnpm)pgvectorOptional MCP servers (only if you want the vr or malware modules):
:18822 -- vr source-repo targets:18821 -- malware, and vr on binary targets:18823 -- vr on android_apk targetsThe platform, vulnerability, forensics, and hello_world modules run without any MCP.
Steps
Clone and install.
git clone https://github.com/project-lambda-zero/AILA.git
cd AILA
make install # pip install -e ".[dev]" + corepack enable && pnpm install
Configure environment.
cp .env.example .env
Set at minimum AILA_DATABASE_URL, AILA_PLATFORM_REDIS_URL, AILA_JWT_SECRET_KEY (openssl rand -hex 32), AILA_ADMIN_PASSWORD (first-boot bootstrap, remove after), and the AILA_PLATFORM_LLM_* group. If you plan to use MCPs, uncomment the AUDIT_MCP_URL / IDA_HEADLESS_URL / ANDROID_MCP_URL block in .env.example. Full reference: docs/ENV_VARS.md.
Bring up Postgres + Redis and initialize the schema.
make dev-up
make db-init # first run only: create tables + stamp Alembic head
# subsequent runs:
# make migrate
Start MCP servers (skip if not using vr or malware). Each in its own terminal:
audit-mcp --mode http --port 18822 # vr
ida-headless-http --port 18821 # malware, and vr on binary targets
python -m android_mcp --mode http --port 18823 # vr on android_apk
See docs/QUICKSTART.md section 4 for clone + install of each MCP.
Start AILA in three terminals.
make backend # REST API on :8000
make frontend # Vite dev server on :3000 (single SPA, all module UIs)
make worker # default ARQ queue
Per-module workers when the corresponding module is in play:
make worker-vr # vulnerability research
make worker-vuln # vulnerability scans
make worker-forensics # DFIR investigations
make worker-malware # malware reverse engineering
For the full walkthrough (admin user creation, MCP install details, smoke tests, common pitfalls), see docs/QUICKSTART.md.
| module_id | Description | Status |
|---|---|---|
vulnerability | SSH package inventory, distro-aware advisory resolution, CVE enrichment, scoring, and reporting. | production |
forensics | Remote forensic evidence triage over SSH: disk images, memory dumps, PCAPs, write-up generation. | production |
vr | Vulnerability research: graph-aware source/binary audit (audit-mcp + IDA Headless MCP), hypothesis-driven reasoning, fuzz campaign proposals (audit\u2192fuzz pipeline), enterprise PDF reports with LLM writer agent, automatic exploit/PoC drafting, variant hunting with child-investigation spawning. | production |
malware | Malware reverse engineering: VR-pattern multi-persona deliberation over ida-headless-mcp-exp only. Six investigation kinds (triage / full_analysis / unpack_only / config_extract / yara_generate / family_attribute), deterministic four-stage C2 config extraction with mandatory string-sweep + xref-chain follow-up, two-stage C2 hunt (Stage 1 loader URLs vs Stage 2 dropped-payload endpoints), fan-out sub-investigation spawning, observation memory with base64 / hex auto-decode, operator controls (reset / re-enqueue / re-synthesize with tone+length / generate-narrative writeup / direct edit_outcome patches), chorus-veto outcome review (VETO_K=2), structured synthesis promoting family / capabilities / IOCs / detection guidance / next actions onto the canonical payload. | production |
hello_world | Minimal reference module proving the ModuleProtocol contract end-to-end. | example |
Modules are auto-discovered at platform boot by scanning src/aila/modules/*.
Packages whose name starts with _ are skipped (used for templates and
fixtures). To add a new module, follow docs/MODULE_STANDARD.md
and the worked tutorial in docs/MODULE_TUTORIAL.md.
Common targets in the root Makefile:
| Target | What it runs |
|---|---|
make install | pip install -e ".[dev]" plus corepack enable && pnpm install |
make dev-up | docker compose -f infra/utilities/docker-compose.yml up -d postgres redis (idempotent) |
make dev-down | Stop dev infra containers (keeps data volumes) |
make dev-reset | Stop containers and wipe data volumes |
make dev-logs | Follow compose service logs |
make dev-status | docker compose ps |
make db-init | python scripts/db_init.py -- create tables + stamp Alembic head (first run only) |
make migrate | cd src/aila && alembic upgrade head |
make dev | Print the canonical dev workflow (no services started) |
make backend | Ensure dev-up + db-init, free port 8000, run uvicorn aila.api.app:app --host 0.0.0.0 --port 8000 --reload |
make frontend | Free port 3000, run pnpm --filter @aila/shell run dev (Vite on :3000) |
make frontend-build | pnpm --filter @aila/shell run build (production SPA bundle) |
make storybook | pnpm --filter @aila/shell run storybook |
make worker | python -m aila worker (default queue) |
make worker-vr | python -m aila worker -q vr |
make worker-vuln | python -m aila worker -q vulnerability |
make worker-forensics | python -m aila worker -q forensics |
make dev-all | Bring up all services in one terminal (Ctrl+C stops everything) |
bash start.sh | Spawn audit-mcp + backend + 4 workers + frontend in one shot (Windows: Git Bash + PowerShell) |
docker compose -f infra/utilities/docker-compose.full.yml up --build | Full-stack containers: postgres + redis + api + 4 workers + frontend. See docs/DEPLOYMENT.md. |
make test | pytest, excluding tests/test_e2e*.py |
make test-e2e | pytest tests/test_e2e.py -v (requires live infrastructure) |
make test-frontend | pnpm --filter @aila/shell run test (shell package only; module frontends use pnpm -r run test) |
make lint | ruff check src/aila/ |
make typecheck | pnpm -r run type-check (every workspace package, shell + modules) |
make honesty | python -m aila.tools.honesty_audit src/aila --whitelist honesty_whitelist.py |
make compile | python -m compileall -q src/aila |
make build | pnpm --filter @aila/shell run build (production SPA bundle) |
make check | lint + honesty + compile + typecheck (the full pre-PR gate) |
make security-scan | pip-audit --strict --desc and bandit -r src/aila -q -ll |
make clean | Remove __pycache__/ directories and coverage artifacts |
Run make check before opening a PR. Contributor workflow, branch policy,
review expectations, and the honesty audit rules are documented in
docs/CONTRIBUTING.md.
The aila entry point (aila = "aila.cli:app") is a Typer application.
Invoke aila --help to list every subcommand and command group; the most
common entry points are summarised below.
| Command | Purpose |
|---|---|
aila serve | Start the FastAPI REST API via uvicorn |
aila worker [-q <queue>] | Start an ARQ worker for the given queue track (default: default) |
aila task "<question>" | Ask a natural-language question routed through the platform agent |
aila analyze [--target <name>] | Run a vulnerability scan across registered targets (or one) |
aila add-ssh ... | Register an SSH-reachable system for the vulnerability module |
aila create-api-key | Mint an admin-role API key for first-boot bootstrap |
aila health | Probe platform and provider readiness |
Command groups expose related subcommands:
aila config (runtime config registry),
aila tool (invoke registered platform tools directly),
aila cache (manage decision and intel caches),
aila policy (scoring policy management),
aila feedback (operator knowledge entries),
aila report (PDF and CSV reporting),
aila schedule (scheduled scans),
aila intel, aila ops, aila auto, aila digest
(fleet intelligence, operational metrics, automation, executive digests).
http://localhost:8000http://localhost:8000/docshttp://localhost:8000/openapi.jsonPOST /auth/login with {"username", "password"} returns a JWT (data.access_token) used as Authorization: Bearer <token> for all subsequent calls; POST /auth/token exchanges an API key for the same envelope. RBAC roles
are admin, operator, reader -- see
docs/SECURITY_MODEL.md./scans/{id}/events, /tasks/{id}/events). Integration patterns are
documented in docs/SSE_GUIDE.md.The OpenAPI document is the source of truth for the route surface; the
/docs UI lists every endpoint, request schema, and response schema.
| Document | Covers |
|---|---|
| docs/ARCHITECTURE.md | System diagram, layer responsibilities, data flow, runtime constraints |
| docs/PLATFORM_INTERNALS.md | X-ray: full request lifecycle traced through every platform layer |
| docs/QUICKSTART.md | Expanded onboarding walkthrough with troubleshooting |
| docs/CONTRIBUTING.md | Contributor workflow, branch policy, review expectations |
| docs/MODULE_STANDARD.md | Required module layout, contracts, and lifecycle (v2.1) |
| docs/MODULE_TUTORIAL.md | Step-by-step authoring of a new module |
| docs/MODULE_AGENT_GUIDE.md | Module context conventions for LLM-driven flows |
| docs/FRONTEND_MODULE_STANDARD.md | Frontend shell and per-module UI contribution contract |
| docs/forensics/ | Forensics module domain reference and design history |
| docs/DB_SCHEMA.md | Database tables, relationships, and ownership |
| docs/DATABASE_MIGRATIONS.md | Alembic policy, conventions, and migration authoring |
| docs/CONFIG_REGISTRY.md | Config resolution chain (env -> registry -> defaults) |
| docs/ENV_VARS.md | Environment variable reference |
| docs/SECURITY_MODEL.md | Auth, RBAC, API keys, JWT lifecycle |
| docs/DATA_PROTECTION.md | Data posture modes, LLM redaction, input/output sanitization |
| docs/API_ERRORS.md | API error catalog |
| docs/OPENAPI_NOTES.md | OpenAPI generation notes and conventions |
| docs/SSE_GUIDE.md | Server-sent events: usage, reconnection, curl examples |
| docs/TASK_QUEUE_OPS.md | ARQ worker operations, queue tracks, retry semantics |
| docs/LLM_INTEGRATION.md | LLM client, pipelines, model selection, transparency posture |
| docs/WORKFLOW_GUIDE.md | Durable state machine: handler contract, do/don't, production examples |
| docs/DEPLOYMENT.md | Production deployment guide |
| docs/TEST_GUIDE.md | Testing conventions, fixtures, e2e gating |
| docs/GOLDEN_RULES.md | Code quality rules enforced by review and tooling |
| docs/HONESTY_AUDIT.md | Structural honesty rules enforced by aila.tools.honesty_audit |
| docs/PITFALL_GUIDE.md | Common mistakes when working on AILA |
| docs/PRODUCTION_RUBRIC.md | Readiness rubric for shipping a module to production |
| docs/vr/ | VR engine internals: reasoning loop, IDA Headless MCP, exploit automation |
| docs/VR_INSTALLATION_GUIDE.md | Standing up audit-mcp + IDA Headless MCP next to AILA |
| CHANGELOG.md | Version history |
AILA is licensed under the GNU Affero General Public License v3.0. See LICENSE for the full text.
Modular AI security platform with pluggable analysis modules: a Python core exposing a Typer CLI and a FastAPI REST API, backed by PostgreSQL with pgvector and an ARQ/Redis task queue, paired with a React + Vite + TypeScript frontend.
+-------------------------------------------------------------+
| Frontend (frontend/) |
| React 19 + Vite + TypeScript shell |
| Module UIs mounted from modules/<id>/frontend/ |
+----------------------------+--------------------------------+
| HTTP / SSE / JWT
+----------------------------v--------------------------------+
| API (src/aila/api/) |
| FastAPI app with 29 platform routers + module-contributed |
| routers, JWT auth, RBAC, |
| SSE event streams, OpenAPI at /docs |
+----------------------------+--------------------------------+
|
+--------------------+--------------------+
| |
+-------v-------------+ +--------v-----------+
| Platform | | Modules |
| src/aila/platform/ | | src/aila/modules/ |
| | | |
| routing/ | | forensics/ |
| runtime/ | <-- ModuleProto. | hello_world/ |
| services/ | contracts -> | vr/ |
| contracts/ | | vulnerability/ |
| tools/ | | |
| llm/ | | |
| tasks/ (ARQ) | | Each module owns |
| workflows/ | | its own runtime, |
| sse/ | | tools, workflow, |
| events/ | | contracts, API |
| automation/ | | router, frontend, |
| config.py, uow.py | | and DB models. |
| | | |
| | | See docs/vr/ for |
| | | the VR engine + |
| | | MCP architecture. |
| | | |
+----+-------------+--+ +---------+----------+
| | |
+----v---+ +-----v------+ +-------v---------+
| Redis | | PostgreSQL | | Per-module ARQ |
| ARQ | | SQLModel + | | queue tracks: |
| queues | | Alembic + | | default, vr, |
| | | pgvector | | vulnerability, |
+--------+ +------------+ | forensics |
src/aila/storage/ | |
+-----------------+
Layer responsibilities
src/aila/platform/) -- routing, runtime construction,
shared services, module/tool contracts, LLM client and pipelines, ARQ task
registration, workflow engine, SSE bus. Never imports from a feature module.src/aila/modules/) -- domain logic. Each module is a
self-contained package implementing ModuleProtocol. One module never
imports from another. Layout is fixed by docs/MODULE_STANDARD.md.
Current modules: vulnerability (CVE/CWE scanning + intel),
forensics (DFIR investigations), vr (vulnerability research --
graph-aware audit, fuzz campaign proposals, enterprise PDF reports,
exploit/PoC writer agent), malware (sample-centric reverse
engineering over ida-headless-mcp-exp), and the hello_world
reference module.src/aila/api/) -- FastAPI application (aila.api.app:app).
Modules contribute additional routers via api_router.py.frontend/) -- top-level Vite + React + TypeScript shell.
Module UIs live under src/aila/modules/<id>/frontend/ and are mounted by
the shell through the frontend module spec. Managed as a pnpm
workspace at the repo root.src/aila/storage/) -- SQLModel models, Alembic migrations,
config registry, secret store. Vector search uses pgvector with
384-dimensional embeddings.For deeper detail see docs/ARCHITECTURE.md.
Every reasoning module in AILA (vr, malware, anything future that
holds a multi-turn LLM conversation against an MCP backend) runs the
same four-phase loop driven by the platform's reasoning engine
(src/aila/platform/services/reasoning.py). Personas differ, tool
surfaces differ, terminal outcome kinds differ -- the loop does not.
+----------------------------------+
| case_state.observables |
| prior tool readings + directives|
| + agent scratchpad (capped) |
+-----------------+----------------+
|
v
+---------+ +------+-------+ +--------+
| THINK |-------->| HYPOTHESIZE |------->| ACT |
| persona | reads | produce or | emits | tool_ |
| prompt | state | refine list | one | run |
| + state | | with claim, | action| OR |
| | | why_plaus., | | submit |
| | | kill_crit. | | OR text|
+---------+ +--------------+ +----+---+
^ |
| |
+-------------- OBSERVE ---------------------+
tool result -> observables_delta
merged into case_state
Think. Each turn, one persona (researcher / critic / implementer)
receives the system prompt + persona prompt + the current
case_state projection. The case_state carries every prior tool
reading the platform decided was worth keeping in attention (tool
observables capped at the last 80, agent scratchpad capped at 15),
plus directives the auto-steering pipeline injected.
Hypothesize. The persona's output is a structured envelope
carrying a hypothesis list. Each hypothesis is a triple of
{claim, why_plausible, kill_criterion} -- the kill_criterion is the
specific evidence that would refute it. Hypotheses persist across
turns and accumulate state (live / rejected / resolved); the
rejection reason and resolution note travel with them. The frontend
renders the live + rejected + resolved aggregate per investigation
so the operator sees the deliberation drift in real time.
Act. The same envelope emits exactly one action: a
tool_run (dispatch one MCP tool call), a submit of a terminal
outcome (AnalysisReport, FindingDraft, TriageVerdict, ...), or a
text-only deliberation turn that updates hypotheses without
external action. The executor dispatches tool_runs through the
module's MCP bridge layer (which adds defenses: auto-poll on
pending, kwarg coercion, alias maps, address-format coercion --
see VR / Malware sections below).
Observe. The tool response flows through a per-adapter pass
that shapes the response into an observables_delta (key-value
pairs the next turn will see) plus a durable observation row in
the module's observation table (cross-investigation memory, kind +
polarity tagged). The case_state merges the delta; the renderer
decides which observables make it back into the next prompt under
the attention cap.
Multi-persona adversarial discipline. The reasoning module's
branch manager spawns multiple personas in parallel against the
same investigation. Each branch holds its own case_state. The
platform's deliberation broker surfaces rejection signal across
branches (a hypothesis rejected by 2+ siblings becomes a
sibling_consensus_rejection directive on the still-live branch).
Claim verifier promotes evidence reaching a confidence floor;
synth assembles the final outcome from the converged claims.
The pattern is what lets two very different modules (source-level vulnerability hunting vs binary malware reverse engineering) share the same engine + the same operator UI + the same observation memory abstraction. Adding a third reasoning module is mostly writing prompts + an MCP bridge + an outcome contract; the loop is already there.
The vulnerability research module (vr) drives a multi-MCP backend with
graph-aware code intelligence, semantic search, and binary analysis. Three
MCP servers run alongside AILA, exposed over HTTP and orchestrated by the
platform's task queue.
+-------------------------------------------------------------+
| AILA backend (Python + ARQ) |
| agent loop -> tool_executor -> bridge tools -> MCP servers |
+----+--------------------+--------------------+--------------+
| | |
+----v-----+ +-------v--------+ +------v---------+
| audit- | | ida-headless- | | semble |
| mcp | | mcp | | (embedded in |
| 18822 | | 18821 | | audit-mcp) |
+----------+ +----------------+ +----------------+
trailmark Hex-Rays + miasm Model2Vec + BM25
graph engine binary engine chunk retrieval
+ GPU CSR + 81 tools
+ 58 tools
/tools for catalog, /tools/<name> for invocation)index_codebase. Per-index cached on disk via DurableIndexStore, recovered automatically on restart.from_trailmark() constructs a GPU CSR adjacency matrix when CUDA is present; powers attack_surface, fuzzing_targets, unreachable_from_entrypoints on monorepo-scale graphs.~/.audit-mcp/semble-cache/<index_id>.pkl after first build -- subsequent restarts load in ~9s instead of rebuildingsemantic_search(query, top_k, alpha, rerank, filter_*), find_related(file, line, top_k), semble_stats(index_id)read_function queries the semble chunk index first (matches by name + definition pattern); falls back to a process-cached TypeResolver instead of rebuilding it per call (was the source of 15-minute hangs on firefox-scale).AUDIT_MCP_WORKERS env / --workers CLI flag. Each worker holds its own engine + semble + TypeResolver caches; AILA's bridge pre-warms all workers on the first call to a new index_id (Linux/macOS only -- Windows uvicorn multi-worker is broken)./tools catalog)ida_headless/poll_mutation so concurrent operator + agent edits don't raceassess_exploitability, prove_overflow, prove_bounds_sufficient)The VR module runs adversarial 3-persona deliberation (researcher / critic / synthesizer) over the MCP tool surface. Each tool call goes through AuditMcpBridgeTool (or its IDA equivalent) which provides:
fuzzing_targets(threshold=...)) before the HTTP round-trip and returns a structured "did you mean" error so the next turn self-correctstop_n -> limit, cutoff -> min_complexity, etc.) per tool's actual signature(server, tool, args) AND (server, tool, error_class) so the agent can't burn turns varying the value of a bad kwarg name; injects a hard pivot directive after 3 consecutive failuresattack_surface, complexity_hotspots, fuzzing_targets, ...) without a source read, forces the agent into read_function / taint_paths_to / callers_of or a finding submissiondead_code and unreachable_from_entrypoints from agents running against C++/Java/Kotlin/C#/Swift/Objective-C/Scala targets (static call graphs are blind to vtable + template dispatch on those languages)fuzzing_targets on firefox return status=pending + task_id; the bridge polls poll_task for up to ~15 min so AILA's 900s HTTP timeout doesn't kill long graph queriesindex_id fires 16 parallel summary + semble_stats requests so every uvicorn worker warms its caches before the agent's real query landsTarget ingestion is split into three independently-tracked stages with per-stage status, attempts counter, and reaper:
INGESTION -- audit_mcp index_codebase clone + parse (timeout 14400s)CAPABILITY_PROFILE -- D-51 capability rule evaluation (timeout 1800s)FUNCTION_RANKING -- fuzzing_targets ranking with GPU CSR (timeout 1800s)Operator can resume a stuck target via POST /vr/targets/{id}/resume-analysis; the endpoint fans out per non-DONE stage. Reaper runs every minute via ARQ cron, flips RUNNING stages past their timeout to FAILED with "reaper: RUNNING for Xs > Ys timeout".
For day-to-day MCP operations and the full VR agent design see docs/vr/.
The malware module ports the VR adversarial-deliberation pattern
onto sample-centric reverse engineering. Same multi-persona loop
(halvar / noor researchers, maddie / yuki critics, renzo / wei
implementers), same SSE-streamed timeline, same observation memory --
but every tool dispatch is constrained to ida-headless-mcp-exp
only. audit_mcp and android_mcp are explicitly hidden from the
agent-facing catalog and rejected at the executor's allowlist
(backend services like the claim verifier still construct their own
bridge instances).
sample upload
|
v
target_analysis stages (Alembic-tracked, per-stage durable)
open_binary -> auto_analysis -> string_classification
|
v
investigation kinds:
triage | full_analysis | unpack_only | config_extract
| yara_generate | family_attribute
|
v
multi-persona reasoning loop:
investigation_setup -> investigation_loop -> investigation_emit
|
v
outcomes:
ANALYSIS_REPORT | TRIAGE_VERDICT | UNPACK_ARTIFACT
| CONFIG_BLOB | YARA_RULE_DRAFT | FAMILY_ATTRIBUTION
| FINDING | OUTCOME_REVIEW
The agent dispatches exclusively against ida-headless-mcp-exp
running on port 18821. The bridge layer
(platform/mcp/bridges/ida_headless.py) adds nine defenses on top
of the raw catalog so the agent's habits don't cost turns:
status: pending -- bridge sleeps + re-POSTs the
same call (2s -> 3s -> 4.5s -> 8s capped) until the per-call
async work lands ready or the 240s budget runs out. poll_analysis
itself is the only excluded action.status=pending +
worker_phase in {exiting_idle, crashed, ""} +
heartbeat_age_s >= 600), the bridge skips the 240s poll and
returns a structured dead_worker_diagnostic error naming the SHA,
heartbeat age, queue depth, and the exact operator action
(restart ida-headless, clear crash_counts.json, re-upload to
force fresh analysis). Threshold override:
IDA_HEADLESS_DEAD_WORKER_HEARTBEAT_S.(action, sorted_kwargs) within
IDA_HEADLESS_DEDUP_TTL_S (default 300s) replays the cached
ready payload. Scoped to 23 read-only actions (xrefs_to/from,
decompile, find_api_call_sites, callers_of, build_call_tree,
list_strings/functions, capa_scan, detect_crypto_primitives,
etc.). State mutators (open_binary, upload, patch_assemble) and
freshness-sensitive (poll_analysis) are excluded. Cached hits
stamp _ida_bridge_dedup: hit so the executor can distinguish
replay from fresh. Per-call bypass: pass _ida_bridge_no_dedup=True.sub_<hex> / loc_<hex> /
unk_<hex> to 0x<hex> before dispatch. avoid_addresses
(list) gets per-element rewriting.encoding value alias -- the string-family tools
(list_strings, get_string_at) accept utf16 / utf-16 /
utf-16le / utf16-le as aliases for the canonical utf16le.
Closes the round-trip with the same-named label the server emits
under by_encoding; an agent passing back the value it just read
from count_only no longer falls into a total=0 false negative.offset / limit / cursor /
top_k get silently stripped when the target tool doesn't declare
them, so snapshot tools no longer TypeError on agent habits.search_pattern alias map -- rewrites pattern / pattern_str /
query to the canonical pattern_type enum kwarg.status=None payload normalization -- tools like
binary_metadata that omit a top-level status field get
status='ready' injected so the downstream executor's whitelist
doesn't synthesize a spurious empty error._ALWAYS_SUPPRESS enforcement -- a curated set of tools the
agent must never reach is checked BEFORE the specialized-adapter
lookup AND subtracted from _effective_tools (which would
otherwise re-introduce them via the runtime bridge catalog).
Current suppress set: ida_headless.classify_strings (regex
categorizer that returns 19 buckets on a 10,254-string binary;
agents derail by treating its empty output as evidence) and
audit_mcp.search_source (deprecated). Suppressed tools also
drop from registered_tools() + specialized_tools() so the
diagnostic listings stay consistent.The XREF adapter additionally surfaces a pagination_hint block
on payload + a one-line directive in the observable when
total > MAX_LIST_PREVIEW=20. The directive names the suppressed
row count + the exact follow-up call shape
(offset=20, limit=20) + the call_id for direct payload
access. The full xref array is preserved verbatim in payload.xrefs
regardless of length; only the per-turn observable preview is trimmed.
The system prompt teaches a four-stage extraction workflow instead of pattern-matching on hostname-shaped substrings:
find_api_call_sites('FindResourceW') for embedded-resource
patterns, decryptor xrefs for crypto blobs, or
RegQueryValueExW for registry-stored configs.read_memory(VA, size) the encrypted
blob, decrypt with binary-sourced constants, parse with the
known offsets, identify the C2 field.WinHttpConnect / InternetOpenUrlA / connect.
If the path doesn't close, it isn't the C2 -- keep going.ida-headless-mcp-exp now eager-initializes _Frontend in main()
/ main_http() before binding the transport, so recover_all()
populates _binaries from cache/<sha>/state.json BEFORE the
server accepts the first HTTP request. AILA workers carrying stale
binary_id values in mcp_handles_json survive restarts without
seeing Unknown binary_id. Defensive _sha fallback handles
out-of-band registrations (e.g. a stdio MCP transport writing
state.json that the HTTP server's already-completed sweep missed).
POST /malware/investigations/{id}/reset wipes
messages + observations + outcomes + forked branches +
workflow_state_cursor archive in one transaction, flips status
back to CREATED. Refuses while RUNNING (operator must pause
first); PAUSED is fine because the cursor wipe handles the
archive that /resume would otherwise rely on.RUNNING and the
reaper missed a stall (engine reports in-flight but no task
heartbeat). Calls the same /re-enqueue endpoint the reaper
would.Synthesis & Narrative card under the primary outcome).
POST /malware/investigations/{id}/synthesize accepts
{ force, tone, length, enumerate_every_suspicious, operator_focus }.
Default force=True; tones operator | executive | technical | analyst | forensic; lengths brief | standard | exhaustive.
enumerate_every_suspicious=True is the "don't drop anything"
mode that walks every persona's answer + reasoning and surfaces
every distinct suspicious item (string, address, function, IOC,
persistence artifact, decoded blob). Runs against an already-
COMPLETED investigation without flipping its status; the
structured panel_summary is overwritten plus every promoted
field (family_attribution, capabilities, iocs,
attribution_rationale, headline_verdict, detection_guidance,
next_actions, panel_dissent, inconclusive_areas,
inconclusive_capabilities) is re-derived.payload.investigation_narrative on the canonical outcome,
alongside (not replacing) panel_summary. Endpoint:
POST /malware/investigations/{id}/narrative with
{ force, tone, length, operator_focus }. Tones blog | incident_report | thriller | academic | casual; lengths
short (~1.5-2.5K words), standard (~3.5-5.5K words), long
(~8-15K words). Schema enforces body.min_length=4000 so the
LLM cannot bail with a 200-char intro stub. UI button opens the
rendered markdown in a side modal with table of contents,
copy-to-clipboard, and the title / tone / length / word count
metadata strip.edit_outcome action.
Counterpart to the deferred request_edit vote (which only
suggests edits the synthesis agent picks up on the next pass);
edit_outcome merges patches immediately. Refused on non-draft
outcomes; protected workflow-owned keys (panel_contributions,
panel_summary, verifier_report, applied_by_synthesis) are
dropped from the merge. Every applied edit writes an audit row.reject vote no longer kills an outcome. A second sibling must
concur (VETO_K=2 in services/outcome_review.py). Single rejects
still record on the outcome and surface in the proposing branch's
prompt so it can react; the state flip waits on the chorus.
Approve quorum (approve_count >= quorum_k) still ships the
outcome; veto is evaluated BEFORE approve so a tied chorus
resolves to rejected.Prerequisites
Required:
corepack enable to activate pnpm)pgvectorOptional MCP servers (only if you want the vr or malware modules):
:18822 -- vr source-repo targets:18821 -- malware, and vr on binary targets:18823 -- vr on android_apk targetsThe platform, vulnerability, forensics, and hello_world modules run without any MCP.
Steps
Clone and install.
git clone https://github.com/project-lambda-zero/AILA.git
cd AILA
make install # pip install -e ".[dev]" + corepack enable && pnpm install
Configure environment.
cp .env.example .env
Set at minimum AILA_DATABASE_URL, AILA_PLATFORM_REDIS_URL, AILA_JWT_SECRET_KEY (openssl rand -hex 32), AILA_ADMIN_PASSWORD (first-boot bootstrap, remove after), and the AILA_PLATFORM_LLM_* group. If you plan to use MCPs, uncomment the AUDIT_MCP_URL / IDA_HEADLESS_URL / ANDROID_MCP_URL block in .env.example. Full reference: docs/ENV_VARS.md.
Bring up Postgres + Redis and initialize the schema.
make dev-up
make db-init # first run only: create tables + stamp Alembic head
# subsequent runs:
# make migrate
Start MCP servers (skip if not using vr or malware). Each in its own terminal:
audit-mcp --mode http --port 18822 # vr
ida-headless-http --port 18821 # malware, and vr on binary targets
python -m android_mcp --mode http --port 18823 # vr on android_apk
See docs/QUICKSTART.md section 4 for clone + install of each MCP.
Start AILA in three terminals.
make backend # REST API on :8000
make frontend # Vite dev server on :3000 (single SPA, all module UIs)
make worker # default ARQ queue
Per-module workers when the corresponding module is in play:
make worker-vr # vulnerability research
make worker-vuln # vulnerability scans
make worker-forensics # DFIR investigations
make worker-malware # malware reverse engineering
For the full walkthrough (admin user creation, MCP install details, smoke tests, common pitfalls), see docs/QUICKSTART.md.
| module_id | Description | Status |
|---|---|---|
vulnerability | SSH package inventory, distro-aware advisory resolution, CVE enrichment, scoring, and reporting. | production |
forensics | Remote forensic evidence triage over SSH: disk images, memory dumps, PCAPs, write-up generation. | production |
vr | Vulnerability research: graph-aware source/binary audit (audit-mcp + IDA Headless MCP), hypothesis-driven reasoning, fuzz campaign proposals (audit\u2192fuzz pipeline), enterprise PDF reports with LLM writer agent, automatic exploit/PoC drafting, variant hunting with child-investigation spawning. | production |
malware | Malware reverse engineering: VR-pattern multi-persona deliberation over ida-headless-mcp-exp only. Six investigation kinds (triage / full_analysis / unpack_only / config_extract / yara_generate / family_attribute), deterministic four-stage C2 config extraction with mandatory string-sweep + xref-chain follow-up, two-stage C2 hunt (Stage 1 loader URLs vs Stage 2 dropped-payload endpoints), fan-out sub-investigation spawning, observation memory with base64 / hex auto-decode, operator controls (reset / re-enqueue / re-synthesize with tone+length / generate-narrative writeup / direct edit_outcome patches), chorus-veto outcome review (VETO_K=2), structured synthesis promoting family / capabilities / IOCs / detection guidance / next actions onto the canonical payload. | production |
hello_world | Minimal reference module proving the ModuleProtocol contract end-to-end. | example |
Modules are auto-discovered at platform boot by scanning src/aila/modules/*.
Packages whose name starts with _ are skipped (used for templates and
fixtures). To add a new module, follow docs/MODULE_STANDARD.md
and the worked tutorial in docs/MODULE_TUTORIAL.md.
Common targets in the root Makefile:
| Target | What it runs |
|---|---|
make install | pip install -e ".[dev]" plus corepack enable && pnpm install |
make dev-up | docker compose -f infra/utilities/docker-compose.yml up -d postgres redis (idempotent) |
make dev-down | Stop dev infra containers (keeps data volumes) |
make dev-reset | Stop containers and wipe data volumes |
make dev-logs | Follow compose service logs |
make dev-status | docker compose ps |
make db-init | python scripts/db_init.py -- create tables + stamp Alembic head (first run only) |
make migrate | cd src/aila && alembic upgrade head |
make dev | Print the canonical dev workflow (no services started) |
make backend | Ensure dev-up + db-init, free port 8000, run uvicorn aila.api.app:app --host 0.0.0.0 --port 8000 --reload |
make frontend | Free port 3000, run pnpm --filter @aila/shell run dev (Vite on :3000) |
make frontend-build | pnpm --filter @aila/shell run build (production SPA bundle) |
make storybook | pnpm --filter @aila/shell run storybook |
make worker | python -m aila worker (default queue) |
make worker-vr | python -m aila worker -q vr |
make worker-vuln | python -m aila worker -q vulnerability |
make worker-forensics | python -m aila worker -q forensics |
make dev-all | Bring up all services in one terminal (Ctrl+C stops everything) |
bash start.sh | Spawn audit-mcp + backend + 4 workers + frontend in one shot (Windows: Git Bash + PowerShell) |
docker compose -f infra/utilities/docker-compose.full.yml up --build | Full-stack containers: postgres + redis + api + 4 workers + frontend. See docs/DEPLOYMENT.md. |
make test | pytest, excluding tests/test_e2e*.py |
make test-e2e | pytest tests/test_e2e.py -v (requires live infrastructure) |
make test-frontend | pnpm --filter @aila/shell run test (shell package only; module frontends use pnpm -r run test) |
make lint | ruff check src/aila/ |
make typecheck | pnpm -r run type-check (every workspace package, shell + modules) |
make honesty | python -m aila.tools.honesty_audit src/aila --whitelist honesty_whitelist.py |
make compile | python -m compileall -q src/aila |
make build | pnpm --filter @aila/shell run build (production SPA bundle) |
make check | lint + honesty + compile + typecheck (the full pre-PR gate) |
make security-scan | pip-audit --strict --desc and bandit -r src/aila -q -ll |
make clean | Remove __pycache__/ directories and coverage artifacts |
Run make check before opening a PR. Contributor workflow, branch policy,
review expectations, and the honesty audit rules are documented in
docs/CONTRIBUTING.md.
The aila entry point (aila = "aila.cli:app") is a Typer application.
Invoke aila --help to list every subcommand and command group; the most
common entry points are summarised below.
| Command | Purpose |
|---|---|
aila serve | Start the FastAPI REST API via uvicorn |
aila worker [-q <queue>] | Start an ARQ worker for the given queue track (default: default) |
aila task "<question>" | Ask a natural-language question routed through the platform agent |
aila analyze [--target <name>] | Run a vulnerability scan across registered targets (or one) |
aila add-ssh ... | Register an SSH-reachable system for the vulnerability module |
aila create-api-key | Mint an admin-role API key for first-boot bootstrap |
aila health | Probe platform and provider readiness |
Command groups expose related subcommands:
aila config (runtime config registry),
aila tool (invoke registered platform tools directly),
aila cache (manage decision and intel caches),
aila policy (scoring policy management),
aila feedback (operator knowledge entries),
aila report (PDF and CSV reporting),
aila schedule (scheduled scans),
aila intel, aila ops, aila auto, aila digest
(fleet intelligence, operational metrics, automation, executive digests).
http://localhost:8000http://localhost:8000/docshttp://localhost:8000/openapi.jsonPOST /auth/login with {"username", "password"} returns a JWT (data.access_token) used as Authorization: Bearer <token> for all subsequent calls; POST /auth/token exchanges an API key for the same envelope. RBAC roles
are admin, operator, reader -- see
docs/SECURITY_MODEL.md./scans/{id}/events, /tasks/{id}/events). Integration patterns are
documented in docs/SSE_GUIDE.md.The OpenAPI document is the source of truth for the route surface; the
/docs UI lists every endpoint, request schema, and response schema.
| Document | Covers |
|---|---|
| docs/ARCHITECTURE.md | System diagram, layer responsibilities, data flow, runtime constraints |
| docs/PLATFORM_INTERNALS.md | X-ray: full request lifecycle traced through every platform layer |
| docs/QUICKSTART.md | Expanded onboarding walkthrough with troubleshooting |
| docs/CONTRIBUTING.md | Contributor workflow, branch policy, review expectations |
| docs/MODULE_STANDARD.md | Required module layout, contracts, and lifecycle (v2.1) |
| docs/MODULE_TUTORIAL.md | Step-by-step authoring of a new module |
| docs/MODULE_AGENT_GUIDE.md | Module context conventions for LLM-driven flows |
| docs/FRONTEND_MODULE_STANDARD.md | Frontend shell and per-module UI contribution contract |
| docs/forensics/ | Forensics module domain reference and design history |
| docs/DB_SCHEMA.md | Database tables, relationships, and ownership |
| docs/DATABASE_MIGRATIONS.md | Alembic policy, conventions, and migration authoring |
| docs/CONFIG_REGISTRY.md | Config resolution chain (env -> registry -> defaults) |
| docs/ENV_VARS.md | Environment variable reference |
| docs/SECURITY_MODEL.md | Auth, RBAC, API keys, JWT lifecycle |
| docs/DATA_PROTECTION.md | Data posture modes, LLM redaction, input/output sanitization |
| docs/API_ERRORS.md | API error catalog |
| docs/OPENAPI_NOTES.md | OpenAPI generation notes and conventions |
| docs/SSE_GUIDE.md | Server-sent events: usage, reconnection, curl examples |
| docs/TASK_QUEUE_OPS.md | ARQ worker operations, queue tracks, retry semantics |
| docs/LLM_INTEGRATION.md | LLM client, pipelines, model selection, transparency posture |
| docs/WORKFLOW_GUIDE.md | Durable state machine: handler contract, do/don't, production examples |
| docs/DEPLOYMENT.md | Production deployment guide |
| docs/TEST_GUIDE.md | Testing conventions, fixtures, e2e gating |
| docs/GOLDEN_RULES.md | Code quality rules enforced by review and tooling |
| docs/HONESTY_AUDIT.md | Structural honesty rules enforced by aila.tools.honesty_audit |
| docs/PITFALL_GUIDE.md | Common mistakes when working on AILA |
| docs/PRODUCTION_RUBRIC.md | Readiness rubric for shipping a module to production |
| docs/vr/ | VR engine internals: reasoning loop, IDA Headless MCP, exploit automation |
| docs/VR_INSTALLATION_GUIDE.md | Standing up audit-mcp + IDA Headless MCP next to AILA |
| CHANGELOG.md | Version history |
AILA is licensed under the GNU Affero General Public License v3.0. See LICENSE for the full text.