Skip to main content

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.

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.

How Pheromone Weighting Works

Every Finding 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.
At read time (every Query or Subscribe delivery), the current weight is computed using exponential decay:
pheromone(t) = PheromoneBase × 0.5 ^ (age_in_seconds / HalfLifeSec)
In Go (from internal/swarm/blackboard/memory.go):
func (b *MemoryBoard) pheromone(f Finding) float64 {
    if f.HalfLifeSec <= 0 {
        return f.PheromoneBase
    }
    age := b.now().Sub(f.CreatedAt).Seconds()
    return f.PheromoneBase * math.Pow(0.5, age/float64(f.HalfLifeSec))
}
In the Postgres backend, the equivalent 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 in config/pheromones.yaml (and embedded as internal/swarm/tuning/pheromones_default.yaml):
Finding typeBaseHalf-lifeTuning intent
TARGET_REGISTERED1.024 hoursRoot target stays hot for the entire engagement
SUBDOMAIN0.72 hoursNewly discovered subdomains are interesting but not urgent
PORT_OPEN0.81 hourOpen ports are actionable; decay once classified
SERVICE0.81 hourService fingerprints have the same urgency as ports
HTTP_ENDPOINT0.62 hoursEndpoints are durable but lower priority than ports
HTTP_ENDPOINT_INTERESTING0.92 hoursKatana-flagged endpoints surface to downstream agents faster
TECHNOLOGY0.52 hoursTech fingerprints are durable but low urgency
CVE_MATCH1.0 (overridden by severity)3 hoursSeverity-aware override applied by the classifier
MISCONFIGURATION0.61 hourMisconfigs are important but lower than confirmed CVEs
EXPLOIT_CHAIN0.91 hourAttack paths stay hot while execution is in progress
EXPLOIT_RESULT0.7 (success: 1.0)30 minutesResults age out; successful exploits overridden to 1.0
SESSION0.815 minutesSession tokens expire quickly in practice
AGENT_ERROR0.310 minutesSurface briefly for observability, then decay
CAMPAIGN_COMPLETE1.05 minutesShort-lived signal; only needs to fire the report agent once
NUCLEI_TEMPLATE_DRAFT0.812 hoursDrafts stay interesting for a reviewer’s workday
(catch-all default)0.51 hourApplied 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 valueMultiplierEffect
low0.7×Lower base weights → agents require higher thresholds → depth-first, focused
med (default)1.0×No change to tuned values
high1.3×Higher base weights → more findings exceed trigger thresholds → breadth-first
# Focus the swarm on what it already has (depth-first)
pentestswarm scan example.com --scope example.com --swarm --exploration-bias low

# Drive the swarm to cover as much surface as possible (breadth-first)
pentestswarm scan example.com --scope example.com --swarm --exploration-bias high
The multiplier is applied in tuning.Settings.Lookup():
func (s *Settings) Lookup(t blackboard.FindingType) (base float64, halfLifeSec int) {
    if e, ok := s.Types[string(t)]; ok {
        return e.Base * s.bias, e.HalfLifeSec
    }
    // ... default fallback
}
The half-life is not scaled by the bias — only the base weight is. This keeps decay curves predictable regardless of bias setting.

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:
// Clamp pheromone to [0, 1]. Defends against MINJA-style memory
// injection where a malicious caller writes PheromoneBase=9999 to
// dominate trigger predicates that rank by pheromone.
if o.pheromoneBase > 1.0 {
    o.pheromoneBase = 1.0
}
if o.pheromoneBase < 0 {
    o.pheromoneBase = 0
}
The same clamping is present in 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:
LayerLocationWhat it does
Pheromone clampblackboard.Write()Caps [0, 1] at write time
MinPheromone gateblackboard.PredicateAgents only see findings above their threshold
MemoryGraft watchdoginternal/swarm/memorygraftFlags burst writes, byte-identical payloads, type-mismatch
Per-agent rate limitinternal/swarm/ratelimitToken-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 at config/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
#
# Higher `base`  = downstream agents act sooner
# Shorter `half_life_sec` = stale findings decay faster
# --exploration-bias multiplies base at write time: low=0.7×, med=1.0×, high=1.3×

types:
  TARGET_REGISTERED:
    base: 1.0
    half_life_sec: 86400    # 24h

  SUBDOMAIN:
    base: 0.7
    half_life_sec: 7200     # 2h

  PORT_OPEN:
    base: 0.8
    half_life_sec: 3600     # 1h

  HTTP_ENDPOINT:
    base: 0.6
    half_life_sec: 7200

  HTTP_ENDPOINT_INTERESTING:
    base: 0.9
    half_life_sec: 7200

  TECHNOLOGY:
    base: 0.5
    half_life_sec: 7200

  CVE_MATCH:
    base: 1.0               # overridden per-finding by severity in ClassifierAgent
    half_life_sec: 10800    # 3h

  MISCONFIGURATION:
    base: 0.6
    half_life_sec: 3600

  EXPLOIT_CHAIN:
    base: 0.9
    half_life_sec: 3600

  EXPLOIT_RESULT:
    base: 0.7               # bumped to 1.0 on success inside the exploit agent
    half_life_sec: 1800

  SESSION:
    base: 0.8
    half_life_sec: 900      # 15m

  AGENT_ERROR:
    base: 0.3
    half_life_sec: 600      # 10m

  CAMPAIGN_COMPLETE:
    base: 1.0
    half_life_sec: 300

  NUCLEI_TEMPLATE_DRAFT:
    base: 0.8
    half_life_sec: 43200    # 12h

# Catch-all for types not listed above
default:
  base: 0.5
  half_life_sec: 3600
To use a custom pheromone file, place it at 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.

Build docs developers (and LLMs) love