OpenHuman 源码深度解构:一个 Rust 驱动的本地优先 AI 个人助手

OpenHuman 源码深度解构:一个 Rust 驱动的本地优先 AI 个人助手

版本:v0.54.7 | 技术栈:Rust + Tauri + React/TypeScript | 基于源码:openhuman-main


引言

OpenHuman 是由 tinyhumansai 开源(GNU 协议)的桌面端 AI 个人助手,定位是"Personal AI super intelligence"。它不是另一个终端 AI 工具------它有桌面 Mascot(会说话、会做表情、能加入 Google Meet)、20 分钟自动同步 118+ 服务的记忆树、本地 SQLite 存储、以及一个叫 TokenJuice 的 token 压缩引擎。

技术选型上,OpenHuman 选择了 Rust 作为后端核心,Tauri 作为桌面壳层,React/TypeScript 作为前端。整个项目约 0.54.7 版本,Rust 源码横跨约 80 个子模块,Cargo.toml 依赖列表读起来像一本"现代 Rust 生态全景图"。

本文从源码出发,多维度解构这个系统的设计。
#mermaid-svg-YmbXVRlO4ihSmiba{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YmbXVRlO4ihSmiba .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YmbXVRlO4ihSmiba .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YmbXVRlO4ihSmiba .error-icon{fill:#552222;}#mermaid-svg-YmbXVRlO4ihSmiba .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YmbXVRlO4ihSmiba .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YmbXVRlO4ihSmiba .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YmbXVRlO4ihSmiba .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YmbXVRlO4ihSmiba .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YmbXVRlO4ihSmiba .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YmbXVRlO4ihSmiba .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YmbXVRlO4ihSmiba .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YmbXVRlO4ihSmiba .marker.cross{stroke:#333333;}#mermaid-svg-YmbXVRlO4ihSmiba svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YmbXVRlO4ihSmiba p{margin:0;}#mermaid-svg-YmbXVRlO4ihSmiba .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YmbXVRlO4ihSmiba .cluster-label text{fill:#333;}#mermaid-svg-YmbXVRlO4ihSmiba .cluster-label span,#mermaid-svg-YmbXVRlO4ihSmiba p{color:#333;}#mermaid-svg-YmbXVRlO4ihSmiba .label text,#mermaid-svg-YmbXVRlO4ihSmiba span,#mermaid-svg-YmbXVRlO4ihSmiba p{fill:#333;color:#333;}#mermaid-svg-YmbXVRlO4ihSmiba .node rect,#mermaid-svg-YmbXVRlO4ihSmiba .node circle,#mermaid-svg-YmbXVRlO4ihSmiba .node ellipse,#mermaid-svg-YmbXVRlO4ihSmiba .node polygon,#mermaid-svg-YmbXVRlO4ihSmiba .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YmbXVRlO4ihSmiba .flowchart-label text{text-anchor:middle;}#mermaid-svg-YmbXVRlO4ihSmiba .node .label{text-align:center;}#mermaid-svg-YmbXVRlO4ihSmiba .node.clickable{cursor:pointer;}#mermaid-svg-YmbXVRlO4ihSmiba .arrowheadPath{fill:#333333;}#mermaid-svg-YmbXVRlO4ihSmiba .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YmbXVRlO4ihSmiba .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YmbXVRlO4ihSmiba .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YmbXVRlO4ihSmiba .edgeLabel p{margin:0;padding:0;display:inline;}#mermaid-svg-YmbXVRlO4ihSmiba .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YmbXVRlO4ihSmiba .labelBkg{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YmbXVRlO4ihSmiba .node .cluster{fill:rgba(255, 255, 222, 0.5);stroke:rgba(170, 170, 51, 0.2);box-shadow:rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;stroke-width:1px;}#mermaid-svg-YmbXVRlO4ihSmiba .cluster text{fill:#333;}#mermaid-svg-YmbXVRlO4ihSmiba .cluster span,#mermaid-svg-YmbXVRlO4ihSmiba p{color:#333;}#mermaid-svg-YmbXVRlO4ihSmiba div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YmbXVRlO4ihSmiba .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YmbXVRlO4ihSmiba .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YmbXVRlO4ihSmiba .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YmbXVRlO4ihSmiba :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} UI 层
React + TypeScript

前端 (app/)
Desktop Mascot

表情 / 声音 / 指向
桥接层
Tauri v2

桌面壳层
CDP Client

:19222 WebView 扫描
Rust 核心 (openhuman crate)
agent/

多 Agent 编排
memory/

Memory Tree + SQLite
tokenjuice/

Token 压缩
subconscious/

后台思考循环
inference/

模型路由
composio/

118+ 集成


一、项目结构总览

复制代码
openhuman-main/
├── src/
│   ├── main.rs                  # openhuman-core 二进制入口
│   ├── rpc/                     # JSON-RPC 分发层
│   └── openhuman/               # 核心业务逻辑(~80 个子模块)
│       ├── agent/               # Agent 编排、会话、工具调用
│       ├── memory/              # Memory Tree + 向量搜索 + SQLite
│       ├── tokenjuice/          # Token 压缩引擎
│       ├── subconscious/        # 后台定时思考引擎
│       ├── inference/           # 模型路由(云端 + 本地 Ollama)
│       ├── composio/            # 118+ OAuth 集成(后端代理)
│       ├── vault/               # 本地文件夹知识库(NotebookLM 风格)
│       ├── scheduler_gate/      # 电池/CPU 感知的后台任务调度器
│       ├── voice/               # STT (whisper.cpp) + TTS (piper)
│       ├── desktop_companion/   # Mascot 会话协调器
│       ├── screen_intelligence/ # 屏幕截图 + 视觉理解
│       ├── wallet/              # Ethereum 钱包集成
│       ├── webhooks/            # 入站 Webhook 处理
│       └── ...(约 60 个其他模块)
├── app/
│   ├── src/                     # React/TypeScript 前端
│   └── src-tauri/               # Tauri 桌面壳层(Rust)
│       ├── src/cdp/             # Chrome DevTools Protocol 客户端
│       └── recipes/             # WebView 注入脚本
└── tests/                       # 集成测试(E2E + 单元)

Cargo.toml 声明了一个主二进制 openhuman-core 和若干工具二进制(slack-backfillgmail-backfill-3dmemory-tree-init-smokeinference-probe),lib crate 是 openhuman_core


二、Agent 域:多 Agent 编排架构

src/openhuman/agent/ 是"大脑",模块注释的第一句话:

"This domain owns the core 'brain' of OpenHuman. It coordinates how LLMs interact with the system via tools, manages conversation history, and handles autonomous behaviors like trigger triage and episodic memory indexing."

2.1 核心组件

