DeerFlow 的 Honcho 记忆后端:用 HTTP 适配器接入远程用户画像记忆,零本地 LLM 调用
DeerFlow 通过 memory.backend_config 的插件化契约支持多种记忆后端,其中 Honcho 后端是一个"远程-only"的 HTTP 适配器:它把 DeerFlow 的用户维度记忆(长期用户建模、偏好、跨会话工作表征)整体委托给 Honcho(自托管或托管,v3 API),本地不发起任何 LLM 调用——事实抽取与表征构建全部由 Honcho 服务端的 deriver 异步完成。读完本文,你可以掌握:如何配置并切换该后端、每个 backend_config 参数的取值约束与启动校验逻辑、多用户隔离的抗碰撞 workspace 派生机制、写入/召回/检索的完整数据链路、失败策略与异步卸载的实现细节,以及它与 DeerMem 默认后端的能力边界。
定位:覆盖"用户维度"的远程记忆后端
DeerFlow 的记忆后端位于 backends 目录,每个子目录是一个可插拔后端,换后端只需改 config.yaml 一行 memory.manager_class,不需要改动 deer-flow 核心代码。该目录下包含:
deermem/:默认后端,deer-flow 自研的结构化事实 + JSON 存储;noop/:空后端,也是新增后端时的模板;openviking/:可选远程后端,基于官方langchain-openviking包(单用户 middleware 模式);honcho/:本文主角,可选的远程 Honcho HTTP 后端。
Honcho 后端的设计定位(源自其上游 RFC #1898,见 honcho_manager.py 头部 docstring)是:Honcho 负责记忆中的用户维度——长期用户建模、偏好、跨会话的工作表征(working representation)——与面向项目/任务的后端形成互补。摄入(ingestion)本身非常廉价:只是把对话轮次以普通消息写入;真正的"智能"发生在 Honcho 服务端的 deriver 上,它从 add() 写入的消息中异步抽取事实、构建表征。因此该后端本地零 LLM 调用,AGENTS.md 中对该后端的总结也反复强调这一点。
后端包内只有四个文件,职责划分清晰:
| 文件 | 职责 |
|---|---|
| config.py | HonchoConfig 数据类 + backend_config 解析与启动期校验 |
| honcho_manager.py | HonchoMemoryManager,实现 MemoryManager 契约 |
| client.py | 极简同步 httpx 客户端,直连 Honcho v3 REST API |
| __init__.py | 导出 MANAGER_CLASS = HonchoMemoryManager,供工厂发现 |
配置:完整参数说明与启动期校验
基本配置
在 config.yaml(仓库根目录)中启用 Honcho 后端的完整示例如下(继承自 后端 README):
memory:
enabled: true
injection_enabled: true
manager_class: honcho
mode: middleware # 或 "tool"
backend_config:
base_url: http://localhost:8000
# api_key: $HONCHO_API_KEY # 托管 Honcho;plain-http + api_key 需 allow_insecure_http: true
workspace_prefix: deerflow-u- # 每个 user id 一个隔离 workspace
# workspace_overrides: {} # 将特定 user id 映射到自定义 workspace
# user_peer_overrides: {} # 将特定 user id 映射到自定义 peer 名
assistant_peer: deerflow
message_char_limit: 8000
max_injection_chars: 6000
timeout_seconds: 10
connect_timeout_seconds: 3
failure_policy:
read: fail_open # fail_open | fail_closed
参数逐项说明(含源码级取值约束)
以下参数表继承自 backends/README.md 的 Honcho 小节,并结合 config.py 的解析逻辑补充了校验细节:
| 键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
base_url |
str | http://localhost:8000 |
Honcho 实例地址(如 http://localhost:8000 或 https://api.honcho.dev)。解析时会 rstrip("/") 去掉尾部斜杠(见 from_backend_config,config.py#L65-L88) |
api_key |
str | 可选 | 托管 Honcho 的 API key,可写 $HONCHO_API_KEY 环境变量语法;plain HTTP 下使用需 allow_insecure_http: true |
allow_insecure_http |
bool | false |
允许带 api_key 的明文 HTTP 连接,本地开发用 opt-in;非本地部署应使用 HTTPS |
workspace_prefix |
str | deerflow-u- |
隔离 workspace 的前缀,最终 workspace 名为 {prefix}{稳定 id} |
workspace_overrides |
dict | {} |
将特定 user id 精确映射到自定义 workspace;值必须非空,否则解析期报错。多个用户映射到同一 workspace 会共享其搜索索引 |
user_peer_overrides |
dict | {} |
将特定 user id 映射为自定义 peer 名;值必须非空,否则解析期报错 |
assistant_peer |
str | deerflow |
存消息时助手侧的默认 peer 名 |
timeout_seconds |
float | 10.0 |
HTTP 调用超时(秒),必须为有限值且 > 0 |
connect_timeout_seconds |
float | 3.0 |
建连超时(秒),必须为有限值且 > 0 |
message_char_limit |
int | 8000 |
单条消息字符上限,超出截断;必须 > 0 |
max_injection_chars |
int | 6000 |
注入系统提示词的记忆字符上限;必须 > 0 |
failure_policy.read |
str | fail_open |
召回失败处理:fail_open(记日志并返回空)或 fail_closed(抛出异常) |
storage_path |
str | "" |
由宿主注入的只读字段(后端通过 backend_config 接收,不 import deer-flow 路径助手) |
源码里这些约束是硬校验而非文档约定:HonchoConfig.__post_init__ 对 timeout_seconds / connect_timeout_seconds 要求 isfinite 且 > 0(config.py#L53-L63),message_char_limit 与 max_injection_chars 要求 > 0。注释里特别解释了一个 Python 细节:截断用的是 text[:n],n <= 0 时要么得到空串(n == 0),要么变成负数切片 text[:-1](n == -1 时删掉的是后缀而非上限)——所以负数必须在解析期拦掉。workspace_overrides / user_peer_overrides 的空值校验同样如此:空字符串是 falsy 会静默落到默认派生,YAML null 会被字符串化成名为 "None" 的 id(config.py#L24-L34),两者都会掩盖操作者意图,故 fail fast。测试文件 test_honcho_memory_backend.py 中的 test_rejects_invalid_timeouts、test_rejects_non_positive_character_limits、test_empty_override_values_rejected 等用例逐一锁定了这些边界。
安全姿态与启动行为
两条关键规则:
- plain HTTP + api_key 组合默认拒绝。
api_key配http://开头的base_url时,除非显式设置allow_insecure_http: true,否则在from_backend_config解析期抛ValueError(config.py#L72-L73),因为 key 会以明文 Bearer header 发送。这与 mem0 后端的姿态一致,是本地开发的显式 opt-in;任何非本地部署应使用 HTTPS。自托管 Honcho 常跑在免认证的明文 HTTP 上,此时不配api_key即可正常通过(test_http_without_api_key_is_fine用例验证)。 - 配置错误 fail fast,连通性不探测。
HonchoMemoryManager.from_config只解析配置、构造客户端,故意不发起连通性探测(见 honcho_manager.py#L110-L123 的 docstring):一个暂时不可达的 Honcho 不应阻塞 Gateway 启动,读侧随后按failure_policy.read降级。
配置变更或后端切换后需要重启 deer-flow——记忆管理器是进程级单例,运行中的进程不会热加载配置或后端代码。
多用户隔离:workspace 派生与抗碰撞设计
这是该后端最值得深入的部分。Honcho 把一切查询限定在单个 workspace 内,因此 DeerFlow 的做法是"每个用户一个 workspace",从构造上保证用户之间看不到彼此的记忆。
派生规则
HonchoMemoryManager._workspace 的解析顺序是:
workspace_overrides[user_id]精确命中 → 直接用覆盖值(匹配键是原始未清洗的 user id);- 否则 →
workspace_prefix + _stable_id(user_id)。
关键在于 _stable_id(honcho_manager.py#L75-L88)。Honcho 的 id 语法是 ^[a-zA-Z0-9_-]+$,sanitize_id 会把任意字符串映射上去:非 [a-zA-Z0-9_-] 的字符串折叠成单个 -,并截断到 64 字符(config.py#L16-L21)。单靠它有损——"user.name@x" 和 "user-name@x" 都会清洗成 "user-name-x",两个不同用户的记忆会被静默合并进同一个 workspace。为此 _stable_id 在可读的 sanitize_id(raw)[:48] 之后追加原始 raw id 的 SHA-256 前 8 位十六进制:
digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()[:8]
readable = sanitize_id(raw)[:48].rstrip("-")
return f"{readable}-{digest}" if readable else digest
这样默认路径既可读又抗碰撞;且 digest 恒为 8 位,即使 sanitize_id 把退化的 raw id(如 "!!!")清洗成空串,结果也非空。测试 TestHonchoIdentityDerivation 专门钉死了这一行为:先断言两个冲突的 raw id 在裸 sanitize_id 下确实相等(证明测试真的在演练这个 bug),再断言 mgr._workspace / mgr._user_peer 对它们的输出互不相同。
三条隔离保证
- 缺失或空
user_id一律 fail closed:写操作变 no-op(记 debug 日志后直接返回),读操作返回空串/空列表,永远不会落到共享的 fallback workspace。 - 故意共享的 override:
workspace_overrides把多个用户映射到同一 workspace 时,是有意共享该 workspace 的搜索索引——search走 Honcho 的 workspace 作用域/search(无 peer 过滤),而get_context/get_memory仍保持 peer 作用域(只取本人 peer 的表征)。 - 会话 id 复用同一派生:session id 是
df-+_stable_id(thread_id)。源码注释点明了同级的隐患:"t.1"和"t-1"裸清洗后同为"t-1",若不加哈希后缀,两条线程的历史会合并进同一个 Honcho 会话。测试test_session_ids_resist_sanitize_collisions验证两个线程 id 产生两个不同 session。
写入链路:消息过滤到 v3 API
mode: middleware 下,每轮对话由 MemoryMiddleware 触发 add()。从 honcho_manager.py#L155-L192 的实现看,一次 add(thread_id, messages, *, user_id, ...) 做四件事:
- 解析身份:
_workspace(user_id)为None或user_id为空 → 直接 return(fail-closed no-op)。用户 peer 取user_peer_overrides[user_id]或_stable_id(user_id);助手 peer 恒为assistant_peer(默认deerflow)。 - 消息规范化与过滤:
_content_to_text把 LangChain 消息的 content(str 或 content-block 列表)归一成纯文本,逐块提取text字段;空文本跳过;human类型消息挂用户 peer,ai/AIMessageChunk挂助手 peer,tool等其他类型被丢弃(测试test_add_maps_messages_to_peers验证了"tool"消息被忽略)。每条消息内容按message_char_limit截断(test_add_truncates_long_content验证超长内容被裁到恰好 N 个字符)。 - get-or-create 幂等序列:
get_or_create_peer(用户)→get_or_create_peer(助手)→get_or_create_session→set_session_peers→add_messages。因为 Honcho 服务端对 peer/session 是 get-or-create 语义,整个序列是幂等的。 - fire-and-forget:整个调用包在
except Exception里,失败仅记 warning 日志后丢弃(at-most-once)。源码注释强调这个except必须宽(catch-all)而非只捕获HonchoRequestError——客户端任何异常(包括裸RuntimeError)都不允许逃出add()打穿MemoryMiddleware.after_agent;测试test_add_swallows_non_honcho_exceptions用RuntimeError注入验证了这一点。本地不做任何缓冲,这也是shutdown_flush返回True即 no-op 的原因。
底层 HTTP 端点
client.py 是一个刻意轻量化的同步 httpx 客户端(不依赖官方 honcho-ai SDK),对应六类 v3 REST 调用(client.py#L51-L71):
| 方法 | 端点 | 说明 |
|---|---|---|
get_or_create_peer |
POST /v3/workspaces/{ws}/peers |
幂等建 peer |
get_or_create_session |
POST /v3/workspaces/{ws}/sessions |
幂等建 session |
set_session_peers |
POST /v3/workspaces/{ws}/sessions/{sid}/peers |
绑定会话双 peer |
add_messages |
POST /v3/workspaces/{ws}/sessions/{sid}/messages |
批量写入消息 |
working_representation |
POST /v3/workspaces/{ws}/peers/{pid}/representation |
取 peer 工作表征(max_conclusions 默认 25) |
search |
POST /v3/workspaces/{ws}/search |
workspace 作用域搜索(无 peer 过滤) |
所有传输层失败(非 2xx、非 JSON 的 200 响应——比如前置代理的维护页)统一包装成单一异常类型 HonchoRequestError,让调用方只需处理一种异常;配置了 api_key 时通过 Authorization: Bearer <key> header 携带(测试 test_api_key_header、test_non_json_200_response_wraps_as_honcho_request_error 覆盖)。
召回与检索:两种 mode 的取舍
mode: middleware:无查询的被动召回
get_context(user_id)(honcho_manager.py#L195-L206)是 query-less 的:它调用 working_representation 取用户 peer 的工作表征(最多 25 条 conclusions),strip 后按 max_injection_chars 截断,注入系统提示词。user_id 缺失返回空串且不发起任何 HTTP 调用。宿主不会对 get_context 施加 token 预算,长度上限完全靠后端自己截断——这也是 backends/README.md "Common Pitfalls" 第 6 条的要求。
mode: tool:查询感知检索 + 保留被动写入
search(query, top_k=5, *, user_id) 走 Honcho 的 workspace 作用域 /search,返回项被映射成 {content, category, session_id, peer_id, created_at} 结构供 memory_search 工具消费。
这里有一个关键的设计决策:HonchoMemoryManager 声明了类变量 requires_passive_writes_in_tool_mode = True(honcho_manager.py#L104)。原因是 Honcho 唯一的写入通路是被动的 add()——它的 deriver 从每轮 add() 消息中学习并构建表征,而后端故意不实现 fact CRUD 钩子。如果 tool 模式像某些后端那样停用被动写入,deriver 就断了粮。所以 tool 模式是"被动写入持续喂 deriver + search() 提供模型主动检索"的组合,这与 mem0 后端同一处 ClassVar 的动机完全一致(源码注释与测试 test_requires_passive_writes_in_tool_mode 都点明了这一点)。
get_memory:DeerMem 形状的最小视图
网关与前端目前硬编码到 DeerMem 形状(facts / user / history / lastUpdated)。Honcho 没有 DeerMem 式的事实 CRUD,所以 get_memory(honcho_manager.py#L234-L258)返回一个最小兼容文档:facts: []、user.workContext.summary 填工作表征(同样按 max_injection_chars 截断)、空 history,由网关按契约补默认字段——与 noop 后端 {"facts": []} 的约定相同。
异步执行与失败行为
同步客户端 + 线程卸载
Honcho HTTP 客户端是同步的(httpx.Client),为了兼容 MemoryManager 契约的同步方法签名。DeerFlow 在每个异步边界通过 asyncio.to_thread 卸载它:aadd / aget_context / asearch 三个 a* 方法(honcho_manager.py#L268-L298)只是把同步实现丢进线程池。这样慢的 Honcho 请求永远不会阻塞 ASGI handler 或 SSE 心跳;该目录还配套了阻塞 IO 静态门禁测试 blocking_io/test_honcho_memory_backend.py 确保 httpx 不会跑在事件循环上。close() 释放 HTTP 客户端,是网关的 shutdown hook(测试 test_close_releases_http_client 验证)。
failure_policy.read 的统一闸门
所有召回路径(get_context / search / get_memory)经过同一个 _read_or_fallback 闸门(honcho_manager.py#L138-L152):
fail_open(默认):召回失败记 warning 日志,返回传入的 fallback(空串 / 空列表 / 空 DeerMem 文档),运行继续,只是少了一块记忆上下文;fail_closed:把后端错误包成MemoryManagerError抛出,沿 prompt 构建链路向上传播并中止本次运行,而不是静默降级——避免向模型和运维掩盖故障(测试test_search_fail_closed_raises_contract_error的 docstring 说明了 search 必须与 get_context 同样遵守该策略)。
这个 except Exception 同样是刻意的宽边界:客户端异常不允许逃出进入 MemoryMiddleware.after_agent。测试矩阵对 MemoryManagerError 与 RuntimeError 两种注入各做了一组 fail-open / fail-closed 断言,锁死行为。
已知限制
按 后端 README 的 Limitations 小节,配合源码确认如下:
- 没有 DeerMem 式的事实 CRUD:
create_fact/delete_fact/update_fact、import_memory及 Settings 页的事实编辑均未实现(继承基类默认 raise,网关捕获NotImplementedError后返回 501,前端会看到Operation 'delete fact' not supported)。get_memory返回的是最小 DeerMem 形状文档;memory_add/memory_update/memory_delete工具返回明确的 unsupported-operation 错误。对话写入仍然通过保留的 middleware 完成。DeerMem 仍是默认后端。 - 没有 DeerMem 存量数据迁移:换后端不会把既有事实搬过去。
- 写入是每次调用 fire-and-forget:失败的写记日志后丢弃(at-most-once),本地无缓冲,
shutdown_flush是 no-op(honcho_manager.py#L260-L262)。 agent_name未映射:Honcho 建模的是用户而非按 agent 划分的事实,add()/get_context()签名里的agent_name参数被忽略。
可验证的测试锚点
该后端的行为有完整的测试套件钉死,backend/tests/test_honcho_memory_backend.py 的主要分组包括:
TestHonchoConfig:默认值、未知键忽略、trailing slash 去除、HTTP+api_key 强制 opt-in、非法超时/字符上限拒绝;TestHonchoClient:六个 v3 端点的路径与 payload 断言(含 peer 绑定 payload)、错误与非 JSON 响应的统一异常包装、Bearer header;TestHonchoManagerWrite:消息到 peer 的映射、无 user 的 no-op(含空字符串)、未映射用户的deerflow-u-前缀派生、长内容截断、异常吞噬(含非 Honcho 异常)、list 型 content 归一化、session id 抗碰撞;TestHonchoManagerRead:表征召回、截断、fail-open / fail-closed 两态、get_memory最小形状;TestHonchoManagerAsync/TestHonchoManagerLifecycle:a*委托、shutdown_flush恒真、tool 模式可构造、close()释放客户端;TestHonchoIdentityDerivation/TestFactoryDiscovery:抗碰撞派生、MANAGER_CLASS可被工厂解析。
另外,客户端构造器接受 transport 参数(httpx.MockTransport 注入),测试无需起真实 Honcho 服务即可验证整条链路。
小结与适用前提
Honcho 后端适合这样的场景:你已有(或愿意自托管一个)Honcho v3 实例,希望由它承担用户画像/偏好/跨会话表征的构建,让 DeerFlow 本地保持"只写消息、只读表征"的轻量角色;多用户部署下靠 per-user workspace 获得构造级隔离。前提与限制是:Honcho 不可达时按 failure_policy.read 降级或中止;没有本地事实 CRUD 与数据迁移;写入 at-most-once。若你需要结构化事实管理、前端事实编辑,DeerMem 默认后端仍是完整选项;本文所有行为描述均基于当前仓库源码与测试,适用前提为 memory.manager_class: honcho 且后端位于 backend/packages/harness/deerflow/agents/memory/backends/honcho/。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00