Field note · May 19, 2026
Your agent sent 621 emails before you got out of bed
A founder we know runs a Series-A SaaS with two agents in production — an SDR agent and a marketing agent. On the 16th she cancelled 79 outreach drafts at 23:47 because something in the copy looked off. She went to sleep.
By the time she opened Slack the next morning, her agents had sent 621 emails to customers. The 79 she cancelled were back, plus 92 new ones the marketing agent had decided to add overnight. The SDR cron had fired three times between 00:00 and 13:00 because two schedulers and a GitHub Action all owned the same handler. And every email used a template that had been deprecated two weeks earlier — the rule resolver was reading from an override row that pointed at a deactivated row.
Four independent failures, one morning. None of them were exotic. All four would have been wraps the scanner emits today.
The timeline
Reconstructed from her DB and her workers' logs. UTC throughout.
23:47 user cancels 79 SDR drafts in the dashboard.
cancel_pending_actions() flips status='pending' → 'cancelled'.
00:00 Celery beat fires sdr_scheduler.run_sdr_batch(). 150 new actions
planned. SDR agent reads from agent_rule_overrides, resolves
SDR-01..05 to template sdr_intro_custom_v1.
(sdr_intro_custom_v1 was set is_active=false on May 3 when
sdr_intro_gap_v3 replaced it. The override row was never updated.)
150 emails sent.
03:14 marketing_agent.replan() wakes. It reads pending_actions without
joining cancelled_intents. The 79 cancelled rows look fresh again.
It also surfaces 92 new prospects from a Supabase view that ran
overnight. 171 emails sent.
12:00 GitHub Actions fires the same run_sdr_batch via a cron: '0 12 * * *'
in .github/workflows/sdr.yml. 150 more emails sent.
13:00 The prod crontab (forgotten from a 2024 migration) fires the same
handler. 150 more emails sent.
Total: 150 + 171 + 150 + 150 = 621 emails to real customers.Why none of this looked dangerous in code review
Each of the four failures is the kind of thing that passes review. The cron entries were each correct in isolation. The override table had been a tactical workaround for a copy A/B test six months earlier. The marketing agent's replan() was a feature, not a bug — it's how it adapts to new prospects appearing in the funnel. The rule resolver was three lines of SQLAlchemy.
The failure is the combination, and combinations don't live in any one file. They live at the chokepoints where the agent actually does something irreversible — the SMTP send, the cron dispatch, the template lookup, the batch insert. That's where the gate goes.
The four wraps (this is the whole thing)
🔒 Wrap 1 · gate the email send, not the planner
Two functions reach resend.emails.send: the cron path and the manual run_agent() path. Putting the gate on the planner means the manual path bypasses it. The gate belongs at the side effect.
from supervisor_guards import supervised
@supervised('email_send')
def send_outreach(recipient: str, body: str) -> None:
resend.emails.send(...)🔒 Wrap 2 · dedup the cron at the chokepoint
The fix isn't to delete the GitHub Action or the prod crontab — both were intentional, just both unaware of each other. The fix is one idempotency key keyed on the scheduled minute, so the second and third caller in the same minute are no-ops.
@supervised('cron_dispatch',
idempotency_key=lambda ctx: f"sdr:{ctx.scheduled_at:%Y-%m-%dT%H:%M}")
def run_sdr_batch() -> None:
...🔒 Wrap 3 · refuse to resolve an override that points at a deactivated row
Polite SQL is the bug here: the override join didn't carry anis_active=true filter, so the row outlived the thing it pointed at. Add a policy that blocks resolution when the destination is dead.
@supervised('rule_resolution')
def resolve(rule_id: str) -> Template:
template = (
db.query(Template)
.join(RuleOverride, RuleOverride.template_id == Template.id)
.filter(RuleOverride.rule_id == rule_id)
.filter(Template.is_active == True) # the missing line
.one_or_none()
)
if template is None:
raise RuleResolutionError('override points at deactivated template')
return template🔒 Wrap 4 · refuse to re-send a batch that references a cancelled intent
The marketing agent isn't wrong to replan. It's wrong to replan onto a recipient list a human just rejected. The policy consults a cancelled_intents ledger written by cancel_pending_actions:
# packages/policies/bulk_send_freeze.base.v1.yaml
applies_to: action_type=email_send, payload.batch_size >= 50
require_evidence:
- field: batch.parent_intent_id
must_not_be_in: cancelled_intents_last_24h
on_violation: blockWhat shadow mode would have shown her at 00:01
With all four wraps installed and SUPERVISOR_ENFORCEMENT_MODE=shadow set, nothing is blocked — but every would-block writes a decision to the supervisor's evidence chain and surfaces it in the morning digest. Here's the email she would have read while coffee brewed:
Subject: 3 actions would have been blocked overnight — vibefixing
Hi,
Between 23:47 and 13:00 UTC, your supervisors saw 4 things they would
have blocked in enforce mode.
1. RULE_TARGET_INACTIVE × 450 calls
resolve('SDR-01') returned a template marked is_active=false.
First seen: 00:00:01 UTC. Source: sdr_scheduler.run_sdr_batch.
Open replay →
2. DUPLICATE_DISPATCH × 2 calls
Cron handler 'sdr' fired at the same scheduled minute by two
different schedulers (Celery beat + GitHub Actions, then again
by /etc/crontab).
Open replay →
3. BULK_SEND_REFERENCES_CANCELLED × 171 calls
marketing_agent.replan re-issued a batch whose parent intent
was cancelled at 23:47 by you.
Open replay →
Flip to enforce when you trust shadow → vibefixing.me/dashboardThat's the difference between finding this at 09:00 in her inbox versus finding it at 14:30 when a customer replies asking who Ariel is and why she's getting cold outreach about a product she already uses.
What I'm not worried about
Her stack is fine. The agents are fine. The schedulers are fine. Nothing in this story requires rewriting any of them — just four@supervised decorators at the four chokepoints we already named, and a one-line env var to flip from shadow to enforce when the team trusts what they see. Total install: under fifteen minutes.
The wraps don't change what her agents do. They change what her agents can't do at 03:14.
scan your repo
Find your four wraps in 30 seconds.
Paste a repo URL. We'll show you which call-sites reach the same SMTP / cron / DB chokepoints, and emit the stubs you copy in. Shadow mode by default. Free.
scan my repo →