核心原理

本章介绍 Ameba-Claw 的整体运行原理。先通过「项目架构」建立全局图景——核心层与能力层如何分层、各自负责什么;再看设备上电后「启动与运行时装配」的分阶段流程,如何按 config、core、capabilities、agent、io、tasks 的顺序把各子系统依次拼起来;最后深入「Agent 核心」,理解上下文拼装流水线、工具调用循环,以及取消与看门狗等运行时保护机制。

项目架构

本文面向开发者,介绍 Ameba-Claw 的整体结构、核心组件及文件系统布局。

架构概览

Ameba-Claw 采用多层架构,用户输入经由 Agent 推理层处理后,由底层 Tool 或者 Capability 执行层完成实际操作:

../../_images/architecture.webp

消息通过 IM 渠道(或串口 AT 接口)进入,claw_agent 启动 ReAct 推理循环:LLM 决策下一步调用哪个 Tool (Capability),对应 Tool 执行后将结果作为工具响应反馈给 LLM,循环持续直到 LLM 生成最终回复。

核心组件

claw_agent

实现 ReAct(Reasoning + Acting)循环,每个会话请求在独立任务中处理:

  1. 携带当前工具描述和对话上下文向 LLM 发送请求。

  2. 解析 LLM 返回的 tool-call 响应。

  3. 将工具调用分发至对应 Tool 或者 Capability 并收集结果。

  4. 将结果追加为工具响应,进入下一轮推理。

  5. 当 LLM 产生最终回复时,将其发送给用户。

Watchdog 任务监控心跳时间戳。若 7 分钟内未收到任何 tool-call 心跳(表明某个 Tool 挂起或 LLM 请求卡死),Watchdog 将通过 sys_reset() 触发软复位。

Capability 系统

Capability 是注册在 C 层的可调用工具,LLM 通过 tool-call 接口直接调用。每个 Capability 具有唯一的 id、JSON 输入 Schema 和执行函数。开发者也可通过 AT+CLAW=cap,<id> 或在 Lua 脚本中使用 cap.call() 来调用。

Capability 按功能划分为 cap_group。部分组默认对 LLM 隐藏,需激活对应 Skill 后方可解锁——例如 board 组需激活 board_hardware_info Skill 后才可见。

详见 Capability 系统

Skill 管理器

管理 rolfs:/skills/ (内置)和 vfs:/skills/ (用户创建)下的 Skill 目录。Skill 管理器负责:

  • 解析 SKILL.md 元数据,提取名称、描述、cap_groups 依赖及 LLM 系统提示注入内容

  • 区分两类 Skill:

    • 脚本型:包含 scripts/main.lua,由 lua_run Capability 执行

    • 知识型:无脚本,仅向 LLM 注入上下文并解锁对应 cap_groups

  • vfs:/session/ 中持久化维护每个会话的激活列表,实现 Session 隔离

详见 Skill 系统

Lua 执行引擎

通过 lua_run (同步)和 lua_run_async (异步)Capability 对外暴露,核心设计要点:

  • 每次调用均创建独立的 lua_State,运行间无共享全局状态

  • Lua VM 运行在独立的 8 KB 任务栈中(非调用方栈),防止脚本解析时栈溢出

  • 沙盒移除 ioosdebugloadloadfiledofile 及 raw 访问原语

  • 同步默认超时 30 秒;取消钩子每执行 500 条 Lua 指令触发一次

  • 异步任务提供 4 个 job 槽(最多 2 个并发,与同步共享),每个 job 带 2 KB 环形日志,可通过 lua_job_get 增量读取

详见 Lua 模块参考

IM 分发器

接收所有支持渠道的消息——Telegram、Feishu(飞书)、WeChat(微信)、QQ、本地 WebUI、串口 AT 接口——并将其封装为标准 claw_agent_request_t 提交至 Agent 循环。每个渠道运行独立的接收任务,并将回复路由回对应的来源会话。

文件系统布局

Ameba-Claw 使用两个独立的 littlefs 分区。

rolfs:/

只读固件分区,嵌入在应用程序二进制中,随 OTA 原子更新,运行时不可写入。

