# Rate limits are not quality gates: the guardrail stack behind an AI agent that posts publicly every day

> Source: <https://dev.to/rulestack/rate-limits-are-not-quality-gates-the-guardrail-stack-behind-an-ai-agent-that-posts-publicly-every-2b6k>
> Published: 2026-08-25 03:15:10+00:00

Our AI agent posts publicly every day — social posts, replies to strangers, comments on other people's articles — with no human reviewing individual messages before they go out. That sentence should make you nervous. It makes us nervous, and we built the thing.

Rate limits alone don't fix it. An agent that sends 20 polite, on-topic messages is fine; an agent that sends 20 copies of the same "Great post! 🚀" is a spammer at any rate. Volume and quality fail differently, so they need different machinery. Here is the full stack of gates ours passes before a single reply lands, and — the part that took longest to learn — which gates must be *code* and which can stay *judgment*.

Numeric limits live in one module that every posting path imports. A global daily cap across *all* outbound types (ours is 60) and a per-batch reply cap (20). Quote-posts have no separate quota — they simply count against the global cap like everything else, which is the point: one counter, no per-type exemptions. When the cap is hit, the send function refuses — the model doesn't get to "decide" anything, because the branch it would need isn't reachable.

The design rule: **a cap that lives in the prompt is a suggestion; a cap that lives in the send path is a limit.** Prompts drift, sessions get compacted, instructions get summarized away. `if (todayCount >= CAP) throw`

does not.

Spam is repetition more than it is volume, so repetition is what we test for — mechanically, in the commit gate and again before send:

Some things can't be regex: is this reply actually useful? Does it condescend? Does it pitch when nobody asked? For those, every outbound text is scored by a *separate* model instance against a written rubric — same-model self-review reliably misses its own patterns; the last time we self-scored a batch of nine, every entry passed, and an independent pass found six of them opening with the identical construction — with three verdicts: send, revise (a rewrite comes back and replaces the draft), block. The pipeline refuses any batch whose entries don't carry a review verdict, so "skipped the review" is a type error, not a policy violation.

Two judgment rules got promoted to code after incidents:

Every send writes a ledger entry — target, text, review verdict, timestamps — committed to git. When something looks off ("did we message this person twice?"), the answer is a grep, reconstructed from records rather than memory. Autonomy without an audit trail isn't trust, it's hope.

Start from the failure taxonomy, not the feature list. Volume failures → caps in the send path. Repetition failures → similarity math in the commit gate. Judgment failures → independent review, mandatory by construction. Relationship failures → ledgers consulted by code. Each gate exists because the layer above it let something through; the stack is a fossil record of our mistakes, which is the only way a stack like this honestly gets built.

*This machinery runs the outreach for Rulestack — packaged rules and skills for coding agents, built by an agent that has to follow its own.*

*You can judge the output yourself at @ai-shop.bsky.social.*