#mermaid-svg-tKpVLyJbfWHZP6VU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tKpVLyJbfWHZP6VU .error-icon{fill:#552222;}#mermaid-svg-tKpVLyJbfWHZP6VU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tKpVLyJbfWHZP6VU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tKpVLyJbfWHZP6VU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tKpVLyJbfWHZP6VU .marker.cross{stroke:#333333;}#mermaid-svg-tKpVLyJbfWHZP6VU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tKpVLyJbfWHZP6VU p{margin:0;}#mermaid-svg-tKpVLyJbfWHZP6VU g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-tKpVLyJbfWHZP6VU g.classGroup text .title{font-weight:bolder;}#mermaid-svg-tKpVLyJbfWHZP6VU .cluster-label text{fill:#333;}#mermaid-svg-tKpVLyJbfWHZP6VU .cluster-label span{color:#333;}#mermaid-svg-tKpVLyJbfWHZP6VU .cluster-label span p{background-color:transparent;}#mermaid-svg-tKpVLyJbfWHZP6VU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tKpVLyJbfWHZP6VU .cluster text{fill:#333;}#mermaid-svg-tKpVLyJbfWHZP6VU .cluster span{color:#333;}#mermaid-svg-tKpVLyJbfWHZP6VU .nodeLabel,#mermaid-svg-tKpVLyJbfWHZP6VU .edgeLabel{color:#131300;}#mermaid-svg-tKpVLyJbfWHZP6VU .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-tKpVLyJbfWHZP6VU .label text{fill:#131300;}#mermaid-svg-tKpVLyJbfWHZP6VU .labelBkg{background:#ECECFF;}#mermaid-svg-tKpVLyJbfWHZP6VU .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-tKpVLyJbfWHZP6VU .classTitle{font-weight:bolder;}#mermaid-svg-tKpVLyJbfWHZP6VU .node rect,#mermaid-svg-tKpVLyJbfWHZP6VU .node circle,#mermaid-svg-tKpVLyJbfWHZP6VU .node ellipse,#mermaid-svg-tKpVLyJbfWHZP6VU .node polygon,#mermaid-svg-tKpVLyJbfWHZP6VU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tKpVLyJbfWHZP6VU .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU g.clickable{cursor:pointer;}#mermaid-svg-tKpVLyJbfWHZP6VU g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-tKpVLyJbfWHZP6VU g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-tKpVLyJbfWHZP6VU .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-tKpVLyJbfWHZP6VU .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-tKpVLyJbfWHZP6VU .dashed-line{stroke-dasharray:3;}#mermaid-svg-tKpVLyJbfWHZP6VU .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-tKpVLyJbfWHZP6VU #compositionStart,#mermaid-svg-tKpVLyJbfWHZP6VU .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #compositionEnd,#mermaid-svg-tKpVLyJbfWHZP6VU .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #dependencyStart,#mermaid-svg-tKpVLyJbfWHZP6VU .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #dependencyStart,#mermaid-svg-tKpVLyJbfWHZP6VU .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #extensionStart,#mermaid-svg-tKpVLyJbfWHZP6VU .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #extensionEnd,#mermaid-svg-tKpVLyJbfWHZP6VU .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #aggregationStart,#mermaid-svg-tKpVLyJbfWHZP6VU .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #aggregationEnd,#mermaid-svg-tKpVLyJbfWHZP6VU .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #lollipopStart,#mermaid-svg-tKpVLyJbfWHZP6VU .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU #lollipopEnd,#mermaid-svg-tKpVLyJbfWHZP6VU .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-tKpVLyJbfWHZP6VU .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-tKpVLyJbfWHZP6VU .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tKpVLyJbfWHZP6VU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tKpVLyJbfWHZP6VU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tKpVLyJbfWHZP6VU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} builds
formats prompts
delegates tasks
loads context
routes triggers
Agent
+run_session(prompt) : SessionResult
+tool_loop(messages) : Messages
AgentBuilder
+with_memory(memory) : Self
+with_tools(tools) : Self
+with_profile(profile) : Self
+build() : Agent
<<enum>>
Dispatcher
XML
JSON
PFormat
+format_tool_calls() : String
+parse_response() : ToolCalls
SubagentRunner
+spawn_subagent(task) : SubagentResult
Triage
+classify_trigger(event) : TriageDecision
+batch_triage(events) : Vec<TriageDecision>
MemoryLoader
+load_context(query) : ContextBlock
+load_tree_summaries() : TreeContext

Dispatcher 是 OpenHuman 一个有意思的设计------它支持三种工具调用格式:

  • XML<tool>...</tool> 格式,用于不原生支持 function calling 的模型
  • JSON:OpenAI function calling 标准格式
  • P-Format(PFormat):一种更紧凑的自研格式,降低 token 消耗

这让 OpenHuman 能适配更广泛的模型,包括不支持 function calling 的小型本地模型。

2.2 Triage:高性能触发器分类器

agent/triage/ 是一个专门的子系统,用于分类和响应外部触发(Webhook、cron 任务)。注释说明:

"A high-performance pipeline for classifying and responding to external triggers using small local models."

这意味着当一个 Gmail Webhook 触发时,不是直接走完整的 Agent 循环,而是先通过 Triage 快速分类(用小模型,低延迟),决定是否需要唤起主 Agent。


三、Memory Tree:四阶段知识图谱构建