rolfs:/
├── skills/            内置 Skill 包
│   └── <name>/
│       ├── SKILL.md   Skill 元数据 + 说明文档
│       └── scripts/   脚本型 Skill 入口(main.lua)
├── docs/              Lua 模块 API 快速参考文档(.md)
└── lib/               Lua 共享库(可在 Skill 脚本中 require)

vfs:/

可读写 Flash 分区,重启和断电后数据持久保留。

vfs:/
├── skills/            用户创建的 Skill 包
│   └── <name>/
│       ├── SKILL.md
│       └── scripts/main.lua
├── scripts/           持久化的用户应用脚本(不在 Skill 目录中)
├── tmp/               临时脚本(每次启动时清空)
├── session/           每会话激活列表、记忆及运行时状态
└── ...                配置文件、收件箱附件等

启动与运行时装配

本文介绍 Ameba-Claw 从上电到各子系统就绪的完整装配顺序。理解这一顺序有助于定位"某功能为什么还不可用""某模块依赖谁先启动"这类问题。

入口:ameba_claw_main()

整个 Agent 运行时由 ameba_claw_main() 一次性装配(源码 ameba_claw_main.c)。它在 RTOS 启动任务里按固定顺序调用六个阶段函数,顺序不可调换

config → core → capabilities → agent → io → tasks

阶段

函数

主要动作

  1. 配置

phase_config

vfs:/claw_config.json 载入配置(claw_config_init);从 rolfs:/SYSTEM.md 读取系统提示词

  1. 核心基础设施

phase_core

