bx-ai-tools — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited bx-ai-tools (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
BoxLang is the AI-native software productivity platform for building, modernizing and running applications, with developers and AI agents working together.
aiTool() BIF// Signature
aiTool( name, description="", callable, autoRegister=true )name: unique tool identifier (snake_case recommended), or an existing ITool instance (returned as is)description: what the tool does (clear, imperative language)callable: lambda/closure called when the AI invokes the toolautoRegister: when true (default) the tool is also registered in aiToolRegistry() under its name. Pass false to skip.The JSON schema is generated from the closure's declared parameters (type, required) plus your descriptions. Types map as: string/any/date to string, numeric/integer/float/double to number, boolean, array, struct to object. Declare typed params (numeric limit) for correct schemas.
// Simple tool: single parameter
weatherTool = aiTool(
"get_weather",
"Get the current weather for a given location",
location -> getWeatherData( location )
)
// Multi-parameter tool: use parameter descriptions
searchTool = aiTool(
"search_database",
"Search the products database by keyword and category",
( keyword, category ) -> {
return queryExecute(
"SELECT * FROM products WHERE name LIKE :kw AND category = :cat",
{ kw: "%#keyword#%", cat: category }
)
}
)Use fluent describe*() methods to tell the AI what each parameter means. describeArg( name, description ) is the explicit form, and describe( "..." ) / describeFunction( "..." ) set the tool description:
// The method name is describe + parameterName
weatherTool = aiTool(
"get_weather",
"Get current weather for a location",
location -> getWeatherAPI( location )
).describeLocation( "City and country, e.g. 'Boston, MA' or 'Paris, France'" )
searchTool = aiTool(
"search_products",
"Search the product catalog",
( keyword, category, maxResults ) -> {
return productService.search( keyword, category, maxResults )
}
)
.describeKeyword( "Search keyword or product name" )
.describeCategory( "Product category: electronics, clothing, food, or all" )
.describeMaxResults( "Maximum number of results (1-50)" )aiChat()tools = [
aiTool( "get_time", "Get current server time", () -> now() ),
aiTool( "get_uptime", "Get server uptime in hours", () -> getServerUptime() ),
aiTool( "calc", "Evaluate a math expression", expr -> evaluate( expr ) )
.describeExpr( "Math expression as a string, e.g. '(15 * 3) + 7'" )
]
result = aiChat(
"What time is it and what is 15% of 320?",
{ tools: tools }
)// Pass tools to aiAgent() for reusable agents (tool instances or registry keys)
agent = aiAgent(
name : "SystemAgent",
instructions: "Use the provided tools to answer system questions accurately",
tools : [
aiTool( "get_users", "List all users", () -> entityLoad( "User" ) ),
aiTool( "get_stats", "Get system stats", () -> getSystemStats() ),
aiTool( "send_email", "Send an email",
( to, subject, body ) -> mailService.send( to, subject, body ) )
.describeTo( "Recipient email address" )
.describeSubject( "Email subject line" )
.describeBody( "Email body in plain text or HTML" )
]
)aiToolRegistry(): Shared Tool CollectionsaiToolRegistry() returns a singleton registry. Keys are name or name@module. Tools can be referenced by key string in tools arrays (e.g. tools: [ "now@bxai" ]).
registry = aiToolRegistry()
// Register an ITool, or build one from name/description/callback
registry.register( aiTool( "weather", "Get weather", loc -> getWeather( loc ), false ) )
registry.register(
name : "stocks",
description: "Get stock price",
callback : sym -> getStock( sym )
)
registry.has( "weather" )
registry.get( "weather" )
registry.getAll() // array of tools
registry.listTools() // struct keyed by registry key: name, description, module
registry.unregister( "stocks" )
// Share across agents
agentA = aiAgent( name: "A", tools: registry.getAll() )@AIToolscanClass( instance, module="" ) registers every function annotated @AITool and returns the tools. The annotation value (or javadoc hint) is the tool description, a struct annotation value with name and description keys overrides them, and param javadoc hints become argument descriptions. scan( packagePath, module="" ) does the same for every .bx class under a package path.
class {
/**
* @city City name, e.g. Boston
*/
@AITool( "Get the current weather for a city" )
function getWeather( required string city ) {
return weatherApi.lookup( city )
}
}aiToolRegistry().scanClass( new WeatherTools(), "myapp" ) // key: getWeather@myapp
agent = aiAgent( name: "W", tools: [ "getWeather@myapp" ] )Registered into the registry at module load under the bxai module: core tools (print, log, sendEmail, now, httpGet), audio (speak, transcribe, translate), image (generateImage) and web search tools. Filesystem tools are opt-in so you can restrict paths:
import bxModules.bxai.models.tools.filesystem.FileSystemTools
aiToolRegistry().scanClass( new FileSystemTools( allowedPaths: [ "/data" ] ), "bxai" )
// readFile@bxai, writeFile@bxai, editFile@bxai, listDirectory@bxai, searchFiles@bxai, ...With no allowedPaths, all paths are allowed. Use with caution.
Any module can register tools, under its own name@module keys. Registering only makes a tool available: an agent gets it only when its tools list names it. getByModule( module ) returns every tool of a module and getKeys() lists all keys (there is no keys() method).
aiToolRegistry().getKeys() // [ "now@bxai", "audioTrim@bxaudio", ... ]
agent = aiAgent( name: "studio", tools: aiToolRegistry().getByModule( "bxaudio" ) )bx-audio registers 26 audio tools (audioInfo@bxaudio, audioTrim@bxaudio, audioMaster@bxaudio, audioSpeak@bxaudio, ...) when both modules are installed. They work on files, write new files and never overwrite, and stay inside the ai.allowedPaths setting of bx-audio. See the bx-audio skill.
A module that offers optional tools registers them from its ModuleConfig in onLoad() and afterModuleActivations(), inside a try around aiToolRegistry() so it still loads without bx-ai, and removes them in onUnload() with aiToolRegistry().unregisterByModule( "mymodule" ). Do not use this.dependencies for this, it makes bx-ai mandatory.
Tools do not have to be written by hand. An MCP server (a URL, or a program launched as a child process) lists its tools and bx-ai wraps each one as an ITool (MCPTool) that forwards the call to the server. Give the server to an agent or a model:
agent = aiAgent(
name : "Researcher",
mcpServers: [
{ url: "https://docs.example.com/mcp", toolFilter: [ "search" ] },
{ command: [ "bxplaywright", "mcp" ], prefix: "browser" }
]
)
agent.closeMCPServers() // stops the stdio child processesprefix exposes prefix__name so two servers cannot clash, toolFilter limits what the model sees. See bx-ai-mcp.
aiGlobalSkills(): Application-Wide SkillsaiGlobalSkills() takes no arguments and returns the array of skills auto-discovered from the skillsDirectory setting (default /.agents/skills, controlled by autoLoadSkills). Every agent created with aiAgent() gets them automatically. Load individual skills with aiSkill( path ).
skills = aiGlobalSkills()get_weather, search_products, send_email// Good error handling in a tool
dbTool = aiTool(
"query_user",
"Look up a user by ID",
userId -> {
try {
var user = entityLoadByPK( "User", userId )
return isNull( user ) ? { found: false } : { found: true, data: user.getMemento() }
} catch ( any e ) {
return { error: true, message: e.message }
}
}
).describeUserId( "Numeric user ID" )tools in the options argument of aiChat(): it belongs in params (2nd argument)aiTool() auto-registers by name, so reusing a name overwrites the earlier registration~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.