Memory Tree 是 OpenHuman 最核心的差异化特性,灵感来自 Karpathy 的 LLM Knowledgebase 工作流。整个流水线分四个阶段建设(对应 Issue #707~#710):
#mermaid-svg-hxy9qfdnWWpMJVHY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hxy9qfdnWWpMJVHY .error-icon{fill:#552222;}#mermaid-svg-hxy9qfdnWWpMJVHY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hxy9qfdnWWpMJVHY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hxy9qfdnWWpMJVHY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hxy9qfdnWWpMJVHY .marker.cross{stroke:#333333;}#mermaid-svg-hxy9qfdnWWpMJVHY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hxy9qfdnWWpMJVHY p{margin:0;}#mermaid-svg-hxy9qfdnWWpMJVHY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hxy9qfdnWWpMJVHY .cluster-label text{fill:#333;}#mermaid-svg-hxy9qfdnWWpMJVHY .cluster-label span{color:#333;}#mermaid-svg-hxy9qfdnWWpMJVHY .cluster-label span p{background-color:transparent;}#mermaid-svg-hxy9qfdnWWpMJVHY .label text,#mermaid-svg-hxy9qfdnWWpMJVHY span{fill:#333;color:#333;}#mermaid-svg-hxy9qfdnWWpMJVHY .node rect,#mermaid-svg-hxy9qfdnWWpMJVHY .node circle,#mermaid-svg-hxy9qfdnWWpMJVHY .node ellipse,#mermaid-svg-hxy9qfdnWWpMJVHY .node polygon,#mermaid-svg-hxy9qfdnWWpMJVHY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hxy9qfdnWWpMJVHY .rough-node .label text,#mermaid-svg-hxy9qfdnWWpMJVHY .node .label text,#mermaid-svg-hxy9qfdnWWpMJVHY .image-shape .label,#mermaid-svg-hxy9qfdnWWpMJVHY .icon-shape .label{text-anchor:middle;}#mermaid-svg-hxy9qfdnWWpMJVHY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hxy9qfdnWWpMJVHY .rough-node .label,#mermaid-svg-hxy9qfdnWWpMJVHY .node .label,#mermaid-svg-hxy9qfdnWWpMJVHY .image-shape .label,#mermaid-svg-hxy9qfdnWWpMJVHY .icon-shape .label{text-align:center;}#mermaid-svg-hxy9qfdnWWpMJVHY .node.clickable{cursor:pointer;}#mermaid-svg-hxy9qfdnWWpMJVHY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hxy9qfdnWWpMJVHY .arrowheadPath{fill:#333333;}#mermaid-svg-hxy9qfdnWWpMJVHY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hxy9qfdnWWpMJVHY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hxy9qfdnWWpMJVHY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hxy9qfdnWWpMJVHY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hxy9qfdnWWpMJVHY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hxy9qfdnWWpMJVHY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hxy9qfdnWWpMJVHY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hxy9qfdnWWpMJVHY .cluster text{fill:#333;}#mermaid-svg-hxy9qfdnWWpMJVHY .cluster span{color:#333;}#mermaid-svg-hxy9qfdnWWpMJVHY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hxy9qfdnWWpMJVHY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hxy9qfdnWWpMJVHY rect.text{fill:none;stroke-width:0;}#mermaid-svg-hxy9qfdnWWpMJVHY .icon-shape,#mermaid-svg-hxy9qfdnWWpMJVHY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hxy9qfdnWWpMJVHY .icon-shape p,#mermaid-svg-hxy9qfdnWWpMJVHY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hxy9qfdnWWpMJVHY .icon-shape .label rect,#mermaid-svg-hxy9qfdnWWpMJVHY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hxy9qfdnWWpMJVHY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hxy9qfdnWWpMJVHY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hxy9qfdnWWpMJVHY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Phase 1: 摄取
Phase 4: 检索
向量搜索

bge-m3 1024维
RetrievalScoreBreakdown

混合召回
关键词搜索

FTS5
Phase 3: Summary Tree
source 节点
topic 节点
global 节点
Phase 2: 评分




Cheap Signals

正则 NER

关键字密度
Definite

Keep ≥0.85?
ScoreResult

kept / tombstone
Definite

Drop ≤0.15?
LLM Extractor

仅边界区间 0.15~0.85
Source Adapter

Email/Chat/Doc
Canonicalize

→ Markdown
Chunker

稳定确定性 ID

≤3k tokens/chunk
SQLite

原始存储

3.1 评分系统的三区间设计

Phase 2 的评分有一个精妙的三区间门控:

rust 复制代码
pub const DEFAULT_DROP_THRESHOLD: f32 = 0.3;      // 总分 < 0.3 → 丢弃
pub const DEFAULT_DEFINITE_KEEP: f32 = 0.85;       // 便宜信号 ≥ 0.85 → 直接保留,不调用 LLM
pub const DEFAULT_DEFINITE_DROP: f32 = 0.15;       // 便宜信号 ≤ 0.15 → 直接丢弃,不调用 LLM

这意味着只有评分在 0.15~0.85 之间的"边界区间" chunk 才需要 LLM 参与判断。大量明显是垃圾(系统日志噪音)或明显是干货(正文内容)的 chunk 直接通过正则信号处理,不消耗 LLM token。

3.2 Chunker 的稳定 ID 设计

Chunker 生成的 ID 是确定性的(deterministic),相同内容在不同时间摄取产生相同 ID,这是实现高效重复摄取去重的基础------只需对比 ID 就知道内容是否已存在,不需要重读内容。

3.3 Obsidian Wiki 输出

Memory Tree 的最终产物:

  • SQLite 本地数据库(向量 + 关键词 + 关系图)
  • Markdown 文件(与 Obsidian 兼容,可以直接在 Obsidian 中打开和编辑)
  • 层级摘要树(source → topic → global 三层)

四、TokenJuice:三层规则覆盖的 Token 压缩

src/openhuman/tokenjuice/vincentkoc/tokenjuice 的 Rust 移植版,声称可减少 80% 的 token 消耗。

4.1 三层规则优先级

#mermaid-svg-RMqjZkx02KSRBrgv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RMqjZkx02KSRBrgv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RMqjZkx02KSRBrgv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RMqjZkx02KSRBrgv .error-icon{fill:#552222;}#mermaid-svg-RMqjZkx02KSRBrgv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RMqjZkx02KSRBrgv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RMqjZkx02KSRBrgv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RMqjZkx02KSRBrgv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RMqjZkx02KSRBrgv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RMqjZkx02KSRBrgv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RMqjZkx02KSRBrgv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RMqjZkx02KSRBrgv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RMqjZkx02KSRBrgv .marker.cross{stroke:#333333;}#mermaid-svg-RMqjZkx02KSRBrgv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RMqjZkx02KSRBrgv p{margin:0;}#mermaid-svg-RMqjZkx02KSRBrgv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RMqjZkx02KSRBrgv .cluster-label text{fill:#333;}#mermaid-svg-RMqjZkx02KSRBrgv .cluster-label span{color:#333;}#mermaid-svg-RMqjZkx02KSRBrgv .cluster-label span p{background-color:transparent;}#mermaid-svg-RMqjZkx02KSRBrgv .label text,#mermaid-svg-RMqjZkx02KSRBrgv span{fill:#333;color:#333;}#mermaid-svg-RMqjZkx02KSRBrgv .node rect,#mermaid-svg-RMqjZkx02KSRBrgv .node circle,#mermaid-svg-RMqjZkx02KSRBrgv .node ellipse,#mermaid-svg-RMqjZkx02KSRBrgv .node polygon,#mermaid-svg-RMqjZkx02KSRBrgv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RMqjZkx02KSRBrgv .rough-node .label text,#mermaid-svg-RMqjZkx02KSRBrgv .node .label text,#mermaid-svg-RMqjZkx02KSRBrgv .image-shape .label,#mermaid-svg-RMqjZkx02KSRBrgv .icon-shape .label{text-anchor:middle;}#mermaid-svg-RMqjZkx02KSRBrgv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RMqjZkx02KSRBrgv .rough-node .label,#mermaid-svg-RMqjZkx02KSRBrgv .node .label,#mermaid-svg-RMqjZkx02KSRBrgv .image-shape .label,#mermaid-svg-RMqjZkx02KSRBrgv .icon-shape .label{text-align:center;}#mermaid-svg-RMqjZkx02KSRBrgv .node.clickable{cursor:pointer;}#mermaid-svg-RMqjZkx02KSRBrgv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RMqjZkx02KSRBrgv .arrowheadPath{fill:#333333;}#mermaid-svg-RMqjZkx02KSRBrgv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RMqjZkx02KSRBrgv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RMqjZkx02KSRBrgv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RMqjZkx02KSRBrgv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RMqjZkx02KSRBrgv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RMqjZkx02KSRBrgv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RMqjZkx02KSRBrgv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RMqjZkx02KSRBrgv .cluster text{fill:#333;}#mermaid-svg-RMqjZkx02KSRBrgv .cluster span{color:#333;}#mermaid-svg-RMqjZkx02KSRBrgv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RMqjZkx02KSRBrgv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RMqjZkx02KSRBrgv rect.text{fill:none;stroke-width:0;}#mermaid-svg-RMqjZkx02KSRBrgv .icon-shape,#mermaid-svg-RMqjZkx02KSRBrgv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RMqjZkx02KSRBrgv .icon-shape p,#mermaid-svg-RMqjZkx02KSRBrgv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RMqjZkx02KSRBrgv .icon-shape .label rect,#mermaid-svg-RMqjZkx02KSRBrgv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RMqjZkx02KSRBrgv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RMqjZkx02KSRBrgv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RMqjZkx02KSRBrgv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是

工具执行输出
Builtin 规则

内嵌 JSON vendored
User 规则

~/.config/tokenjuice/rules/
Project 规则

.tokenjuice/rules/ in cwd
输出 < 240 chars?
Passthrough

不做压缩
classify_execution

匹配规则
reduce_execution_with_rules

应用变换
CompactResult

.inline_text

规则优先级:Builtin < User < Project(高优先级覆盖低优先级)。

4.2 实际压缩效果示例

reduce.rs 中的 rewrite_git_status_line 函数展示了一个具体的压缩策略------把 git status 的冗余输出压缩为精简格式:

rust 复制代码
// 原始: "On branch main" → 删除(毫无信息量)
// 原始: "(use \"git add <file>...\" to update what will be committed)" → 删除
// 原始: "Changes not staged for commit:" → 压缩为 "Changes not staged:"
// 原始: "Changes to be committed:" → 压缩为 "Staged changes:"

对于 catsedheadbat 等文件内容检查工具,is_file_content_inspection_command() 识别后走专用的文件内容压缩路径,不做通用压缩。

4.3 240 字符的 Passthrough 门槛

输出少于 240 字符时直接透传,不做任何压缩处理。这个常量 TINY_OUTPUT_MAX_CHARS 的存在防止了对短输出做无意义的规则匹配(命中不了任何规则,浪费 CPU)。


五、Subconscious Engine:后台定时思考循环

src/openhuman/subconscious/ 是 OpenHuman 的"后台大脑"------即使用户没在对话,它也在定时运行,评估任务、产生 Reflection、创建 Escalation。
前端 UI LLM (云端/本地) SchedulerGate SQLite SubconsciousEngine 前端 UI LLM (云端/本地) SchedulerGate SQLite SubconsciousEngine #mermaid-svg-kzCxAySuBhmqlOfz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kzCxAySuBhmqlOfz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kzCxAySuBhmqlOfz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kzCxAySuBhmqlOfz .error-icon{fill:#552222;}#mermaid-svg-kzCxAySuBhmqlOfz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kzCxAySuBhmqlOfz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kzCxAySuBhmqlOfz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kzCxAySuBhmqlOfz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kzCxAySuBhmqlOfz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kzCxAySuBhmqlOfz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kzCxAySuBhmqlOfz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kzCxAySuBhmqlOfz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kzCxAySuBhmqlOfz .marker.cross{stroke:#333333;}#mermaid-svg-kzCxAySuBhmqlOfz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kzCxAySuBhmqlOfz p{margin:0;}#mermaid-svg-kzCxAySuBhmqlOfz .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kzCxAySuBhmqlOfz text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-kzCxAySuBhmqlOfz .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kzCxAySuBhmqlOfz .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-kzCxAySuBhmqlOfz .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-kzCxAySuBhmqlOfz .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-kzCxAySuBhmqlOfz #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-kzCxAySuBhmqlOfz .sequenceNumber{fill:white;}#mermaid-svg-kzCxAySuBhmqlOfz #sequencenumber{fill:#333;}#mermaid-svg-kzCxAySuBhmqlOfz #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-kzCxAySuBhmqlOfz .messageText{fill:#333;stroke:none;}#mermaid-svg-kzCxAySuBhmqlOfz .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kzCxAySuBhmqlOfz .labelText,#mermaid-svg-kzCxAySuBhmqlOfz .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-kzCxAySuBhmqlOfz .loopText,#mermaid-svg-kzCxAySuBhmqlOfz .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-kzCxAySuBhmqlOfz .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kzCxAySuBhmqlOfz .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-kzCxAySuBhmqlOfz .noteText,#mermaid-svg-kzCxAySuBhmqlOfz .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-kzCxAySuBhmqlOfz .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kzCxAySuBhmqlOfz .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kzCxAySuBhmqlOfz .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kzCxAySuBhmqlOfz .actorPopupMenu{position:absolute;}#mermaid-svg-kzCxAySuBhmqlOfz .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-kzCxAySuBhmqlOfz .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kzCxAySuBhmqlOfz .actor-man circle,#mermaid-svg-kzCxAySuBhmqlOfz line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-kzCxAySuBhmqlOfz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt act 任务 alt escalate 任务 loop 每隔 interval_minutes(最少5分钟) wait_for_capacity() LlmPermit (Throttled时睡眠) 加载 due 任务 → 标记 in_progress build_situation_report()\n从 Memory Tree 拉取新增行 evaluate(tasks + situation_report) TaskEvaluation (act / escalate / skip) executor::execute(task) 更新 log entry 创建 Escalation 推送 Escalation 通知 persist_reflections()\n去重后写入 reflection_store 更新 last_tick_at (重启后恢复断点)

5.1 Generation Counter:防止 tick 重叠

rust 复制代码
pub struct SubconsciousEngine {
    tick_generation: AtomicU64,  // 单调递增
    ...
}

如果新 tick 在老 tick 仍在飞行时启动,老 tick 的 in_progress 条目被标记为 cancelled,结果被丢弃。这防止了 LLM 调用慢于 interval 时的状态累积问题。

5.2 持久化 last_tick_at:重启后恢复断点

注释说明了为什么要持久化这个时间戳:

"Without this, every restart cold-starts the LLM, which sees the same memory-tree rows again and re-emits near-duplicate reflections."

last_tick_at 保存在 SQLite 的 subconscious_state 表中。重启后 Situation Report 只会包含该时间点之后的新增 Memory Tree 行,避免重复产生近似内容的 Reflection。


六、Inference 域:云端 + 本地混合路由

src/openhuman/inference/ 是所有推理相关能力的统一入口:云端 LLM、本地 Ollama、语音 STT/TTS、情绪分析。

6.1 ModelTier:基于内存的本地模型分级

#mermaid-svg-iTQztC7toNU5lX9N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iTQztC7toNU5lX9N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iTQztC7toNU5lX9N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iTQztC7toNU5lX9N .error-icon{fill:#552222;}#mermaid-svg-iTQztC7toNU5lX9N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iTQztC7toNU5lX9N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iTQztC7toNU5lX9N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iTQztC7toNU5lX9N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iTQztC7toNU5lX9N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iTQztC7toNU5lX9N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iTQztC7toNU5lX9N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iTQztC7toNU5lX9N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iTQztC7toNU5lX9N .marker.cross{stroke:#333333;}#mermaid-svg-iTQztC7toNU5lX9N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iTQztC7toNU5lX9N p{margin:0;}#mermaid-svg-iTQztC7toNU5lX9N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iTQztC7toNU5lX9N .cluster-label text{fill:#333;}#mermaid-svg-iTQztC7toNU5lX9N .cluster-label span{color:#333;}#mermaid-svg-iTQztC7toNU5lX9N .cluster-label span p{background-color:transparent;}#mermaid-svg-iTQztC7toNU5lX9N .label text,#mermaid-svg-iTQztC7toNU5lX9N span{fill:#333;color:#333;}#mermaid-svg-iTQztC7toNU5lX9N .node rect,#mermaid-svg-iTQztC7toNU5lX9N .node circle,#mermaid-svg-iTQztC7toNU5lX9N .node ellipse,#mermaid-svg-iTQztC7toNU5lX9N .node polygon,#mermaid-svg-iTQztC7toNU5lX9N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iTQztC7toNU5lX9N .rough-node .label text,#mermaid-svg-iTQztC7toNU5lX9N .node .label text,#mermaid-svg-iTQztC7toNU5lX9N .image-shape .label,#mermaid-svg-iTQztC7toNU5lX9N .icon-shape .label{text-anchor:middle;}#mermaid-svg-iTQztC7toNU5lX9N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iTQztC7toNU5lX9N .rough-node .label,#mermaid-svg-iTQztC7toNU5lX9N .node .label,#mermaid-svg-iTQztC7toNU5lX9N .image-shape .label,#mermaid-svg-iTQztC7toNU5lX9N .icon-shape .label{text-align:center;}#mermaid-svg-iTQztC7toNU5lX9N .node.clickable{cursor:pointer;}#mermaid-svg-iTQztC7toNU5lX9N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iTQztC7toNU5lX9N .arrowheadPath{fill:#333333;}#mermaid-svg-iTQztC7toNU5lX9N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iTQztC7toNU5lX9N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iTQztC7toNU5lX9N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iTQztC7toNU5lX9N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iTQztC7toNU5lX9N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iTQztC7toNU5lX9N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iTQztC7toNU5lX9N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iTQztC7toNU5lX9N .cluster text{fill:#333;}#mermaid-svg-iTQztC7toNU5lX9N .cluster span{color:#333;}#mermaid-svg-iTQztC7toNU5lX9N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iTQztC7toNU5lX9N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iTQztC7toNU5lX9N rect.text{fill:none;stroke-width:0;}#mermaid-svg-iTQztC7toNU5lX9N .icon-shape,#mermaid-svg-iTQztC7toNU5lX9N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iTQztC7toNU5lX9N .icon-shape p,#mermaid-svg-iTQztC7toNU5lX9N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iTQztC7toNU5lX9N .icon-shape .label rect,#mermaid-svg-iTQztC7toNU5lX9N .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iTQztC7toNU5lX9N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iTQztC7toNU5lX9N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iTQztC7toNU5lX9N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} <8GB
≥8GB
1GB
2~4GB
4~8GB
8~16GB
16GB+
设备探测

DeviceProfile
total_ram_gb
默认云端 fallback

可手动覆盖
默认启用本地 AI
RAM 精确值
Gemma 3 270M QAT

纯文本,无视觉
Gemma 3 1B QAT ← MVP

bge-m3 embedding

无视觉
Gemma 3 1B + Moondream

按需视觉
更大模型

全视觉支持
最强本地模型

捆绑视觉

当前 MVP 版本锁定了最高 Ram2To4Gb tier(Gemma 3 1B),更高 tier 的代码已有但对用户屏蔽。device_supports_local_ai()total_ram_gb() >= 8 作为默认启用本地 AI 的门槛------这是推荐,不是硬限制。

6.2 Embedding 维度锁定

注释中有一个关键约束:

rust 复制代码
// bge-m3 --- 1024 dims required by memory tree's on-disk format
// and 8192-token context for long-chunk embeds.
embedding_model_id: "bge-m3",

Memory Tree 的磁盘格式固定使用 1024 维向量(bge-m3 输出维度)。如果未来要换 embedding 模型,需要迁移存量向量------这是一个有意识的技术债务决策。

6.3 情绪分析(Sentiment)

inference/sentiment.rs 说明 inference 域不只是做文本生成,还承担了情绪分析任务,结果(SentimentResult)被 Agent 和 meet_agent 消费,用于调整 Mascot 的表情和响应风格。


七、Composio 集成层:后端代理模式

OpenHuman 的 118+ 集成通过 Composio 实现,但架构选择有意思------Rust core 不直接调用 Composio API
Composio Webhook Composio API OpenHuman 后端 Rust Core Composio Webhook Composio API OpenHuman 后端 Rust Core #mermaid-svg-SDr6q9D0gV6upB8d{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SDr6q9D0gV6upB8d .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SDr6q9D0gV6upB8d .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SDr6q9D0gV6upB8d .error-icon{fill:#552222;}#mermaid-svg-SDr6q9D0gV6upB8d .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SDr6q9D0gV6upB8d .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SDr6q9D0gV6upB8d .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SDr6q9D0gV6upB8d .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SDr6q9D0gV6upB8d .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SDr6q9D0gV6upB8d .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SDr6q9D0gV6upB8d .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SDr6q9D0gV6upB8d .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SDr6q9D0gV6upB8d .marker.cross{stroke:#333333;}#mermaid-svg-SDr6q9D0gV6upB8d svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SDr6q9D0gV6upB8d p{margin:0;}#mermaid-svg-SDr6q9D0gV6upB8d .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SDr6q9D0gV6upB8d text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-SDr6q9D0gV6upB8d .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SDr6q9D0gV6upB8d .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-SDr6q9D0gV6upB8d .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-SDr6q9D0gV6upB8d .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-SDr6q9D0gV6upB8d #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-SDr6q9D0gV6upB8d .sequenceNumber{fill:white;}#mermaid-svg-SDr6q9D0gV6upB8d #sequencenumber{fill:#333;}#mermaid-svg-SDr6q9D0gV6upB8d #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-SDr6q9D0gV6upB8d .messageText{fill:#333;stroke:none;}#mermaid-svg-SDr6q9D0gV6upB8d .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SDr6q9D0gV6upB8d .labelText,#mermaid-svg-SDr6q9D0gV6upB8d .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-SDr6q9D0gV6upB8d .loopText,#mermaid-svg-SDr6q9D0gV6upB8d .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-SDr6q9D0gV6upB8d .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SDr6q9D0gV6upB8d .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-SDr6q9D0gV6upB8d .noteText,#mermaid-svg-SDr6q9D0gV6upB8d .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-SDr6q9D0gV6upB8d .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SDr6q9D0gV6upB8d .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SDr6q9D0gV6upB8d .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SDr6q9D0gV6upB8d .actorPopupMenu{position:absolute;}#mermaid-svg-SDr6q9D0gV6upB8d .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-SDr6q9D0gV6upB8d .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SDr6q9D0gV6upB8d .actor-man circle,#mermaid-svg-SDr6q9D0gV6upB8d line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-SDr6q9D0gV6upB8d :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} composio_* RPC(通过 Socket.IO) 代理调用(拥有 API key + billing) 响应 返回结果 触发 webhook(HMAC 验证) composio:trigger 事件(Socket.IO fan-out) ComposioTriggerSubscriber.handle() DomainEvent::ComposioTriggerReceived

这个设计的用意:

  1. OpenHuman 后端拥有 Composio API key------用户不需要自己注册 Composio 账号
  2. 后端做 HMAC 验证------防止 Webhook 伪造
  3. 一个订阅(OpenHuman)包含所有集成------对用户来说是"一个账号"而非"N 个 API key"

代价是:用户使用托管模式时,数据会经过 OpenHuman 后端。文档提到可以通过配置 direct mode + 自己的 Composio API key 绕过后端。

7.1 Auto-fetch 的 20 分钟周期

composio/periodic.rs 实现了每 20 分钟的自动同步循环:

  • 遍历所有已连接的 Composio 集成
  • 调用各 Provider 的 sync() 方法拉取新数据
  • 新数据直接摄入 Memory Tree 流水线(Phase 1 → Phase 4)

结果:用户醒来打开 OpenHuman,Agent 已经知道昨晚的 Gmail、Notion 更新,不需要手动告知。


八、Vault:NotebookLM 风格的本地文件夹知识库

src/openhuman/vault/ 对标 Google NotebookLM 的本地版本。

注释:

"A Vault points at a local directory; on vault.sync we walk it, route files to extractors by extension, and feed them into the memory pipeline under namespace vault:. Per-file dedup uses (path, mtime, content hash) so re-syncs only touch what changed."

去重逻辑用 (path, mtime, content_hash) 三元组:

  • path 相同 + mtime 相同:文件没变,跳过
  • path 相同 + mtime 变了:重新读取,检查 content_hash(防止 touch 产生的假阳性)
  • path:新文件,摄入

支持格式按扩展名路由到不同 extractor,PDF 支持是可选 feature(rag-pdf = ["dep:pdf-extract"])。


九、Scheduler Gate:电池感知的后台任务调度

src/openhuman/scheduler_gate/ 解决了一个实际问题:后台 AI 任务(Memory Tree 摘要、Embedding、Subconscious tick)在笔记本电量低时不应该全速运行。
#mermaid-svg-rjxiMiE8pKNferBR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rjxiMiE8pKNferBR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rjxiMiE8pKNferBR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rjxiMiE8pKNferBR .error-icon{fill:#552222;}#mermaid-svg-rjxiMiE8pKNferBR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rjxiMiE8pKNferBR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rjxiMiE8pKNferBR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rjxiMiE8pKNferBR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rjxiMiE8pKNferBR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rjxiMiE8pKNferBR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rjxiMiE8pKNferBR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rjxiMiE8pKNferBR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rjxiMiE8pKNferBR .marker.cross{stroke:#333333;}#mermaid-svg-rjxiMiE8pKNferBR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rjxiMiE8pKNferBR p{margin:0;}#mermaid-svg-rjxiMiE8pKNferBR defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-rjxiMiE8pKNferBR g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-rjxiMiE8pKNferBR g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-rjxiMiE8pKNferBR g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-rjxiMiE8pKNferBR g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-rjxiMiE8pKNferBR g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-rjxiMiE8pKNferBR .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-rjxiMiE8pKNferBR .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-rjxiMiE8pKNferBR .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-rjxiMiE8pKNferBR .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-rjxiMiE8pKNferBR .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-rjxiMiE8pKNferBR .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-rjxiMiE8pKNferBR .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-rjxiMiE8pKNferBR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rjxiMiE8pKNferBR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rjxiMiE8pKNferBR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rjxiMiE8pKNferBR .edgeLabel .label text{fill:#333;}#mermaid-svg-rjxiMiE8pKNferBR .label div .edgeLabel{color:#333;}#mermaid-svg-rjxiMiE8pKNferBR .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-rjxiMiE8pKNferBR .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-rjxiMiE8pKNferBR .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-rjxiMiE8pKNferBR .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-rjxiMiE8pKNferBR .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-rjxiMiE8pKNferBR .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rjxiMiE8pKNferBR .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rjxiMiE8pKNferBR #statediagram-barbEnd{fill:#333333;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rjxiMiE8pKNferBR .cluster-label,#mermaid-svg-rjxiMiE8pKNferBR .nodeLabel{color:#131300;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-rjxiMiE8pKNferBR .note-edge{stroke-dasharray:5;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-note text{fill:black;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram-note .nodeLabel{color:black;}#mermaid-svg-rjxiMiE8pKNferBR .statediagram .edgeLabel{color:red;}#mermaid-svg-rjxiMiE8pKNferBR #dependencyStart,#mermaid-svg-rjxiMiE8pKNferBR #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-rjxiMiE8pKNferBR .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rjxiMiE8pKNferBR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务器模式\n(无电量/CPU 限制)
切换到桌面模式
电量低 OR CPU > 70%
接上电源 AND CPU 回落
用户手动暂停
用户重新启用
Aggressive
Normal
Throttled
Paused
绕过所有限制\n立即执行
按计划执行
串行化 + 减速
无限期延迟\nwait_for_capacity() 轮询

信号采样(每 30 秒刷新)

  • power_state:是否接通 AC 电源,或电量 ≥ 80%
  • cpu_usage:近期全局 CPU 使用率,< 70% 为"足够空闲"
  • deployment_mode:服务器/容器宿主机直接走 Aggressive

wait_for_capacity() 是一个 async future,在 Aggressive/Normal 下立即 resolve,在 Throttled 下睡眠,在 Paused 下持续轮询(用户切换回来立即恢复)。

实现使用了 starship-battery crate(battery crate 的维护分支),以及 sysinfo 的 CPU 监控------Cargo.toml 注释专门解释了选择 starship-battery 而非原始 battery 的原因。


十、Voice 域:端到端语音管线

OpenHuman 的语音能力完整到令人惊讶------不是调用云 API,而是本地运行:
#mermaid-svg-Im1t2Ap68gjIHRd5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Im1t2Ap68gjIHRd5 .error-icon{fill:#552222;}#mermaid-svg-Im1t2Ap68gjIHRd5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Im1t2Ap68gjIHRd5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .marker.cross{stroke:#333333;}#mermaid-svg-Im1t2Ap68gjIHRd5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Im1t2Ap68gjIHRd5 p{margin:0;}#mermaid-svg-Im1t2Ap68gjIHRd5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .cluster-label text{fill:#333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .cluster-label span{color:#333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .cluster-label span p{background-color:transparent;}#mermaid-svg-Im1t2Ap68gjIHRd5 .label text,#mermaid-svg-Im1t2Ap68gjIHRd5 span{fill:#333;color:#333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .node rect,#mermaid-svg-Im1t2Ap68gjIHRd5 .node circle,#mermaid-svg-Im1t2Ap68gjIHRd5 .node ellipse,#mermaid-svg-Im1t2Ap68gjIHRd5 .node polygon,#mermaid-svg-Im1t2Ap68gjIHRd5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Im1t2Ap68gjIHRd5 .rough-node .label text,#mermaid-svg-Im1t2Ap68gjIHRd5 .node .label text,#mermaid-svg-Im1t2Ap68gjIHRd5 .image-shape .label,#mermaid-svg-Im1t2Ap68gjIHRd5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Im1t2Ap68gjIHRd5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Im1t2Ap68gjIHRd5 .rough-node .label,#mermaid-svg-Im1t2Ap68gjIHRd5 .node .label,#mermaid-svg-Im1t2Ap68gjIHRd5 .image-shape .label,#mermaid-svg-Im1t2Ap68gjIHRd5 .icon-shape .label{text-align:center;}#mermaid-svg-Im1t2Ap68gjIHRd5 .node.clickable{cursor:pointer;}#mermaid-svg-Im1t2Ap68gjIHRd5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .arrowheadPath{fill:#333333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Im1t2Ap68gjIHRd5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Im1t2Ap68gjIHRd5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Im1t2Ap68gjIHRd5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Im1t2Ap68gjIHRd5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Im1t2Ap68gjIHRd5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Im1t2Ap68gjIHRd5 .cluster text{fill:#333;}#mermaid-svg-Im1t2Ap68gjIHRd5 .cluster span{color:#333;}#mermaid-svg-Im1t2Ap68gjIHRd5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Im1t2Ap68gjIHRd5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Im1t2Ap68gjIHRd5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Im1t2Ap68gjIHRd5 .icon-shape,#mermaid-svg-Im1t2Ap68gjIHRd5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Im1t2Ap68gjIHRd5 .icon-shape p,#mermaid-svg-Im1t2Ap68gjIHRd5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Im1t2Ap68gjIHRd5 .icon-shape .label rect,#mermaid-svg-Im1t2Ap68gjIHRd5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Im1t2Ap68gjIHRd5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Im1t2Ap68gjIHRd5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Im1t2Ap68gjIHRd5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 本地
云端
本地
云端
特殊场景
Google Meet Agent

以参会者身份加入

实时语音对话
用户按下热键

或麦克风输入
audio_capture

cpal 实时采集
STT 路由
whisper-rs

(whisper.cpp bindings)

本地 Whisper 模型
cloud_transcribe

whisper-v1 API
转写文本
Agent 处理
TTS 路由
piper

本地 TTS 模型

DEFAULT_PIPER_VOICE
ElevenLabs

API 调用
音频输出

  • Mascot 口型同步

macOS 优化Cargo.tomlwhisper-rs 在 macOS 下额外开启 metal feature,使用 Metal GPU 加速推理。

Windows TLS 策略:注释解释了为什么 Windows 用 native-tls(SChannel)而非 rustls:

"Windows: tokio-tungstenite uses native-tls (schannel) so wss:// connections honor the Windows cert store, including corporate CAs installed by AV / TLS-inspection proxies."

这是一个很实际的企业用户场景考量------公司 TLS 审查代理会安装自签证书,Rustls 不查系统证书库,SChannel 会查。


十一、CDP 层:WebView 内容扫描

app/src-tauri/src/cdp/ 实现了一个完整的 Chrome DevTools Protocol 客户端,用于扫描 Tauri 内嵌的 WebView(Discord、Telegram、WhatsApp、Slack、LinkedIn 等)。

rust 复制代码
/// Remote debugging host
pub const CDP_HOST: &str = "127.0.0.1";
pub const CDP_PORT: u16 = 19222;  // 注意:不是 9222

注释解释了为什么端口是 19222 而不是标准的 9222:

"Port was 9222 originally but collided with ollama's 127.0.0.1:9222 listener (silent CDP-attach failure → blank child webviews)."

和 Ollama 的端口冲突是一个很典型的"两个开源项目都选了同一个'不常见'端口"问题。更改后需要同时更新 lib.rscdp/mod.rs,注释里有明确提示。

recipes/ 目录下的每个平台(discord/telegram/whatsapp/linkedin/ 等)都有一个 manifest.json 和可选的 recipe.js,定义了如何注入 JavaScript 来提取内容。


十二、安全与隐私架构

12.1 本地加密

src/openhuman/encryption/src/openhuman/vault/ 使用:

  • AES-256-GCMaes-gcm crate)
  • ChaCha20-Poly1305chacha20poly1305 crate)
  • Argon2 (密钥派生,argon2 crate)

