Pheromone weights are the mechanism by which Pentest Swarm AI avoids thrashing on old data and naturally focuses attention where it matters most. The name comes from ant-colony optimisation: ants lay chemical trails that other ants follow, and trails that lead nowhere evaporate over time. In Pentest Swarm AI, every finding on the blackboard starts with a pheromone weight and loses it exponentially as time passes. Agents only trigger on findings above a minimum pheromone threshold, so stale paths are pruned automatically — no garbage collection, no explicit expiry logic.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Armur-Ai/Pentest-Swarm-AI/llms.txt
Use this file to discover all available pages before exploring further.
How Pheromone Weighting Works
EveryFinding has two pheromone-related fields set at write time:
PheromoneBase— the initial weight, in the range[0.0, 1.0].HalfLifeSec— how many seconds it takes for the weight to halve.
Query or Subscribe delivery), the current weight is computed using exponential decay:
internal/swarm/blackboard/memory.go):
swarm_pheromone(pheromone_base, half_life_sec, age_seconds) SQL function is called at query time so the two implementations stay consistent.
The result is always in [0.0, 1.0]. Pheromone values are clamped to this range on write — see the security note below.
Pheromone Decay Per Finding Type
Each finding type has a tuned base weight and half-life. These are defined inconfig/pheromones.yaml (and embedded as internal/swarm/tuning/pheromones_default.yaml):
| Finding type | Base | Half-life | Tuning intent |
|---|---|---|---|
TARGET_REGISTERED | 1.0 | 24 hours | Root target stays hot for the entire engagement |
SUBDOMAIN | 0.7 | 2 hours | Newly discovered subdomains are interesting but not urgent |
PORT_OPEN | 0.8 | 1 hour | Open ports are actionable; decay once classified |
SERVICE | 0.8 | 1 hour | Service fingerprints have the same urgency as ports |
HTTP_ENDPOINT | 0.6 | 2 hours | Endpoints are durable but lower priority than ports |
HTTP_ENDPOINT_INTERESTING | 0.9 | 2 hours | Katana-flagged endpoints surface to downstream agents faster |
TECHNOLOGY | 0.5 | 2 hours | Tech fingerprints are durable but low urgency |
CVE_MATCH | 1.0 (overridden by severity) | 3 hours | Severity-aware override applied by the classifier |
MISCONFIGURATION | 0.6 | 1 hour | Misconfigs are important but lower than confirmed CVEs |
EXPLOIT_CHAIN | 0.9 | 1 hour | Attack paths stay hot while execution is in progress |
EXPLOIT_RESULT | 0.7 (success: 1.0) | 30 minutes | Results age out; successful exploits overridden to 1.0 |
SESSION | 0.8 | 15 minutes | Session tokens expire quickly in practice |
AGENT_ERROR | 0.3 | 10 minutes | Surface briefly for observability, then decay |
CAMPAIGN_COMPLETE | 1.0 | 5 minutes | Short-lived signal; only needs to fire the report agent once |
NUCLEI_TEMPLATE_DRAFT | 0.8 | 12 hours | Drafts stay interesting for a reviewer’s workday |
| (catch-all default) | 0.5 | 1 hour | Applied when a type is not listed above |
The
CVE_MATCH base weight in the YAML is 1.0, but the Classifier agent overrides it per-finding based on severity. Critical findings are written with PheromoneBase: 1.0 and a 6-hour half-life; Low findings get PheromoneBase: 0.4 and a 30-minute half-life. The YAML entry is a fallback only.Exploration Bias
The--exploration-bias flag applies a global multiplier to every pheromone base at write time, letting you steer the swarm between breadth-first and depth-first exploration without editing YAML:
| Flag value | Multiplier | Effect |
|---|---|---|
low | 0.7× | Lower base weights → agents require higher thresholds → depth-first, focused |
med (default) | 1.0× | No change to tuned values |
high | 1.3× | Higher base weights → more findings exceed trigger thresholds → breadth-first |
tuning.Settings.Lookup():
Pheromone Security
Pheromone weights are clamped to[0.0, 1.0] on every write. This is a deliberate defence against MINJA-style pheromone-flood attacks, where a compromised or buggy agent writes PheromoneBase=9999 to dominate all trigger predicates that rank by pheromone weight.
From internal/swarm/blackboard/memory.go:
PostgresBoard.Write. The hardening test at internal/swarm/blackboard/injection_test.go verifies that a write with PheromoneBase=9999 is stored as 1.0 and does not cause it to be preferentially dispatched over legitimately high-weight findings.
Additional hardening layers that interact with pheromone weights:
| Layer | Location | What it does |
|---|---|---|
| Pheromone clamp | blackboard.Write() | Caps [0, 1] at write time |
| MinPheromone gate | blackboard.Predicate | Agents only see findings above their threshold |
| MemoryGraft watchdog | internal/swarm/memorygraft | Flags burst writes, byte-identical payloads, type-mismatch |
| Per-agent rate limit | internal/swarm/ratelimit | Token-bucket cap per agent per second |
For a full writeup of the four-layer pheromone defence (clamp / provenance / heuristic detector / rate limit), see
docs/security/swarm-hardening.md in the repository. The document also covers Ed25519 agent provenance signing and the MINJA / MemoryGraft threat models from which these defences are derived.Pheromone Config File
The full pheromone configuration is atconfig/pheromones.yaml. You can override any type’s values or add new types without recompiling — the file is loaded at startup and the embedded defaults are used as a fallback:
config/pheromones.yaml relative to the working directory. The loader in internal/swarm/tuning/tuning.go falls back to the embedded defaults if the file is absent or if a specific type is not listed, so you only need to override the entries you want to change.