初始化 Capability 注册表(claw_cap_init)、记忆系统(claw_memory_init)、会话管理器(cap_session_mgr_init

  1. 能力注册

phase_capabilities

驱动每个 cap 的 INIT 钩子,注册各自的 capability 组

  1. Agent 核心

phase_agent

初始化 Agent(claw_agent_init)、挂接 context provider、启动 Agent 任务

  1. I/O 层

phase_io

启动事件路由、HTTP 服务器、IM 通道、串口 AT 控制台等出入口

  1. 后台任务

phase_tasks

创建 Lua 运行时任务与 Wi-Fi 管理任务

各阶段详解

阶段 1:配置与系统提示词

claw_config_init()vfs:/claw_config.json 读取全部用户可见配置到内存;文件缺失时回退到编译期默认值。随后从只读分区读取 rolfs:/SYSTEM.md 作为 Agent 的系统提示词基座。详见 配置说明

阶段 2:核心基础设施

  • claw_cap_init() —— 初始化 Capability 运行时(工具注册表)。必须早于记忆初始化,因为记忆模块本身也会注册一个 capability 组。

  • claw_memory_init() —— 配置记忆系统的目录:会话历史 vfs:/session、长期记忆 vfs:/memory、人设/画像文件根目录 vfs:。所需文件不存在时自动创建。

  • cap_session_mgr_init() —— 初始化 (渠道, chat_id) session_id 映射,会话根目录与记忆一致。

阶段 3:能力注册(INIT 钩子)

调用 claw_cap_registry_run(CLAW_CAP_PHASE_INIT, cfg),按顺序驱动每个 capability 的 INIT 钩子——每个 cap 在此把自己的工具组注册进 claw_cap。此处**不点名任何具体 cap**:cap 通过自注册机制声明自己(见 Capability 运行时与自注册)。此阶段注册的组,会被下一阶段的"可见性快照"看到。

阶段 4:Agent 核心

  1. claw_config 里的 LLM 设置(api_key / model / backend / max_tokens / max_tool_iterations)覆盖 Agent 配置,调用 claw_cap_start_all() 启动全部能力,再 claw_agent_init()

  2. 按**固定顺序**挂接核心 context provider(顺序决定系统提示词的拼装次序):

    • claw_memory_profile_provider —— 人设与用户画像

    • claw_memory_compaction_summary_provider —— 历史压缩摘要

    • claw_memory_session_history_provider —— 当前会话历史

    • claw_memory_long_term_label_provider —— 长期记忆摘要标签目录

    • tools provider —— 当前会话对 LLM 可见的工具列表

  3. 调用 claw_cap_registry_run(CLAW_CAP_PHASE_AGENT, cfg),各 cap 在此按升序 order 追加自己的 context provider / 完成观察器。

  4. 最后挂接 IM 会话上下文 provider(把当前渠道与 chat_id 注入系统提示词),并注册记忆自动抽取观察器,然后 claw_agent_start() 启动 Agent 任务。

Agent 核心的内部机制见 Agent 核心

阶段 5:I/O 层

按以下子步骤,把数据出入口接到 Agent 核心上:

  1. 事件路由claw_event_dispatcher_init() + claw_event_dispatcher_start()。它拥有 (匹配 动作) 规则和 session_builder (把渠道/chat_id 映射成 session_id)。必须在任何模块向它发布事件之前启动。 详见 事件路由与自动化

  2. HTTP 服务器初始化claw_http_server_init()。所有 HTTP 路由必须在服务器启动前注册,所以 init 早于下面注册路由的 cap 钩子。

  3. 各 cap 的 IO 钩子claw_cap_registry_run(CLAW_CAP_PHASE_IO, cfg),按升序 order 依次启动依赖事件路由的服务(如调度器)、Wi-Fi 连接回调、HTTP 路由注册、各 IM 通道等。

  4. 串口 AT 控制台at_claw_init() 注册 serial 出站通道。

  5. 启动 HTTP 服务器claw_http_server_start()。此后不再允许新增路由。

阶段 6:后台任务

创建 lua_task (Lua 运行时)与 wifi_mgr (Wi-Fi 管理)两个后台任务。Wi-Fi 任务在调度器之后启动,因为其底层 API 要求 RTOS 已运行。

LLM 未配置时会发生什么

Agent 核心依赖 LLM 配置。若 api_key 为空,Agent 仍会初始化并启动(phase_agent 不做条件跳过),但发出的请求会因缺少凭证而失败。此时:

  • 事件路由、自动化规则、本地 Capability、串口 AT / Lua REPL 仍可正常使用

  • 任何依赖 LLM 的功能(ask、把消息默认路由给 Agent、图像识别等)会失败,直到补全 LLM 配置。

因此调试期即使没配 LLM,也能用 AT / REPL 验证硬件与本地能力。

文件系统布局

运行期持久数据落在 vfs:/ 与只读分区 rolfs:/ 下:

vfs:/
├── claw_config.json        所有用户可见配置
├── memory/                 长期记忆与人设/画像文件
├── session/                每会话历史(s_<id>_<哈希>.json)
├── router_rules/           事件路由规则(router_rules.json)
├── scheduler/              持久化的定时任务
├── skills/                 用户安装的 Skill
├── scripts/                持久化用户 Lua 脚本
└── tmp/                    临时脚本(每次启动清空)

rolfs:/
├── SYSTEM.md               系统提示词基座(启动时读取)
├── skills/                 内置 Skill
├── docs/                   Lua 模块 API 速查文档
└── lib/                    Lua 共享库

Agent 核心

claw_agent 是设备侧的 Agent 推理核心。它在一个独立的 FreeRTOS 任务里,把"用户说了什么、来自哪个会话与通道"的请求,拼装成完整的 LLM 提示词,调用大语言模型,解析工具调用(tool call),执行对应 Capability,并按配置迭代多轮,直到模型给出最终文本或出错。

本文介绍 Agent 核心的内部机制。对外的能力调用方式见 Capability 系统,装配顺序见 启动与运行时装配

请求与响应

一次交互由 claw_agent_request_t 描述,主要字段:

字段

说明

request_id

请求唯一编号(用于取消 / 追踪)

session_id

会话标识,决定注入哪条会话历史、以及每会话工具可见性

user_text

用户输入正文

source_channel / source_chat_id / source_sender_id / source_message_id

来源通道与聊天元数据,原样传给 Capability,用于定向回复

source_cap

产生该请求的来源 cap 名

请求有两种回收方式:

  • 异步(默认):调用方 claw_agent_submit() 投递后立即返回;Agent 完成后通过 on_response 回调把结果送回(ameba_claw_main.c 里的 on_response 负责路由到原始 IM 通道)。

  • 同步:投递时设置 CLAW_AGENT_REQUEST_FLAG_SYNC_RECEIVE 标志,再用 claw_agent_receive_for(request_id, …) 阻塞取回该请求的响应;期间其他请求的响应会被缓存在待取列表中。

响应结构 claw_agent_response_t 含最终文本 text、错误信息 error_message、可读的工具调用轨迹 tool_trace,以及从请求复制来的路由元数据。

上下文拼装:Context Provider 流水线

Agent 核心本身不知道"记忆""工具"这些概念,它只按注册顺序依次调用一串 context provider,每个 provider 往当前请求填一块内容。每块内容有类型 claw_agent_context_kind_t

  • CLAW_AGENT_CONTEXT_KIND_SYSTEM_PROMPT —— 追加到系统提示词

  • CLAW_AGENT_CONTEXT_KIND_MESSAGES —— 作为对话消息注入

  • CLAW_AGENT_CONTEXT_KIND_TOOLS —— 作为工具列表注入

provider 的**注册顺序决定拼装次序**,因而直接影响系统提示词的稳定性与缓存命中。启动时挂接的顺序(见 启动与运行时装配)为:人设/画像 → 历史压缩摘要 → 会话历史 → 长期记忆摘要标签 → 工具列表 →(各 cap 追加的 provider)→ IM 会话上下文。

备注

provider 返回的 content 必须用 libc malloc 系列分配(malloc / strdup / cJSON_PrintUnformatted),Agent 用 libc free 释放。用错分配器会破坏堆元数据,且问题往往在数天后的无关分配里才暴露。

因此,LLM 最终看到的工具列表既取决于注册了哪些 Capability,也取决于当前会话的可见性白名单(见 Capability 运行时与自注册)。

工具调用循环

Agent 核心通过 call_cap 回调处理模型发出的工具调用。核心**只负责**把"能力名 + JSON 参数 + 当前请求上下文"交给回调;查找并执行对应 Capability 的工作由 claw_cap 完成(ameba_claw_main.c 里的 call_cap 桥接函数把请求上下文映射成 claw_cap_call_context_t)。

一次请求内允许的工具轮数由 max_tool_iterations 限制。为保证多步骤任务(如创建 Skill)有足够轮次,装配时会给它设一个下限。

每次工具执行前会触发 on_tool_progress 回调,可用于向来源 IM 通道推送"🔧 正在调用 xxx…"这类阶段性进度(受每通道的速率限制约束)。

支持两类 LLM 后端:OpenAI 兼容(Authorization: Bearer)与 Anthropic(x-api-key + anthropic-version),由配置的 backend 选择。响应里的真实 prompt_tokens 会驱动记忆层的按 token 预算压缩(见 记忆内部机制)。

请求取消

取消是**协作式中断**,主要用于中止进行中的 LLM HTTP 请求:

  • claw_agent_cancel_request(request_id) —— request_id 为 0 表示取消任意当前在飞请求;非 0 时仅当当前在飞请求编号匹配才生效。

  • claw_agent_cancel_for_session(session_id) —— 若当前在飞请求属于该会话则取消。事件路由在把同一会话的新消息送进 Agent 前会先调用它,实现"新消息抢占旧请求"。

被取消的请求,其错误信息统一标记为 request cancelled,上层据此识别并静默处理(不当作真错误弹给用户)。

完成观察器

claw_agent_add_completion_observer() 可注册完成观察器,在每次请求结束后收到一份 claw_agent_completion_summary_t

字段

说明

request_id / session_id

请求与会话标识

final_text

本轮最终回复文本(可能为空)

context_providers_csv

本轮注入了非空内容的 provider 列表

tool_calls_csv

本轮触发过的工具调用列表

这个机制适合做审计、统计或"结果一致性"检查。框架里有两个内置观察器基于它工作:记忆自动抽取(claw_memory_extract_observer),以及一致性审计(cap_honesty——检查模型声称"已执行"却没有对应工具调用时打 WARN,只观测不拦截)。

Watchdog

Agent 核心带一个 Watchdog 任务:每 15 秒检查一次在飞请求的心跳时间戳。若某个请求在飞状态持续 7 分钟 (420 秒)没有任何心跳更新(表明某个 Capability 挂死或 LLM 请求卡死),Watchdog 触发软复位,避免设备永久卡住。