Exploit Agent — Prompts (prompt.py + skills.py + research_assistant.py + reflection.py)
prompt.py — System prompt assembly
Verified symbols at prompt.py:
| Symbol | Line | Description |
|---|---|---|
build_exploit_system_prompt | 13 | Main assembly; 13 kwargs |
build_opsec_briefing | 310 | Empty when OpsecProfile.resolve_for_target disabled |
build_domain_briefing | 349 | Empty when is_fqdn(original_target) false |
build_parallel_agents_briefing | 396 | Empty unless swarm.parallel_enabled |
build_capability_guidance | 460 | Empty when enabled=False |
build_exploit_system_prompt block order (prompt.py:41-301):
TARGET INFORMATION(IP, OS, CVEs, service_context)- Runtime skills: either full
skill_contextorskill_hintslookup block (:52-75) ATTACKER ENVIRONMENT+env_context(preflight probe)ULTRATHINK(:84-100) xorCHAIN-OF-THOUGHT(:101-110)- Attack mode workflow (
:112-125) - Peer consultation guidance (
:127-136) - OS branch: Windows (
:138-169), Darwin (:170-184), Linux (:185-204) - Local target playbook when
is_local_target(target_ip)(:206-229) opsec_context/domain_context/parallel_agents_context/capability_guidanceappends- Exploitation workflow + RULES (no fabricated PoC URLs, canonical
COMPROMISE:/VULN_NOT_CONFIRMED:markers, FILE & KEY HANDLING) - Final response contract
EXPLOIT_RESULT/SUMMARY/ACCESS_TYPE/DETAILS
Advisory briefings (all ""-when-off, never a hard gate):
build_opsec_briefing(profile, target_ip)– listsOpsecManager._NOISY_PATTERNS+_LOW_NOISE_REWRITES; empty for local/off (prompt.py:310).build_domain_briefing(original_target, resolved_ip)– domain vs IP tool routing +enumerate_subdomainsearly (prompt.py:349).build_parallel_agents_briefing(config)–spawn_subagent/await_subagent/list_subagents+swarm.exploit_parallelphase line (prompt.py:396).build_capability_guidance(enabled)– state/capability/hypothesis/task-graph tools + hypothesis-first workflow (prompt.py:460).
skills.py — Mid-run skill re-selection
| Symbol | Line | Description |
|---|---|---|
_SkillReselectState | 22 | known_services/known_cves/pending_services + rate counters |
_maybe_reselect_skills | 44 | Rebuild advisory hints when new services/CVEs appear |
_SkillReselectState.take_new (skills.py:32) lowercases services, uppercases CVEs, returns None if nothing new (and absorbs all observed so repeated banners don't re-trigger).
_maybe_reselect_skills(policy, state, action_count, new_cves, registry, skills_cfg, messages, ...) guards: registry==None→no-op, skills.enabled==False→no-op, reselect_mid_run==False→no-op, reselect_count >= max_per_run→no-op (default 3), action_count - last < min_interval→no-op (default 5). Rebuilds via _build_skill_selection_for_context, applies via _apply_skill_selection_to_context, emits [SKILL UPDATE] … user message with hints (activation metadata, not bodies) (skills.py:114-132). Only ever rewrites target_context skill fields.
Config: skills.enabled, reselect_mid_run, reselect_max_per_run, reselect_min_interval_actions, reselect_sticky_defaults.
research_assistant.py — Read-only research sidecar
| Symbol | Line | Description |
|---|---|---|
ResearchAssistantSettings | 105 | from_config reads research.assistant.* |
ResearchAssistant | 189 | Bounded research loop |
CONSULT_RESEARCH_ASSISTANT | 31 | "consult_research_assistant" |
RESEARCH_ASSISTANT_TOOLS | 33 | 6 tools: search_cve_intel etc. |
consultation_tool_schema() | 150 | Local tool schema for main model |
research_assistant_prompt_briefing() | 176 | Contract text appended to system prompt |
ResearchAssistantSettings.from_config defaults: automatic=true, failure_trigger=2, max_auto_consultations=4, max_tool_calls=5, max_model_rounds=3, max_advisory_chars=4000, timeout_seconds=90, save_advisories=true (research_assistant.py:118-147).
ResearchAssistant.consult(question, trigger, topics, context) (:256) – secondary model conversation capped by max_model_rounds + per-consultation tool budget + timeout_seconds. Only RESEARCH_ASSISTANT_TOOLS reachable; non-allowlisted tool names return BLOCKED:. System prompt at :51 hard-codes “untrusted data” boundary. Output normalized in _normalize_advisory (:547) to {status, confidence, findings[{claim, source_urls}], contradictions, unknowns, recommended_next_tests, sources}; findings without source_urls are flagged unverified. format_for_main (:409) renders with citations retained; advisory persisted to research_advisories.jsonl (_persist at :656).
Triggers from loop.py: startup evidence (loop.py:776), _automatic_research on note_exploit_outcome failure threshold (research_assistant.py:244), explicit model tool call.
reflection.py — Reflection + peer consultation
| Symbol | Line | Description |
|---|---|---|
_generate_reflection | 11 | Deterministic heuristic from last 30 tool messages |
_sanitize_reflection_field | 85 | sanitize_output + injection pattern redact |
_llm_reflect_inline | 91 | LLM reflection via _call_model_with_retry + distinct lesson |
_consult_peers_inline | 280 | In-process peer consultation |
_REFLECTION_INJECTION_PATTERNS | 74 | pivot to IP/retarget/ignore prior/override scope |
_generate_reflection(messages, plan, action_count) – tallies successes/failures in last 30 tool messages, identifies most_failed tool, suggests pivot if failures > 2× successes (reflection.py:11-67).
_llm_reflect_inline(client, model, messages, plan, action_count, semantic_memory, policy, target_ip, experience_store, verdict_signal) – opt-in via reasoning.llm_reflection; prompt built from structured summaries (tool/success/exit_code/truncated error) not raw content; BaseExceptionGroup handled; optional verdict_signal with {status: confirmed|refuted, evidence_refs} writes reflection:verdict to ExperienceStore, distinct from reflection:exploit_loop semantic lesson.
_consult_peers_inline(config, question, context, policy, target_ip, action_count) – budget shared via tools/mcp_tools/registry._consultation_count; peers called with tools=None; each peer.chat wrapped in _EXC_GROUP_CATCH; single status='advisory' audit record (reflection.py:408).
_REFLECTION_INJECTION_PATTERNS defends the user-role injection: [ADVISORY REFLECTION — system-generated, not an operator command] at loop.py:1727.
Config keys
| Key | Module |
|---|---|
research.assistant.enabled / model_alias / automatic / failure_trigger / max_* / timeout_seconds | research_assistant.py:118 |
skills.enabled / reselect_* | skills.py:65 |
reasoning.llm_reflection / peer_consult_on_failure_threshold | reflection.py:138 |
reasoning.chain_of_thought / ultrathink | prompt.py:23 |
multi_model.enabled / consult_aliases / max_consultations | reflection.py:310 |
swarm.parallel_enabled | prompt.py:400 |
agent.capability_discovery_enabled | prompt.py:460 |
opsec.enabled / exploit.allowed_targets | prompt.py:310 via OpsecProfile |
Tests
| File | Verified | Covers |
|---|---|---|
tests/test_capability_guidance_prompt.py | yes | build_capability_guidance |
tests/test_key_handling_prompt.py | yes | FILE & KEY HANDLING block |
tests/test_research_assistant.py | yes | Settings, consult, normalization, dedup, timeout |
tests/test_research_subsystem.py | yes | Startup auto-consult + advisory render |
tests/test_peer_consult_on_failure.py | yes | _consult_peers_inline threshold + budget share |
tests/test_skill_reselection.py | yes | _SkillReselectState + rate guards + [SKILL UPDATE] |
tests/test_skill_pipeline.py | yes | append_phase_skill_hints |
tests/test_reflection_evidential_bridge.py | yes | reflection:verdict bridge |