Exploit Agent — Loop (tools/exploit_agent/runner/_impl.py)
Main async loop (run_exploit_agent at tools/exploit_agent/runner/_impl.py:301, ~2.2K
LOC file). Called from tools/exploit_session.py (CLI/API service) and the
swarm bridge. Imported into the package by tools/exploit_agent/runner/loop.py
(a plain from ._impl import ...); tools/exploit_agent/loop.py is a
deprecated re-export shim.
Verified symbols
| Symbol | Kind | Location | Notes |
|---|---|---|---|
run_exploit_agent | async def | _impl.py:301 | Entry point; all args keyword-only |
_PhaseTracker | class | tools/exploit_agent/phase_tracker.py:9 | PHASE_ORDER + MIN_ACTIONS + can_terminate |
_InMemoryExperienceStore | class | _impl.py:196 | Fallback when DB unreachable |
CheckpointContext | dataclass | _impl.py:99 | kind = access|no_path |
CheckpointOutcome | dataclass | _impl.py:117 | action + objective_text |
CheckpointHook | Protocol | _impl.py:138 | Mid-run operator gate |
_resolve_attacker_os | def | _impl.py:148 | auto → platform.system() mapping |
_resolve_allowed_targets | def | _impl.py:252 | Unions exploit.allowed_targets |
_load_attack_memory_settings | def | _impl.py:282 | memory.attack_memory_* |
_debug_enabled / _debug_print | def | _impl.py:182/186 | AI_NMAP_DEBUG env |
_emit + _EVENT_TYPE | def/const | _impl.py:68/59 | WebUI event stream |
Init sequence (pre-round-loop, _impl.py after line 301)
- Bind
policy._target_ip/_locked_ip/_allowed_targets(immutable target lock). - Resolve
attacker_osvia_resolve_attacker_os. - Build
profile = _build_context_profile(model). - Wire
ExperienceStore/SemanticMemoryManager/ExploitMutator(adaptive exploits). SessionManager.resume_or_new(+persist_messagespropagation).AttackMemoryStorevia_load_attack_memory_settings.AttackPlannerplan load/create.- Preflight env probe
render_env_context. build_opsec_briefing/build_domain_briefing/build_parallel_agents_briefing/build_capability_guidance.build_exploit_system_prompt(...)(+ research-assistant briefing).- Seed
messages(resume or fresh). - Auto research consultation on startup CVEs.
Round loop (for _round in range(max_rounds) at _impl.py:889)
messages = _refresh_attack_memory_message(...)
messages = _refresh_reasoning_advisory_message(...)
if time budget: _stream_model(...) ; break
if not policy.can_proceed: _stream_model(...) ; break
if _should_compact_context(...): _build_compacted_messages(...)
response = await _call_model_with_retry(..., _round_tools())
tool_calls, invalid = _filter_and_validate_tool_calls(...)
if not tool_calls:
can_term, reason = phase_tracker.can_terminate()
if enforcement on and not can_term:
goal_complete (compromise/cred-dump) may still break
else inject finish-early warning and continue
checkpoint_hook may fire (no_path) and continue/finish/cancel
break
for tc in tool_calls:
approved = await policy.approve_action(...)
result = await session.call_tool(name, args)
messages.append({"role":"tool", "content": sanitize_output(...)})
_action_result = normalize_action_result(...)
outcome_tracker.record_compromise/cred_dump(...)
if outcome_judgment_flow_a: await judge_flow_a(...)
await policy.record(...)
record_exploit_success/failure(...)
if reflection_every: await _llm_reflect_inline(...)
if should_consult_peers: await _consult_peers_inline(...)
_maybe_reselect_skills(...)
phase_tracker.record_action(...)
Phase tracking
_PhaseTracker.PHASE_ORDER = recon → service_enumeration → vulnerability_research → validation → reporting (tools/exploit_agent/phase_tracker.py:16). MIN_ACTIONS enforces recon ≥2, service_enumeration ≥ max(1, detected services), vulnerability_research ≥ max(1, identified versions), reporting ≥1. can_terminate() → (bool, reason) at phase_tracker.py:42; remaining_requirements() lists what is missing.
Budgets
max_rounds = policy.settings.effective_max_rounds (tools/exploit_agent/policy.py); max_duration = attack_max_duration_minutes*60 if attack_mode else 0. num_ctx only threaded to Ollama when long_session_enabled.
Config keys
| Key | Usage |
|---|---|
exploit.attacker_os | _resolve_attacker_os branch |
exploit.allowed_targets | _resolve_allowed_targets |
memory.attack_memory_* | _load_attack_memory_settings |
memory.semantic_enabled / embedding_model | Semantic memory wiring |
memory.experience_min_samples / time_decay_days | ExperienceStore gating |
long_session.enabled / persist_messages | Checkpoint + num_ctx |
nmap.* | Not in loop; passed via recon |
swarm.parallel_enabled | Parallel briefing |
agent.capability_discovery_enabled | Capability-guidance prompt block |
Tests
| File | Verified | What it covers |
|---|---|---|
tests/test_agent_loop.py | yes | Happy path, budgets, blocked replan, approval, checkpoint hook (access/no_path) |
tests/test_long_session.py | yes | num_ctx, persist, max_rounds override |
tests/test_reasoning_loop.py | yes | _parse_reasoning_block + advisory refresh |
tests/test_ultrathink.py | yes | [REASONING] capture + sanitization |
tests/test_context_compaction.py | yes | _should_compact_context / _build_compacted_messages |