OpenHuman 潜意识工厂 Phase 4 架构解析:多世界实例的 Factory/Registry 生命周期、Heartbeat 扇出与逐实例 JSON-RPC 设计
2026/9/9 21:03:29 网站建设 项目流程

OpenHuman 潜意识工厂 Phase 4 架构解析:多世界实例的 Factory/Registry 生命周期、Heartbeat 扇出与逐实例 JSON-RPC 设计

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

本文以仓库内设计文档 docs/plans/subconscious-factory/phase-4-factory-registry-rpc.md 为主体,结合同目录的前后阶段文档与仓库中真实存在的配置 Schema、about_app 模块源码展开解读。该文档属于Subconscious factory(潜意识工厂)重构计划的第四阶段,核心目标是打通“制造潜意识”的统一入口:用factory.rs实例化任意一组 world、让 heartbeat 统一驱动它们、并通过 JSON-RPC 暴露逐实例的状态与触发能力。读者读完本文后,将能完整理解这套多世界潜意识层的装配模型、生命周期管理与向后兼容的协议扩展思路。

一、为什么需要 Factory:一个引擎里塞了两个世界

OpenHuman 的 subconscious(潜意识 / 深度反思层)是一个离线、cron 驱动的循环:它消费“某个 world 如何变化”的压缩视图,产出用于引导系统其余部分的高密度输出。按照 subconscious-factory/README.md 的目标形态,未来会有两个(甚至更多)world:

  • memory——用户连接的各类记忆源(Gmail/Slack/Notion/文件夹)构成的高层世界,基于 baseline checkpoint 观察memory_diff,由精简决策 agent 做反思(to-do、goal、notify_user、委派);
  • tinyplace——tiny.place 编排世界,观察经过 20:1 压缩的执行历史与累积世界态差异,由无工具 steering 综合输出STEERING_DIRECTIVE给 reasoning core。

当前实现的痛点全部集中在单个subconscious/engine.rstick_inner是一个硬编码的“复合体”——stage 0 调用orchestration::ops::run_orchestration_review(tinyplace 世界),stage 1–3 再跑 memory 世界(memory_diff → context scout → decision agent)。结果是一个 tick lock、一个熔断器、一个状态对象、一个 baseline store,却服务着两个互不相关的世界:它们拿不到各自的 cadence、provider 签名、halt 状态和 status,也无法在不动这个复合体的情况下新增第三个世界。

Phase 4 正是在 phase 1(SubconsciousProfiletrait + 泛型SubconsciousInstancerunner)、phase 2(抽取 memory profile)、phase 3(用profiles/tinyplace.rs包装 orchestration review)打好的地基上,解决“多个实例如何被创建、登记、驱动、对外暴露”的问题。按设计文档的表述,其目标是:

the "make subconscious" surface — instantiate any set of worlds, drive them from the heartbeat, expose per-instance status/trigger over JSON-RPC.

二、4.1factory.rsSubconsciousKind与唯一的装配点

设计文档给出工厂层的核心契约如下:

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SubconsciousKind { Memory, TinyPlace } impl SubconsciousKind { pub fn id(self) -> &'static str; // "memory" | "tinyplace" pub fn parse(s: &str) -> Option<Self>; /// Which kinds should run for this config (bootstrap set). pub fn enabled_kinds(config: &Config) -> Vec<Self> { // Memory ⇐ heartbeat.enabled && mode != Off (today's gate) // TinyPlace ⇐ orchestration.enabled (today's gate) } } pub fn make_subconscious(kind: SubconsciousKind, config: &Config) -> SubconsciousInstance;

几个值得注意的工程决策:

  1. 枚举即目录id()返回的"memory"/"tinyplace"字符串会被用作 store 的命名空间前缀、日志前缀与 RPC 中的 instance 名(这一点在 phase 1 的SubconsciousProfile::id契约中已有同样约定)。parse用于从 RPC 请求参数等外部输入反查类型。
  2. serde(rename_all = "snake_case")保证 JSON-RPC 载荷里的大小写风格与项目其余 serde 类型一致。
  3. “新增一个世界 = 一个 profile 文件 + 一个 match 分支 + 一行enabled_kinds。文档强调make_subconscious是 profile 被构造的唯一位置——测试与 trigger RPC 也必须经由它,从而把“如何造实例”收敛到单点,避免散落各处的Arc::new(...::new(config))

门控逻辑对应到仓库现有配置 Schema 是能一一印证的:

  • heartbeat.enabled即 src/openhuman/config/schema/heartbeat_cron.rs 中HeartbeatConfig.enabled(opt-in,注释明确“ticks may call hosted models and integration APIs depending on routing and enabled collectors”);
  • “mode != Off” 对应HeartbeatConfig.subconscious_mode: SubconsciousMode,其默认值是Off,并带有is_enabled()辅助方法;同文件还实现了向后兼容的解析:当subconscious_mode未显式设置时,会回退到遗留的enabled && inference_enabled语义;
  • src/openhuman/config/schema/subconscious.rs 则说明当前实际存在 “engine selection” 概念(local/ 兼容遗留的medulla),印证了文档中所述的心跳 tick 已走 observe/reflect/commit 的认知管线。

