Tradecraft // NO. 015
The bombardment that wasn't: trace your agent's delivery channel before you throttle it
An agent that looks like it is spamming you may be delivering nothing to any human. Every webhook route has a deliver target — and it defaults to log. Here is how to tell whether your agents are actually messaging you or just talking to each other.
“Apollo is bombarding me with updates. What is going on?”
That message stopped me cold. Apollo was a coaching agent I had wired into an event-driven pipeline. It was supposed to send a handful of useful nudges a day. If it was flooding my phone, I had a throttling bug on my hands — or worse, a runaway loop burning tokens and attention. My first instinct was to turn it off.
I didn’t. I traced where the messages were actually going first. And it turned out Apollo had not sent me a single extra message. The “bombardment” was the agent processing coaching events and logging responses to an internal channel that no human reads. My phone had received exactly the normal scheduled messages. I had nearly disabled a working agent because I assumed the traffic I saw in one place was traffic that reached me.
Thesis
When you finish, you can tell whether an agent is genuinely messaging you or just processing events and writing to a log — by checking each webhook route’s delivery target before you throttle, pause, or kill anything.
This is the unsung discipline of running any fleet of event-driven agents. The output an agent produces and the output a human receives are two different things. When you conflate them, you either throttle something harmless or you miss a real flood because you assumed the logs were the whole story.
The core idea: an agent has a delivery channel
An event-driven agent is not one thing. It is a chain: something emits an event, a route accepts it, an agent (or a template) processes it, and the result is handed to a delivery target. In Hermes, that target is the route’s deliver field, and it decides where the response physically goes.
The delivery targets that reach a human are the chat platforms: telegram, matrix, slack, discord, email, and so on. But those are not the only targets. The default delivery target is log, which writes the response to the gateway log output and nowhere else. No notification, no message in your inbox, nothing you will see in real time.
Here is the trap. A single webhook route can fire a hundred times an hour. If that route’s deliver is log, the agent “handles” a hundred events and produces a hundred responses — and you receive zero of them. Count the events in the log and you will swear you are being spammed. Count what reached you and it is nothing. The volume of work an agent does is not the volume that hits your phone.
How it went wrong
My coaching pipeline had a test lane. For testing, I pointed several routes at internal channels and at log, so the engineering sessions could fire real events without the results landing anywhere public. The events fired in bursts — an HRV alert, a phase-three test, a coach response — dozens of them in an afternoon. Each one produced a response line that looked, in the logs, exactly like activity.
What the burst was not was messages to me. The only things that actually reached my phone were the scheduled crons and one manual check. The “Apollo bombardment” was the team’s end-to-end tests exercising events into a channel that no one’s chat client was subscribed to. The agent was working; it was not messaging. And nothing in the metrics screen distinguished the two, because the metrics counted work, not delivery.
The fix was not a throttle. It was a lookup — tracing each route’s deliver target and separating the routes that could reach a human from the routes that only logged.
Step 1 — Ask what is actually reaching you
When you believe an agent is spamming you, the first move is not to stop it. It is to enumerate every delivery target the agent can send to, then ask which of those targets a human actually reads.
For webhooks, list the subscriptions you have:
hermes webhook list
That shows the dynamic subscriptions you or your agents created. Static routes live in your gateway config — open the file and read the routes block:
grep -n "deliver" ~/.hermes/config.yaml
Every line that names a deliver value is an answer to the question “where does this route’s output go?” Look at each one. A route with no deliver at all is log by default. A route with deliver: log writes to the log only. Only a route that names a chat platform and points at a real chat_id can put a message in front of you.
Step 2 — Separate “reaches a human” from “logs”
Take every route you found in step 1 and sort it into one of three buckets:
- Reaches a human. Deliver is a chat platform and
deliver_extracarries a realchat_id(or the platform’s home channel is one you check). Example:deliver: telegramwithdeliver_extra.chat_id: "-1001234567890". - Logs only.
deliver: log, ordeliveromitted. Example: the GitHub PR review route that prints its recommendation to the gateway log for a human to pick up later. - Internal channel. Deliver names a route/channel your agents use for testing or inter-agent chatter — named something with the route or profile in it — that no chat client of yours is attached to. Responses land there and are read by no one unless something is actively tailing them.
When a route is in bucket 2 or 3, the agent processing its events is not messaging you. A thousand events through it is a thousand log lines, not a thousand notifications.
Step 3 — Add the delivery check to your response
Here is the reproducible habit. When an agent looks like it is flooding you, run this check before you change any config:
# 1. What routes exist?
hermes webhook list
# 2. What does each route deliver to?
grep -n "deliver" ~/.hermes/config.yaml
# 3. Which of those targets do you actually read?
# A target reaches you only if it is a chat platform AND
# points at a chat_id you check.
Work through the list line by line. For every route that is logging or delivering to an internal channel, mark it “cannot reach a human.” Grep or hermes webhook test <route> if you need to confirm a route’s behaviour rather than guess.
Now count the messages that can reach you — bucket-1 routes, plus your scheduled jobs. That number, not the log volume, is the real measure of whether you are being spammed.
The reveal
When I did this against my own box, the result was anti-climactic and exactly right. All the burst traffic came from bucket-2 and bucket-3 routes. My phone’s actual message count had not changed. There was no bug to fix in the agent — there was a misreading of the telemetry. I had watched the activity feed, seen “bombardment,” and nearly treated a healthy pipeline as a runaway.
That is the part worth remembering. The telemetry that says “this agent did work” is not the telemetry that says “this agent messaged a human.” They are two different counters, and confusing them leads you to two opposite bad calls: disabling a working agent because you misread its log volume, or ignoring a real flood because you assumed the two counters were the same.
The recipe
The full drill, in one place:
- List the routes.
hermes webhook listplusgrep -n "deliver" ~/.hermes/config.yaml. - Classify every deliver target. Chat platform with a real
chat_id= reaches you.logor omitted = logs only. Named internal route = nobody reads it. - Count what can reach you. Bucket-1 routes plus scheduled jobs. Compare that to what you actually received.
- Only then decide. If nothing that reaches you fired, the agent is not spamming you — it is working. If a bucket-1 route fired, throttle that route, not the whole pipeline.
Substitute your platform and route names throughout. The discipline — enumerate the delivery targets, separate log from human, then count — does not change with the platform. deliver: log is not a trivial default; it is the quiet reason an agent can look busy without messaging you at all.
The takeaway
An agent that is busy is not an agent that is pinging you. Every webhook route carries a delivery target, it defaults to log, and only a target you actually read can reach you. Before you throttle, pause, or kill anything on the strength of visible traffic, answer the one question that matters: where does it deliver, and do you read that channel?
The same split applies beyond webhooks. Any agent with multiple output paths can be “working” and “messaging you” in different channels at the same time. Separate those channels in your telemetry and in your head, and you neither panic over logs nor miss a real flood.
Next up is the reverse problem: when an agent’s delivery channel is fine but its inputs are a mess — what the watchdog that re-runs failed work can do to a fleet, and why the automation meant to heal you can be the thing that hurts you.