Vault 数据(连接的账户凭证、本地知识库)加密存储在本地 SQLite 中,密钥通过 Argon2 从用户主密码派生。

12.2 Linux Landlock 沙箱

Cargo.toml 中有一个 feature:

toml 复制代码
[features]
sandbox-landlock = ["dep:landlock"]

Linux 上可选启用 Landlock(内核级文件系统访问控制),为核心进程提供系统调用级别的沙箱隔离。sandbox-bubblewrap 是另一个可选沙箱后端(通过 bwrap)。

12.3 敏感数据过滤(Sentry)

Cargo.toml 的 Sentry 集成注释说明了 before_send 过滤器:

toml 复制代码
sentry = { version = "0.47.0", features = ["backtrace", "contexts", "panic", "tracing", ...] }

tests/observability_smoke.rs 中的 smoke test 专门验证这个过滤器在发送崩溃报告前是否正确过滤了敏感数据。


十三、技术债务与工程决策

13.1 html2md 被移除------性能问题复盘

Cargo.toml 中有一段很罕见的注释,完整记录了一次性能问题的根因分析:

toml 复制代码
# (Removed `html2md` dep. dhat-rs profiling on real Gmail inboxes
# showed `html2md::walk` and `html2md::tables::handle` allocating
# ~894 MB peak heap on a 10 KB HTML input from Otter.ai-style emails
# (deeply-nested table-as-layout HTML). Cause: recursive walker holding
# per-frame Vec state across nesting layers + 5 sequential
# `regex::replace_all` passes in `clean_markdown` each producing a
# fresh full-size String. ...
# We now use a linear-time tag-and-entity stripper (`fast_html_to_text`
# in providers/gmail/post_process.rs) and prefer the email's
# `text/plain` MIME part when available.)