这些字段的存在意味着 phase 4 的enabled_kinds只是把已有的两组门控重新解释成“启动哪组 world 实例”的集合,而非引入新的开关面。

三、4.2registry.rs:从“单例引擎”到“键控注册表”

现状是global.rs里一个OnceLock<Arc<Mutex<Option<SubconsciousEngine>>>>——同一时刻只能有一个引擎。Phase 4 的目标形态是:

static REGISTRY: OnceLock<Mutex<HashMap<SubconsciousKind, Arc<SubconsciousInstance>>>>;

设计文档明确了三组生命周期方法:

3.1get_or_init_instance(kind)——惰性按需构造

沿用今天get_or_init_engine的“先 load config 再 insert”流程:每个 kind 首次被触及时才构建,避免启动阶段为从未启用的世界白白加载配置与 profile。注意每个 value 是Arc<SubconsciousInstance>不是Mutex<Option<..>>——文档给的理由非常明确:实例内部已有的tick_lock/state互斥量已经序列化了需要序列化的部分;而 status 读取路径按 invariant 5 必须保持无锁,因此外层再包一层可变 Option 只会引入多余竞争点。

3.2bootstrap_after_login()——登录后启动整个 enabled 集合

流程不变式保持不变:先用BOOTSTRAPPEDswap 守卫保证只执行一次;随后初始化enabled_kinds(config)的每一个成员、启动 heartbeat,以及(保持现状的)opt-in trigger orchestrator。enabled_kinds是集合语义的体现——当未来新增第三个世界(如 per-team world、channels world)时,只要它的 profile 写好了,这行集合逻辑就会自动把它纳入登录后的启动序列。

3.3stop_heartbeat_loop()/reset_engine_for_user_switch()——用户切换的全量重建

用户切换工作空间时,需要:中止 heartbeat → 关闭 trigger orchestrator →清空整张 map,从而让下一次 bootstrap 针对新的 workspace 重建每一个实例。这比今天的单例切换更彻底:因为 memory 与 tinyplace 的 KV 状态都挂在各自实例的命名空间下,逐个实例重建才不会让旧工作空间的残留状态污染新用户。

3.4 过渡期的向后兼容别名

文档规定:在 RPC 处理器全部迁移完成前,保留get_or_init_engine()作为get_or_init_instance(Memory)deprecated alias,迁移完毕再删除。这是一个值得借鉴的渐进式重构手法——调用方可以零成本地逐点切换到新 API,而不是在一个提交里原子性地改完所有触点。

四、4.3 Heartbeat 扇出:一次心跳,驱动所有到期实例

heartbeat/engine.rs目前在自己的 interval 上调用单一引擎tick()。改造后:

  • 每个 heartbeat interval,遍历 registry;
  • 对每个cadence 已到期now - last_tick_at >= cadence)的实例执行tick()——phase 1 已为每个实例维护独立的last_tick_at与 cadence 键,因此这一步是纯读判断,不同世界互不干扰;
  • 多个实例的 tick并发执行(每个实例tokio::spawn,并与既有的 cancel/abort 语义 join),文档给出动机示例:“a slow memory tick must not delay a tinyplace review”——memory 世界一次耗时的反思绝不应当阻塞 tinyplace 世界按时产出评审;
  • heartbeat 原有的 event-planner 职责(meetings/reminders)保持不变

这里继承自 phase 1 的一个结构性好处值得强调:tick_inner那种“一个 tick 里手工编排两个世界”的顺序耦合被彻底拆散,cadence 成为 profile 的属性(SubconsciousProfile::cadence(&self, config)),调度 shell 只负责“谁到期就 tick 谁”。这也与 phase 1 中“runner 内保留 cadence loop trigger、tick lock、generation 计数、TICK_TIMEOUT、provider gate、rate-cap halt 等调度器/熔断器关注点”的分层一致——profile 决定一个世界“做什么”,runner/registry/heartbeat 决定它“何时被做、并行地做”

五、4.4 RPC surface:subconscious命名空间的向后兼容扩展

RPC 契约的扩展遵循“今天的 UI 与调用方不被破坏”的硬约束,两处改动都是增量的。

5.1subconscious.status:保留顶层字段 + 新增instances

变更项说明
顶层字段保持不变,数据来自memory实例——现有 UI 继续工作,零改动
instances: [SubconsciousStatus]新增字段,已注册的每个 kind 一行;每行再带instance: "memory" \| "tinyplace"标识

换句话说,既有消费者看到的是和以前一模一样的 status 结构;想感知多世界的调用方则读新增的instances数组。

5.2subconscious.trigger:可选kind参数

语义
"memory"(默认)今天的既有行为,完全不变
"tinyplace"定向触发 tinyplace 实例的反思
"all"触发全部注册实例

