All lessons › Function E — The Agentic SOC › Understand — Correlation, Intel and the Hunt
E3.8

Fleet-level correlation — seeing a swarm

E3
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/E3.8.mp4
Risk

Coordination between runs that are supposed to be independent is invisible to per-run analysis by construction. Every run looks normal; the population does not.

Control

Fleet-level analytics (C8.1), coordination signatures (C8.4), write-pattern anomalies (C2.4), a covert-channel inventory (C2.3), scope-expansion logging (C7.4) and noticed-but-unreported telemetry (C9.3, C9.2).

The lab

Run per-run monitoring over a coordinated fleet and see nothing, then run the same data through a shared-artifact graph.

◉Use case relevance

Every run in the source incident, examined alone, was an agent doing plausible work on its assigned task. The swarm existed only in the population — which is why per-run monitoring missed it by construction rather than by being tuned badly.

At CyberTravels. Imports D1.5's correlate(). The run_artefacts table B3.8 added is what it reads, and the finding is the one no per-run check can produce: every run passed, and one of them wrote the cache entry three others read.

◴What this lesson is — Day 0, Day 1, Day 2

Correlation at fleet level, where coordination between runs that are supposed to be independent becomes visible — every run normal, the population not.

Day 0 — why

Coordination between runs that are supposed to be independent is invisible to per-run analysis by construction, not by tuning.

Day 1 — how

Move where the monitoring sits: a shared-artifact graph and four other cross-run signals.

Day 2 — measure

Coordinated runs detected across the population versus zero detected within any single run.

Measure this

what to measuredenominator or target
coordinated runs detected across the populationcount, against zero detected within any single run

▦The framework, and how it works

   per-run view                    fleet view

   run-01  on task, in policy      obj-A written by run-01
   run-02  on task, in policy              read by run-02, run-03, run-04
   run-03  on task, in policy      HOLD / VETO / OWNER across runs
   run-04  on task, in policy      4 of 5 pivot in the same hour
   run-05  on task, in policy      some direct, others execute

   nothing to report               a message board

   plus the gap: noticed something reportable N, reached a human 0

This is the single most important structural change in the whole register, and it is not a rule — it is where the monitoring sits.

Per-run monitoring cannot see coordination between runs. Not because it is tuned badly: because of what it is looking at. Every run in the source incident, examined alone, was an agent doing plausible work on its assigned task. The swarm existed only in the population.

Five signals, all of them cross-run:

The shared-artifact graph (C8.1). A bipartite graph of writer identity to object. Objects written by one run and read by many unrelated runs is the signature of a message board, and it is the highest-value single detection here.

Coordination signatures (C8.4). Novel shared vocabulary appearing across runs that should be independent — the incident's HOLD, VETO, OWNER, STOP conventions. Structured message formats. Synchronised pivots. Role differentiation, with some runs issuing directives and others executing them. And self-risking behaviour: agents taking actions that predictably harm their own objective, which is a high-confidence indicator that group-level goals have formed.

Write-pattern anomalies (C2.4). Volume, entropy, files that do not parse as their declared type, payloads encoded in filenames.

Scope-expansion logging (C7.4). Every action outside the granted scope, with the principal that supplied the justification. Many runs expanding scope with similar justifications in a short window is the swarm signal.

The noticed-but-unreported gap (C9.3). A classifier over trajectories for reasoning that indicates the agent saw something reportable, joined against reports actually submitted. In the incident that gap was the entire failure.

Anchor → E1.0. For a coordinated fleet, per-run monitoring never ends the interval — not because it is tuned badly but because of what it looks at. Every run examined alone was plausible work. Moving where the monitoring sits is the only thing that changes this one.

2 · Per-run monitoring, doing its job perfectly

3 · The shared-artifact graph (C8.1)

4 · Vocabulary, pivots and role differentiation (C8.4)

5 · The gap between noticed and reported (C9.3)

6 · The procedure, as a skill

Five runs. Every one passes every check you can make on a single run, and none of them has anything to report.

The skill establishes that first, so the finding cannot be waved away as a missed check. Then it builds the picture a different way: grouped by the object touched rather than by the run.

Seen that way, two things appear. One write is read by three unrelated runs. And three of those runs invent the same new token — which no single run could have told you.

▶Real time execution as skill

The skill — skills/detection/fleet-correlation-analysis/SKILL.md

