AEGIS
— docs

Extending AEGIS

Add a tool, a probe, a phase, or a custom backend.

Three of the most common extension paths, with the actual file changes.

Adding a new tool

Say you want to add httprobe. Steps:

1. Add a wrapper

src/aegis/tools/wrappers/httprobe.py:

from aegis.tools.base import BaseTool
from aegis.tools.catalog import TOOL_CATALOG
from aegis.tools.models import ToolResult, Observation

class HttprobeWrapper(BaseTool):
    spec = TOOL_CATALOG["httprobe"]

    async def run(self, target: str, **kw) -> ToolResult:
        stdout, stderr, rc = await self._run(["-c", "50"], cwd=None)
        live_hosts = [l.strip() for l in stdout.splitlines() if l.strip()]
        return ToolResult(
            tool=self.spec.name, target=target, success=rc == 0,
            observations=[Observation(kind="http_alive", value=h) for h in live_hosts],
            raw_stdout=stdout, raw_stderr=stderr, returncode=rc,
        )

2. Register in the catalog

src/aegis/tools/catalog.py:

TOOL_CATALOG["httprobe"] = ToolSpec(
    name="httprobe",
    binary="httprobe",
    arch_install=InstallSpec(manager="go",
        package="github.com/tomnomnom/httprobe@latest"),
    timeout_sec=300,
)

3. Wire into the executor

src/aegis/tools/executor.py:

wrapper_map = {
    # …
    "httprobe": ("aegis.tools.wrappers.httprobe", "HttprobeWrapper"),
}

4. Surface as MCP tool

src/aegis/mcp_server/server.py:

@mcp.tool()
async def httprobe_check(target: str) -> dict:
    """Probe a list of hosts and return which respond on HTTP/HTTPS."""
    result = await ctx.executor.run_tool("httprobe", target)
    return {"alive": [o.value for o in result.observations], "rc": result.returncode}

5. Add a parser (optional, for richer output)

src/aegis/tools/parsers/httprobe.py — only needed if the tool emits structured output you want to lift into typed observations.

That’s it. aegis env install --missing will pick up the catalog entry; the next aegis run will let Claude call your tool.

Adding a verification probe

src/aegis/verify/probes.py:

async def _probe_xss_reflected(target: str, payload: str = '"<svg/onload=1>') -> dict:
    """Check whether a `q` query param is reflected without escaping."""
    async with httpx.AsyncClient() as client:
        r = await client.get(target, params={"q": payload}, timeout=10)
        verified = payload in r.text and "Content-Type" in r.headers
        return {
            "verified": verified,
            "evidence": r.text[:500] if verified else "no reflection",
            "confidence": "high" if verified else "low",
        }

PROBE_TYPES["xss_reflected"] = _probe_xss_reflected

Now aegis_verify(probe_type='xss_reflected', target='https://x/?q=test') routes to your probe.

Adding a custom backend

Your shop has its own LLM gateway? Subclass BaseBackend:

# src/aegis/llm/my_backend.py
from aegis.llm.base import BaseBackend
from aegis.llm.models import CompletionResult, Tier

class MyBackend(BaseBackend):
    supports_tool_use = True
    supports_prompt_caching = True
    name = "my-backend"

    async def complete(self, messages, tools, tier: Tier, **kw) -> CompletionResult:
        # call your gateway, return a CompletionResult
        ...

Register in src/aegis/llm/factory.py:

if preferred == "my-backend":
    from aegis.llm.my_backend import MyBackend
    return MyBackend()

Adding a phase

Phases are configured declaratively in src/aegis/methodology/phases.py. Add an enum entry, a PHASE_CONFIGS entry with objective + budget + prompt template + allowed tools, and the runner’s state machine picks it up.

Consider whether you actually need a new phase or whether the work belongs as an escalation rule in src/aegis/analysis/adaptive.py — most “new phases” are really targeted vulnerability runs that should be triggered by findings.