Mediabot v3 just gained another layer of restraint around proactive AI.
MB701 allowed Wit to finally cross the gates and speak on IRC, but only after a long chain of authorization checks.
MB702 adds an important rule before that conversation can even begin:
When a channel becomes too noisy, Wit should stop asking AI providers for answers β and if a reply is already on its way, it must still be revoked before it can reach IRC.
This release also fixes a completely separate production issue discovered during the first real Wit rollouts: newly-added channel settings could remain invisible to a running process because a missing database lookup had been cached forever.
The result is a smaller, safer and more predictable proactive-AI runtime.
The new module:
Mediabot::AI::ConversationFloodGuard
maintains lightweight per-channel pressure state in memory.
Its default policy is intentionally simple:
20 public lines
within 10 seconds
β suppress proactive AI for 180 seconds
The state is:
Traffic received while a channel is already suppressed does not keep extending the suppression window forever.
That matters because the guard is meant to be a circuit breaker, not a punishment system.
The important part is where the new guard sits in the pipeline.
Before MB702:
public IRC line
β ConversationPolicy
β provider request
β decision
β emission authorization
β sender
β IRC
With MB702:
public IRC line
β FloodGuard
β inflight gate
β ConversationPolicy
β provider request
β decision
β emission authorization
β sender
β IRC
When flood suppression is active, the result becomes:
action=no_reply
reason=flood_suppression
and no new Anthropic or OpenAI request is submitted.
This is important for three reasons:
If the flood guard itself fails unexpectedly, the path fails closed:
reason=flood_guard_error
No proactive provider call is allowed through.
This is the subtle part.
Imagine the following sequence:
1. Channel is calm.
2. Wit submits an AI request.
3. The channel suddenly floods.
4. FloodGuard opens the circuit.
5. The provider returns the already-generated reply.
Without a second check, that reply could still arrive after the channel had become unsuitable.
MB702 therefore performs another flood-state read at the provider completion boundary.
If suppression became active while the request was in flight:
provider reply
β flood state re-check
β revoked
β never reaches on_candidate
β never reaches ConversationEmission
β never reaches ConversationSender
β never reaches IRC
The second check is side-effect free:
This closes the timing window between provider submission and provider completion.
MB702 does not replace any of the previous safeguards.
The MB701 layers remain active:
+Wit channel setting
runtime active/connected state
real JOIN generation
late emission authorization
WIT_SEND_ARMED master switch
ConversationSender
independent sender rate limit
final mutable-state revalidation
IRC delivery
The flood guard sits above them.
So an AI reply must now survive both:
channel pressure checks
AND
the entire MB701 emission authorization chain
That separation is deliberate.
A failure or policy change in one layer should never silently weaken another one.
While rolling Wit out on real Mediabot instances, one particularly interesting issue appeared.
The database contained the new chanset:
Wit
id=24
but on a process that had already been running, this could still happen:
u chanset #Phat +Wit
Undefined chanset Wit
Restarting the bot fixed it.
That was suspicious enough to deserve a proper investigation.
The root cause was in:
Mediabot::Helpers::getIdChansetList()
A missing lookup was being cached as:
undef
So the sequence was effectively:
process starts
β lookup "Wit"
β row does not exist yet
β cache undef
migration adds Wit=24
next lookup
β cache says undef
β database is never queried again
The new database row remained invisible until process restart.
MB702 changes the cache contract:
successful lookup β cache it
missing lookup β do not cache it
Therefore:
first lookup: missing
β undef, not cached
migration adds Wit=24
second lookup:
β DB queried again
β 24 returned
β 24 cached
Positive cache behavior remains unchanged.
This is deliberately minimal: no TTL, no invalidation framework, no broader cache rewrite.
Just the behavior required to make runtime database additions visible safely.
MB702 adds four focused regression contracts:
937 β FloodGuard foundation
938 β runtime provider gate
939 β late in-flight flood revocation
940 β negative chanset cache refresh
The historical chanset safety test 77 was also strengthened.
The coverage verifies, among other things:
The first frozen final full suite found one failure:
625_mb407_channels_canonical_lc_key.t
The flood guard used a private member named:
$self->{channels}
That name collided with a much older static sentinel protecting Mediabotβs canonical runtime channel registry.
Functionally, FloodGuard already normalized keys correctly through its own _channel_key() logic.
But weakening the historical sentinel would have been the wrong answer.
Instead, the private state was renamed:
channels
β channel_state
No runtime behavior changed.
That kept the MB407 protection intact while making the new module unambiguous.
After the repair:
625 + MB702 exact regression: 154/154 PASS
targeted suite: 807/807 PASS
fast lane: 6254/6254 PASS
security audit: GO β 14/14 invariants
The current public worktree was scanned with Gitleaks:
Gitleaks 8.18.2
current public worktree:
no leaks found
The full Git history still reports four previously-known Twitter API findings.
They were reviewed separately and classified for this release gate as:
REVIEW_ACCEPTED_TWITTER_API
That historical classification does not weaken the requirement for the current public tree: the release candidate itself is clean.
The built-in Mediabot security audit also passed all 14 invariants.
The final MB702 code was loaded into the real DEV process and exercised on IRC.
The master switch was temporarily armed and +Wit enabled on #boulets.
A normal under-threshold sentence produced the complete live path:
[WIT_DRYRUN]
action=consider
reason=eligible
[WIT_EMIT_DRYRUN]
action=emit
reason=authorized
[WIT_SEND]
action=sent
reason=delivered
[WIT_AI_DRYRUN]
action=reply
reason=model_reply
provider=anthropic
model=claude-haiku-4-5-20251001
No:
flood_guard_error
runtime error
unexpected flood suppression
appeared.
The DEV instance was immediately returned to its safe state afterward:
WIT_SEND_ARMED=0
WIT_ENABLED_CHANNELS=0
The final candidate was frozen before the last full suite.
VERSION
3.4dev-20260826_115634
Frozen manifest:
b59e4bb2c23c721dd0c045288c3a8096d7d3dbffa43dbea38b0c13a1e60cc30c
The final full suite then completed successfully:
823 / 823 test files
16914 / 16914 tests PASS
RC_FULL=0
After the suite:
frozen manifest unchanged
git diff --check = 0
WIT_SEND_ARMED = 0
WIT_ENABLED_CHANNELS = 0
DEV service = active/running
NRestarts = 0
The exact bytes that passed the full suite were then committed and pushed.
bc1c4b7
MB702 π‘οΈπ¦ tame the flood before Wit speaks β revoke late replies and refresh new chansets without a restart πͺπ
Published version:
3.4dev-20260826_115634
With MB700, Wit learned how to observe and decide.
With MB701, Wit was allowed to speak β but only through a guarded emission path.
With MB702, Wit now also knows when the room is simply too loud.
And, perhaps more importantly, Mediabot now knows that an answer which was safe when it was requested may no longer be safe when it comes back.
That little distinction is exactly the kind of thing that turns a clever bot into a dependable one.
π¦ The owl may speak.
π The flood may rise.
π‘οΈ But the gates still decide what gets through.
You must be logged in to reply.