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
| Severity | Condition |
|---|---|
crash | WebSocket close code 1006 (abnormal closure) or connection error |
bug | Unexpected close code (not 1000 normal or 1001 going away) |
jank | Server 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 limitingNotInBattle– Agent tried a battle action while exploringAlreadyInBattle– Agent tried to start a battle while in oneRoomNotFound– Invalid room transitionTravelDenied– Level too low for a station
Findings
| Severity | Condition |
|---|---|
bug | Any error code not in the expected list |
bug | ConnectRejected response from server |
warning | An 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
- Groups all playing agents by room (using
station:roomas the key) - For rooms with 2+ agents, checks each agent’s
playersInRoomlist - 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
| Severity | Condition |
|---|---|
bug | Agent 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
| Severity | Condition |
|---|---|
jank | Latency spike (>3x median, >500ms) |
jank | Latency 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
| Severity | Condition |
|---|---|
bug | Invariant violation with default or bug severity |
warning | Invariant 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
| Severity | Condition |
|---|---|
warning | 10+ identical consecutive received messages |
jank | Message 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(),
// ...
];