Architecture
How the orchestrator, TUI, and tool layer fit together.
This is the doc to read before you start building on top of AEGIS — adding a new tool, a new verification probe, a new phase, or a custom backend.
Process model
┌─ aegis run <engagement-dir> (Python, asyncio main thread) ┐
│ │
│ ┌─ TUI bridge ──────────────────────────────────────────────┐ │
│ │ spawns: node tui/dist/index.js │ │
│ │ • stdin ← ServerEvent NDJSON (Python writes) │ │
│ │ • stdout → ClientCommand NDJSON (Python reads) │ │
│ │ • /dev/tty ↔ Ink draws + keys (bypasses pipes) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ EngagementRunner ─────────────────────────────────────────┐ │
│ │ state machine over 7 PTES phases │ │
│ │ ↓ │ │
│ │ Backend (Claude API / Claude Code / Ollama) │ │
│ │ ↓ │ │
│ │ MCP tool surface — Python tool wrappers + verification │ │
│ │ probes + Knowledge Base + Findings DB │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Components
src/aegis/orchestrator/
loop.py—EngagementRunner. State machine over the 7 phases. Owns the LLM call loop, the operator-message queue, the audit log.events.py—EngagementEventandEventKind: every signal that flows from runner → on_event callbacks (LLM_THINKING, LLM_DECISION, PHASE_START, PHASE_ADVANCE, TOOL_DONE, FINDING, …).planner.py— strategic decisions: which tools to queue next, which phase to advance to.
src/aegis/methodology/
phases.py—MethodologyPhaseenum andPHASE_CONFIGS. Each phase has an objective, default budget, prompt template, exit criteria, and the set of MCP tools it’s allowed to call.engine.py— phase advance / change logic.
src/aegis/mcp_server/
The MCP server surface. When AEGIS is run as aegis serve (Claude Code as orchestrator) or when EngagementRunner runs Claude Code in subprocess mode, this is what the LLM talks to.
server.py— FastMCP server. Defines every tool the LLM can call:subfinder_enum,nuclei_scan,aegis_verify,aegis_record_finding, etc.__main__.py—python -m aegis.mcp_serverentry.
src/aegis/tools/
catalog.py—TOOL_CATALOG: every external binary AEGIS knows about, with its install spec (pacman, yay, go install, pipx, npm, binary, manual).executor.py—ToolExecutor. Routesrun_tool(name, target, **kwargs)to the right wrapper. Enforces scope, rate limit, environment.base.py—BaseToolabstract class. Subclasses implementrun(target, **kwargs) -> ToolResult.wrappers/— one file per tool:nmap.py,nuclei.py,subfinder.py, … 14 wrappers cover the high-frequency tools; the rest run through generic subprocess invocations.parsers/— per-tool output parsers that convert raw output intoObservationobjects.
src/aegis/verify/
probes.py— 33 verification probe types: timing oracle, CSRF, SSTI, prototype pollution, race condition, header injection, OAuth flow… Called by theaegis_verifyMCP tool after a candidate finding is identified.
src/aegis/analysis/
adaptive.py— adaptive escalation: takes findings and queues follow-up tool runs.chains.py— attack chain detection across services.port_profiles.py— port-based attack profiles for ACTIVE_RECON.
src/aegis/kb/
Local knowledge bases:
nvd.py— NVD CVE feed sync + lookup.ghsa.py— GitHub Security Advisories.nuclei_index.py— local index of nuclei templates by CVE / tech.template_selector.py— given a tech stack, return the most relevant templates.
src/aegis/scope/
guard.py—ScopeGuard. Every HTTP request, every tool target, every probe destination passes throughis_in_scope(). Out-of-scope is a hard exception, not a warning.
src/aegis/cli/
main.py— Typer CLI:aegis run,aegis report,aegis engagement new, etc.tui_bridge.py— spawn the Node TUI, translate runner events to ServerEvent NDJSON, translate operator commands back intoinject_operator_message()/ flag flips.shells.py—ShellTracker: process-scoped registry of every tool subprocess so the TUI can show streaming output.
tui/
Node + React + Ink terminal UI. Strict TypeScript, esbuild ESM bundle to a single 38 KB file.
src/protocol/types.ts— wire protocol (ServerEvent / ClientCommand) shared with Python.src/protocol/bridge.ts— NDJSON line buffer over stdin/stdout.src/hooks/useEngagement.ts— single useReducer that turns ServerEvents into UI state.src/components/— StatusBar, ActivityFeed, ThinkingPane, InputBar, ShimmerText.
Backend selection
aegis.llm.factory.create_backend() picks in order:
ANTHROPIC_API_KEYset →AnthropicAPIBackend.claudeonPATHand authenticated →ClaudeCodeBackend(uses your subscription, no API costs).OLLAMA_HOSTset →OllamaBackend.
Override with --backend <name> or via ~/.config/aegis/config.toml.
Adding a new tool
- Write a wrapper in
src/aegis/tools/wrappers/your_tool.py. SubclassBaseTool, implementrun(). - Add an entry to
TOOL_CATALOGinsrc/aegis/tools/catalog.pywith install spec. - Add a parser in
src/aegis/tools/parsers/if the tool’s output is structured. - Surface it as an MCP tool in
src/aegis/mcp_server/server.pyso Claude can call it. - Add a phase mapping in
src/aegis/methodology/phases.pyso the right phase can use it.
See wrappers/nuclei.py end-to-end — it’s the canonical example.
Adding a verification probe
- Add a new function
_probe_yourcheck(target, **kwargs) -> dictinsrc/aegis/verify/probes.py. - Register it in the
PROBE_TYPESdict at the top of that file. - The MCP tool
aegis_verify(probe_type='yourcheck', target=...)will route to it automatically.
Wire protocol
The TUI talks to Python over NDJSON. See tui/src/protocol/types.ts for exact types. The interesting events:
| Event | Carries |
|---|---|
hello | engagement ID, client, model, scope |
phase_start | phase name, objective description |
llm_thinking | heartbeat status string |
llm_decision | tool name + label |
tool_start | id, name, command tokens |
tool_output | id, single output line |
tool_done | id, status, rc, elapsed |
finding | severity, title, target |
agent_note | Claude’s analytical commentary |
phase_done | phase name, cost |
complete | scan summary |
Commands the TUI sends back:
guidance, stop, pause, resume, focus, hunt, quit.