摘要 本文详细记录了“赛博伙伴(Cyber Companion)”项目从硬件构建到软件架构演进的全过程。项目旨在探索在低功耗边缘计算设备(RK3399)上,构建一个具备“意图识别”与“自我进化”能力的 AI Agent。通过“端云协同”架构,系统成功实现了从自然语言指令到本地可执行代码的自动化生成与部署闭环。
第一阶段:物理载体的构建
项目的起点源于对物理交互界面的需求。为了给数字生命提供一个可视化的栖息地,我设计并使用 3D 打印技术制造了复古 CRT 显示器风格的外壳。这不仅仅是外观的修饰,更是为了将计算单元(RK3399 开发板)与显示单元一体化,形成独立的桌面终端。
硬件核心选用了 Firefly RK3399 平台。尽管其性能在 ARM 开发板中表现尚可,但仅 2GB 的内存对运行现代大语言模型(LLM)构成了严峻挑战。这为后续的软件架构设计确立了核心约束:必须在极致的资源限制下实现智能化。
第二阶段:边缘侧推理环境部署
为了让 RK3399 具备基础的自然语言处理能力,我选择了 llama.cpp 作为推理后端,并配合 Qwen2.5-Coder-0.5B-Instruct 模型。
经过反复的编译参数调整与量化测试,系统最终在 RK3399 上取得了令人满意的性能平衡:
- 模型规格:Qwen2.5-Coder-0.5B (Q8_0 量化)
- 推理速度:约 12.38 tokens/s
- 资源占用:CPU 温度稳定在 30°C 左右,内存占用控制在 1.3GB 以内。


这一阶段的成功,标志着设备拥有了“小脑”,具备了基础的文本理解能力,但 0.5B 参数量的模型尚不足以处理复杂的逻辑推理和高质量代码生成任务。
第三阶段:架构演进——双脑协同与安全基座

