ToolBox Plugin Reference

March 18, 2026 · View on GitHub

All ToolBox classes implement the abstract ToolBox base from OPM (agent_tools.py:56). Each registers under opm.agents.toolbox and implements the single required method discover_tools() -> List[AgentTool].


SkillMDToolBox

Entry point ID: ovos-skill-md-toolbox Source: ovos_agentic_loop/skills/toolbox.py:48

Converts installed SKILL.md files into AgentTool instances. Each SKILL.md yields one tool; tool execution calls a sub-ChatEngine with the SKILL.md body as the system prompt.

Tool: (one per SKILL.md, dynamically generated)

Tool name is slugified from the SKILL.md name frontmatter field via _slugify()toolbox.py:12:

  • Lower-case, non-alphanumeric characters replaced with underscores, leading/trailing underscores stripped.
  • Example: "web-search""web_search".
ArgumentTypeRequiredDescription
taskstryesFree-text instruction for the skill
contextstrnoAdditional context or constraints
Output fieldTypeDescription
resultstrNatural-language response from the sub-LLM
skill_usedstrThe SKILL.md name field of the invoked skill

Tool Execution (_invoke_skill) — toolbox.py:96

  1. Raises RuntimeError if no brain has been set.
  2. Builds user content: args.task if no context, else "{task}\n\nContext: {context}".
  3. Assembles [SYSTEM: <skill body>, USER: <user_content>] and calls self._brain.continue_chat(messages).
  4. Returns SkillCallOutput(result=response.content, skill_used=entry.name).

Config Keys

KeyTypeDefaultDescription
extra_skill_md_pathsList[str][]Additional SKILL.md paths to load

Brain Injection

set_brain(brain: ChatEngine)toolbox.py:87. Must be called before any tool invocation. The owning ReActLoopEngine does not auto-share its brain; manual wiring is required (see AUDIT ISSUE-002).

Example Persona Config

{
  "ovos-skill-md-toolbox": {
    "extra_skill_md_paths": ["/home/user/custom/SKILL.md"]
  }
}

FileSystemToolBox

Entry point ID: ovos-filesystem-tools Source: ovos_agentic_loop/tools/filesystem.py:85

Provides five tools for local filesystem interaction.

Tool: read_file

ArgumentTypeRequiredDescription
pathstryesAbsolute or relative path to read
Output fieldTypeDescription
contentstrUTF-8 file contents or error message
pathstrResolved absolute path

On any Exception, returns the exception message in content rather than raising — filesystem.py:127.

Tool: write_file

Guarded by allow_write config flag — filesystem.py:140.

ArgumentTypeRequiredDescription
pathstryesTarget file path (parent dirs created automatically)
contentstryesUTF-8 text to write
Output fieldTypeDescription
successboolTrue if write succeeded
pathstrResolved absolute path
messagestrHuman-readable status

When allow_write=False, the tool is still registered in discover_tools()filesystem.py:264 — with a description noting it is disabled, so the LLM knows the tool exists but is unavailable. The _write_file handler checks the flag at call time and returns success=False.

Tool: list_directory

ArgumentTypeDefaultDescription
pathstrDirectory to list
patternstr"*"Shell glob filter
Output fieldTypeDescription
entriesList[str]Matching filesystem entry paths (sorted)
pathstrResolved directory path

Tool: search_in_files

ArgumentTypeDefaultDescription
patternstrRegular expression to search
pathstr"."Root directory
globstr"**/*"Glob to select files
Output fieldTypeDescription
matchesList[Dict]Each dict: {file, line_number, line}
totalintCount of matching lines

Invalid regex returns empty results without raising — filesystem.py:186.

Tool: find_files

