# Fifty-Three Agents Before Lunch

> Every coding agent on your laptop keeps a record of what you asked it and what it did. BotSpy reads those records from Claude Code, Codex, Cursor, Antigravity and Grok into one schema. The first thing it showed me was what one Saturday morning of my own work actually looked like: 19 agents working…

By Ryan Porter · Published 2026-11-02

Canonical: https://dataparade.io/articles/fifty-three-agents-before-lunch/

Our weekly Claude Code credits reset at noon on Saturday, and on the morning of Saturday 31 October we still had a lot of them left. Letting them expire felt like a waste, so I spent the morning in a blitz, handing every ticket I could find to an agent before the clock ran out. By 8:52 AM there were 19 agents working at once. I knew I was running a lot in parallel. I did not know it was 19, because nothing I use shows me that number.

The data was already on my machine. Claude Code, Codex, Cursor, Antigravity and Grok each keep a local history of every conversation: your messages, the agent's replies, its tool calls, the sub-agents it started. Each tool stores it differently, in JSON lines, SQLite or per-session folders, and none of them is meant to be read by anything but the tool itself.

BotSpy is a small Rust tool that reads those histories into one common schema: sessions, messages, tool calls, sub-agents and forks. It is open source under the MIT licence ([DataParade-io/BotSpy](https://github.com/DataParade-io/BotSpy)) and on crates.io as `botspy`.

## What it does, and what it never does

BotSpy only reads. It never writes to an agent's store, and it reads live SQLite databases through a private snapshot copy so it cannot interfere with a tool that is running. Those are properties of the code, not intentions; you can check both in the source.

The command line covers the basics:

- `botspy sources` lists the agent histories it found on this machine, and `botspy doctor` says why one it expected is missing.
- `botspy sessions` and `botspy show` list and display sessions in the common schema.
- `botspy scan` imports everything into a local store, and `botspy search` and `botspy stats` query it.

The interesting part is what you can build once all of that history has one shape. The repository has examples for three: a timeline, a meter and a privacy audit.

## The timeline

::image{src="assets/draft-unsectioned-4b5d99369d81c5da/botspy-1.png" srcDark="assets/draft-unsectioned-4b5d99369d81c5da/botspy-1.dark.png" alt="Timeline of 53 Claude Code sessions in five projects on Saturday 31 October, 6:30 AM to 12:30 PM Eastern. A chart at the top counts agents working at once, rising to a peak of 19 at 8:52 AM. Below it, each session has a lane of bars for working turns and dotted lines for waiting, with sub-agent lanes nested under the sessions that started them." caption="One Saturday morning: 53 sessions, 44 of them sub-agents, peaking at 19 working at once."}

The figure is Saturday from 6:30 AM to 12:30 PM Eastern: 53 Claude Code sessions across five projects. The strip at the top counts agents working at once. Below it, each session has a lane, grouped by project. A bar is an agent working on a turn, from my message to its last reply. A dotted line is the agent waiting for me. The purple connectors mark where a session spawned a sub-agent, which then gets its own lane under its parent.

44 of the 53 sessions are sub-agents: I started nine sessions, and they handed the tickets out.

Two details are deliberate. Each agent platform gets a pattern as well as a colour, because I am colourblind and a chart that relies on hue alone does not work for me. And the timeline never shows session titles or message text. Titles are written by the agent from what you asked, so they can repeat a person's name, a customer or a project you would not put on a chart.

To draw your own, from a BotSpy checkout at v0.4.0:

```
cargo run --release --example timeline -- \
  --since 2026-10-31T10:30:00Z --until 2026-10-31T16:30:00Z \
  --source claude_code --no-ids --png timeline.png <data-root>
```

`<data-root>` is a folder of links to the agents' history folders, such as `claude_code/projects` pointing at `~/.claude/projects`. Add `--theme dark` for the dark version. Without a data root, the example draws BotSpy's bundled synthetic sessions, so you can try it before pointing it at your own.

## Every platform at once

::image{src="assets/draft-unsectioned-4b5d99369d81c5da/botspy-2.png" srcDark="assets/draft-unsectioned-4b5d99369d81c5da/botspy-2.dark.png" alt="Timeline of 75 sessions in four projects on 8 September, 10 AM to 4 PM Eastern, across Claude Code (diagonal stripes), Cursor (horizontal lines) and Antigravity (vertical lines). A chart at the top counts agents working at once, peaking at 9 just after 10 AM. Chattic.us has 63 sessions, with Cursor and Claude Code lanes side by side and dozens of short dashed Cursor sub-agent bars marked approximate. One Cursor session and one Antigravity session, each in a project of its own, run through most of the day." caption="One weekday with three agent products: 75 sessions, peaking at 9 working at once."}

This is a six-hour slice of 8 September, 10 AM to 4 PM Eastern, when Claude Code, Cursor and Antigravity were all running: 75 sessions in four projects, peaking at 9 agents at once. Each agent product has its own pattern. The Chattic.us project was open in both Cursor and Claude Code, and BotSpy groups it as one project because both agents were working in the same folder. The many short dashed bars are Cursor command-line sub-agents. Their transcripts carry no timestamps, so BotSpy places them by file time and marks them as approximate.

The same example draws every agent when you leave out `--source`:

```
cargo run --release --example timeline -- \
  --since 2026-09-08T14:00:00Z --until 2026-09-08T20:00:00Z \
  --no-ids --png timeline.png <data-root>
```

## A meter for your own manners

::image{src="assets/fifty-three-agents-before-lunch/botspy-good-day.png" srcDark="assets/fifty-three-agents-before-lunch/botspy-good-day.dark.png" alt="Bar chart of one day, 20 October, by the hour in Eastern time. Thanks rise above the axis and F-bombs, hatched, hang below it. There are 2 thanks at 3 PM, 2 at 7 PM and 4 at 8 PM, and 2 F-bombs at 9 PM." caption="A good day, picked on purpose: 8 thanks and 2 F-bombs on 20 October."}

The meter example counts how many of my own messages to agents say thanks and how many contain an F-bomb. It is a keyword classifier and nothing more. It counts; it does not show any message. Above is one of my better days, hour by hour.

The count gets more useful over time, because it works as a rough measure of how the day went. When I swear at an agent, something usually is not working.

::image{src="assets/fifty-three-agents-before-lunch/botspy-every-day.png" srcDark="assets/fifty-three-agents-before-lunch/botspy-every-day.dark.png" alt="Bar chart of every day from 24 August to 1 November. Thanks rise above the axis and F-bombs, hatched, hang below it. Most days have small bars or none. F-bombs spike to 21 on 6 September, 12 on 25 September, 10 on 1 October, 8 on 10 October and 10 on 18 October. Thanks peak at 8 on 20 October." caption="Every day for ten weeks: the frustrating days stand out."}

Most days are quiet, and the bad ones are easy to see: 21 F-bombs on 6 September, 12 on 25 September, and 10 each on 1 and 18 October. The five worst days account for 61 of the 115 F-bombs on this chart. That gives me a list of dates to go back to and ask what went wrong.

Across all my history, 24 August to 1 November, I typed 69 thanks and 149 F-bombs: about two F-bombs for every thanks. The chart shows 56 and 115 of them, the ones with a timestamp, because Cursor's command-line agent does not record when a message was sent.

| Agent | Sessions | Thanks | F-bombs |
| --- | --- | --- | --- |
| Claude Code | 2,892 | 49 | 68 |
| Cursor (editor and command line) | 992 | 18 | 62 |
| Antigravity | 86 | 2 | 19 |
| Codex | 8 | 0 | 0 |

The first version of the meter said the opposite: two thanks for every F-bomb. Agents store text they add themselves under the user's name. In Claude Code that is skill instructions, summaries of earlier conversation and messages between sessions; of 17,796 "user" messages, I typed 5,711. Cursor's editor had simulated 1,723 of its 3,330. If you count anything in these histories, check who actually wrote the message first.

```
cargo run --release --example meter -- --by day --utc-offset -4 \
  --since 2026-08-24T04:00:00Z --until 2026-11-02T03:59:59Z --png days.png <data-root>
cargo run --release --example meter -- --by hour --utc-offset -4 \
  --since 2026-10-20T04:00:00Z --until 2026-10-21T03:59:59Z --png day.png <data-root>
```

Without `--png` it prints the counts as text.

## The history is full of secrets

Here is the problem with all of this. Agent histories are where people paste things. API keys to get a call working, a `.env` file to debug configuration, a database URL, a customer's email from a support ticket. The agent reads them, and the history keeps them. A tool that makes that history easy to mine also makes those secrets easy to move into whatever you build on top: a dashboard, a dataset, a fine-tuning run.

So BotSpy's examples do not read raw text. They read text that has been through [LeftBlock](https://github.com/DataParade-io/LeftBlock), our open source secret and personal-data scanner (on PyPI as `leftblock`). LeftBlock started as a pre-commit gate built on gitleaks rules, with an optional deep scan that uses OpenAI's Privacy Filter model. Version 0.9.0 added a streaming mode for exactly this: `leftblock scan --jsonl` reads `{id, text}` records on standard input and returns findings as byte ranges with a rule and a kind. It never returns the matched value. It exits 0 when clean, 1 when it found something and 2 on error.

BotSpy's `examples/privacy` sends every text-bearing part of every session through it: messages, the agent's thinking, tool results and the arguments of tool calls. `audit` reports what it found by rule and kind. `redact` replaces each range with `[REDACTED:<rule>]`. The meter runs only on redacted text, and transcript text is never written to disk. Those are enforced in the example's code, not left to whoever runs it.
How much does each layer catch? We measured it with LeftBlock 0.6.0 on Samsung's CredData dataset (commit `fc9b64bed812`), a labelled set of credentials in real repositories whose values have been replaced with same-shape fakes. A *critical* secret here is a line labelled as a cloud key, private key, provider token, password or authentication header. The model is too slow to read all 19 million lines, so every detector ran on the same windows: all 5,884 critical lines plus a stratified random sample of 1,095 others, five lines either side, about 4.9 MB of text. Rates are weighted back to the full set.

| Detector | Critical-secret recall (95% interval) | Precision |
| --- | --- | --- |
| Gitleaks, stock rules | 29.8% (28.7–31.0) | |
| Gitleaks with LeftBlock's rules | 31.6% (30.4–32.8) | 81.1% |
| Privacy Filter model alone | 83.8% (82.9–84.7) | 95.5% |
| LeftBlock rules and the model together | 85.8% (84.9–86.7) | 90.3% |

The rules miss the secrets that have no fixed shape. They found 8.5% of the passwords and 15.6% of the authentication headers; the model found 78.1% and 91.5%. Private keys, which do have a shape, were at 94.1% with rules alone and 99.7% with both. Precision with LeftBlock's rules is lower partly by design: they also flag personal data such as email addresses, which a credentials-only label set counts as a false positive.

The first time we ran OpenAI's Privacy Filter over our own work, it found a real service password that our existing rules had missed. We replaced it with a generated secret and confirmed the old one no longer opens anything.

To repeat it, from a clone of LeftBlock:

```
python3 -m venv .venv-eval && . .venv-eval/bin/activate
pip install -e . -r eval/requirements.txt
python -m eval check
python -m eval reproduce
```

`--skip-deep` runs only the rules half. The model half took about 80 minutes on a laptop CPU. Versions are pinned in `eval/pins.py`, and the published results are in `eval/results/0.6.0.md`. LeftBlock has changed since 0.6.0 (0.8.1 added checksum validation for one kind of private key), and the evaluation has not been re-run on 0.9.0.

Neither layer catches everything, which is why the examples redact before they analyse rather than trusting anyone to remember not to look.

## What to check in your own setup

If you build anything on agent histories, yours or your team's, hold it to one property: **no transcript text leaves the machine, or reaches storage, without passing a secret scanner first.** Then check the inputs you forgot about. Tool-call arguments and tool results are where the agent pastes the files it read, so they can hold secrets that no message mentions. We have not measured how often.

## Limits

- Cursor's editor does not record which conversation started a sub-agent, so on Cursor sub-agents are not linked to their parents yet.
- Cursor's command-line transcripts have no timestamps, so those sessions are drawn as approximate spans from file times, with a dashed outline.
- The meter is a toy keyword classifier. It will miss sarcasm and count "thanks, but no".
- The recall figures are for one dataset and LeftBlock 0.6.0. Precision was measured on CredData's hard negatives, not on code in general. CredData's fake values also break the shape of many AWS key ids, which overstates how many of those the rules miss: on the 102 that kept a valid shape, stock gitleaks found all of them. Your secrets will not look like CredData's, and 85.8% means some get through. Redaction lowers the risk; it does not remove it.
- The model scan is optional, slow on a CPU, and never runs in LeftBlock's commit hook.
- The agent formats change without notice. BotSpy reads what each agent stores today, and I expect to fix a reader every few weeks.

Versions: BotSpy 0.4.0 for the figures, examples and counts, plus the meter's `--by` and `--utc-offset` options, which came after it and are on the `develop` branch; LeftBlock 0.9.0 for the redaction; LeftBlock 0.6.0 for the recall measurements.