10KB HTML 产生 894MB 堆分配------这是 html2md 对深度嵌套 table 布局 HTML 的已知性能问题。OpenHuman 用 profiling(dhat-rs)定位到了根因,用线性时间的 tag stripper 替换,同时优先使用 text/plain MIME 部分。这种在 Cargo.toml 里用注释记录 WHY 的做法值得学习。

13.2 Embedding 维度锁定的技术债务

前文提到 Memory Tree 的磁盘格式固定 1024 维(bge-m3),这是一个已知的技术债务:未来换 embedding 模型需要全量重新向量化历史数据。相比之下 Hermes Agent 的 MemoryProvider 抽象可以更灵活地切换后端。

13.3 MVP 模型 Tier 锁定

rust 复制代码
pub const MVP_MAX_TIER: ModelTier = ModelTier::Ram2To4Gb;

is_mvp_allowed() 方法只对 Ram2To4Gb 返回 true。更高 tier 的代码完整存在(4-8GB、8-16GB、16+GB),但被 MVP gate 屏蔽。这是一个典型的"代码写好了,产品决策还没到"的 feature flag 模式。


十四、可观测性基础设施

OpenHuman 有生产级的可观测性配置:

工具 用途
tracing + tracing-subscriber 结构化日志(替代 log crate)
tracing-appender 异步文件追加写入(不阻塞主路径)
prometheus metrics 采集(无 default features,按需启用)
opentelemetry + opentelemetry-otlp 分布式追踪(OTLP 协议推送)
sentry 崩溃报告(含 before_send 过滤)

