Mediabot has just learned a new trick.
Not another command. Not another timer blindly throwing messages into an IRC channel.
MB703 introduces +Spark, an opt-in conversational micro-event engine designed to notice when a channel has gone quiet and occasionally give the conversation a small, carefully controlled kick.
Think less “bot says something random every 20 minutes” and more “someone quietly opened a suspiciously interesting door in the corridor.” 🚪✨
And, importantly, Spark has been given permission to have a little bite.
No painfully wholesome small talk. No “What is everyone’s favourite colour today?” energy.
Its intended voice is dry, witty, slightly caustic when appropriate, and capable of the occasional well-aimed verbal Bludger. 🏏😈
MB703 currently introduces three event families.
Fork presents a compact A/B choice intended to provoke an immediate opinion rather than demand a formal bot command.
A typical idea might be something along the lines of choosing between two equally questionable alternatives. Humans can simply answer naturally.
Portal asks several people for short contributions and turns them into a small collaborative result. It is deliberately designed to involve more than one person rather than turning Mediabot into the centre of attention.
Callback is the most context-aware form. Spark can look at a small, recent and strictly bounded slice of the conversation and reopen something that was genuinely discussed, left hanging, or deserves a sharp follow-up.
Callback is also allowed to decide that there is simply nothing worth saying.
Sometimes the smartest spell is NO_SPARK. 🧙♂️
Spark uses the provider-neutral AI architecture introduced in MB699.
That means the same abstraction can work with providers such as Anthropic or OpenAI without tying the feature itself to either one.
AI can help formulate:
But the model does not decide when Spark may speak.
All timing and safety decisions remain deterministic inside Mediabot:
+Spark;So the AI writes the spell.
Mediabot still decides whether the wand is allowed to move. 🪄🔐
Spark’s observer keeps only a small in-memory window of recent public activity.
The context is:
Bot commands and direct bot triggers are excluded from AI context as well.
The runtime logs deliberately contain metadata such as:
[SPARK_DRYRUN]
[SPARK_AI_DRYRUN]
[SPARK_SEND]
[SPARK_EVENT]
but not the underlying conversation, prompt, or generated punchline.
No mysterious Pensieve full of channel chatter hiding in the basement. 🫙🌙
A Spark event is short-lived, normally somewhere between roughly 30 and 90 seconds.
If humans engage naturally, the event is considered successful and Spark backs away.
If nobody cares, Mediabot does not become increasingly desperate.
Instead it applies an adaptive cooldown:
first miss → 45 minutes
second miss → 90 minutes
third miss → 4 hours
following misses → 8 hours
A successful interaction resets the failure streak.
And if a normal conversation simply starts again while Spark is preparing something, that is not considered a failure.
Spark gets out of the way.
As it should. 🧹
Visible Spark delivery is protected by two independent controls.
First, the channel must explicitly enable:
+Spark
Second, the process-wide delivery switch must be armed:
SPARK_SEND_ARMED=1
The default remains:
SPARK_SEND_ARMED=0
The sender rechecks both conditions immediately before delivery, along with IRC membership, game state, pending Wit work and the current event generation.
If anything changed while the AI provider was thinking, the response can be revoked instead of being delivered late.
That protects against one of the classic asynchronous bot problems:
bot decides to respond
↓
AI starts thinking
↓
humans resume conversation
↓
AI finally answers
↓
Mediabot: "Too late."
No resurrection spell required. ☠️➡️🚫
Before allowing an actual Spark sender into the codebase, the entire path was exercised in dry-run mode on the development bot.
A real quiet-channel candidate produced a Fork, followed by a real Anthropic request:
kind=fork
provider=anthropic
action=ready
reason=generated
A second candidate exercised Callback:
kind=callback
provider=anthropic
action=ready
reason=generated
Both completed through the real asynchronous provider path.
Neither could send anything to IRC during that test.
The development instance has now been returned to conservative settings:
SPARK_SEND_ARMED=0
SPARK_MIN_SILENCE_SECONDS=1200
SPARK_CANDIDATE_PROBE_SECONDS=300
The temporary +Spark pilot activation was also removed from the development channel.
The real social test will happen later on a normal bot/channel where actual humans can react naturally.
The first final full-suite attempt exposed seven failures in five historical regression files.
Interestingly, they were not failures of Spark itself.
They revealed assumptions in older tests that had become too broad once a second AI-driven conversational subsystem existed.
Among them:
on_candidate callback;->arm() in mediabot.pl as belonging to Wit.Those contracts were repaired without weakening their protection.
They are now scoped to the feature they actually protect.
After the repair:
historical regression repair : 127/127 PASS
MB703 targeted : 304/304 PASS
fast validation lane : 6375/6375 PASS
Then the complete suite was invoked again on the corrected tree:
849 test files
17251 tests
17251 PASS
🟢 Zero failures.
That second full run was necessary because the first full correctly stopped the commit and the source tree was subsequently repaired.
MB703 introduces the complete Spark foundation:
Mediabot::Spark::Policy
Mediabot::Spark::State
Mediabot::Spark::Event
Mediabot::Spark::Selector
Mediabot::Spark::Generator
Mediabot::Spark::Observer
Mediabot::Spark::Orchestrator
Mediabot::Spark::DryRun
Mediabot::Spark::Sender
It also includes the new Spark chanset migration, configuration controls, runtime wiring and regression tests 941 through 966.
The final commit contains:
48 files changed
4826 insertions
6 deletions
Commit:
dbe7369
MB703: kindle guarded +Spark micro-events — sharp sparks behind two wards ⚡🪄
Version:
3.4dev-20260827_074503
The code is now capable of real guarded Spark delivery, but the default remains deliberately disarmed.
The next meaningful step is not another synthetic test.
It is to deploy this build on nbot, enable +Spark on a real channel, explicitly arm delivery, and see whether Spark can do the one thing no unit test can truly measure:
make humans want to answer it.
If it manages that without becoming an annoying enchanted portrait that refuses to shut up, we will have something genuinely useful. ⚡😈🪄
You must be logged in to reply.