触发仍是fire-and-forget:spawn 后立刻返回,不阻塞调用方等待反思结果。

5.3 读取路径的约束(invariant 5 的落地)

状态读取严格保持 SQLite-only、且永不触碰 tick mutex

  • 每个实例的last_tick_at来自命名空间化的 KVmemory:last_tick_at/tinyplace:last_tick_at,phase 1 的store.rs已完成键前缀化与遗留键迁移);
  • 进程内的计数器(failures、halt reason)来自实例的status()——它只拿细粒度的state互斥量,绝不拿tick_lock

因此“看状态”永远不会与“正在 tick”的反思过程互相阻塞。前端消费这些新字段属于 phase-7-ui.md 的范围(Subconscious 页的 instance cards、TinyPlace Orchestration 页的 steering header),文档明确标注不是 Rust 工作的阻塞项——因为向后兼容的协议扩展可以先于 UI 落地并被现有 UI 安全忽略。

六、4.5about_app:面向用户的描述同步更新

设计文档的最后一项是一个仓库规约要求:用户可见的功能变更必须同步更新 about_app 文案(对应模块 src/openhuman/platform/about_app)。subconscious 的对外描述将从“单一引擎”更新为per-world 实例的口径:

  • memory→ memory awareness(记忆感知);
  • tinyplace→ tiny.place orchestration steering(编排引导)。

这条看似“收尾”的规定实际上保证了:即便内部架构从单引擎变成工厂多实例,用户在“关于”页读到的产品叙事仍与真实行为一致——描述的最小单元从“引擎”切换成“实例”。

七、贯穿始终的不变式:重构的红线

Phase 4 的每一处改动都必须让 README 中列出的六条不变式存活。与本阶段最相关的是:

不变式内容Phase 4 中的落点
1. 隔离(Isolation)subconscious 永不主动外联tinyplace profile 保持无工具 provider chat;memory profile 的 agent 工具集继续通过subconscious_agent_tool_surface_has_no_channel_or_effect_tools之类测试守卫
2. Taint对外部内容做出反应的 tick 记为SubconsciousTaintedmemory 由 diff 是否携带外部内容决定;tinyplace 恒为 tainted
3. 只在成功时推进被 supersede 的 tick 丢弃结果registry 层不改动这一语义,逐实例保留
4. Quiet tick 零成本observe()为空就不调 LLM逐实例判定,互不牵连
5. status 不碰 tick 锁subconscious.status只读 SQLite见上文 5.3,registry 用Arc而非Mutex<Option<..>>正是为此
6. 向后兼容旧 DB 键迁移到memory:命名空间;RPC 保留镜像 memory 实例的 legacy 顶层字段4.4 的 status 顶层字段即该不变式的协议侧体现

尤其 invariant 5 在 registry 设计上的推论最值得记住:正因为实例状态必须锁外可读,注册表里存放的才必须是Arc<SubconsciousInstance>(内部细粒度锁负责写串行化),而不是一个大而全的Mutex<Option<Engine>>

八、在整条时间线中的位置与验收路径

Subconscious factory 被拆成七个可分别编译、可分别提交的阶段(见 subconscious-factory/README.md 的 phase 表):

  • phase 1–3 引入抽象并做纯抽取(无行为变化):SubconsciousProfiletrait、泛型实例 runner(tick 本体是一条tinyagents CompiledGraph,见 phase-1-profile-and-engine.md)、按命名空间分键的 store、memory 与 tinyplace 两个 profile;
  • **phase 4(本文)**把“单个引擎”升级为“工厂 + 注册表 + 扇出 + 逐实例 RPC”,是全计划从抽象走向可运行多实例的转折点;
  • phase 5 补齐测试矩阵与迁移测试、更新 README/文档并负责 rollout(phase-5-tests-and-docs.md);
  • phase 6 落到 tinyagents 侧的上游能力补齐(deadline、cancel token、checkpoint GC,见 phase-6-tinyagents-reuse.md);
  • phase 7 由前端消费逐实例字段(phase-7-ui.md)。

对测试工程而言,phase 4 的验收重点是:get_or_init_instance的惰性构造语义、bootstrap_after_login的幂等守卫、用户切换时全 map 清空后按新 workspace 重建、heartbeat 扇出下“慢 memory tick 不阻塞 tinyplace”的并发性,以及 status/trigger 在单实例与"all"两种模式下的行为一致性。整体上,这是把“今天的单一 subconscious 引擎”演化为“一个按 world 生长的实例集合”的关键一役——新增第三个世界从此只是“一个 profile 文件 + 一个 match 分支 + 一行 enabled 集合”,而不再是一场对巨型复合tick_inner的手术。

【免费下载链接】openhumanOpenHuman is an open source personal AI for Mac, Windows and Linux — local-first memory, agent orchestration, and deep research.项目地址: https://gitcode.com/GitHub_Trending/op/openhuman

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询