tests/observability_smoke.rs 是一个专门验证可观测性路径的 smoke test,使用 Sentry 的 TestTransport 在不发网络请求的情况下验证事件过滤逻辑。


十五、优缺点分析

优点

1. Memory Tree 的工程质量

四阶段流水线(摄取→评分→摘要树→检索)、确定性 chunk ID、三区间 LLM 节省策略------这不是"先做再说"的实现,而是有完整 LLD 文档(docs/MEMORY_ARCHITECTURE_LLD.md)指导的设计。评分系统特别精妙:只有"边界区间"才调用 LLM,明显垃圾和明显干货用正则处理,大幅降低摄取成本。

2. TokenJuice 的三层规则架构

Builtin/User/Project 三层优先级覆盖,让用户和项目都可以自定义压缩规则。Rust 移植避免了 Node.js 的性能开销,240 字符 passthrough 门槛防止了过度处理短输出。

3. Scheduler Gate 的实用性

电池感知的四级调度策略(Aggressive/Normal/Throttled/Paused)是真正的工程考量,不是 PPT 上的 feature。LlmPermit 的 cooperative 设计让后台任务自愿退出而不是被强制中断。

4. 完整的端到端语音管线

本地 whisper.cpp + piper,无需外部 API,Mascot 口型同步,Google Meet 集成------这是一套完整的本地 Voice AI 基础设施,不依赖任何云服务。

