Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/AdithyaaSivamal/Agentic-AFL/llms.txt

Use this file to discover all available pages before exploring further.

AgentLoop is the central orchestration daemon. It polls for AFL++ stalls, processes them through the full Extract→Translate→Solve→Inject pipeline, and manages the ReAct self-correction loop. Each stall is placed on a priority queue by severity and processed one at a time; successful solves are permanently suppressed so the agent never re-processes an already-solved address. When used via CampaignRunner, AgentLoop is instantiated and managed automatically. You can also instantiate it directly for custom integrations that need finer control over stall processing, harvest mode, or signal handling.

Import

from agentic_afl.orchestrator.agent_loop import AgentLoop

Constructor

afl_output_dir
Path | str | None
default:"settings.afl_output_dir"
AFL++ output directory (the -o argument passed to afl-fuzz). The stall detector reads fuzzer_stats and the queue from subdirectories inside this path. Falls back to settings.afl_output_dir from the environment configuration if not provided.
afl_sync_dir
Path | str | None
default:"settings.afl_sync_dir"
Sync directory for payload injection. Solved payloads are written here as .bin files; AFL++ ingests them on its next cycle via the -F sync flag. Falls back to settings.afl_sync_dir if not provided.
target_binary
Path | str | None
default:"None"
Path to the binary being fuzzed. Used by the GDB-based offset probe (extract_arg_memory) to discover the base offset of the input buffer pointer and capture runtime register/memory state at the stall address. When None, GDB probing is skipped and the LLM relies solely on static P-Code and decompiled C.
stall_address_override
str | None
default:"None"
Force a specific hex address string (e.g., "0x08004210") as the stall site, bypassing the automatic stall detector entirely. Useful for unit tests, benchmarking, and targeted experiments against a known hard branch.
min_stall_cycles
int | None
default:"settings.min_stall_cycles"
Minimum number of AFL++ cycles at a plateau before the stall detector fires. Higher values reduce noise from transient slowdowns; lower values make the agent more responsive. Falls back to settings.min_stall_cycles if None.
harvest_mode
bool
default:"false"
Enable harvest mode. When True, CARM template retrieval is disabled (forcing zero-shot LLM generation) and every successful solve is verified by measuring the AFL++ edge delta after injection. Verified solves are automatically committed back to the CARM corpus. Use this to build up a template library from scratch on a new target class.
register_signals
bool
default:"true"
Register SIGINT/SIGTERM handlers that call shutdown(). Set to False when the caller (e.g., CampaignRunner) manages signal handling itself, to prevent the agent’s handler from overwriting the caller’s.

Methods

async setup() -> None

Initialize all sub-components. Must be called once before run(). Instantiates and configures:
  • SpecStore — PostgreSQL connection for persisting VulnerabilitySpec records
  • PCodeSlicer — Ghidra-backed P-Code backward slice extractor
  • ConstraintProfiler — Structural constraint tag analyzer
  • SpecExporter — Writes specs to PostgreSQL
  • CARMRetriever — Cosine/Jaccard similarity search over the spec store
  • LLMClient — Z3Py script generator (reads provider from settings)
  • Z3Sandbox — Isolated subprocess executor for LLM-generated Z3 scripts
  • StallDetector — Polls fuzzer_stats and the AFL++ queue for coverage plateaus
  • PayloadInjector — Writes solved payloads to the sync directory
setup() establishes a live PostgreSQL connection via SpecStore.initialize(). Ensure settings.postgres_dsn is set correctly in your environment before calling it.

async run() -> None

Main daemon loop. On each iteration the loop:
  1. Calls StallDetector.detect() to collect new stall reports
  2. Enqueues each new stall by StallSeverity priority (CRITICAL first)
  3. Dequeues the highest-priority stall and processes it through the full pipeline
  4. Sleeps for settings.stall_poll_interval seconds before the next iteration
The loop continues until _running is set to False (via shutdown() or external assignment). When it exits, the PostgreSQL connection is closed and final metrics are logged.

get_metrics() -> dict

Return a snapshot of runtime metrics accumulated since run() started. See Runtime Metrics below for field descriptions.

async shutdown() -> None

Gracefully stop the daemon loop by setting _running = False. The loop will complete its current iteration (including any in-flight stall processing) before exiting. The PostgreSQL connection is closed as part of the exit sequence.

Runtime Metrics

get_metrics() returns a dict with the following fields:
stalls_detected
int
Total stall reports enqueued since run() started. Includes stalls that were later skipped because the address was already solved.
stalls_processed
int
Total stall reports that completed the full pipeline (whether or not they resulted in a SAT solve).
payloads_injected
int
Total payloads successfully written to the AFL++ sync directory. Includes both primary solves and diversity-generator variants.
react_turns_total
int
Total ReAct loop iterations consumed across all processed stalls. Each turn generates K Z3 scripts and executes them in the sandbox.
llm_calls_total
int
Total LLM API calls made. One call per ReAct turn (each call generates K candidate scripts in parallel).
queue_size
int
Current number of stalls waiting in the priority queue (not yet dequeued for processing).
active_stalls
int
Number of stalls currently being processed concurrently. Under the current single-consumer design this is always 0 or 1.
harvest_verified
int
(Harvest mode only) Solves that were confirmed by a positive edge delta after injection.
harvest_committed
int
(Harvest mode only) Verified solves committed back to the CARM template corpus.

Usage: CampaignRunner vs Standalone

AgentLoop is used internally by CampaignRunner, which handles setup, signal registration, and AFL++ process management for you. Use AgentLoop directly when you need:
  • Integration with an existing AFL++ deployment not started by CampaignRunner
  • Harvest mode for template corpus building
  • Custom stall address overrides for benchmarking
  • Fine-grained access to per-stall metrics
import asyncio
from pathlib import Path
from agentic_afl.orchestrator.agent_loop import AgentLoop

async def main():
    agent = AgentLoop(
        afl_output_dir=Path("./afl_output"),
        afl_sync_dir=Path("./afl_output/agentic/queue"),
        target_binary=Path("./harness"),
    )
    await agent.setup()
    try:
        await agent.run()
    finally:
        metrics = agent.get_metrics()
        print(metrics)

asyncio.run(main())
When running AgentLoop standalone alongside an externally managed AFL++ process, ensure the afl_sync_dir matches the path passed to afl-fuzz -F. The sync directory must exist before run() is called — PayloadInjector does not create it.

Build docs developers (and LLMs) love