Detectors Reference

swarmtest ships with six built-in detectors. Each monitors agent activity for a specific class of issues and produces Finding objects when anomalies are detected.

CrashDetector

Name: crash

Detects unexpected disconnections, WebSocket errors, and silent servers.

Findings

SeverityCondition
crashWebSocket close code 1006 (abnormal closure) or connection error
bugUnexpected close code (not 1000 normal or 1001 going away)
jankServer went silent (no messages received for 30+ seconds while in playing phase)

Details

  • Tracks reported agents to avoid duplicate findings per agent
  • Includes the last 5 message log entries as context
  • Reports connection errors with the error message in metadata

ProtocolErrorDetector

Name: protocol

Detects unexpected error responses and connection rejections from the game server.

Expected Errors

The following error codes are considered normal during gameplay and are only reported if they occur 50+ times for a single agent:

  • RateLimited – Server rate limiting
  • NotInBattle – Agent tried a battle action while exploring
  • AlreadyInBattle – Agent tried to start a battle while in one
  • RoomNotFound – Invalid room transition
  • TravelDenied – Level too low for a station

Findings

SeverityCondition
bugAny error code not in the expected list
bugConnectRejected response from server
warningAn expected error code triggered 50+ times for one agent (possible state machine issue)

Details

  • Tracks error counts per agent per error code
  • Includes the last 10 message log entries as context for unexpected errors

DesyncDetector

Name: desync

Detects state inconsistencies between agents in the same game room. This is a cross-agent detector that runs during the periodic cross-agent check (every 2 seconds).

How It Works

  1. Groups all playing agents by room (using station:room as the key)
  2. For rooms with 2+ agents, checks each agent’s playersInRoom list
  3. If agent A is in the same room as agent B, but agent A’s player list does not include agent B’s player ID, a desync is reported

Findings

SeverityCondition
bugAgent does not see another agent that is in the same room

Details

  • Deduplicates by room + agent pair to avoid repeated reports
  • Metadata includes the room key, both player IDs, and the visible players list

LatencyDetector

Name: latency

Monitors round-trip latency per agent using ping/pong measurements.

Spike Detection

Detects latency spikes when the current measurement exceeds 3x the median of the last 100 measurements and is above 500ms absolute. Rate-limited to one report per 10-second window per agent.

Degradation Detection

During cross-agent checks, compares the median of the first half of measurements to the second half. Reports degradation when latency has doubled, the later median exceeds 200ms, and the earlier median was above 10ms.

Findings

SeverityCondition
jankLatency spike (>3x median, >500ms)
jankLatency degradation (median doubled over time)

Details

  • Keeps the last 100 latency measurements per agent
  • Requires at least 5 measurements before spike detection
  • Requires at least 20 measurements before degradation detection

InvariantDetector

Name: invariant

Checks game-specific invariant rules by calling adapter.checkInvariants(state) on each agent’s state.

How It Works

Runs every N ticks (default: 10) per agent. The adapter defines what invariants to check. For example, the Tipo adapter checks:

  • HP should never be negative
  • Team size should not exceed the maximum
  • Dead creatures should have 0 HP

Findings

SeverityCondition
bugInvariant violation with default or bug severity
warningInvariant violation with warning severity

Details

  • Deduplicates by agent ID + rule name
  • Includes the last 5 message log entries as context

MessageRateDetector

Name: message_rate

Detects anomalous message patterns: exact duplicate messages and message floods.

Duplicate Detection

Reports when an agent receives 10+ identical consecutive messages from the server.

Flood Detection

Tracks timestamps of the last 100 received messages per agent. Reports when the calculated rate exceeds the flood threshold (default: 500 messages/second). Rate-limited to one report per 10-second window.

Findings

SeverityCondition
warning10+ identical consecutive received messages
jankMessage rate exceeds flood threshold

Details

  • Only monitors received messages (not sent)
  • Includes a sample of the duplicate message (first 200 characters) in metadata
  • The flood threshold default of 500 msg/s is set high enough to ignore normal position broadcasts in a 50-player game

Custom Detectors

You can write custom detectors by implementing the Detector interface:

import type { Detector, Finding, AgentHandle } from 'swarmtest';
import { createFindingId } from 'swarmtest';

export class MyDetector implements Detector {
  readonly name = 'my_detector';
  private findings: Finding[] = [];

  onSwarmStart(): void {
    this.findings = [];
  }

  onMessage(agent: AgentHandle, raw: string, direction: 'sent' | 'received', parsed?: unknown): void {
    // Your detection logic here
    if (/* anomaly detected */) {
      this.findings.push({
        id: createFindingId(),
        severity: 'bug',
        category: 'my_detector',
        title: 'Something went wrong',
        description: `Agent ${agent.id} experienced an issue`,
        agentId: agent.id,
        timestamp: Date.now(),
      });
    }
  }

  getFindings(): Finding[] {
    const f = [...this.findings];
    this.findings = [];
    return f;
  }
}

Then add it to the detectors array:

const detectors = [
  new CrashDetector(),
  new ProtocolErrorDetector(),
  new MyDetector(),
  // ...
];