5. 性能问题的主动复盘记录

html2md 的 894MB 堆分配问题被完整记录在 Cargo.toml 注释里,包括 profiling 方法、根因、解决方案。这种"为什么做了这个决定"的文档化习惯在开源项目中很少见。

6. 供应链安全意识

Cargo.toml 的 profile.release 中有:

toml 复制代码
debug = "line-tables-only"
split-debuginfo = "packed"

只保留文件+行号调试信息(不含完整类型信息),用于 Sentry 崩溃符号化,同时保持发布二进制的体积可控。


缺点

1. Composio 后端依赖是核心风险

118+ 集成通过 OpenHuman 后端代理,这意味着服务可用性依赖 tinyhumansai 的后端。用户数据(至少触发事件)经过第三方后端。direct mode 存在但需要用户自己维护 Composio 账号,学习成本不低。

2. Embedding 维度硬编码是长期技术债

1024 维(bge-m3)锁定在磁盘格式里。随着 embedding 模型快速迭代(更高质量的 embedding 普遍超过 1024 维),未来的模型迁移成本会很高。Hermes 的 MemoryProvider 抽象在这一点上更有弹性。

3. MVP Tier 锁定影响本地 AI 价值

当前 MVP 只有 Gemma 3 1B,8GB RAM 以上设备无法用到更强的本地模型。对于宣称"强大的本地 AI"的产品,这是一个 credibility gap。

