老吾老以及人之老,幼吾幼以及人之幼。——孟子

一个不肯把记忆藏起来的家教

先说一个奇怪的事实

所有的 AI 家教都说自己「记得你」。

你上一次问过什么、卡在哪一步、哪种解释方式对你有效、你更喜欢先看例子还是先看定义——这些东西被悄悄收进某个向量库,变成一堆你永远看不到的浮点数。它能用,但你不知道它记了什么,也不知道它记错了没有。

DeepTutor 在这件事上做了一个很少见的选择:把记忆写成文件。

不是藏在数据库里的嵌入向量,是 Markdown,是 JSONL,是在你的目录里躺着、可以打开、可以改、可以删的东西。文档里对这件事的措辞很硬:记忆是一套你可以读、可以整理、可以审计的三层系统,刻意不是一个隐藏的向量库。

项目地址:https://github.com/HKUDS/DeepTutor

一句话定位:终身个性化的家教。

「终身」这个词在 AI 产品里通常只是修辞。但放在这里,它其实是在说一个技术约束:如果这套东西要陪你很多年,那它记住的东西就不能锁在一个你打不开的黑箱里。

它到底是什么

官方定义是一个「agent 原生的学习工作台」:把答疑、解题、出题、调研、可视化、掌握度练习连成一套可扩展的系统。

拆开看,最要紧的是这一句:九种模式共用一套能力运行时与会话上下文。

Chat、Ask Questions、Quiz、Research、Visualize、Solve、Mastery Path、Immersive Reading、Audio Overview——九种。它们各自保留为每个目的定制的循环和流水线,但共享同一个运行时。

这件事的实际意义,比它听起来大得多。

大多数学习类工具的形态是「一堆功能」:这里是聊天、那里是刷题、再旁边是思维导图。你在聊天里说了一半的困惑,切到刷题页就没了;你在阅读里标注的一段话,做题时引用不到。功能越多,割裂越深。

共用运行时意味着相反的方向:上下文是连续的,模式只是不同的入口。

同一段对话里,你可以正常聊天、调工具、用选定的知识库做依据、读附件、生成图片、咨询子 agent、写笔记,然后下一轮接着这个上下文继续。这不是九个产品,是一个人的九种姿态。

记忆:三层金字塔,全部可读

这是整个项目里我最想推荐的部分。

记忆被分成三层,每一层都是一类文件:

L1 是工作区镜像加一条只追加的事件轨迹,路径长这样:trace/<surface>/<date>.jsonl。它记录的是「发生过什么」。

L2 是按面整理的要点,L2/<surface>.md,带着指回 L1 实体的引用。它记录的是「从中提炼出了什么」。

L3 是跨面的综合,`L3/<profile recent scope preferences>.md`,并且标注了它是由哪些 L2 面贡献出来的。它记录的是「综合起来你是谁」。

三层之上还有一个记忆图:L3 在中心,L2 在中环,L1 在最外圈,每条 L2 到 L1 有确切的证据边,每条 L3 到贡献它的面有链接。

这套设计解决的是一个很实际的问题。当系统说「你偏好先看例子」,你该能点进去问:哪一次对话让你这么判断的?

出处可以一路追到某天某个面的一条记录。这不是「可解释性」这种大词,这是可以查账。

记忆覆盖的面也写得很具体:chat、notebook、quiz、kb、book、partner、cowriter。整理器的更新、审计、去重预算都在设置里调。

值得一提的是它的取舍。用向量库做长期记忆,工程上更省事,召回也更「智能」。但代价是:你不知道里面有什么,也就没法纠正它。而一个要陪你很多年的家教,如果记错了你,还必须错下去,那它再聪明也没用。

伙伴:一个有性格、也有手机号的会话

Partners 这个概念,文档里有一句非常漂亮的定义:

一个伙伴,就是一个有性格、也有手机号的聊天。

它的技术含义也一样干脆:每一条进来的网页或 IM 消息,都变成一个普通的编排轮次,跑在这个伙伴的私有或绑定工作区里。它不是另一套机器人引擎,它就是普通会话,外加一层身份。

