跨设备智能体系统,类似钢铁侠的贾维斯 — 一个大脑,多个躯体。
- 一个大脑:所有设备共享统一的记忆和推理能力,大脑运行在云端服务器
- 多个躯体:智能体可在手机、电脑、嵌入式设备等同时运行,每个设备是一个交互入口
- 无处不在:用户可以在任意设备上用自然语言与大脑对话,大脑可以感知用户状态并调用任意设备的能力
┌──────────────────────────────┐
│ 云端大脑 (Brain) │
│ │
│ 对话引擎 LLM推理层 │
│ 工具路由 记忆系统 │
│ 会话管理 Context组装 │
└──────────────┬───────────────┘
│ WebSocket (WSS)
┌──────────────┬─────────────────┼─────────────────┬──────────────┐
│ │ │ │ │
┌────────▼─────┐ ┌──────▼──────┐ ┌────────▼────────┐ ┌──────▼─────────┐ ┌──▼───────────┐
│ 桌面端躯体 │ │ 微信网关躯体 │ │ Android 躯体 │ │ 嵌入式躯体 │ │ 外部服务 │
│ (body-pc) │ │(body-wechat)│ │ (body-android) │ │ (body-embedded)│ │ HA / Photos │
│ Electron │ │ Node.js │ │ Kotlin 原生 │ │ Python │ │ (第二版) │
│ +React+Vite │ │ iLink Bot │ │ (第二版) │ │ (第二版) │ │ │
└──────────────┘ └─────────────┘ └─────────────────┘ └────────────────┘ └──────────────┘
每个设备同时承担两个角色:
- Chat Client:用户交互入口,WebSocket 与大脑对话
- Tool Provider:暴露本地能力(截图、文件、shell 等),通过同一 WebSocket 通道被大脑调用
所有跨设备传递的文件和图片(无论来自用户上传还是工具返回)均通过 Artifact 机制统一管理,不直接在 LLM 上下文中传递原始字节。
用户上传图片
│
▼
brain 拦截,并发写入 ArtifactStore(PostgreSQL 元数据 + 文件系统字节)
同时并行执行 build_system_prompt(embedding 检索)
│
▼
生成 artifact_id(格式:art_xxxxxxxxxxxxxxxx)
│
▼
LLM 上下文中只出现 JSON 引用:
{"artifact_id": "art_xxxx", "kind": "image/jpeg", "name": "user_image.jpg",
"size": 3129964, "note": "...", "source": "user_upload"}
│
├─► 用户要分析图片 → LLM 调用 load_artifact(artifact_id=...)
│ └─► brain 读取字节,在线程池压缩(最大 1024px / JPEG q75),注入 LLM 上下文
│
└─► 用户要保存到设备 → LLM 调用 save_file(file_path=..., artifact_id=...)
└─► brain 从 store 读取字节并展开,通过 WebSocket 发给目标设备执行
为什么这样设计:
- 避免大文件/高分辨率图片直接塞满 LLM 上下文窗口
- 大脑统一持久化所有文件,支持跨设备引用(手机拍的照片可以直接保存到 PC 桌面)
- LLM 只在真正需要时才加载文件内容,其余情况只需传递轻量 ID
关键文件:
brain/artifacts/store.py— ArtifactStore,持久化存储brain/chat/engine.py—_store_user_images(),用户上传图片的拦截入口brain/main.py—_load_artifact_handler(),加载时自动压缩图片brain/mcp_client/router.py—_expand_artifact_refs(),工具调用时自动展开字节
Skills 是可插拔的行为指令模块(Markdown 文件),在对话时动态注入 system prompt,让 LLM 在特定场景下具备更精准的上下文和行为约束。
三层来源:
- Brain 内置:
brain/skills/builtin/*.md,随代码部署 - 用户自定义:存数据库,运行时动态增删
- 设备注册:body 连接时通过
register消息携带,设备下线自动失效
触发方式:
- 关键词自动匹配(当轮):消息命中 skill 的
triggers词时自动注入 system prompt /skill <name>(会话级):用户显式激活,存 Redis,新建会话自动清除- LLM 主动加载:LLM 可调用
list_skills/load_skill工具查看和加载 skill
Skill 文件格式(YAML front matter + Markdown body):
---
name: travel-planner
description: 旅行规划助手:当用户询问旅行路线、景点推荐时使用
triggers:
- 旅行
- 出行
- 景点
---
# 旅行规划模式
你现在是用户的专属旅行规划师...关键文件:
brain/skills/loader.py—Skilldataclass + 解析 .md 文件brain/skills/manager.py—SkillManager,匹配/激活/设备 skill 管理brain/skills/store.py— 数据库 CRUD(用户自定义 skill)brain/skills/builtin/— 内置 skill 文件目录body-pc/electron/skills/— 桌面端设备 skill,注册时携带
| 层级 | 技术 | 说明 |
|---|---|---|
| 服务端 | Python 3.12+, FastAPI | 异步高性能,WebSocket 原生支持 |
| LLM | litellm | 统一 OpenAI/Anthropic 等多种模型格式 |
| 数据库 | PostgreSQL + pgvector | 结构化数据 + 知识记忆向量检索 |
| 缓存 | Redis | 对话记忆(会话级)、活跃会话管理 |
| 向量数据库 | Qdrant | RAG 文档检索(第二版) |
| ORM | SQLAlchemy (async) + Alembic | 数据库迁移 |
| 桌面端 | Electron + React + Vite | 原生桌面应用,多模态交互(文本+图片) |
| Android | Kotlin + Ktor + OkHttp | 原生实现(第二版) |
| 部署 | Docker Compose | 一键启动大脑及依赖服务 |
hance/
├── brain/ # 云端大脑(服务器运行)
│ ├── main.py # FastAPI 入口,生命周期管理
│ ├── config.py # 配置(环境变量)
│ ├── models.py # SQLAlchemy ORM 模型
│ ├── db.py # 数据库连接
│ ├── chat/
│ │ ├── engine.py # 对话引擎(LLM + 工具 + 记忆编排)
│ │ ├── context.py # Context 组装(设备状态/记忆/工具/skill 注入)
│ │ └── session.py # 会话管理(创建/归档/切换)
│ ├── llm/
│ │ ├── provider.py # litellm 封装,可切换模型
│ │ └── prompts.py # System prompt 模板
│ ├── artifacts/
│ │ └── store.py # ArtifactStore:元数据→PostgreSQL,字节→文件系统
│ ├── mcp_client/
│ │ └── router.py # 工具路由,通过 WebSocket 转发调用
│ ├── memory/
│ │ ├── conversation.py # 对话记忆(Redis 滑动窗口)
│ │ ├── knowledge.py # 知识记忆(pgvector 语义检索)
│ │ └── manager.py # 会话归档时累积摘要 + 知识抽取
│ ├── skills/
│ │ ├── loader.py # Skill dataclass + 解析 .md 文件
│ │ ├── store.py # 数据库 CRUD(用户自定义 skill)
│ │ ├── manager.py # SkillManager:匹配/激活/设备 skill 管理
│ │ └── builtin/ # 内置 skill 文件(.md)
│ └── ws/
│ ├── protocol.py # WebSocket 消息协议(Pydantic)
│ ├── device_registry.py # 设备注册表(在线状态/工具列表)
│ └── server.py # WebSocket 服务端,消息分发
├── body-pc/ # 桌面端躯体(Linux/Mac/Windows,Electron)
│ ├── electron/ # Electron 主进程
│ │ ├── main.js # 入口,窗口管理、系统托盘
│ │ ├── preload.js # 渲染进程安全桥接
│ │ ├── ws-client.js # WebSocket 客户端(对话+工具调用)
│ │ ├── tool-executor.js # 工具执行器(本地调用)
│ │ ├── config.json # 躯体配置(Brain URL、设备 ID 等)
│ │ ├── skills/ # 设备专属 skill 文件(.md),注册时携带
│ │ └── tools/
│ │ ├── desktop.js # 截图、列出窗口、鼠标点击、应用启动
│ │ ├── filesystem.js # 文件读写搜索
│ │ ├── accessibility.js # AT-SPI 无障碍树(LLM 驱动的 UI 检查)
│ │ └── shell.js # Shell 命令执行
│ ├── src/ # React 渲染进程(Vite)
│ │ ├── App.jsx # 根组件
│ │ ├── components/
│ │ │ ├── Titlebar.jsx # 自定义无边框标题栏(最小化/关闭)
│ │ │ ├── Sidebar.jsx # 会话列表(新建/切换会话)
│ │ │ ├── ChatArea.jsx # 对话消息流
│ │ │ ├── InputBar.jsx # 输入框(支持拖拽/粘贴图片)
│ │ │ └── StatusDot.jsx # WebSocket 连接状态指示
│ │ └── store/
│ │ └── chat.js # Zustand 全局状态
│ └── resources/ # 应用图标 + 托盘图标
├── body-wechat/ # 微信躯体(Node.js 网关,无 UI)
│ ├── main.js # 入口:加载账号,启 WSClient/bridge,长轮询 iLink getUpdates
│ ├── login-cli.js # 一次性扫码登录脚本(npm run login)
│ ├── config.js / config.json # 配置加载(环境变量 + 默认值)
│ ├── ws-client.js # WebSocket 客户端,连接 brain
│ ├── bridge.js # 双向桥接:微信↔brain,含 artifact 自动转发
│ ├── tool-executor.js # 暴露 send_wechat_media 工具
│ ├── session-map.js # from_user_id ↔ session_id + context_token 持久化
│ ├── weixin/ # iLink 协议封装层
│ │ ├── api.js # HTTP 调用(认证头/长轮询/发送)
│ │ ├── login.js # 扫码登录
│ │ ├── send.js # 文本/图片/视频/文件发送
│ │ ├── media.js # 接收媒体下载解密 + SILK→WAV
│ │ └── cdn.js # AES-128-ECB + CDN 上传/下载
│ └── state/ # 运行时状态(账号、sessions、临时媒体)
├── body-android/ # Android 躯体(第二版,Kotlin)
│ ├── app/ # 主应用(前台服务 + WebSocket + 工具执行)
│ │ └── src/main/
│ │ ├── aidl/ # IShellService.aidl(与 shell-server 共享接口)
│ │ ├── assets/ # shell-server.dex + start-shell.sh(随 APK 打包)
│ │ └── java/com/hance/body/
│ │ ├── shell/ # ShellConnector(Binder 轮询)+ ShellToolRegistry(动态注册工具)
│ │ ├── service/ # HanceService(前台服务入口)
│ │ ├── tools/ # 普通工具:相机、文件、定位、通知等
│ │ └── ws/ # WSClient(WebSocket)+ ToolExecutor
│ └── shell-server/ # 特权扩展进程(以 shell UID 运行)
│ └── src/main/java/com/hance/shell/
│ ├── ShellMain.kt # app_process 入口,向 ServiceManager 注册 Binder
│ ├── ShellServiceImpl.kt # IShellService.Stub 实现,分发工具调用
│ └── tools/ # InputTool / ScreenCaptureTool / PackageTool / SystemInfoTool
├── body-embedded/ # 嵌入式躯体(第二版,Python)
├── services/ # 外部服务 MCP(第二版)
│ ├── homeassistant/
│ └── google_photos/
├── alembic/ # 数据库迁移
├── docs/
│ └── superpowers/
│ ├── specs/ # 设计文档
│ └── plans/ # 实现计划
├── docker-compose.yml
├── pyproject.toml
└── .env.example
cp .env.example .env编辑 .env,至少填写:
HANCE_LLM_API_KEY=sk-xxx # LLM API Key
HANCE_LLM_MODEL=gpt-4o # 模型名(litellm 格式)
HANCE_DEVICE_TOKENS=["token-desktop-1"] # 设备认证 Token 列表# 启动 PostgreSQL + Redis + Brain
docker compose up -d
# 初始化数据库
docker compose exec brain uv run alembic upgrade head
# 查看日志
docker compose logs -f braincd body-pc
# 安装依赖
npm install
# 开发模式启动,通过环境变量传入配置
HANCE_DEVICE_ID=desktop-home \
HANCE_DEVICE_NAME="Home Desktop" \
HANCE_DEVICE_TOKEN=token-desktop-1 \
HANCE_BRAIN_WS_URL=ws://your-server:18731/ws \
npm run dev
# 多实例时指定不同端口(避免 Vite 冲突)
PORT=5174 \
HANCE_DEVICE_ID=desktop-office \
HANCE_DEVICE_NAME="Office Desktop" \
HANCE_DEVICE_TOKEN=token-desktop-2 \
npm run dev
# 未设置的环境变量从 electron/config.json 读取默认值
# 打包为可执行文件
npm run build用 Android Studio 构建并安装 APK(body-android/)。
应用启动后,在配置页填入 Brain WS URL 和 Device Token,然后通过 ADB 启动特权扩展进程(需一次性执行,之后自动后台运行):
adb shell sh /sdcard/Android/data/com.hance.body/files/start-shell.sh脚本会自动终止旧进程并在后台启动新进程。日志通过 adb logcat -s HanceShell 查看。
shell-server 启动后,brain 将自动收到以 shell_ 为前缀的额外工具(截图、输入注入、dumpsys 等)。shell-server 停止时这些工具自动从 brain 注销。
- 文字对话,上下文跨轮连贯
/image <path>发图片让 Hance 分析- 「帮我截个屏看看屏幕上有什么」
- 「帮我执行 df -h 看看磁盘」
- 「在 ~/Documents 下找所有 .py 文件」
/new新建会话,知识记忆保留但对话历史清空- 两台电脑同时连接,在 A 上说「截取 desktop-b 的屏幕」
- v1(已完成):大脑 + 桌面端躯体 + 微信躯体,对话/工具/记忆/会话管理
- v1.5(已完成):Android 躯体,含 shell-server 特权扩展(截图/输入注入/系统调试工具)
- v2(进行中):嵌入式躯体、RAG 知识库、事件驱动、定时任务、Home Assistant
- v3:语音交互、Google Photos 等云服务接入
TODO