Blog

Luis Majano

April 20, 2026

Spread the word


Share your thoughts

BoxLang AI 3.1 is here, and it's a release that makes your agents smarter, faster, and more capable than ever. 🎉

While 3.0 rewrote the rules on multi-agent orchestration and skills, 3.1 fills in the gaps your production applications have been waiting for — full audio support, non-blocking async execution, concurrent multi-model pipelines, a secure filesystem tool suite, and the ElevenLabs voice provider. Plus 21 bug fixes that make the entire stack more reliable under production load.

Here's everything landing in 3.1:

  • 🎤 Audio Models — aiSpeak(), aiTranscribe(), and aiTranslate() for text-to-speech, speech-to-text, and audio translation
  • 🤖 ElevenLabs Provider — high-quality multilingual neural voice synthesis
  • ⚡ Async Runnables — runAsync() on every IAiRunnable returning a BoxFuture
  • 🔀 aiParallel() BIF — fan-out a single input to multiple models concurrently, collect named results
  • 🗂️ FileSystem Agent Tools — 19 opt-in, path-guarded tools for full filesystem operations
  • 🎤 Audio Agent Tools — speak@bxai, transcribe@bxai, translate@bxai auto-registered in the Global Tool Registry
  • 🧠 New Memory Events — onHybridMemoryAdd and onVectorSearch interception points
  • 🔊 Audio Module Configuration — centralized defaults for all audio operations
  • 📡 6 New Audio Events — full before/after interception for speech, transcription, and translation

Let's dig in. 🎉


🎤 Audio BIFs — Your Agents Can Now Speak and Listen

3.1 brings full audio support to BoxLang AI through three new global Built-in Functions that follow the same familiar API you already know:

BIFDescription
aiSpeak( text, params, options )Convert text to natural speech audio
aiTranscribe( audio, params, options )Transcribe audio file/URL/binary to text
aiTranslate( audio, params, options )Translate any-language audio to English text
// Text-to-speech — save to file
audio = aiSpeak( "Welcome to BoxLang AI!" )
	.saveToFile( "/audio/welcome.mp3" )

// Speech-to-text — get plain text
text = aiTranscribe( "/recordings/meeting.mp3" )

// Audio translation — any language → English
english = aiTranslate( "/recordings/french-meeting.mp3" )

aiSpeak() returns an AiSpeechResponse with everything you need: saveToFile(), getBase64(), toDataURI(), getMimeType(), getAudioFormat(), and getSize(). aiTranscribe() and aiTranslate() both return an AiTranscriptionResponse with getText(), getWordCount(), getFormattedDuration(), getSegments(), and word-level timestamp data via getWords().

Both response types extend AiBaseResponse — giving you model, provider, metadata, timestamp, and chainable setMetadataValue() out of the box. 📦


🤖 ElevenLabs — Neural Voice Synthesis

The new elevenlabs provider brings high-quality multilingual neural voice synthesis to your BoxLang applications. 🎙️

Default model: eleven_multilingual_v2 — plug in any voice ID from your ElevenLabs voice library via params.

audio = aiSpeak(
    "Bonjour, bienvenue dans BoxLang AI.",
    { voice_id: "21m00Tcm4TlvDq8ikWAM" },
    { provider: "elevenlabs" }
)
audio.saveToFile( "/audio/bonjour.mp3" )

Configure it once in config/boxlang.json and forget about it:

{
    "modules": {
        "bxai": {
            "settings": {
                "providers": {
                    "elevenlabs": {
                        "params": {
                            "model": "eleven_multilingual_v2"
                        }
                    }
                }
            }
        }
    }
}

Set ELEVENLABS_API_KEY in your .env — that's all the wiring you need. ✅


⚡ Async Runnables — Non-Blocking AI Execution