4. 架构复杂度高,贡献门槛陡

80 个子模块,Rust 异步,Tokio、axum、rusqlite、Tauri 全栈,加上 React/TypeScript 前端。CONTRIBUTING.md 里的先决条件已经让大多数人望而却步:Git、Node.js 24+、pnpm 10.10.0、Rust 1.93.0、CMake、Ninja、ripgrep,以及各平台桌面构建依赖。

5. 单体 RPC 架构的可扩展性

所有模块的 RPC 注册在 core::all 中统一注册,随着模块增加,编译时间和启动时间会线性增长。目前 v0.54.7 是单进程,没有服务拆分路径。

6. Tauri CEF 的平台差异

CEF(Chromium Embedded Framework)在不同平台的行为不完全一致,cef_preflight.rscef_profile.rs 的存在说明有专门的跨平台处理逻辑。CDP port 冲突(9222 vs Ollama)这类问题在多平台上可能还有更多。

7. Early Beta 稳定性

README 明确标注"Early Beta: Under active development. Expect rough edges.",v0.54.7 版本号高但实际成熟度仍有限。E2E 测试脚本(app/scripts/e2e-*.sh)覆盖了登录、支付、Gmail、Telegram 等关键流程,说明基础功能是有测试的,但整体稳定性保障尚在建设中。


结语

OpenHuman 代表了 AI 个人助手的一个不同路径:不是"更好的命令行工具",而是"真正住在你桌面上、了解你生活的 AI"。Memory Tree 的四阶段流水线、TokenJuice 的压缩引擎、Scheduler Gate 的电池感知调度------这些都是有工程深度的设计,不是 Demo 级别的实现。

但它的最大赌注是 Composio 后端依赖和 Memory Tree 的工程复杂度------前者是商业风险,后者是维护成本。能否在保持"Simple, UI-first & Human"的产品承诺的同时,支撑起这个技术债务栈,是它能否成功的核心问题。


本文基于 openhuman v0.54.7 源码,核心文件包括 Cargo.tomlsrc/openhuman/agent/mod.rssrc/openhuman/memory/mod.rssrc/openhuman/memory/tree/src/openhuman/tokenjuice/src/openhuman/subconscious/engine.rssrc/openhuman/inference/presets.rssrc/openhuman/composio/mod.rssrc/openhuman/vault/mod.rssrc/openhuman/scheduler_gate/mod.rssrc/openhuman/voice/mod.rssrc/openhuman/desktop_companion/mod.rsapp/src-tauri/src/cdp/mod.rs

相关推荐
冰西瓜6001 小时前
深度学习的数学原理(四十一)—— KV Cache
人工智能·深度学习
一点一木1 小时前
🚀 2026 年 5 月 GitHub 十大热门项目排行榜 🔥
人工智能·github·ai编程
心怀梦想的咸鱼1 小时前
OpenCode 接入 API 报错 read ECONNRESET:基于环境变量的证书校验绕过方案
开发语言·php
Chunyyyen1 小时前
【第四十七周】自然语言处理课程作业记录
人工智能·自然语言处理
zhangfeng11332 小时前
ai 模型加密,强化版终极防盗方案 支持烧录的显卡列表
人工智能·pytorch·python
阿里云大数据AI技术2 小时前
逐际动力 x 阿里云 PAI:携手开启具身智能走向物理世界新篇章
人工智能·机器人
半个落月2 小时前
Prompt Engineering 完全指南:从入门到写出高质量提示词
人工智能
小p2 小时前
claude code 工程化学习3: 如何创建一个复杂的 Skill
人工智能
程序大视界2 小时前
【Python系列课程】Python入门教程
开发语言·人工智能·python