ITool architecture
Every capability in Buril is an ITool. A tool is a C# class that:
- Implements the
IToolinterface. - Has a unique, lowercase, underscore-separated name (e.g.,
scene_open,gameobject_create,vroid_export_vrm). - Takes a
JObject inputand returns anobjectresult. - Lives under
Editor/Tools/<family>/so the registry can find it.
That's the entire contract. Everything else — multi-agent orchestration, DCC bridges, AI pipelines — composes from this single interface.
The interface
public interface ITool
{
string Name { get; }
string Description { get; }
JSchema InputSchema { get; }
object Run(JObject input);
}
| Member | Purpose |
|---|---|
Name | Stable identifier the LLM uses to call the tool. Never rename a shipped tool without a migration shim — agents have its name in their planning context. |
Description | One-paragraph English description the LLM sees when planning. This is the most under-rated field — clarity here directly improves tool selection. |
InputSchema | JSON Schema (via JSchema) describing the expected input shape. The dispatcher validates against this before invoking Run. |
Run | Synchronous. Long-running work delegates to a coroutine or task and returns a job handle the agent can poll. Editor-thread restrictions apply — see "Threading" below. |
Naming convention
Tools follow <family>_<verb>_<noun>:
| Family | Examples |
|---|---|
editor_ | editor_state, editor_window_open, editor_play_mode_toggle |
scene_ | scene_open, scene_save, scene_list_gameobjects |
gameobject_ | gameobject_create, gameobject_set_position, gameobject_destroy |
asset_ | asset_import_fbx, asset_create_material, asset_move |
script_ | script_create, script_compile_status, script_find_references |
vroid_ | vroid_export_vrm, vroid_apply_blendshape |
cascadeur_ | cascadeur_keyframe_run, cascadeur_export_alembic |
agent_team_ | agent_team_handoff, agent_team_query |
mcp_ | mcp_register_external, mcp_list_servers |
Family prefixes are not enforced by code, but are enforced by review. Inconsistent naming hurts the LLM's planning hit-rate.
Threading
Most Unity APIs (AssetDatabase, scene mutations, EditorApplication) require the main editor thread. Buril's dispatcher pins ITool execution to the main thread by default — your Run method runs synchronously inside an EditorApplication.update tick.
For tools that genuinely need to run off-thread (HTTP calls, large file I/O), use the [OffThread] attribute:
[OffThread]
public class MyHttpTool : ITool
{
public object Run(JObject input)
{
// Safe to do blocking I/O here.
// Do NOT touch AssetDatabase or scene state directly.
}
}
If an off-thread tool needs to apply changes to Unity, marshal back via EditorApplication.delayCall or use the MainThreadDispatcher helper.
Error handling
Tools have three legal outcomes:
| Outcome | How |
|---|---|
| Success | Return any JSON-serializable object. Wrap user-meaningful data in { "ok": true, "data": ... }. |
| Validation failure | Throw ToolValidationException("reason"). The dispatcher catches, formats the message, and returns it to the LLM. The agent can retry with adjusted input. |
| Runtime failure | Throw any other exception. The dispatcher logs it, returns { "ok": false, "error": "...", "type": "..." }. The agent can decide whether to retry, ask the user, or abandon the plan. |
Never return null to signal failure — null tools confuse the LLM into thinking the call succeeded with empty data.
Writing a new tool
Minimal example:
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Buril.Tools;
namespace Buril.Tools.MyFamily
{
public class MyFamilyGreetTool : ITool
{
public string Name => "myfamily_greet";
public string Description =>
"Returns a friendly greeting for the named user. " +
"Use when the user explicitly asks to be greeted.";
public JSchema InputSchema => JSchema.Parse(@"{
'type': 'object',
'properties': {
'name': { 'type': 'string', 'minLength': 1 }
},
'required': ['name']
}");
public object Run(JObject input)
{
var name = input.Value<string>("name");
return new { ok = true, greeting = $"Hello, {name}!" };
}
}
}
Drop this into Editor/Tools/MyFamily/MyFamilyGreetTool.cs. On the next compile, the registry auto-discovers it. No manifest, no registration call.
What an ITool is not
- It is not a runtime feature. Tools live in
Editor/, compile under theUNITY_EDITORdefine, and never ship in builds. - It is not a state container. Tools should be stateless — anything persistent goes in
EditorPrefs,Library/BurilCache/, or a project-tracked asset. - It is not a UI element. Tools are headless. If you need UI, build an EditorWindow that calls the tool — keep them separated.
Read next
- Tool registry — how 3,691 ITools get auto-discovered.
- Multi-Agent Studio — when a single agent isn't enough.
- Agent Team tools — the
agent_team_*family that orchestrates the studio.