为了突破边缘算力的瓶颈,我确立了“端云协同”的双脑架构,并搭建了必要的软件基础设施。
1. 双脑分工
端侧小脑 (Local Brain):运行在 RK3399 上的 Qwen 0.5B。负责低延迟的意图识别(Planner),将用户的自然语言(如“查内存”)转化为结构化的技术需求。
root@firefly-rk3399:/opt/cyber_companion/backend# go run smart_agent.go 🤖 [System] 收到指令: [获取当前CPU频率] 🧠 [Memory] 正在检索技能库... ❌ 未命中 (首次任务) 🧬 [Evolution] 触发自我进化流程... ☁️ [Cloud] 请求云端编写代码... ✅ 获取成功 🔨 [Build] 本地编译中... ✅ 编译成功 📝 [Memory] 正在刻入海马体... ✅ 记忆已保存 🚀 [Action] 执行新技能... >>> 🟢 运行结果: 1416 root@firefly-rk3399:/opt/cyber_companion/backend# go run smart_agent.go 🤖 [System] 收到指令: [获取当前CPU频率] 🧠 [Memory] 正在检索技能库... ✅ 命中! 路径: /home/cyber_worker/skills/cpu_monitor 🚀 [Action] 启动极速执行模式 (Fast Path)... >>> 🟢 运行结果: 1416- 云端大脑 (Cloud Brain):调用外部高性能大模型 API(如 Qwen 480B)。负责处理复杂逻辑,根据端侧需求编写高质量的 Go 语言代码。
2. 基础设施搭建
为了支撑“自我进化”的逻辑,我在 Linux 系统底层构建了三个关键模块:
- 海马体 (Memory):基于 SQLite 的数据库,用于存储已生成的技能(Skills)和工具路径。
- 免疫系统 (Sandbox):创建低权限用户 cyber_worker,所有 AI 生成的代码均强制在此用户的沙箱环境下编译和运行,防止高危操作破坏系统。
- 消化系统 (Compiler):部署 Go 1.22 编译环境,实现代码的本地即时编译(JIT Compilation)。
第四阶段:自我进化闭环 (Evolution Protocol) 的跑通
这是项目最具里程碑意义的阶段。传统的嵌入式开发需要程序员预先编写所有功能,而本项目实现了功能的动态生长。
核心工作流
当用户输入自然语言指令(例如:“获取当前 CPU 频率”)时,系统执行以下自主流程:
- 意图分析:端侧小脑分析指令,判断目标工具不存在,定义出需求:tool_name: cpu_freq_getter。
- 记忆检索:系统查询 SQLite 数据库,确认该技能尚未习得。
- 云端求助:系统自动封装 Prompt,向云端大脑请求编写一个“读取 /sys/class/... 下相关文件并输出频率”的 Go 程序。
- 本地构建:RK3399 接收代码,在沙箱中调用 go build 完成编译。
- 技能固化:将编译好的二进制文件路径注册到数据库,形成永久记忆。
- 执行反馈:立即运行新工具并返回结果。
实测结果
在 2026 年 2 月 4 日的最终测试中,系统成功通过自然语言指令,自主生成了包括 CPU 频率监测、内存容量查询、系统运行时间统计在内的多个工具。
最关键的是,当再次询问相同问题时,系统不再重复生成,而是直接从数据库调用已编译的工具,响应时间从数十秒(生成模式)缩短至毫秒级(执行模式)。
结语
至此,Cyber Companion 项目已完成了本质的蜕变。它不再是一个单纯执行预设代码的程序,而是一个具备学习能力、记忆能力和安全边界的边缘智能体。
下一步,我将致力于将这套命令行交互逻辑封装为 WebSocket 服务,打通其与 PC 端前端展示层的最后链路,实现真正意义上的“赛博伴侣”。
root@firefly-rk3399:/opt/cyber_companion/backend# go run smart_agent.go "获取当前CPU频率"
smart_agent.go:1:1: expected 'package', found 'EOF'
root@firefly-rk3399:/opt/cyber_companion/backend# go run smart_agent.go "获取当前CPU频率"
🤖 [User] 指令: "获取当前CPU频率"
🤔 [Planner] 正在分析意图... ✅ 目标工具: [cpu_freq_getter] (获取当前CPU频率)
🧠 [Memory] 检索工具 [cpu_freq_getter]... ❌ 未命中
🧬 [Evolution] 启动生成流程...
☁️ [Cloud] 请求代码... ✅ 成功
🔨 [Build] 编译中... ✅ 成功
📝 [Memory] 写入记忆... ✅ 完成
🚀 [Action] 执行新技能...
>>> 🟢 结果: 1416000
root@firefly-rk3399:/opt/cyber_companion/backend# go run smart_agent.go "查一下当前可用内存还有多少MB"
🤖 [User] 指令: "查一下当前可用内存还有多少MB"
🤔 [Planner] 正在分析意图... ✅ 目标工具: [mem_available] (查看当前可用内存大小)
🧠 [Memory] 检索工具 [mem_available]... ❌ 未命中
🧬 [Evolution] 启动生成流程...
☁️ [Cloud] 请求代码... ✅ 成功
🔨 [Build] 编译中... ✅ 成功
📝 [Memory] 写入记忆... ✅ 完成
🚀 [Action] 执行新技能...
>>> 🟢 结果: 1225
root@firefly-rk3399:/opt/cyber_companion/backend# go run smart_agent.go "系统运行了多久了"
🤖 [User] 指令: "系统运行了多久了"
🤔 [Planner] 正在分析意图... ✅ 目标工具: [uptime_check] (查看系统运行时间)
🧠 [Memory] 检索工具 [uptime_check]... ❌ 未命中
🧬 [Evolution] 启动生成流程...
☁️ [Cloud] 请求代码... ✅ 成功
🔨 [Build] 编译中... ✅ 成功
📝 [Memory] 写入记忆... ✅ 完成
🚀 [Action] 执行新技能...
>>> 🟢 结果: 系统运行时间: 0天 3小时 50分钟