Every IAiRunnable object — AiModel, AiAgent, AiRunnableSequence, AiTransformRunnable, and AiRunnableParallel — now exposes runAsync(). It dispatches to the io-tasks virtual thread pool and returns a BoxFuture you can chain, combine, or block on when you're ready. 🚀

model = aiModel( "openai" )

// Fire and forget until you need it
future = model.runAsync( "Summarize this document..." )

// Block when you need the result
result = future.get()

// Or go fully reactive with a callback
future.then( result -> {
    println( "Done: #result#" )
} )

Where runAsync() really shines is running independent tasks simultaneously:

tasks = [
    aiModel( "openai" ).runAsync( "Summarize topic A" ),
    aiModel( "openai" ).runAsync( "Summarize topic B" ),
    aiModel( "openai" ).runAsync( "Summarize topic C" )
]

// All three in-flight at once — collect when ready
summaries = tasks.map( f => f.get() )

Three LLM calls. One wall-clock wait. No threading code. ⏱️


🔀 aiParallel() — Run Multiple Models Concurrently

The new aiParallel() BIF creates an AiRunnableParallel that fans out a single input to multiple named runnables at the same time and collects results into a named struct. Compare models, aggregate perspectives, and build truly parallel AI pipelines. 🧠

parallel = aiParallel({
    openai:  aiModel( "openai",  { params: { model: "gpt-4o-mini" } } ),
    claude:  aiModel( "claude",  { params: { model: "claude-3-haiku-20240307" } } ),
    mistral: aiModel( "mistral", { params: { model: "mistral-small-latest" } } )
})

results = parallel.run( "What is the capital of France?" )

println( results.openai  )   // "Paris"
println( results.claude  )   // "Paris"
println( results.mistral )   // "The capital of France is Paris."

Because AiRunnableParallel implements IAiRunnable, it composes naturally into larger pipelines:

pipeline = aiParallel({ fast: aiModel( "groq" ), smart: aiModel( "openai" ) })
    .transform( results => "Fast: #results.fast#\nSmart: #results.smart#" )

combined = pipeline.run( "Explain quantum entanglement in one sentence" )
println( combined )

Fan out, collect, transform. Zero boilerplate. 🎯


🗂️ FileSystem Agent Tools — Power With a Security-First Design

3.1 ships FileSystemTools — a class with 19 @mcpTool-annotated methods covering the full filesystem lifecycle. Unlike audio tools, this one is not auto-registered by design. You opt in explicitly, and you supply the paths you want to allow. 🔒

// Register with explicit path restrictions
aiToolRegistry().scanClass(
    new FileSystemTools( allowedPaths: [ "/app/data", "/app/uploads" ] )
)

// Then assign specific tools to your agent
agent = aiAgent(
    name:    "file-manager",
    aiModel: "openai",
    tools:   [ "readFile@bxai", "writeFile@bxai", "listDirectory@bxai" ]
)

Every path argument is canonicalized and validated against your allowedPaths list at invocation time — directory-traversal attacks are blocked before any file operation runs. The full 19-tool suite covers read, write, append, edit, move, copy, delete, search, zip, unzip, and more.


🎤 Audio Agent Tools — Voice-Enabled Agents in One Line

Three audio tools are auto-registered in the Global Tool Registry at module startup:

Tool KeyDescription
speak@bxaiConvert text to speech; auto-generates a temp file if no outputFile is supplied
transcribe@bxaiTranscribe a local audio file or URL to plain text
translate@bxaiTranslate any-language audio to English text

Opt-in by listing them in your agent definition:

agent = aiAgent(
    name:    "voice-assistant",
    aiModel: "openai",
    tools:   [ "speak@bxai", "transcribe@bxai", "translate@bxai" ]
)

Your agents can now receive voice input, reason over it, and speak back — all with the same tool system you already know. 🔊


🧠 New Memory Events

Two new interception points give you visibility into what your memory subsystem is doing at runtime:

EventWhen Fired
onHybridMemoryAddWhen a message is added to a HybridMemory instance
onVectorSearchWhen a semantic search runs against a vector memory
BoxRegisterInterceptor( "onVectorSearch", event => {
    println( "Vector search: query='#event.query#' found #event.results.len()# results" )
} )

Pair these with the existing event system to build observability pipelines, debug RAG relevance, or feed search telemetry into your monitoring stack. 📊


🔊 Audio Module Configuration

A new audio section in module settings gives you global defaults for all audio operations — no more repeating params across every call:

{
    "modules": {
        "bxai": {
            "settings": {
                "audio": {
                    "defaultVoice":              "alloy",
                    "defaultOutputFormat":       "mp3",
                    "defaultSpeechModel":        "tts-1",
                    "defaultTranscriptionModel": "whisper-1"
                }
            }
        }
    }
}

Six new audio interception events — beforeAISpeech, afterAISpeech, beforeAITranscription, afterAITranscription, beforeAITranslation, afterAITranslation — round out the observability story for every audio operation. 📡


🐛 21 Bug Fixes Worth Calling Out

3.1 ships 21 bug fixes across the stack. The biggest ones:

  • Streaming onAITokenCount never fired — all streaming calls were invisible to usage tracking and billing interceptors. Fixed. 💥
  • AiModel.stream() missing middleware — streaming was skipping middleware injection. Fixed and now consistent with run().
  • Closure scoping in tool calls — ArgumentsScope resolution failures inside .each() / .map() closures across multiple providers. Fixed by capturing outer variables before closures.
  • onAITokenCount standardization — event data shape was inconsistent across providers; standardized across Bedrock, Claude, Cohere, and Gemini.
  • MCPServer.scan() / scanClass() — path-resolution edge cases across dot-notation, absolute, relative, and instance inputs. Now reliable.
  • aiAgent() single skill normalization — skills and availableSkills now accept a single instance or an array without wrapping in [].
  • WindowMemory.count() off-by-one — returned maxMessages + 1 instead of actual count. Fixed.
  • Gemini null chunk on final SSE — streaming responses occasionally emitted a null chunk on the final event. Now handled gracefully.
  • AWS Bedrock signature trailing slash — caused SignatureDoesNotMatch errors on certain endpoints. Fixed.
  • HybridMemory deduplication — combining window and vector search results could produce duplicate messages. Fixed.
  • aiTranscribe URL redirect — HTTP 301/302 from CDNs were not followed. Now follows up to 5 redirects.

See the full changelog for the complete list.


No Breaking Changes

3.0 code runs on 3.1 without modification. Upgrade, run your tests, and start exploring. ✅


Get Started

# Install or upgrade via BoxLang
install-bx-module bx-ai

# Install or upgrade via CommandBox
install bx-ai

Professional Support

Remember that BoxLang and BoxLang AI are professional support software. We are there for you when you need it the most: https://www.boxlang.io/plans

📖 Full Documentation 🛠️ Changelog 🐛 Report Issues 💬 Community Slack 💼 BoxLang+ Plans

Add Your Comment

Recent Entries

QR Codes Come to BoxLang Images

QR Codes Come to BoxLang Images

The bx-image module can now generate and read QR codes. Two new functions, a string member function, and two new bx:image actions. No extra dependencies for you to manage.

Luis Majano
Luis Majano
October 14, 2026
ACF2021 extended support ends on November 10. Is Your Application Ready?

ACF2021 extended support ends on November 10. Is Your Application Ready?

Adobe ColdFusion 2021 reaches the end of extended support on November 10, 2026. If your application still runs on this release, the time to decide what comes next is now.

Adobe ended core support for ColdFusion 2021 on November 10, 2025. The current extended support period is best effort and does not include security patches or hotfixes. When it ends on November 10 2026, organizations will no longer have that support option from Adobe for ColdFusion 2021.

What ...

Cristobal Escobar
Cristobal Escobar
October 13, 2026