Local Tools
A local tool is a function that runs on the device. The agent calls it from the cloud — "set volume to 30", "what time is it", "turn off the bedroom light" — and the firmware executes it locally and reports back.
This is different from a cloud tool, which runs on the platform.
Tools that ship today
Voice Chat (friend)
| Tool | What it does |
|---|---|
set_volume | Set speaker volume (0–100). |
set_emotion | Set the on-screen emoji to one of: Sad, Happy, Confused, Shocked, Angry, Cry, Wink, Neutral. |
set_reminder | Schedule a local reminder that fires later. |
get_local_time | Returns current device time. |
Matter Controller (matter_controller)
All of the above, plus:
| Tool | What it does |
|---|---|
get_device_list | Lists Matter nodes in the controller's fabric. |
control_device | Sends a Matter command. |
How they work
When the agent decides to call a local tool, it sends a tool_request over the WebSocket. The firmware's tool handler runs, returns a result, and the firmware sends a tool_response back. The agent then continues its reply with the tool's output incorporated.
Cloud agent ──── tool_request ────► Device (firmware)
│
▼
handler runs
│
Cloud agent ◄──── tool_response ──── result returned
│
▼
Agent reply continues with tool output
Tool handlers are short, synchronous C functions. They don't block the audio path — they run on a separate task.
Adding a tool
Steps to add say_hello (a trivial example):
1. Write the handler
In examples/voice_chat/main/app_tools.c:
static esp_err_t say_hello_handler(
esp_agent_handle_t handle,
const char *tool_name,
esp_agent_tool_param_t params[],
size_t num_params,
void *user_data,
char **result)
{
// Read the "name" parameter
const char *name = NULL;
for (size_t i = 0; i < num_params; i++) {
if (strcmp(params[i].name, "name") == 0) {
name = params[i].value.string_val;
}
}
if (!name) name = "stranger";
// Build the result
asprintf(result, "{\"greeting\":\"Hello, %s!\"}", name);
return ESP_OK;
}
2. Register the handler
In app_tools_register():
app_agent_register_tool("say_hello", say_hello_handler, NULL);
3. Declare the tool to the agent
The tool's JSON schema lives in the agent's configuration (in the Admin Dashboard, not the firmware). Add a local-type tool entry to your agent:
{
"type": "local",
"name": "say_hello",
"description": "Greet the user by name.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Name of the person to greet" }
},
"required": ["name"]
}
}
}
You declare this in Advanced mode of the agent editor (see Advanced Configuration). The name field must match the C string you registered.
4. Build and flash
idf.py build flash monitor
5. Test
In a chat with the agent: "Say hello to Alice." The agent calls say_hello with name="Alice"; the device returns {"greeting":"Hello, Alice!"}; the agent replies "Hello, Alice!".
Conventions
- Names are exact strings. A typo between the firmware and the agent config means the tool isn't found at runtime.
- Return JSON strings. The agent treats the result as text, but most agents work better with structured JSON than free text.
- Keep handlers fast. They block the agent's reply until they return. Anything > 100 ms degrades the conversation.
- Don't access shared state without locks. Handlers run on a separate task from audio.
- Validate input. The schema is checked server-side before the call, but defensive validation in the handler protects against unusual inputs.
- Handle errors clearly. Return JSON with an
errorfield —{"error": "device_unreachable", "message": "..."}— so the agent can explain the failure to the user.
Where the implementation lives
| Path | Tools |
|---|---|
examples/common/app_common/src/app_common_tools.c | set_volume, set_reminder, get_local_time (shared). |
examples/voice_chat/main/app_tools.c | Voice Chat–specific (set_emotion). |
examples/matter_controller/main/app_tools.c | Matter Controller–specific (get_device_list, control_device). |
examples/matter_controller/main/controller_rest_apis/matter_device.c | Where control_device translates JSON → Matter commands. |
Tips
- Look at existing tools first. Copying
set_volumeis faster than starting from scratch. - Iterate on the agent side first. A good tool description in the agent config matters as much as the firmware code.
- Test in a fresh conversation after agent changes — the LLM caches tool descriptions per turn.
- Watch the serial monitor during development. Tool calls and errors are logged.
Read next
- WebSocket Protocol → Tool Calls — exactly what
tool_request/tool_responselook like on the wire. - Admin → Tools — how the agent's tool list is configured.