每个伙伴有自己的 SOUL.md、模型选择、渠道、工具策略和分配的库。库可以是拷贝,也可以是链接——拷贝会把知识库、技能、收藏复制进伙伴自己的目录,链接则指向已有工作区。

渠道那一层是 schema 驱动的,按装了什么扩展、配了什么凭证,可以接飞书、Telegram、Slack、Discord、钉钉、QQ、企业微信、WhatsApp、Zulip、Mattermost、Matrix、Mochat、Microsoft Teams。

还有一个细节挺妙:飞书和企业微信的渠道页可以直接在浏览器里画一个二维码,扫码就能建应用或登录个人微信,不用去服务器日志里翻。飞书会自动识别账号域名并把扫码的人设为初始允许发送者;企业微信保留已有白名单,否则默认放开给所有能触达机器人的人,并给一条醒目的开放访问警告。

这条设计里有一种很务实的判断:配置的摩擦,才是这类功能真正的门槛。 让人去后台复制 token、填三个字段、重启服务,绝大多数人会在第二步放弃。

借用别人的脑子

DeepTutor 不假装自己是唯一聪明的那个。

在 Chat 里,你可以现场咨询一个跑在你机器上的 agent:Claude Code、Codex、Grok CLI、Antigravity、Kimi、opencode、MiMo、Hermes、OpenClaw、DeepSeek,或者你自己的某个 Partner。

注意「现场」这个词。它不是把对方的回答粘贴过来,而是真的去运行那个 agent,把它的工作流实时流进 Activity 面板。选择它、设定轮次上限、用 @ 过滤同一份列表,选择会跟着会话走。

这套机制的边界也写得很清楚。以 Grok CLI 为例:装在跑 DeepTutor 后端的那台机器上,先在那里 grok login,确认 grok --help 里能看到 --output-format streaming-json,然后在 My Agents 里连。检测只验证可执行文件的协议支持,不验证登录状态和模型权限。默认权限模式是 dontAsk,用的是 CLI 自己的规则和只读处理,需要批准的操作会被拒绝——文档还专门补了一句:这是 CLI 的权限策略,不是文件系统沙箱。

这句话很关键。把别人的 agent 接进来,最容易产生的错觉是「它的安全边界等于我的安全边界」。文档没有被这个错觉带走。

除了现场咨询,还有一条路是导入过去的对话:ChatGPT 的官方导出文件、Claude Code 按项目或工作目录的历史、Codex 按日期的历史。导入之后,它可以被当作第三方记录来读——文档强调,那仍然是「他们的对话」,不是 DeepTutor 自己的声音。

这个区分很讲究。把别人的历史当自己的记忆混进去,是最容易发生、也最难察觉的污染。

知识库:十个引擎,一个基座绑一个

知识中心大概是这个项目最能体现「工程野心」的地方。

它支持的不是一种检索,是十种:LlamaIndex、PageIndex、GraphRAG、LightRAG、远端 LightRAG Server、自托管 WeKnora、腾讯 IMA、MarginNote 4、Kiwix 的 ZIM 归档、以及一个链接的 Obsidian 库。

每一种都对应一种不同的「知识在哪里」:有的在向量里,有的在页面结构里,有的在知识图谱里,有的根本不在你机器上——IMA 是你自己策展的库,通过 OpenAPI 搜索、浏览、写回;Kiwix 是一个通过 HTTP 提供服务的离线归档;Obsidian 是一个就地读写的 vault。

每个知识库绑定一个引擎。这个约束很重要——它没有假装能「自动选最好的」,而是把选择权交给你,并且让你自己验证这个选择对不对。

所以有了 deeptutor kb eval。

这一节值得整段抄下来。写一份 QA 集,一行一个 JSON 对象,包含一个查询和理想检索应该返回的原文片段:

1
{"query": "Why do transformers scale attention?", "gold": ["Scaling keeps the dot products from growing with the dimension."]}

然后打分:

1
2
deeptutor kb eval my-kb --dataset qa.jsonl --top-k 5 --mode hybrid
deeptutor kb eval-diff baseline.json candidate.json

每个用例走的都是聊天轮次用的那条检索路径,排好序的引用再和 gold 片段做匹配,报告给出 Recall@k、Precision@k、nDCG@k、MRR、MAP、Hit@k。