ArgumentTypeDefaultDescription
globstrGlob pattern (e.g. **/*.py)
pathstr"."Root directory
Output fieldTypeDescription
filesList[str]Matched file paths (files only, sorted)
totalintCount of matched files

Config Keys

KeyTypeDefaultDescription
allow_writeboolTrueSet to False for read-only agents

Example Persona Config

{
  "ovos-filesystem-tools": {
    "allow_write": false
  }
}

ShellToolBox

Entry point ID: ovos-shell-tools Source: ovos_agentic_loop/tools/shell.py:26

Exposes one tool for executing shell commands.

Tool: run_command

Uses subprocess.run(..., shell=True, ...)shell.py:75.

ArgumentTypeDefaultDescription
commandstrShell command to execute
cwdstr"."Working directory
timeoutint30Requested timeout (seconds); capped at max_timeout config
Output fieldTypeDescription
stdoutstrStandard output
stderrstrStandard error (also used for error messages when blocked/timed-out)
returncodeintExit code (-1 on timeout or error)
successboolTrue if returncode == 0

Timeout cap: effective_timeout = min(args.timeout, config["max_timeout"])shell.py:72.

On TimeoutExpired, returns returncode=-1 and a message in stderrshell.py:90.

When allow_shell=False, returns immediately with returncode=-1 and "Shell execution is disabled" in stderrshell.py:63.

Config Keys

KeyTypeDefaultDescription
allow_shellboolFalseMust be explicitly set to True to enable execution
max_timeoutint120Maximum allowed timeout in seconds
allowed_commandslist[str][]Permitted command prefixes. Empty = all allowed. Non-empty = only matching prefixes execute — shell.py:85

When allowed_commands is non-empty, the first word of the command is matched against each prefix. Non-matching commands return returncode=-1 and "not permitted" in stderr without any subprocess execution.

Security Note

shell=True passes the command string directly to /bin/sh. Always combine allow_shell: true with a tight allowed_commands list in production environments.

Example Persona Config

{
  "ovos-shell-tools": {
    "allow_shell": true,
    "allowed_commands": ["git", "ls", "cat"],
    "max_timeout": 30
  }
}

WebSearchToolBox

Entry point ID: ovos-web-search-tools Source: ovos_agentic_loop/tools/web.py:25

DuckDuckGo text search. Requires optional dependency duckduckgo-search>=6.0.

Install: pip install 'ovos-agentic-loop[web]'

ArgumentTypeDefaultDescription
querystrSearch query string
max_resultsint5Maximum results to return
Output fieldTypeDescription
resultsList[Dict]Each dict: {title, url, snippet}
querystrThe query that was executed

If duckduckgo-search is not installed, returns one result with title="Package not installed" and an install instruction in snippetweb.py:65. No exception is raised; the LLM receives a friendly error observation.

If the search call itself fails, returns one result with title="Search error" and the exception message in snippetweb.py:93.

URL field normalisation: tries r.get("href", r.get("url", "")) to handle API key differences across package versions — web.py:84.

Config Keys

None. The config dict is accepted but unused.

Example Persona Config

{
  "ovos-web-search-tools": {}
}

ClockToolBox

Entry point ID: ovos-clock-tools Source: ovos_agentic_loop/tools/clock.py:22

No external dependencies. Returns the current system date and time.

Tool: get_current_datetime

No input arguments.

Output fieldTypeDescription
isostrFull ISO-8601 datetime with timezone offset
datestrYYYY-MM-DD
timestrHH:MM:SS
timezonestrSystem timezone name (e.g. "UTC", "EST") or UTC offset string

Implementation: datetime.now().astimezone()clock.py:53. Timezone name falls back to str(now.utcoffset()) if tzname() returns empty — clock.py:54.

Config Keys

None.


MathToolBox

Entry point ID: ovos-math-tools Source: ovos_agentic_loop/tools/math.py

Safe mathematical operations. No external dependencies required for three of the four tools; solve_equation uses sympy when installed.

Tool: evaluate_expression

ArgumentTypeRequiredDescription
expressionstryesMath expression string
Output fieldTypeDescription
resultfloatNumeric result (nan on error)
expressionstrOriginal input
errorstr|NoneError message on failure

Evaluation uses ast.parse + a whitelist of operators and functions — no eval()math.py:71. Supported: +, -, *, /, //, %, **, and functions abs, round, sqrt, ceil, floor, log, log10, log2, exp, sin, cos, tan, asin, acos, atan, atan2, degrees, radians, factorial, gcd, lcm. Constants: pi, e, tau, inf.

Tool: unit_convert

ArgumentTypeRequiredDescription
valuefloatyesNumeric value to convert
from_unitstryesSource unit (case-insensitive)
to_unitstryesTarget unit (case-insensitive)
Output fieldTypeDescription
resultfloatConverted value
categorystrMeasurement category
errorstr|NoneError on failure

Supported categories: length, mass, volume, time, speed, area, data, temperature. Temperature uses affine conversion (Celsius/Fahrenheit/Kelvin); all others use SI-base linear factors — math.py:136. Incompatible categories return an error.

Tool: statistics_summary

ArgumentTypeRequiredDescription
numberslist[float]yesValues to summarise
Output fieldTypeDescription
countintNumber of values
meanfloatArithmetic mean
medianfloatMedian
stdevfloat|NoneSample std deviation (None when n < 2)
minimumfloatMin value
maximumfloatMax value
totalfloatSum

Tool: solve_equation

ArgumentTypeRequiredDescription
equationstryesPython expression equal to zero (e.g. x**2 - 4)
Output fieldTypeDescription
solutionslist[float]Real-valued roots
methodstr"symbolic" (sympy) or "numeric" (bisection fallback)
errorstr|NoneError on failure

Tries sympy.solve first; falls back to bisection scan over [-1000, 1000]math.py:375.

Config Keys

None.