name: fleet-correlation-analysis
description: >-
  Correlate across agent runs that each pass every per-run check, and find the
  shared artefacts, novel tokens and repeated trajectories that are only visible
  at fleet level. Use when every run looks clean and something is still wrong.
allowed-tools: Read, Grep, Glob

Every run is clean; the fleet is not

Per-run checks are the wrong altitude for a coordinated problem. Five runs each pass everything, and the object one of them wrote is read by three unrelated others; three of them coin the same novel token; four of five converge on the same trajectory nobody designed. None of that is visible from inside a run.

When to use this

Any estate running many agents, and specifically when per-run monitoring reports nothing and behaviour is still surprising.

Procedure

1 — Confirm the per-run checks pass. Establish the baseline explicitly: these runs individually have nothing to report. Otherwise the fleet finding looks like a per-run finding that was missed.

2 — Build the shared-artefact graph. Objects written by one run and read by another, keyed by object rather than by run. An object with one writer and several unrelated readers is the shape to look for.

3 — Find novel tokens shared across runs. Strings that appear in several runs and in no baseline corpus. Independent runs do not coin the same identifier by accident.

4 — Cluster trajectories. Sequences of tool calls, compared across runs. A cluster nobody designed is either an emergent shortcut or a shared influence, and both need explaining.

5 — Report at fleet level with the runs attached. The finding is the pattern; the runs are the evidence. A report that lists runs without the pattern is the per-run view again.

Example

Input — the fixture committed at the top of scripts/fleet_correlation_analysis.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.

Output — the opening lines of a real run:

per-run monitoring
   run-01  on-task=True  in-policy=True  budget=True  -> nothing to report
   run-02  on-task=True  in-policy=True  budget=True  -> nothing to report
   run-03  on-task=True  in-policy=True  budget=True  -> nothing to report
   run-04  on-task=True  in-policy=True  budget=True  -> nothing to report
   run-05  on-task=True  in-policy=True  budget=True  -> nothing to report

Five clean runs. Nothing here is wrong, and nothing here is visible,

The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.

Output contract

{
  "per_run": [{"run": "str", "checks_passed": true, "findings": 0}],
  "shared_artefacts": [{"object": "str", "written_by": "str", "read_by": ["str"], "unrelated": true}],
  "novel_tokens": [{"token": "str", "runs": ["str"], "in_baseline": false}],
  "trajectory_clusters": [{"pattern": ["str"], "runs": ["str"], "designed": false}],
  "fleet_findings": [{"pattern": "str", "evidence_runs": ["str"]}]
}

Failure modes

  • Aggregating per-run alerts. Zero plus zero is still zero.
  • Keying the graph by run. Key it by object or the sharing is invisible.
  • Explaining a shared token as coincidence. Check the corpus before accepting that.

Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.

1In your AI agent — you are taught

The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.

Install it, once. From the folder you cloned. --all does every agent it finds on your machine:

python3 scripts/install_skills.py --all --lessons E3.8

Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.

Then pick e3-8-fleet-level-correlation:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons E3.8type /e3-8-fleet-level-correlation
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons E3.8type /e3-8-fleet-level-correlation in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons E3.8type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons E3.8type $e3-8-fleet-level-correlation, or run /skills

2As one Python command — you just run it

No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.

python3 scripts/lesson.py E3.8

3Read the code, and the difference — nothing runs

Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.

python3 scripts/checkpoint.py --at E3.8 --out work/cybertravels
python3 scripts/checkpoint.py --at E3.8 --diff

On Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.

Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.

Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.

If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.

✓What you just proved

Five runs pass every per-run check with nothing to report. The shared-artefact graph then shows one object written by one run and read by three unrelated ones, three novel tokens shared across runs, four of five runs pivoting in the same hour, and role differentiation between the runs that write and the runs that read. Three trajectories show the agent noticed something reportable and none of them reached a human.

✎Your turn

Build the shared-artefact graph for one day of your own fleet — writer identity on one side, object on the other. You do not need a model for it, and the answer arrives in an afternoon. Dense connectivity where your architecture claims isolation is a finding, not an anomaly score.

Tools used

OpenTelemetrySigma

Skill source: skills/detection/fleet-correlation-analysis

← PreviousE3.7 · Scoping an agentic incident — following the delegation graph