而这一节里最狠的一句是:指标是排名上的纯函数,没有模型来评判输出。

再补一句:匹配用的是精确文本或词面重叠,不评估语义等价。

这两句话把一件很容易糊弄的事变得不能糊弄。用模型当裁判,你可以得到一个永远很好看、也永远说不清为什么的分数;用纯函数,你只能得到真相,包括难看的真相。

更细致的是它的诚实之处:PageIndex 用推理做检索、不返回排好序的块,所以被排除在评测之外,而不是硬塞一个不合适的指标进去。检索失败的用例不带分数,单独报告,而不是把平均值拖下水。

一个评测工具愿意承认「这个引擎不适合被这样评」,比它多给三个指标可信得多。

书:先给你目录,再写内容

Book 这个功能,文档里叫「活的书」。

它不是一份静态 PDF,是一个用类型化区块搭出来的阅读环境。可以从知识库、收藏、题库或聊天历史起头,但关键在流程:生成内容之前,先提出章节大纲,让你审形状,而不是接受一次盲写。

每章会编译成可编辑的区块:文本、提示框、测验、抽认卡、时间线、代码、图、交互 HTML、动画、概念图、深挖、以及你自己的笔记。每种区块可以插入、移动、重新生成、改写、换类型。选中的段落会进一个可复核的学习捕获收件箱。

还有一个细节很贴心:进度、书签、测验尝试、捕获、页面聊天,每个读者各自私有,即使管理员共享了这本书,或者开了协作编辑。共享删除始终只有管理员能做。

这句话里藏着一个真实的产品难题。共享与私有在同一个对象上,界限稍微划错,就会出现「别人看到你的笔记」或者「你改了别人的进度」。文档把这条线写在功能说明里,而不是 FAQ 里。

安全:两处最容易被忽略的地方

第一处是技能导入。

任何来源的技能,在碰到你的工作区之前,都要过同一道闸门:先查注册表的安全判定,被标记的包直接拒绝,除非你显式放行;解压时做路径穿越、条目数、大小、压缩比、后缀、符号链接的检查;剥掉可执行位;把 frontmatter 规范化到自己的 schema,并且剥掉 always:。

最后这一条值得单独说。always: 是一个让技能强行进入每一次系统提示的字段。如果不剥掉,一个下载来的技能就能让自己永久驻留在你的上下文里——这是最安静的一种劫持。文档把它写成一句话,没有解释为什么,但做过这件事的人一看就懂。

来源、版本、判定、安装时间,会写进 .hub-lock.json,供审计和更新用。

第二处是执行沙箱。

内置的 office 技能——docx、pdf、pptx、xlsx——是靠模型写一段 Python 脚本、用单个 exec 工具跑出来、再展示文件。所以沙箱不是可选项。

它按强度依次选择:runner sidecar 把执行路由到一个加固过的最小权限服务;Linux 上用 bubblewrap 隔离进程和文件;最后才是受限子进程兜底,而且只有在被允许时才用。

关于内容工作区,规则是:在 outputs/ 之外是只读的。想把生成的文件拷到工作区别处,需要针对那个确切源和目标做一次「允许一次」的确认。系统沙箱或 Docker runner 可用时,由它们执行这条边界;退回到本地受限子进程时,设置页会如实标注为尽力而为。

「尽力而为」这四个字,很多产品会用「已保护」代替。这里没有。

装起来:四条路

文档给了四种安装方式,共用一套运行时主目录布局:私有设置在启动目录下的 data/user/settings/,或者 DEEPTUTOR_HOME 指定的位置。

最省事的是 Docker,一条命令映射 3782 端口,挂一个命名卷保住数据。适合「先看看它长什么样」。

不装 Docker 的话,从 PyPI 装全量包,三步走完:

1
2
3
4
mkdir -p my-deeptutor && cd my-deeptutor
pip install -U "deeptutor[full]"
deeptutor init # 问端口、模型提供方、可选的向量与搜索
deeptutor start # 前后端一起起,终端别关

init 会问后端端口、前端端口、模型提供方与密钥、可选的向量提供方、可选的搜索提供方。想跳过也行,用默认端口空配置起来,之后再在设置里填。

