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 脚本:通过 cap Lua 模块使用 cap.call("id", json_string)

  • 内部 C 代码claw_cap_call() C API

Capability 组与门控

所有 Capability 按功能划分到对应的 cap_group。大多数组始终对 LLM 可见;部分组默认隐藏,需激活对应 Skill 后才解锁。这一机制确保 LLM 在调用硬件相关或资源密集型工具前获得必要的上下文。

组名

门控 Skill

包含的 Capability

files

(始终可见)

file_read, file_write, file_delete, file_list, file_move, file_copy, file_stat

skill_mgr

(始终可见)

skill_list, skill_activate, skill_save, skill_delete, skill_deactivate

lua

(始终可见)

lua_run, lua_run_async, lua_job_get, lua_job_list, lua_job_stop

scheduler

(始终可见)

scheduler_add_job, scheduler_list_jobs, scheduler_get_job, scheduler_remove_job, scheduler_enable_job, scheduler_disable_job, scheduler_pause_job, scheduler_resume_job, scheduler_trigger_now

system

(始终可见)

get_heap_info, get_task_list, get_info, get_wifi, get_ip, restart

time

(始终可见)

get_current_time, sync_time

http_request

(始终可见)

http_request

net_discover

(始终可见)

net_discover_start, net_discover_stop, net_discover_peer

vision

(始终可见)

vision_describe

web_search

(始终可见)

web_search

memory

(始终可见)

memory_store, memory_recall, memory_update, memory_forget, memory_list

im_local / im_telegram / 等

(始终可见)

local_send_text, telegram_send_text, feishu_send_message, wechat_send_text, qq_send_message, im_send_media

board

board_hardware_info (需先激活)

board_list_devices, board_get_device, board_query_peripheral, board_schema, board_reload

audio_stream

camera_capture (需先激活)

audio_stream_start, audio_stream_tx_start, audio_stream_rx_start, audio_stream_stop, audio_stream_pause, audio_stream_status

Capability 速查表

文件管理

Capability

说明

file_read

vfs:/rolfs:/ 读取文件内容

file_write

写入或追加文件内容

file_delete

删除文件(会先停止使用该路径的 Lua 任务)

file_list

列出指定路径下的文件和子目录

file_move

移动或重命名文件

file_copy

将文件复制到新路径

file_stat

获取文件元数据(大小、类型、修改时间)

Skill 管理

Capability

说明

skill_list

列出所有可用 Skill(内置 + 用户)

skill_activate

在当前会话中激活 Skill;注入其 SKILL.md 上下文并解锁对应 cap_groups

skill_save

保存新的用户 Skill(SKILL.md + 可选 scripts/main.lua)

skill_delete

删除用户创建的 Skill 目录

skill_deactivate

从会话激活列表中停用 Skill

Lua 执行

Capability

说明

lua_run

同步执行 Lua 脚本;返回 run() 返回值及 print() 输出

lua_run_async

以后台任务方式启动 Lua 脚本;立即返回 job_id

lua_job_get

获取后台任务状态及增量日志输出(通过 since_seq 参数)

lua_job_list

列出所有后台 Lua 任务及其状态

lua_job_stop

协作式停止正在运行的后台任务

调度器

Capability

说明

scheduler_add_job

添加或更新调度任务(按 id upsert);触发类型 kind 支持 once / interval / cron / on_event,动作 action 支持 agent / cap / emit

scheduler_list_jobs

列出所有已注册的调度任务

scheduler_get_job

按 ID 获取单个调度任务的完整详情

scheduler_remove_job

按 ID 删除调度任务

scheduler_enable_job

启用之前已禁用的任务

scheduler_disable_job

禁用任务而不删除(保留定义,停止触发)

scheduler_pause_job

临时暂停任务而不禁用(如"跳过今天的闹钟"),用 scheduler_resume_job 恢复

scheduler_resume_job

恢复被暂停的任务

scheduler_trigger_now

立即执行一次任务的动作(用于测试),不改变其调度计划

板级硬件 (需先激活 board_hardware_info )

Capability

说明

board_list_devices

列出板上所有已注册的硬件设备(传感器、显示屏等)

board_get_device

获取设备完整信息:芯片、接口引脚分配、驱动参数

board_query_peripheral

查询某类外设是否可用及哪些引脚/实例空闲

board_schema

获取板级硬件描述 Schema(芯片定义、约束条件)

