Commit: 83d2d36
Version: 3.4dev-20260826_064048
Status: merged and pushed to master
MB700 introduces the full architectural foundation for Wit, Mediabotβs future proactive conversational layer.
The key design choice is simple:
Wit can now observe, decide and consult a real AI provider β but it still cannot speak on IRC.
That boundary is intentional.
Before giving a proactive AI the ability to talk in a public channel, MB700 makes every stage explicit, bounded, testable and safe-by-default.
+Wit: opt-in only, OFF everywhere by defaultMB700 adds a new channel setting:
+Wit
The migration is deliberately conservative:
Wit entry in CHANSET_LIST;CHANNEL_SET entry;The real DEV database was migrated and checked twice.
Final state before commit:
WIT_ROWS=1
WIT_ID=24
WIT_ENABLED_CHANNELS=0
So Wit exists, but nothing happens unless an administrator explicitly enables it on a channel.
The first new layer is:
Mediabot::AI::ConversationPolicy
It performs purely mechanical filtering before any model is involved.
It rejects, among other things:
Its only positive outcome is:
action=consider
reason=eligible
It cannot produce a reply.
No HTTP.
No database.
No worker.
No persistence.
No IRC output.
Mediabot::AI::ConversationObserver converts a public IRC line into a policy decision.
Dry-run logging deliberately exposes metadata only:
[WIT_DRYRUN] channel=#boulets action=consider reason=eligible language=fr provider=auto
Command traffic is rejected before AI:
[WIT_DRYRUN] channel=#boulets action=no_reply reason=command language=fr provider=auto
The Wit log does not contain:
This was verified live on IRC.
A proactive model must not be allowed to return arbitrary prose and hope the caller interprets it correctly.
MB700 therefore introduces:
Mediabot::AI::ConversationDecision
The accepted wire format is exactly one of these:
NO_REPLY
or:
REPLY: <single-line reply>
Everything else fails closed.
That includes:
NO_REPLY.A valid reply is:
If the model does not respect the contract, Wit does not get a reply.
Mediabot::AI::ConversationRequest builds the provider-neutral Wit request.
Its public API intentionally accepts only:
provider
language
message
It refuses caller-supplied data such as:
nick
channel
model
system
prompt
memory
api_key
That prevents accidental identity enrichment, arbitrary prompt injection from the caller, provider-specific model coupling and credential leakage.
The request budget is deliberately modest:
purpose = wit
max_output_tokens = 120
temperature = 0.7
timeout_seconds = 20
input ceiling = 800 characters
The fixed system instructions also tell the model to:
NO_REPLY when appropriate;The next layer is:
Mediabot::AI::ConversationExecutor
It connects the new Wit request to the provider-neutral AI layer introduced in MB699:
ConversationRequest
β
AI::Client
β
OpenAI / Anthropic
β
ConversationDecision
Provider-specific HTTP formats remain hidden behind AI::Client.
Failures fail closed:
action=no_reply
reason=provider_error
Malformed model output becomes:
action=no_reply
reason=invalid_output
A successful answer becomes:
action=reply
reason=model_reply
The raw provider response is not retained in the safe execution summary.
Before wiring anything into the IRC runtime, MB700 exercised the new Wit path against both configured real providers.
provider=openai
model=gpt-4o-mini
action=reply
reason=model_reply
provider_fallback=0
model_fallback=0
reply_chars=121
OPENAI_WIT_SMOKE=PASS
provider=anthropic
model=claude-haiku-4-5-20251001
action=reply
reason=model_reply
provider_fallback=0
model_fallback=0
reply_chars=88
ANTHROPIC_WIT_SMOKE=PASS
Both providers respected the strict output contract.
No fallback was required.
The final runtime layer added by MB700 is:
Mediabot::AI::ConversationDryRun
The complete live flow is now:
public IRC line
β
+Wit opt-in gate
β
ConversationPolicy
β
consider / eligible
β
ConversationExecutor async
β
AI::Client worker
β
ConversationDecision
β
safe metadata log
And still:
NO IRC EMISSION
The orchestrator also adds two important protections:
A busy discussion therefore cannot turn into one API request per message.
#bouletsAfter a controlled restart, +Wit was explicitly enabled on one test channel.
A normal French message produced:
[WIT_DRYRUN] channel=#boulets action=consider reason=eligible language=fr provider=auto
Two seconds later, the real async callback arrived:
[WIT_AI_DRYRUN] channel=#boulets action=reply reason=model_reply provider=anthropic model=claude-haiku-4-5-20251001 provider_fallback=0 model_fallback=0 reply_chars=114
A second controlled request also completed correctly:
[WIT_DRYRUN] channel=#boulets action=consider reason=eligible language=fr provider=auto
[WIT_AI_DRYRUN] channel=#boulets action=reply reason=model_reply provider=anthropic model=claude-haiku-4-5-20251001 provider_fallback=0 model_fallback=0 reply_chars=54
Then a second message inside the cooldown window was stopped before another provider call:
[WIT_DRYRUN] channel=#boulets action=no_reply reason=cooldown language=fr provider=auto retry_after=9
The administrative cleanup command was itself correctly recognized as a command:
[WIT_DRYRUN] channel=#boulets action=no_reply reason=command language=fr provider=auto
Finally:
m chanset #boulets -Wit
returned the database to:
WIT_ENABLED_CHANNELS=0
Immediately before commit:
MainPID=1403
NRestarts=0
ExecMainStatus=0
ActiveState=active
SubState=running
WIT_ROWS=1
WIT_ENABLED_CHANNELS=0
No Wit runtime error was found in mediabot.log.
The final pre-commit scope contained 19 functional MB700 files, plus the normal VERSION update produced by commit.sh.
Validation:
MB700 targeted : 354/354 PASS
Fast lane : 6226/6226 PASS
Full suite : 16510/16510 PASS
Gitleaks : no leaks found
diff --check : PASS
After fixing two harmless trailing blank lines detected by the commit safety gate, the final full suite was run again on the exact bytes being committed:
16510/16510 PASS
The final commit contains:
20 files changed
2462 insertions(+)
4 deletions(-)
Commit:
83d2d36
MB700 πͺπ¦ teach Wit to think before it speaks β safe proactive AI enters dry-run at Hogwarts π°βοΈ
And it is now pushed to master.
It would have been easy to add one final line that turns:
action=reply
into:
botPrivmsg(...)
MB700 deliberately does not do that.
That would mix two very different milestones:
Keeping those separate gives us:
The spellbook is written.
The owls have successfully carried messages to OpenAI and Anthropic.
The wards are active.
But the wand is still holstered. πͺπ¦π°
That is exactly where MB700 should end.
You must be logged in to reply.