另外两条路是从源码装,和只装 CLI。前者适合要改代码的人,后者适合不需要网页界面的人——装完 deeptutor init --cli,然后 deeptutor chat。

1.7.0 把安装做成了模块化:deeptutor[full] 是全量,裸的 deeptutor 只有核心聊天。之后可以用 deeptutor module list/install/uninstall/enable/disable 逐块增减,比如 solve、research、questions、knowledge、parsing、reading、mastery、books、memory、notebook、partners。依赖它的模块会阻止卸载,核心不能卸载,禁用或卸载都保留设置、对话、知识库和生成的文件。

这套设计的价值不在于「能装得小」,而在于能说得清自己在跑什么。

CLI:给人用,也给 agent 用

CLI 有两条入口,一条给活在终端里的人,一条给别的 agent。

给人用的那条是 REPL:

1
2
3
deeptutor chat
deeptutor chat --capability deep_solve --kb my-kb
deeptutor run chat "Explain the Fourier transform" --kb textbook

给 agent 用的那条,是在 run 后面加 --format json。每一轮都流成 NDJSON:一行一个事件,content、tool_call、tool_result、done,每行都带着自己的 session_id。

1
deeptutor run deep_solve "Find d/dx[sin(x^2)]" --tool reason --format json

这里有一个很细但很关键的设计:无头安全。当 ask_user 在没有 TTY 的环境里暂停时,它会用一个空回复自动放行,而不是挂死。

做过自动化的人会明白这句话的分量。一个会停下来等人类输入的程序,在流水线里就是一个定时炸弹——它不会报错,只会安静地卡在那里,直到超时。

会话也是可串的:跑一轮,把 done 事件里的 session_id 取出来,下一轮带上它,上下文就接上了。仓库根目录还放了一份 SKILL.md,是一份给 agent 看的交接文档——你可以让自己的 agent 直接读它,然后由它来操作 DeepTutor。

一个工具愿意为「被另一个工具操作」写文档,说明它认真考虑过自己的使用者不一定是人。

多用户:默认是关的

鉴权默认关闭,单用户运行。要开,就打开配置里的开关,重启,在 /register 注册第一个管理员。

第一个注册的人成为管理员,拥有模型目录、提供方凭证、共享知识库、技能、共享书,以及给别人的授权。之后的本地用户可以选标准、学习者、自定义三种。学习者会锁住学习类能力和材料策略,加一份自适应档案,支持带过期和每日限额的可撤销设备凭证;被授权的监护人能看报告、批准材料、重置凭证。

其他用户拿到的是隔离的工作区,加上被授权范围内的模型、知识库、技能、伙伴和共享书,拿不到原始 API key。

这套权限模型有一个很实在的默认值:先单用户,需要了再开。大多数自托管工具反过来做——先给你一套复杂的账号系统,再让你想办法关掉它。

它长得多快

文档里有一串新闻,我把它当作项目自述来引用。

2025 年 12 月 29 日正式发布。39 天到一万星,111 天到两万星,9 个月到四万星。2026 年 4 月论文上了预印本,5 月官方文档站上线。

这个速度说明它踩中了什么。我猜不是「AI 家教」这个概念——这个概念已经热了很久。踩中的可能是另一件事:它把「个性化」做成了看得见的东西。

一个能打开 L2/chat.md 看到自己被怎么理解的家教,和一个只会在下一次回答里「显得懂你」的家教,信任成本完全不同。

最后

回到开头那个奇怪的事实。

大部分 AI 产品把记忆当成一个性能问题:怎么记得更多、更准、召回更快。DeepTutor 把它当成一个所有权问题:这些关于你的东西,应该以什么形式存在,归谁看管。

它的答案是文件。Markdown 和 JSONL,在你自己的目录里,可以 diff,可以备份,可以删掉某一条。

这套选择有代价。文件比向量笨,检索比嵌入慢,也更容易被你自己改坏。但它换来一件更贵的东西:当系统说「我记得你」,你可以回一句「让我看看你记了什么」。

一个能陪你很多年的东西,大概都得经得起这句追问。