board_reload

从 VFS 重新加载 board.json 配置

系统信息

Capability

说明

get_heap_info

获取空闲堆内存及历史最小值

get_task_list

列出所有 FreeRTOS 任务的状态、优先级和栈水位

get_info

获取芯片型号、固件版本、运行时间

get_wifi

获取 Wi-Fi SSID、信道、RSSI 及 IP 地址

get_ip

获取当前 IP 地址

restart

短暂延迟后软复位设备

时间

Capability

说明

get_current_time

获取当前挂钟时间(UTC+8)及 Unix 时间戳

sync_time

触发 SNTP 时间同步并将结果写入系统时钟和 RTC

网络

Capability

说明

http_request

发起 HTTP/HTTPS 请求(GET/POST/PUT/PATCH/DELETE/HEAD);返回状态码和响应体

net_discover_start

启动持续的 UDP 广播/监听对端发现;在对端出现/消失时触发回调

net_discover_stop

停止对端发现后台服务

net_discover_peer

一次性对端发现(阻塞直至找到或超时)

音频流 (需先激活 camera_capture )

Capability

说明

audio_stream_start

一次性同时启动 RX(UDP→扬声器)和 TX(DMIC→UDP)流

audio_stream_tx_start

启动 DMIC 到 UDP 的发送流(C 层控制推送开关)

audio_stream_rx_start

启动 UDP 到扬声器的接收流;应在 TX 之前调用

audio_stream_stop

停止 TX 和 RX 流任务

audio_stream_pause

暂停音频流

audio_stream_status

获取 TX/RX 运行状态及 UDP 包计数

视觉

Capability

说明

vision_describe

对摄像头图像进行视觉描述

网络搜索

Capability

说明

web_search

执行网络搜索并返回摘要结果

长期记忆

Capability

说明

memory_store

写入一条长期记忆(content 必填;source、tags 可选,tags 为逗号分隔)

memory_recall

按关键词检索长期记忆(keyword、max_results 可选)

memory_update

按 id 更新一条长期记忆的内容(id、content 必填)

memory_forget

按 id 删除一条长期记忆

memory_list

列出所有长期记忆(max_results 可选,默认 20)

这五个工具的内部机制(结构化记忆、摘要标签检索、自动抽取)见 记忆内部机制

即时通讯

Capability

说明

local_send_text

向本地 WebUI 发送文本消息

telegram_send_text

通过 Telegram Bot 发送文本消息

feishu_send_message

通过飞书发送消息

wechat_send_text

通过微信发送文本消息

qq_send_message

通过 QQ 发送消息

im_send_media

向 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 描述单个能力,核心字段:

字段

说明

id / name

标识与展示名(调用时用)

family

逻辑分组标签(列表输出时带上)

description / input_schema_json

给人和给模型看的说明,以及 JSON Schema 格式的参数定义

kind

CLAW_CAP_KIND_INVOKE (以调用为主)、CLAW_CAP_KIND_EMITTER (会产事件)、CLAW_CAP_KIND_BOTH

cap_flags

标志位,如 CLAW_CAP_FLAG_LLM_ACCESS (允许被 LLM 调用)、CLAW_CAP_FLAG_EVENTS_OUT

init / start / stop

可选的生命周期钩子

execute

真正执行时收到 JSON 字符串输入、调用上下文 claw_cap_call_context_t (含 session_id / channel / chat_id / caller),把结果 malloc 出来写入 *output (调用方负责 free

一批相关描述符放进 claw_cap_group_t,通过 claw_cap_register_group() 注册为一个组,便于按组统一启停与可见性管理。

调用路径

同一个 claw_cap_call 入口,服务三类调用方(由 claw_cap_call_context_t.caller 区分):

  1. Agent:LLM 发出工具调用 → call_cap 桥接 → claw_cap_callcaller = LLM

  2. AT 控制台AT+CLAW=cap,<id>[,<json>]caller = MANUAL

  3. 事件路由:规则动作 rtk_capcaller = 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

on_init

注册 capability 组;在 claw_cap_start_all() 之前

AGENT

on_agent

追加 context provider / 完成观察器 / 设可见性;在 claw_agent_initclaw_agent_start 之间

IO

on_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_luacap_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 表示成功;分配失败时保持 *outputNULL 并返回 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]),
};

descriptioninput_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 (只挂完成观察器、不注册工具组)。