Capability 系统
本章讲 Ameba-Claw 的能力(capability)体系——也就是 Agent 能调用的一个个工具是怎么组织和接入的。先看「能力系统」,了解能力如何按组暴露给 LLM、以及可见性如何控制;再看「Capability 运行时与自注册」,理解 C 层的描述符模型和分阶段自注册机制;最后用「实现一个 Capability」走一遍从描述符定义、执行回调到自注册的完整流程,照着就能写出自己的能力。
Capability 系统
什么是 Capability
Capability 是注册在 C 层的可调用工具,LLM 通过 tool-use API 以 function-call 方式直接调用。每个 Capability 具有唯一的字符串 id、JSON 输入 Schema 和执行函数。除 LLM 调用外,还支持以下方式触发:
AT 命令:
AT+CLAW=cap,<id>[,<json_args>]Lua 脚本:通过
capLua 模块使用cap.call("id", json_string)内部 C 代码:
claw_cap_call()C API
Capability 组与门控
所有 Capability 按功能划分到对应的 cap_group。大多数组始终对 LLM 可见;部分组默认隐藏,需激活对应 Skill 后才解锁。这一机制确保 LLM 在调用硬件相关或资源密集型工具前获得必要的上下文。
组名 |
门控 Skill |
包含的 Capability |
|---|---|---|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
(始终可见) |
|
|
board_hardware_info (需先激活) |
|
|
camera_capture (需先激活) |
|
Capability 速查表
文件管理
Capability |
说明 |
|---|---|
|
从 |
|
写入或追加文件内容 |
|
删除文件(会先停止使用该路径的 Lua 任务) |
|
列出指定路径下的文件和子目录 |
|
移动或重命名文件 |
|
将文件复制到新路径 |
|
获取文件元数据(大小、类型、修改时间) |
Skill 管理
Capability |
说明 |
|---|---|
|
列出所有可用 Skill(内置 + 用户) |
|
在当前会话中激活 Skill;注入其 SKILL.md 上下文并解锁对应 cap_groups |
|
保存新的用户 Skill(SKILL.md + 可选 scripts/main.lua) |
|
删除用户创建的 Skill 目录 |
|
从会话激活列表中停用 Skill |
Lua 执行
Capability |
说明 |
|---|---|
|
同步执行 Lua 脚本;返回 |
|
以后台任务方式启动 Lua 脚本;立即返回 |
|
获取后台任务状态及增量日志输出(通过 since_seq 参数) |
|
列出所有后台 Lua 任务及其状态 |
|
协作式停止正在运行的后台任务 |
调度器
Capability |
说明 |
|---|---|
|
添加或更新调度任务(按 id upsert);触发类型 |
|
列出所有已注册的调度任务 |
|
按 ID 获取单个调度任务的完整详情 |
|
按 ID 删除调度任务 |
|
启用之前已禁用的任务 |
|
禁用任务而不删除(保留定义,停止触发) |
|
临时暂停任务而不禁用(如"跳过今天的闹钟"),用 |
|
恢复被暂停的任务 |
|
立即执行一次任务的动作(用于测试),不改变其调度计划 |
板级硬件 (需先激活 board_hardware_info )
Capability |
说明 |
|---|---|
|
列出板上所有已注册的硬件设备(传感器、显示屏等) |
|
获取设备完整信息:芯片、接口引脚分配、驱动参数 |
|
查询某类外设是否可用及哪些引脚/实例空闲 |
|
获取板级硬件描述 Schema(芯片定义、约束条件) |
|
从 VFS 重新加载 board.json 配置 |
系统信息
Capability |
说明 |
|---|---|
|
获取空闲堆内存及历史最小值 |
|
列出所有 FreeRTOS 任务的状态、优先级和栈水位 |
|
获取芯片型号、固件版本、运行时间 |
|
获取 Wi-Fi SSID、信道、RSSI 及 IP 地址 |
|
获取当前 IP 地址 |
|
短暂延迟后软复位设备 |
时间
Capability |
说明 |
|---|---|
|
获取当前挂钟时间(UTC+8)及 Unix 时间戳 |
|
触发 SNTP 时间同步并将结果写入系统时钟和 RTC |
网络
Capability |
说明 |
|---|---|
|
发起 HTTP/HTTPS 请求(GET/POST/PUT/PATCH/DELETE/HEAD);返回状态码和响应体 |
|
启动持续的 UDP 广播/监听对端发现;在对端出现/消失时触发回调 |
|
停止对端发现后台服务 |
|
一次性对端发现(阻塞直至找到或超时) |
音频流 (需先激活 camera_capture )
Capability |
说明 |
|---|---|
|
一次性同时启动 RX(UDP→扬声器)和 TX(DMIC→UDP)流 |
|
启动 DMIC 到 UDP 的发送流(C 层控制推送开关) |
|
启动 UDP 到扬声器的接收流;应在 TX 之前调用 |
|
停止 TX 和 RX 流任务 |
|
暂停音频流 |
|
获取 TX/RX 运行状态及 UDP 包计数 |
视觉
Capability |
说明 |
|---|---|
|
对摄像头图像进行视觉描述 |
网络搜索
Capability |
说明 |
|---|---|
|
执行网络搜索并返回摘要结果 |
长期记忆
Capability |
说明 |
|---|---|
|
写入一条长期记忆(content 必填;source、tags 可选,tags 为逗号分隔) |
|
按关键词检索长期记忆(keyword、max_results 可选) |
|
按 id 更新一条长期记忆的内容(id、content 必填) |
|
按 id 删除一条长期记忆 |
|
列出所有长期记忆(max_results 可选,默认 20) |
这五个工具的内部机制(结构化记忆、摘要标签检索、自动抽取)见 记忆内部机制。
即时通讯
Capability |
说明 |
|---|---|
|
向本地 WebUI 发送文本消息 |
|
通过 Telegram Bot 发送文本消息 |
|
通过飞书发送消息 |
|
通过微信发送文本消息 |
|
通过 QQ 发送消息 |
|
向 IM 平台发送媒体文件 |
AT 命令接口
列出所有已注册的 Capability:
AT+CLAW=cap
列出所有已安装的 Skill(调用 skill_list Capability):
AT+CLAW=cap,skill_list
调用 Capability 并传入 JSON 参数:
AT+CLAW=cap,<capability_id>,<json_args>
示例:
AT+CLAW=cap,get_info
AT+CLAW=cap,file_list,{"path":"vfs:/skills/"}
AT+CLAW=cap,lua_run,{"path":"vfs:/tmp/test.lua","args":{}}
AT+CLAW=cap,skill_activate,{"name":"board_hardware_info"}
指定会话 ID(用于控制每会话 cap_groups 可见性):
AT+CLAW=cap,<capability_id>,<json_args>,sid,<session_id>
在 Lua 脚本中调用 Capability
在 Skill 脚本中使用 cap Lua 模块。cap.call 始终返回 两个 值:
local cap = require("cap")
local cjson = require("cjson")
-- 正确:同时接收 ok(布尔值)和 result_json(字符串)
local ok, result_json = cap.call("get_current_time", "{}")
if not ok then
return '{"error":' .. (result_json or '"cap call failed"') .. '}'
end
local t = cjson.decode(result_json)
-- 使用 t.timestamp、t.datetime 等字段
-- 错误:只接收一个返回值时,变量得到的是布尔值 ok,JSON payload 被丢弃
-- local result = cap.call("get_current_time", "{}") -- 请勿这样写
Capability 运行时与自注册
本文面向开发者,介绍 Capability 在 C 层的运行时模型与自注册机制。"如何调用 Capability"见 Capability 系统,"如何从零写一个新 Capability"见 实现一个 Capability。
两个不同的"注册"
Ameba-Claw 里有两个名字相近但职责不同的模块,务必区分:
claw_cap—— 工具运行时。所有"可被调用的一段能力"(给 LLM 当 tool、给 AT 控制台当cap命令、被事件路由直接调用)都先登记成带元数据的描述符,再经统一入口执行。claw_cap_registry—— 启动生命周期注册表。它管理每个 capability 插件在**开机装配**时的三阶段钩子。每个 cap 在自己的.c里自注册,没有一张中心表去列举所有 cap。
Capability 描述符
claw_cap_descriptor_t 描述单个能力,核心字段:
字段 |
说明 |
|---|---|
|
标识与展示名(调用时用) |
|
逻辑分组标签(列表输出时带上) |
|
给人和给模型看的说明,以及 JSON Schema 格式的参数定义 |
|
|
|
标志位,如 |
|
可选的生命周期钩子 |
|
真正执行时收到 JSON 字符串输入、调用上下文 |
一批相关描述符放进 claw_cap_group_t,通过 claw_cap_register_group() 注册为一个组,便于按组统一启停与可见性管理。
调用路径
同一个 claw_cap_call 入口,服务三类调用方(由 claw_cap_call_context_t.caller 区分):
Agent:LLM 发出工具调用 →
call_cap桥接 →claw_cap_call,caller = LLM。AT 控制台:
AT+CLAW=cap,<id>[,<json>],caller = MANUAL。事件路由:规则动作
rtk_cap,caller = INTERNAL(见 事件路由与自动化)。
LLM 工具可见性
claw_cap_build_llm_tools_json() 根据当前上下文生成工具列表,由 tools provider 交给 Agent。已注册 ≠ 对 LLM 可见:
所有注册的组,AT 控制台和事件路由都能直接调用;
但是否让 LLM"原生"看到某组工具,由可见性白名单控制。这是为了避免一次性把全部工具塞进上下文,导致提示词过长、模型能力退化。
每个会话还有独立的可见性范围(
claw_cap_set_session_llm_visible_groups):激活某个 Skill 会把它绑定的组加入当前会话的白名单,实现"按需披露"。
运维 API:claw_cap_list / claw_cap_list_groups 枚举能力与组;claw_cap_enable_group / claw_cap_disable_group 按组启停;claw_cap_unregister_group 卸载(带超时等待活跃调用结束)。
自注册机制:CLAW_CAP_REGISTER
每个 cap 在自己的 .c 里用一行宏声明并自注册一个生命周期描述符,无需在任何中心文件登记:
CLAW_CAP_REGISTER(time, {
.group = "time",
.order = 10,
.on_init = time_on_init,
.on_agent = time_on_agent,
.on_io = time_on_io,
});
宏展开成一个 static const 描述符(放在 rodata)加一个 C 构造函数;构造函数在 RTOS/堆/串口就绪**之前**运行,因此**只能登记,不能** malloc、碰 RTOS 或读配置。配置通过各阶段钩子的 const claw_config_t * 参数传入。
三个生命周期阶段
ameba_claw_main() 按阶段驱动所有已注册 cap 的钩子(见 启动与运行时装配):
阶段 |
钩子 |
时机与用途 |
|---|---|---|
INIT |
|
注册 capability 组;在 |
AGENT |
|
追加 context provider / 完成观察器 / 设可见性;在 |
IO |
|
通道 / 后台服务 / Wi-Fi 回调 / HTTP 路由;在事件路由与 http_init 之后、http_server_start 之前 |
执行顺序由 order 字段决定
同一阶段内,钩子按 order 升序、稳定执行——从不依赖链接顺序或注册顺序。order 的取值集中记录在 claw_cap_registry.h 的注释表里,作为唯一协调点,不要散落到各 cap 文件里写冲突的值。
只有**顺序敏感**的关系才必须成立,例如:AGENT 阶段各 provider 的相对顺序(cap_time 10 < cap_skill_mgr 20 < cap_board_mgr 30)决定系统提示词的拼装次序;cap_router_mgr (200)排在最后,使它的组在 AGENT 阶段可见性快照之后才注册,从而默认对 LLM 隐藏。其余只注册组、顺序无关的 cap 可以共用一个值。
CLAW_CAP_FLAG_CORE 标志表示该 cap 始终启用、忽略运行时启停名单(如 cap_lua、cap_webui)。
实现一个 Capability
本文演示如何为 Ameba-Claw 写一个新的 C 层 Capability,完整覆盖从描述符定义、执行回调到自注册的全流程。运行时模型的背景见 Capability 运行时与自注册。
备注
本文讲的是**编译进固件的 C 能力**。如果你只想让 Agent 在运行时获得新功能而不重新编译,用 Lua Skill 即可(见 Skill 系统),无需改 C 代码。
目录结构
每个 capability 是 claw_capabilities/ 下的一个组件目录:
claw_capabilities/cap_my_feature/
├── CMakeLists.txt
├── include/
│ └── cap_my_feature.h (可选,仅在需要跨模块暴露接口时)
└── src/
└── cap_my_feature.c 描述符 + 执行回调 + 自注册
实现执行回调
execute 是能力的核心:收到 JSON 字符串输入,把结果 malloc 出来写入 *output (调用方负责 free)。签名固定:
#include "claw_cap.h"
#include <cJSON.h>
static int my_feature_execute(const char *input_json,
const claw_cap_call_context_t *ctx,
char **output)
{
cJSON *root = cJSON_Parse(input_json ? input_json : "{}");
if (!root) {
return claw_cap_set_output(output, "{\"error\":\"invalid JSON\"}");
}
/* 需要时可从 ctx 取调用上下文:session_id / channel / chat_id / caller */
cJSON *param = cJSON_GetObjectItem(root, "param");
if (!cJSON_IsString(param)) {
cJSON_Delete(root);
return claw_cap_set_output(output, "{\"error\":\"param is required\"}");
}
int rc = claw_cap_set_output(output, "{\"ok\":true,\"echo\":\"%s\"}",
param->valuestring);
cJSON_Delete(root);
return rc;
}
关键约定:
结果写入
*output, 不要 直接打印到串口。用claw_cap_set_output(output, fmt, ...)助手可自动分配足够大的缓冲区。返回
RTK_SUCCESS表示成功;分配失败时保持*output为NULL并返回RTK_ERR_NOMEM。execute里分配的临时内存必须在返回前释放。若输出内容可能很大,考虑返回文件路径让 LLM 进一步查询,而不是塞一大段文本。
定义描述符与组
static const claw_cap_descriptor_t s_caps[] = {
{
.id = "my_action",
.name = "my_action",
.family = "my_feature",
.description = "Perform my custom action with the given param.",
.kind = CLAW_CAP_KIND_INVOKE,
.cap_flags = CLAW_CAP_FLAG_LLM_ACCESS, /* 允许 LLM 调用 */
.input_schema_json =
"{\"type\":\"object\","
"\"properties\":{\"param\":{\"type\":\"string\"}},"
"\"required\":[\"param\"]}",
.execute = my_feature_execute,
},
};
static const claw_cap_group_t s_group = {
.group_id = "my_feature",
.plugin_name = "cap_my_feature",
.version = "1",
.descriptors = s_caps,
.descriptor_count = sizeof(s_caps) / sizeof(s_caps[0]),
};
description 与 input_schema_json 直接决定 LLM 怎么理解和调用这个工具,务必写清楚。
自注册
用 CLAW_CAP_REGISTER 声明生命周期钩子并自注册,无需在任何中心文件登记。一个 cap 只实现它需要的阶段:
#include "claw_cap_registry.h"
/* INIT 阶段:注册工具组 */
static void my_feature_on_init(const claw_config_t *cfg)
{
(void)cfg;
claw_cap_register_group(&s_group);
}
CLAW_CAP_REGISTER(my_feature, {
.group = "my_feature",
.order = 90, /* 见 claw_cap_registry.h 的 order 分配表 */
.on_init = my_feature_on_init,
});
警告
CLAW_CAP_REGISTER 展开出的构造函数在 RTOS / 堆 / 串口就绪**之前**运行,所以构造函数本身只能登记描述符——不能 malloc、碰 RTOS 或读配置。真正的初始化放到 on_init 里做(它收到 const claw_config_t *)。
三个阶段钩子(on_init / on_agent / on_io)的含义、以及 order 取值规则见 Capability 运行时与自注册。若你的 cap 还要注入系统提示词或挂完成观察器,就实现 on_agent;若要跑后台任务、注册 Wi-Fi 回调或 HTTP 路由,就实现 on_io。
CMakeLists.txt
ameba_add_library(cap_my_feature SRCS src/cap_my_feature.c)
# 至少依赖 claw_cap 运行时与 cJSON;如自注册还需 claw_cap_registry。
# 具体写法参照同目录其它 cap_*/CMakeLists.txt(本工程统一走 SDK 的编译宏)。
最后把组件目录加入 claw_capabilities/CMakeLists.txt 的声明列表(参照现有 cap 的写法)。由于是自注册, 不需要 在 ameba_claw_main.c 里手动调用注册——编译进固件后构造函数会自动登记,装配阶段按 order 驱动它的钩子。
产生事件的能力
若能力需要主动产生事件(如接收到 IM 消息、外设中断),把 kind 设为 CLAW_CAP_KIND_EMITTER (或 BOTH),并在后台任务里调用事件发布接口:
#include "claw_event_publisher.h"
/* 产生一条文本消息事件 */
claw_event_dispatcher_publish_message(
"my_gateway", /* source_cap */
"my_channel", /* channel */
chat_id,
text,
sender_id,
message_id);
/* 产生一条自定义触发事件 */
claw_event_dispatcher_publish_trigger(
"my_gateway", /* source_cap */
"my_custom_event", /* event_type */
event_key,
payload_json);
事件产生后,由事件路由按规则决定是触发自动化、还是交给 Agent 推理。详见 事件路由与自动化。
参考实现
工程里的现有 cap 就是最好的模板:cap_time (三阶段全用,含 context provider 与 Wi-Fi 回调)、cap_system (纯查询工具)、cap_files / cap_lua (文件系统 + 路径校验)、cap_honesty (只挂完成观察器、不注册工具组)。