尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OpenHuman threads 模块深度解析:对话线程生命周期、turn_state 快照恢复与任务看板代理

OpenHuman threads 模块深度解析:对话线程生命周期、turn_state 快照恢复与任务看板代理 OpenHuman threads 模块深度解析对话线程生命周期、turn_state 快照恢复与任务看板代理【免费下载链接】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/openhumanOpenHuman 的src/openhuman/threads模块是会话线程conversation thread与消息管理的核心层它拥有线程生命周期创建 / 列出 / upsert / 删除 / purge、按线程的消息 CRUD、基于推理提供商的 AI 线程标题生成、可跨冷启动恢复的 in-flight 回合快照turn_state、按线程的看板任务面板透传以及一次性的 welcome-agent → orchestrator 工作区迁移。本文基于仓库内 threads/README.md 及对应源码mod.rs、ops.rs、schemas.rs、error.rs、title.rs、welcome_migration.rs、turn_state/子模块展开读者读完可掌握该模块的 RPC 契约全貌、AI 标题生成的降级链路、turn_state 快照的落盘与冷启动恢复机制以及其中最容易踩的并发与持久化陷阱。模块定位与职责总览threads模块自身的持久化职责刻意保持薄线程与消息的存储委托给memory::conversationsJSONL 线程/消息存储turn 快照则委托给专门的turn_state快照存储本模块是覆盖在这两者之上的 RPC/控制器层。从 mod.rs 的模块文档可见其核心职责列出、创建upsert使用调用方提供的 id或create_new自动生成 id 并附带占位标题、删除与清空purge会话线程在线程内列出、追加与元数据 patch 消息更新线程标签与用户指定的标题通过推理提供商基于首条用户消息 助手回复生成持久化线程标题并带有确定性降级从用户消息派生标题跳过非占位标题维护可跨重启存活的 in-flight agent 回合快照turn_state通过 RPC 提供 get / list / clear由 web-channel 进度桥接层通过TurnStateMirror写入暴露按线程的 kanban 任务面板 get/put代理到agent::task_board在线程删除 / purge 时使进程内的 web-channel 会话失效并清理孤儿 turn 快照一次性、幂等的旧 welcome-agent 产物迁移剥离onboarding标签、将welcome*会话转录重命名为orchestrator*。关键文件地图文件作用mod.rs导出中枢模块文档、mod声明、再导出ThreadsError、THREAD_NOT_FOUND_KIND、控制器 schema/注册表对、welcome 迁移入口ops.rs拆分自 ops_part_01.rs、ops_part_02.rs业务逻辑 / RPC 入口返回RpcOutcomeApiEnvelopeT所有线程 消息 turn-state 操作集中于此结果以带 request-id/count 元数据的ApiEnvelope包装schemas.rs拆分自schemas_part_01.rs、schemas_part_02.rsControllerSchema定义、all_controller_schemas、all_registered_controllers以及委托给ops的handle_*函数同时承载task_board_get/task_board_put代理到agent::task_boarderror.rsThreadsError分类NotFound/Message在控制器边界把NotFound编码为StructuredRpcErrorkind: ThreadNotFound、expected_user_state: true前端处理过期线程引用时无需字符串匹配、也不会制造 Sentry 噪音title.rs纯函数、无 provider 依赖的标题助手占位标题检测、原始标题清洗、空白折叠、从用户消息派生兜底标题、prompt 构建、日志指纹单元测试覆盖密集welcome_migration.rs一次性、marker 守卫的旧 welcome-agent 线程/转录 → orchestrator 命名迁移turn_state/mod.rsin-flight 回合快照子模块导出枢纽turn_state/types.rs线上/存储类型TurnState、TurnLifecycle、TurnPhase、ToolTimelineEntry/Status、SubagentActivity/ToolCall以及 get/list/clear 的请求/响应载荷字段以 camelCase 序列化镜像chatRuntimeSlice.tsturn_state/store.rsTurnStateStore——按线程的原子 JSON 快照存储tempfile persist 目录 fsync、进程级互斥锁put/get/delete/list/clear_all/mark_all_interrupted及自由函数包装turn_state/mirror.rs拆分自mirror_part_01.rs、mirror_part_02.rsTurnStateMirror——把agent::progress::AgentProgress事件翻译为TurnState变更含有序的 narration/thinking/tooltranscript在迭代 / 工具边界落盘完成时标记Completed并保留供View processing面板重放已结束回合若桥接层未观测到TurnCompleted就退出则标记Interrupted*_tests.rs同级测试套件ops_tests.rs、schemas_tests.rs、turn_state/store_tests.rs、turn_state/mirror_tests.rs另含error.rs、title.rs、welcome_migration.rs内联测试RPC / 控制器层threads 命名空间命名空间为threadsJSON-RPC 方法形如openhuman.threads_function。Schema 与处理器通过all_registered_controllers注册并在 src/core/all.rs 中以all_threads_*对扩展进注册表同时覆盖 JSON-RPC 与 CLI 注册表参见 src/core/jsonrpc.rs 的传输路由。函数操作list列出线程摘要upsert创建/刷新线程调用方 id、标题、created_at、可选 labelscreate_new新线程自动 id Chat date time占位标题messages_list列出线程内消息message_append追加消息返回类型化的ThreadsError即结构化NotFoundmessage_updatepatch 消息的extra_metadatagenerate_title基于首条用户 助手消息由 LLM 生成标题带降级update_labels替换标签空 vec 即清空全部标签update_title设置用户指定标题拒绝空串delete删除线程 消息日志使 web 会话失效清理 turn 快照purge移除所有线程/消息clear_allturn 快照turn_state_get/turn_state_list/turn_state_clear读取 / 列出 / 删除持久化的 in-flight turn 快照task_board_get/task_board_put把按线程的 kanban 面板代理到agent::task_boardcreate_new 的占位标题create_new是前端最常见的建线程入口。从 ops_part_01.rs 可以看到占位标题的生成逻辑let title format!(Chat {} {}, now.format(%b %-d), now.format(%-I:%M %p));即形如Chat Jan 5 1:23 PM的占位标题。这类标题被title.rs中的is_auto_generated_thread_title精确识别是可由 LLM 标题替换的判定基准见下文标题生成一节。结构化错误处理ThreadNotFounderror.rs是本模块最值得单独讲解的工程细节。错误分类为两个变体error.rspub enum ThreadsError { NotFound { thread_id: String }, Message(String), }关键设计error.rs在于只有NotFound会被编码为结构化 RPC 错误且编码结果携带稳定判别符与预期用户状态标记Some(StructuredRpcError { message: self.to_string(), data: Some(json!({ kind: THREAD_NOT_FOUND_KIND, // ThreadNotFound thread_id: thread_id, })), expected_user_state: true, // 不会转发给 Sentry避免噪音 })这带来三个可验证的工程收益传输层不做字符串嗅探README 明确指出ThreadsError::NotFound是ThreadNotFound成为线上形态结构化错误的唯一位置error.rs 的FromThreadsError for String转换JSON-RPC 传输层不检查方法名或错误字符串。前端优雅处理过期引用前端拿到kind: ThreadNotFound后可以清掉过期的线程引用无需字符串匹配或弹用户可见的错误 toastexpected_user_state: true让监控系统把它当作常规用户状态而非内部故障。防误清错线程from_thread_scoped_store_error只有在解析出的缺失 id 与请求的thread_id一致时才提升为NotFounderror.rs否则返回Message避免前端清掉错误的过期线程。AI 线程标题生成与确定性降级title.rs是纯函数、无 provider模块专门从ops中抽出以便不引入Config、provider 运行时和 RPC 接线就能做单元测试。其完整规则如下形状约束pub const THREAD_TITLE_MAX_WORDS: usize 3; // 最多 3 个词 pub const THREAD_TITLE_MAX_CHARS: usize 48; // 硬性字符上限设计意图线程列表是被扫读而非精读的第四个词总是把关键信息挤出窄行单个超长词也会撑宽行所以有 48 字符的硬顶。系统提示词title.rs 中的系统提示词要求模型只输出名字、最多 3 个词、以动词或主语开头、去掉填充词、不带引号/markdown/标点You name chat threads from the first user message and the assistant reply. Return only the name: at most 3 words, like Fix session handoff or Gmail OAuth retry. Lead with the verb or the subject and drop filler words. No quotes. No markdown. No punctuation.降级链路fallback 顺序正常路径generate_title用首个用户消息 助手回复构建请求调用推理提供商生成标题。占位标题判定is_auto_generated_thread_title只接受Chat Mon d h:mm AM|PM这种占位形状title.rs 逐字节校验了月份、日期、小时、冒号与 AM/PM 后缀用户重命名的线程永远不会被覆盖。清洗sanitize_generated_title取首个非空行防止话痨模型把第二行注释并进名字再交给shorten_title。形状强制shorten_title是形状执行者而非请求——按非字母数字切词、丢弃FILLER_WORDStitle.rs 中约 70 个填充词如 a/about/and/okay/please/so保留前 3 词并提前在 48 字符处截断若全为填充词则退化为未过滤词can you please 也能稳定命名单词拼写保持原样OAuth、Gmail小写会读错。无 provider 兜底title_from_user_message从首个有用用户消息派生标题——先折叠空白再只取第一个句子。!?\n分割最后走shorten_title。永不报错provider 初始化失败、清洗失败都会降级为确定性兜底标题而不是 RPC 错误。请求构建的关键陷阱#5637build_title_requesttitle.rs刻意不设置model字段ModelRequest::new(vec![ Message::system(THREAD_TITLE_SYSTEM_PROMPT), Message::user(build_title_prompt(user_message, assistant_message)), ]) .with_temperature(0.2)原因有二调用方已通过构建summarization角色的 provider 解析好了模型ModelRequest::model是逐请求覆盖项设置它会替换掉正确模型历史教训是曾用hint:summarize覆盖——该别名在所有 hint 别名表中都不存在都拼作summarization字符串原样穿透到后端变成字面模型 id导致每次调用返回400 Model hint:summarize is not available标题生成在四个月内静默降级为关键词标题而无人升级issue #5637。不设置model也是对四种 provider 分支managed 后端、Claude Agent SDK / Claude Code 模型、本地运行时、BYOK 云 slug都正确的唯一形式。turn_state可跨冷启动恢复的回合快照turn_state是 threads 模块中最有技术含量的子模块由三个文件组成类型types.rs、存储store.rs、事件翻译mirror.rs。类型模型types.rsTurnState是一个线程的 agent 回合快照types.rs核心字段包括thread_id、request_id、lifecycle、iteration/max_iterations、phase、active_tool、active_subagent、streaming_text、thinking、tool_timeline、transcript、可选的task_board与时间戳。生命周期状态机为types.rsStarted用户发送、agent 循环即将进入迭代Streaming首个进度信号到达后Interrupted启动时对任何挺过了进程重启的快照打上此标记——没有活的驱动者能恢复它UI 应展示重试入口Completed正常结束。快照被保留不删除供聊天View processing面板在重载/冷启动后重放完整转录 工具时间线启动时的 interrupted 标记会跳过该状态线程的下一个回合会覆盖它。配套类型还包括TurnPhaseThinking/ToolUse/Subagent、ToolTimelineStatusRunning/Success/Error、ToolTimelineEntry、SubagentActivity/SubagentToolCall/SubagentTranscriptItem、TranscriptItemNarration/Thinking/ToolCall的带序交织记录与PersistedToolFailure。TranscriptItem尤其值得注意与扁平的ToolTimelineEntry列表不同transcript 保留了 narration、hidden reasoning、tool calls 三者流式到达的精确交织顺序seq是每回合单调递增的排序键因为仅靠round无法对同一轮内的叙述与思考排序工具条目只持有指向tool_timeline的call_id指针使状态/标签只存在于一个地方。所有 turn-state 类型在线上统一序列化为camelCase与前端 chatRuntimeSlice.ts 一一对应快照无需翻译层即可直接应用到该 sliceSubagentTranscriptItem等 tagged enum 还同时使用rename_all_fields camelCase处理字段级命名。存储实现store.rsTurnStateStore采用每回合环形布局store.rsworkspace/memory/conversations/turn_states/hex(thread_id)/hex(request_id).json即每个 turn 一个 JSON 文件。相比每线程一个文件的旧布局多回合线程能保留每个回合的工具时间线Agentic task insights轨迹而不只是最新一个。配套机制原子写tempfile 写入 →sync_allfsync 文件→persist原子 rename→ 尽力而为的目录 fsyncUnix 上打开目录句柄 syncWindows 上为 no-op依赖 NTFS 日志进程级互斥所有变更经parking_lot::Mutex串行化进度消费者无法在 RPC 处理器读同一文件时交错 flushCompleted 保留与裁剪COMPLETED_RETENTION 20store.rs在下一个 Completed 写入时把每线程的已完成回合裁剪到最新 20 个保证历史有界呼应时间线注册表的软上限哲学——绝不无界旧版扁平文件迁移旧核心写入的turn_states/hex(thread_id).json扁平文件在首次访问时原地迁移读一次 → 改写为hex(thread_id)/hex(request_id).json→ 删除扁平文件幂等启动打标mark_all_interrupted(now_rfc3339)把所有非Completed/Interrupted快照标记为Interrupted并清空active_tool/active_subagent幂等free-fn 包装put/get/get_turn/delete/list/list_thread/clear_all/mark_all_interrupted镜像memory::conversations::store的签名RPC 层无需自行实例化 store。事件翻译mirror.rsTurnStateMirror把agent::progress::AgentProgress事件翻译为TurnState变更。两条关键策略mirror.rs只在迭代/工具边界落盘流式文本、thinking、工具参数这类高频增量只改内存不触发磁盘 flush——比迭代/工具边界更细的频率会在流式负载下打爆文件系统终态处理正常完成时快照标记Completed并保留若桥接层从未观测到AgentProgress::TurnCompleted就退出如 agent 循环返回错误快照标记Interrupted并持久化UI 据此给出重试入口。冷启动恢复闭环综合store.rs与mark_all_interrupted恢复链路为进程启动 → 扫描所有 turn 快照 → 非终态打Interrupted→ RPCturn_state_list供 UI 列出上次进程遗留的回合 → View processing面板可用get/get_turn重放Completed回合的完整转录与工具时间线。启动侧的调用方在 platform/startup/ops.rs调用 welcome 迁移与/或 turn-state 启动处理web 侧的写入驱动方在 web_chat驱动TurnStateMirror/ turn-state store并消费invalidate_thread_sessions。welcome-agent → orchestrator 一次性迁移welcome_migration.rs 处理被移除的旧 onboarding 流程留下的两类足迹旧 React onboarding 流程创建的线程带onboarding标签——该标签已无运行时含义只会让线程在存储中显得特殊WELCOME_THREAD_LABEL onboardingwelcome-agent 聊天的会话转录以welcome*agent 名持久化——现在所有聊天都路由到 orchestrator转录应规范为orchestrator*命名避免未来检查/恢复界面暗示已删除的 agent 仍然存在welcome→orchestrator前缀重写含welcome_suffix形式。迁移入口migrate_welcome_agent_artifacts(workspace_dir)的行为marker 守卫state/migrations/welcome_to_orchestrator_v1.done存在即跳过返回already_done: true保证幂等线程标签剥离遍历conversations::list_threads对含onboarding标签的线程执行update_thread_labels过滤掉该标签转录重写与重命名扫描workspace/session_raw/*.jsonl解析首行_meta把agent字段从welcome*重写为orchestrator*必要时重命名文件并把sessions/下的同名 markdown 伴随文件一并重命名失败闭合fail closed目标文件已存在destination collision或任何单项失败都计入 failures出现任一失败即返回partial migration错误且不写 marker后续重试可以继续marker 只在全部成功后写入。返回的WelcomeMigrationResult统计threads_updated、transcripts_updated、transcript_files_renamed、markdown_files_renamed与already_done。持久化布局总览线程 消息委托给memory::conversations工作区下的 JSONL 存储本模块不拥有每次调用都走memory_conversations::blocking::*tokio::task::spawn_blocking绝不直接调用同步入口原因见下文陷阱节Turn 快照turn_state/store.rs每线程每回合一个 JSON位于workspace/memory/conversations/turn_states/整文件原子覆盖 进程级互斥冷启动后残留的非终态文件标记InterruptedCompleted快照刻意保留供处理重放并被启动打标跳过线程的下一个回合覆盖之任务面板由agent::task_board::TaskBoardStore在工作区下持久化本模块仅代理迁移标记state/migrations/welcome_to_orchestrator_v1.done守卫 welcome 迁移。依赖关系与调用方依赖均来自crate::内部见 README Dependencies 节memory/memory_conversations线程与消息存储类型与 CRUDensure_thread、list_threads、get_messages、append_message、update_thread_*、ConversationStore等以及ApiEnvelope/ApiMeta/请求响应 DTOconfig::Config解析workspace_dir及推理/运行时/密钥设置load_or_initinference::provider构建用于 AI 标题生成的智能路由 providercreate_intelligent_routing_provider、ProviderRuntimeOptionsweb_chatinvalidate_thread_sessions删除线程时使其实时 web 会话失效防止已删线程的会话继续追加消息agent::task_boardTaskBoard、TaskBoardCard、TaskBoardStore、board_for_thread任务面板 RPC也是TurnState上可选task_board字段的来源agent::progress::AgentProgressTurnStateMirror消费的进度事件core::allControllerFuture、RegisteredController注册表core::{ControllerSchema, FieldSchema, TypeSchema}schema 定义rpc::{RpcOutcome, StructuredRpcError}RPC 结果包装与结构化错误编码。调用方src/core/all.rs注册控制器/schema 进 JSON-RPC CLI 注册表、src/core/jsonrpc.rs传输路由引用、src/openhuman/web_chat/驱动TurnStateMirror/ turn-state store消费invalidate_thread_sessions、src/openhuman/platform/startup/ops.rs启动时执行 welcome 迁移与/或 turn-state 启动处理。注意事项与陷阱README 的 Notes / gotchas 一节浓缩了本模块最宝贵的一线经验绝不从这些 handler 直接调用同步memory_conversationsAPI必须用memory_conversations::blocking::*。每个存储入口点持有进程级parking_lot::Mutex并在持锁期间做 fsync 的 JSONL IO且单次调用成本随用户历史增长threads.jsonl几乎每次操作都折叠、每条消息增加约 2 行且不压缩。若在 handler 内联调用handler 会把 tokioworker线程阻塞在该 mutex 上一旦排队的会话操作数超过 worker 数运行时停止轮询任何东西——包括欠客户端响应的 HTTP 任务——一次单条追加就能击穿前端的 30 秒 RPC 预算错误现象UnhandledRejection: Core RPC openhuman.threads_create_new timed out after 30000msSentry TAURI-REACT-10 / #5156。blocking包装器把锁等待放到阻塞池上存储串行化程度不变但执行器保持存活。唯一刻意例外是welcome_migration.rs它是同步、一次性、marker 守卫的启动迁移不属于请求路径。generate_title只替换匹配Chat Mon d h:mm AM|PM占位形状的标题is_auto_generated_thread_title用户重命名的线程永不被覆盖。provider/初始化/清洗失败一律降级为从首条用户消息派生的确定性兜底标题绝不报错。delete先使 web-channel 会话失效、再清理 turn 快照顺序有讲究代码内联注释为证快照清理失败会以 RPC 错误上浮让调用方看到部分失败而非磁盘上静默漂移。purge使用clear_all而非 list deletelist()会跳过损坏/半写的快照文件listdelete 会把这些文件静默留在磁盘clear_all直接遍历删除包括不可读文件在内的所有 JSON 快照保证破坏性清理什么都不剩。ThreadsError::NotFound是ThreadNotFound成为线上结构化错误的唯一位置传输层不嗅探方法名或错误字符串。from_thread_scoped_store_error仅在解析出的 id 与请求 thread id 匹配时才提升为NotFound避免前端清错过期线程。turn-state 类型刻意以 camelCase 序列化镜像app/src/store/chatRuntimeSlice.ts快照无需翻译直接应用。TurnStateMirror只在迭代/工具边界落盘流式文本/thinking/工具参数等高频增量只改内存避免流式负载下的文件系统抖动。rename 后的目录 fsync 是尽力而为Windows 上是 no-op依赖 NTFS 日志。welcome 迁移幂等marker 守卫且失败闭合目标冲突或任一单项失败返回partial migration错误且不写 marker后续重试可继续。测试覆盖本模块的测试纪律与生产代码几乎 1:1ops_tests.rs、ops_tests_part_01_tests.rs、ops_tests_part_02_tests.rs覆盖业务逻辑与 RPC 入口schemas_tests.rs覆盖控制器 schema 与处理器接线turn_state/store_tests.rs覆盖环形布局、原子写、旧版迁移、Completed 裁剪与mark_all_interruptedturn_state/mirror_tests.rs覆盖AgentProgress→TurnState的事件翻译与终态处理title.rs、error.rs、welcome_migration.rs则以内联测试#[path ..._tests.rs]形式把纯函数规则占位标题识别、词数/字数约束、填充词过滤、结构化错误编码、迁移幂等性钉死。这一测试分层也印证了模块设计意图把可单元测试的纯逻辑标题、错误分类、迁移从需要Config/provider/RPC 接线的大逻辑ops、schemas、mirror中剥离出来。【免费下载链接】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),仅供参考
返回列表