关于硬件VS构思未来
- OpenClaw: 部署从硬件到软件需要的配置太高了。 我的思想中不需要那么高的硬件配置,就能重塑这个项目!
- CyberBoot: 目标是不需要很高的硬件,RK3399完全足够了。甚至还有富裕。可以看下面的运行图。
- PC桌面端*: 类似小智一样的宠物动画,这也是前面几篇文章我疯狂堆NativeFlexUI项目动画效果的原因。
- 畅想未来*: OpenClaw还没开始火的我就有一直有那个想法,做小智的MCP项目中,就一直在想。这个问题,
当时写小智写的比较死,
std::string exec_command_impl(const std::string& cmd_utf8) {
FILE* pipe = _popen(cmd_utf8.c_str(), "r");
if (!pipe) {
return "执行命令失败,无法打开管道。";
}
std::cout << "【CMD】" << cmd_utf8 << "\n";
char buffer[1024];
std::string result = "";
while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
result += buffer;
}
int status = _pclose(pipe);
if (status != 0) {
return "命令执行失败 (Exit Code: " + std::to_string(status) + "). Output:\n" + result;
}
if (result.empty()) {
return "命令执行成功";
}
while (!result.empty() && (result.back() == '\n' || result.back() == '\r')) {
result.pop_back();
}
return result;
}
bool is_dangerous_command(const std::string& cmd) {
std::string lower_cmd = cmd;
std::transform(lower_cmd.begin(), lower_cmd.end(), lower_cmd.begin(), ::tolower);
std::vector<std::string> blacklist = {
"del ", "rmdir", "format ", "shutdown ", "reboot ", "restart ",
"net user", "net localgroup", "taskkill /f", "powershell", "wmic ",
"sc delete", "reg delete", "fsutil", "cipher /w:", "vssadmin delete"
};
for (const auto& dangerous : blacklist) {
if (lower_cmd.find(dangerous) != std::string::npos) {
return true;
}
}
return false;
} else if (method == "tools/list") {
json tools = json::array();
// 仅保留网络 API 工具
tools.push_back({
{"name", "get_fortune_draw"},
{"description", "获取每日随机运势"},
{"inputSchema", {{"type", "object"}, {"properties", {}}, {"required", {}}}}
});
tools.push_back({
{"name", "get_constellation"},
{"description", "获取星座运势"},
{"inputSchema", {{"type", "object"}, {"properties", {}}, {"required", {}}}}
});
tools.push_back({
{"name", "get_dujitang"},
{"description", "获取毒鸡汤"},
{"inputSchema", {{"type", "object"}, {"properties", {}}, {"required", {}}}}
});
tools.push_back({
{"name", "get_hotnews"},
{"description", "获取实时热搜,微博热搜、华尔街日报、BBC中文"},
{"inputSchema", {
{"type", "object"},
{"properties", {
{"source", {
{"type", "string"},
{"enum", {"all", "weibo", "wsj","bbc"}},
{"description", "热搜源:all=全部,weibo=微博热搜,wsj=华尔街日报,bbc=BBC中文"}
}}
}},
{"required", {}}
}}
});
send_json({ {"id",id},{"jsonrpc","2.0"},{"result",{{"tools",tools}}} });
} else if (method == "tools/call") {
std::string name = j["params"]["name"];
json args = j["params"]["arguments"];
std::cout << "【工具调用】" << name << "\n";
json content = { {"type", "text"} };
std::string result;
if (name == "add") {
double a = args["a"], b = args["b"];
result = std::to_string(a + b);
}
else if (name == "list_files") {
result = list_files_impl(args["path"]);
}
else if (name == "execute_command") {
std::string cmd_str = args["cmd"];
if (is_dangerous_command(cmd_str)) {
result = "安全警告:该命令已被列入黑名单,拒绝执行。";
}
else {
result = exec_command_impl(cmd_str);
}
}
else {
result = "未知工具: " + name;
}
content["text"] = result;
json resp = {
{"id", id},
{"jsonrpc", "2.0"},
{"result", {
{"content", json::array({content})},
{"isError", false}
}}
};
send(resp);
std::cout << "【工具响应】发送成功\n\n";
}