1. 项目概述一个能扛住kill -9的 AI 记忆守护进程最近几个月我一直在捣鼓一个让我自己都觉得有点“强迫症”的项目。起因很简单作为一个重度 AI 工具使用者我受够了每次在不同的 AI 应用之间切换时都要像个复读机一样重复介绍自己。我告诉 Ollama “我刚搬到奥斯汀”转头打开 Claude Desktop它却对我一无所知我还得再说一遍。这感觉就像你家里的每个房间都有一个独立的管家但他们之间从不交流你每次进一个新房间都得重新自我介绍一遍。这种数据孤岛的问题在当前的 AI 应用生态里太普遍了。ChatGPT 不知道你和 Claude 聊过什么Perplexity 也不知道你向本地运行的模型透露过什么信息。市面上当然有解决方案比如一些云端的“记忆即服务”但它们无一例外都要求你把数据上传到别人的服务器上。对于很多涉及个人隐私、公司内部信息或者单纯就是不想数据出境的场景来说这显然不是最优解。所以我决定自己动手造一个不一样的轮子。我想要的是一个本地运行的守护进程Daemon任何 AI 客户端都能连接到它共享同一份记忆。这个守护进程的核心要求就两个字可靠。如果它一崩溃数据就丢了那它毫无价值。我的目标很明确即使在最粗暴的kill -9命令下只要它之前返回了写入成功的信号数据就必须毫发无损。这就是BubbleFish Nexus诞生的初衷——一个用 Go 语言编写的、开源的、具备崩溃安全性的 AI 记忆共享守护进程。2. 核心设计思路为何选择这样的架构2.1 问题本质从数据孤岛到统一记忆层在深入技术细节之前我们先拆解一下问题的核心。当前的 AI 应用无论是云端大模型客户端还是本地模型服务其“记忆”功能即对话历史、用户信息等上下文通常是应用自身维护的。这导致了几个痛点重复劳动用户需要在不同工具间反复输入相同的基础信息如姓名、职业、项目背景。上下文割裂在一个对话中建立的复杂上下文无法延续到另一个工具降低了连续思考和工作的效率。隐私与控制权使用云端记忆服务意味着将敏感数据托付给第三方数据安全和合规性存在顾虑。Nexus 的解决方案是引入一个统一的记忆层。它不替代任何 AI 模型本身而是作为一个独立的后端服务专门负责存储、检索和管理这些“记忆”数据。所有 AI 客户端都成为这个记忆层的“消费者”和“生产者”。2.2 技术选型为什么是 Go SQLite选择 Go 语言作为实现语言是基于多方面的考量并发与网络服务原生优势Go 的 goroutine 和 channel 机制非常适合构建高并发、高性能的网络服务端。Nexus 需要同时处理来自多个客户端的 HTTP、MCP 等协议请求Go 在这方面的表现非常出色且代码简洁易懂。部署简便性Go 编译生成的是静态链接的单一二进制文件没有任何外部依赖。用户只需要下载这个二进制文件就能运行无需安装复杂的运行时环境如 JVM、Python 解释器及其包管理极大降低了部署门槛。强大的标准库与生态Go 的标准库已经覆盖了 HTTP 服务器、JSON 处理、加密等 Nexus 所需的核心功能。对于需要的外部依赖如 OAuth 库、SQLite 驱动Go 的模块管理也非常清晰。数据存储方面我选择了SQLite作为默认和核心的存储引擎原因如下进程内数据库SQLite 以库的形式存在无需单独部署数据库服务。这与 Nexus 作为一个独立守护进程的定位完美契合保持了系统的简洁和一体化。ACID 与可靠性SQLite 严格遵循 ACID原子性、一致性、隔离性、持久性原则特别是其预写日志WAL模式为 Nexus 实现崩溃安全提供了坚实的基础。WAL 模式能确保即使在写入过程中发生系统崩溃数据库也能恢复到一致状态不会损坏。轻量且高效对于个人或小团队使用的记忆服务来说SQLite 的性能完全足够甚至绰绰有余。它避免了维护一个独立数据库服务的开销。注意虽然当前版本深度集成了 SQLite但 Nexus 的架构在设计上考虑了存储后端的可插拔性。理论上可以适配 PostgreSQL、MySQL 等其他数据库以满足更复杂的分布式或高可用场景。不过对于“本地、可靠、简单”的核心场景SQLite 是目前的最佳实践。2.3 核心设计原则崩溃安全高于一切这是 Nexus 区别于许多其他“玩具项目”或原型系统的根本。我将其视为一个基础设施组件而基础设施的第一要义是可信赖。我定下的死规矩是一旦客户端收到一个成功的写入响应HTTP 200那么这条记忆就必须被持久化即使下一秒守护进程就被强制杀死。为了实现这一点整个系统的设计围绕“持久化先行”展开。写入请求的处理管道被精心设计认证与策略检查首先验证客户端身份和操作权限。持久化写入这是最关键的一步。在返回成功给客户端之前记忆数据必须已经以事务形式安全地写入 SQLite 数据库启用了 WAL。队列分发写入成功后该事件被放入一个内部队列用于异步通知其他子系统例如未来可能的消息推送或索引更新。目标提交队列消费者处理事件更新其他相关状态。这个顺序至关重要。它确保了数据的持久性不依赖于后续异步步骤的成功。即使队列处理环节崩溃核心记忆数据已经安然无恙地躺在磁盘上了。3. 深入解析崩溃安全是如何实现的3.1 写入管道的可靠性保障让我们深入到代码层面看看一次记忆写入请求是如何变得“刀枪不入”的。假设一个客户端通过 HTTP POST/v1/memories发送了一条记忆。// 伪代码展示核心逻辑流程 func (h *MemoryHandler) CreateMemory(w http.ResponseWriter, r *http.Request) { // 1. 认证与授权 clientID, ok : auth.Authenticate(r) if !ok { ... } // 2. 解析与验证请求体 var req CreateMemoryRequest if err : json.NewDecoder(r.Body).Decode(req); err ! nil { ... } // 3. 开启数据库事务 tx, err : h.db.Begin() if err ! nil { ... } defer tx.Rollback() // 确保函数退出时事务回滚除非显式提交 // 4. 在事务内执行核心写入操作 memoryID, err : h.insertMemory(tx, clientID, req.Content, req.Metadata...) if err ! nil { ... } // 5. 提交事务 - 这是持久化的关键时刻 if err : tx.Commit(); err ! nil { ... } // 一旦 Commit() 成功返回数据就已通过 SQLite WAL 机制持久化到磁盘。 // 6. 只有在此之后才向客户端返回成功 w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(CreateMemoryResponse{ID: memoryID}) // 7. 异步处理将写入事件放入内存队列供其他消费者使用 // 即使这一步崩溃或守护进程被杀数据也已安全。 go h.dispatcher.Dispatch(memoryID) }关键点在于第5步的tx.Commit()。在 SQLite 的 WAL 模式下Commit()调用会将事务的更改提交到 WAL 文件。这个操作是同步的取决于PRAGMA synchronous的设置Nexus 中设置为FULL或EXTRA以确保安全意味着在函数返回前数据已经写入磁盘的 WAL 文件。此后即使进程崩溃SQLite 在下次启动时也能从 WAL 文件中恢复出已提交的事务。3.2 针对kill -9的防御性设计kill -9SIGKILL是操作系统级别的强制终止信号进程无法捕获或忽略。它模拟了最极端的情况电源故障、系统崩溃或 OOM Killer内存溢出杀手的处决。为了证明 Nexus 能抵御这种攻击我构建了一个自动化测试场景这也是项目bubblefish demo命令的核心批量写入程序快速连续创建 50 条包含随机内容的记忆。暴力中断在写入过程中的某个随机时间点执行kill -9 nexus_pid。恢复与验证重新启动 Nexus 守护进程。完整性检查查询所有记忆断言 50 条记忆全部存在且内容完整无误。重复数据检测确保没有因为部分写入或重复提交而产生重复记录。这个测试反复运行了上百次没有一次失败。其背后的保障来自于SQLite 的 WAL 和事务原子性保证了单个写入操作要么完全成功数据落盘要么完全失败如同从未发生没有中间状态。Nexus 的“先持久化后响应”流程确保客户端看到的“成功”是真实的成功而不是“我以为我成功了”。3.3 读路径多阶段检索与一致性光写得好不够读也要读得聪明、读得准。Nexus 的读取管道是一个多阶段检索系统旨在从记忆库中找到最相关、最准确的信息。元数据过滤首先根据查询附带的元数据如来源客户端、记忆类型、时间范围等进行快速筛选缩小搜索范围。这就像先去档案室找到正确的文件柜。语义搜索利用嵌入模型例如集成 SentenceTransformers 或 OpenAI 的 embeddings将记忆文本和查询文本转换为向量然后进行向量相似度搜索。这能找到在“意思”上最接近的记忆即使没有完全相同的关键词。时间感知重排序这是处理信息矛盾的关键一步。系统会检查检索结果中是否存在事实冲突例如一条旧记忆说“住在纽约”一条新记忆说“搬到奥斯汀”。通过一个时间衰减函数让更新的记忆在排名中获得更高的权重。这样当用户问“我住在哪”时系统会优先返回“奥斯汀”而不是陈旧的“纽约”。重排序算法会综合考虑语义相似度和时间新鲜度给出最终排序。整个读取过程是幂等的且不修改核心数据因此也具有很高的容错性。即使检索过程中发生错误也不会破坏已存储的记忆。4. 集成挑战连接七个不同的 AI 客户端4.1 协议与认证的“七国演义”如果说崩溃安全是 Nexus 的“内功”那么多客户端集成就是它的“外功”。让 Claude Desktop、ChatGPT、Ollama 等七个客户端都能无缝使用 Nexus其复杂性远超实现一个单一的 API。每个客户端都有自己的一套“语言”和“规矩”传输协议有的是简单的 HTTP REST API如自定义脚本有的是通过MCPModel Context Protocol这种新兴协议被 Claude Desktop 和某些 IDE 插件使用还有的需要处理OAuth 2.1的完整授权码流程如集成到 ChatGPT 的插件商店。认证模型有的使用静态 API 密钥有的使用 OAuth 动态令牌有的如本地 Ollama可能最初不需要认证但为了安全仍需加固。数据格式即使都是“记忆”每个客户端发送的 JSON 结构、字段命名、内容编码方式都可能存在细微差别。我的策略是“适配器Adapter模式”。Nexus 的核心定义了一套内部统一的“记忆Memory”数据模型和一套核心的存储/检索接口。针对每一个客户端我编写一个独立的适配器层。这个适配器的职责是将客户端特定的协议HTTP/MCP/OAuth翻译成对内部统一接口的调用。将客户端特定的数据格式转换/映射为内部的统一记忆模型。处理该客户端特有的认证和授权逻辑。例如对于 Claude Desktop 的 MCP 集成适配器实现了 MCP 服务器协议将 Claude 的tools/memory_store等工具调用翻译成 Nexus 的CreateMemory和SearchMemories操作。4.2 实操以 ChatGPT OAuth 2.1 集成为例这是集成工作中最具挑战性的一部分因为它涉及外部重定向、用户交互和令牌管理。目标是在 ChatGPT 的插件市场中让用户能安全地授权 ChatGPT 访问他们自己本地运行的 Nexus 服务。步骤简述本地服务暴露由于 ChatGPT 是云端服务它无法直接访问你本地的localhost:8080。你需要使用本地隧道工具如ngrok、cloudflared或bore将你的 Nexus 服务临时暴露一个公网可访问的 HTTPS 地址。OAuth 服务端配置在 Nexus 中配置 OAuth 2.1 服务端。这包括设置客户端 ID、密钥、授权端点 (/oauth/authorize)、令牌端点 (/oauth/token) 以及重定向 URI指向 ChatGPT 插件配置的回调地址。插件清单配置创建 ChatGPT 插件的ai-plugin.json清单文件其中auth字段需配置为oauth类型并正确填写你的 OAuth 端点 URL即上一步中隧道暴露的地址。授权流程用户在 ChatGPT 界面点击安装你的插件。ChatGPT 将用户重定向到你配置的授权端点通过隧道地址。用户在你的 Nexus 授权页面上批准授权。Nexus 将授权码通过重定向 URI 传回 ChatGPT。ChatGPT 用授权码向你的令牌端点请求访问令牌。Nexus 验证后颁发令牌ChatGPT 即可用此令牌访问 Nexus 的 API。重要心得处理 OAuth 时状态state参数和PKCEProof Key for Code Exchange是安全必备。State 参数用于防止跨站请求伪造攻击PKCE 则用于保护公共客户端如浏览器中运行的代码的授权码流程。即使 Nexus 主要面向本地服务在通过隧道暴露时也必须遵循这些最佳实践来保障安全。4.3 统一记忆后端的价值体现完成所有这些复杂的集成工作后其带来的价值是立竿见影的。当你通过命令行用 Ollama 告诉 Nexus “我是一名 Go 开发者正在开发一个开源项目”然后你打开 Claude Desktop 问它“我的职业是什么”Claude 能准确回答“Go 开发者”。这个看似简单的场景背后是协议转换、数据同步和即时检索在毫秒级内完成。这种“一处写入处处可读”的能力打破了应用间的壁垒开始让 AI 助手真正像一个统一的、有连续记忆的智能体为你工作而不是一堆互不相干的工具。5. 开发与质量保障单人项目的工程实践5.1 测试策略607 次绿色的坚持在一个以可靠性为核心卖点的项目中测试不是可选项而是生命线。Nexus 的代码库包含了607 个测试分布在 31 个包中。这些测试覆盖了单元测试针对每个内部函数、方法的核心逻辑。集成测试测试数据库层、HTTP 处理程序、各客户端适配器与核心逻辑的交互。端到端测试模拟完整的客户端请求-服务器响应流程包括上文提到的崩溃恢复演示 (bubblefish demo)。我坚持几个原则竞态检测所有测试必须在 Go 的-race标志下运行通过。并发安全对于网络服务至关重要竞态检测器能揪出那些在压力下才会暴露的隐藏 bug。跨平台验证虽然主要开发环境是 macOS/Linux但所有测试也必须在 Windows 上通过使用CGO_ENABLED1来支持竞态检测。这确保了二进制文件在不同系统上的行为一致性。三次通过法则任何新功能或修复在合并到主分支前其相关的所有测试必须连续通过三次完整运行。这有助于排除偶发性的测试不稳定因素。表驱动测试大量使用表驱动测试来覆盖不同的输入和边界条件使测试用例清晰且易于扩展。5.2 部署与使用极简主义我深知一个工具再好如果安装配置太复杂也会劝退大部分用户。因此Nexus 的部署设计力求极简方案一从源码构建适合开发者git clone https://github.com/bubblefish-tech/nexus.git cd nexus go build -o bubblefish ./cmd/bubblefish/方案二使用预编译二进制适合所有用户直接从 GitHub Releases 页面下载对应你操作系统Windows、macOS、Linux的bubblefish二进制文件。在终端中赋予执行权限Unix-like 系统chmod x bubblefish。运行即可无需安装 Go 或任何其他依赖。快速启动简单模式# 安装为系统服务或用户级服务 ./bubblefish install --mode simple # 启动服务 ./bubblefish start在简单模式下安装程序会自动生成一个 SQLite 数据库文件和一个随机的 API 密钥并将服务绑定到localhost。它会打印出 API 密钥和一个示例curl命令让你可以立即开始测试。5.3 架构的可观测性与未来扩展目前的 README 提供了高层次的架构说明涵盖了核心组件HTTP/MCP 服务器、认证层、策略引擎、持久化存储SQLite、内存队列、检索管道等。更深层的实现细节如具体的队列实现、缓存策略、向量索引的选型等暂时保留在代码中。这种设计为未来留下了清晰的扩展点存储后端如前所述可以抽象出存储接口未来接入 PostgreSQL 等。检索增强可以插入不同的向量化模型、尝试混合检索算法关键词语义。分发机制当前的内存队列可以扩展为更健壮的分布式消息队列以支持跨多个 Nexus 实例的记忆同步迈向“个人记忆云”。客户端 SDK为不同语言Python、JavaScript、Rust提供官方 SDK进一步降低集成门槛。6. 常见问题与故障排查在实际使用和开发过程中我遇到并解决了一些典型问题。这里记录下希望能帮你避开同样的坑。6.1 安装与启动问题问题现象可能原因解决方案运行./bubblefish提示Permission denied(Unix)二进制文件没有执行权限。运行chmod x bubblefish赋予执行权限。运行./bubblefish提示cannot execute binary file下载的二进制文件与系统架构不匹配如下载了 Linux 版在 macOS 上运行。去 GitHub Releases 页面下载正确操作系统和架构如darwin_arm64对应 M 系列 Mac的版本。服务启动失败提示端口被占用默认 8080端口 8080 已被其他程序如另一个开发服务器使用。1. 使用./bubblefish start --port 8081指定其他端口。2. 停止占用端口的程序。install命令在 Windows 上失败Windows 服务安装可能需要管理员权限或路径包含空格/特殊字符。1. 在管理员权限的终端中运行。2. 将二进制文件放在简单的英文路径下如C:\nexus\。3. 也可以不安装为服务直接使用./bubblefish run在前台运行。6.2 客户端连接与认证问题问题现象可能原因解决方案Claude Desktop 无法连接 NexusClaude Desktop 的 MCP 服务器配置不正确或 Nexus 的 MCP 服务未启用/端口不对。1. 检查 Nexus 启动日志确认 MCP 服务器已启动默认端口可能与 HTTP 不同。2. 正确配置 Claude Desktop 的claude_desktop_config.json指向 Nexus 的 MCP 端点。HTTP API 调用返回401 Unauthorized请求头中未携带Authorization: Bearer API_KEY或 API 密钥错误。1. 使用安装时打印的 API 密钥。2. 确保在请求头中正确设置Authorization: Bearer your_api_key_here。OAuth 流程中ChatGPT 重定向后显示错误本地隧道ngrok/cloudflared中断或重启导致回调地址变化或 OAuth 配置中的重定向 URI 不匹配。1. 重启隧道获取新的公网地址。2. 更新 Nexus 的 OAuth 配置和 ChatGPT 插件清单中的相关地址。3. 确保使用了state和PKCE参数。写入成功但其他客户端查询不到1. 客户端使用了不同的 API 密钥或用户身份导致记忆隔离。2. 检索查询条件如元数据过滤不匹配。1. 检查不同客户端配置的认证信息是否指向同一“用户”或“上下文”。2. 尝试使用更宽泛的查询如不指定元数据来检索看记忆是否存在。6.3 性能与数据问题问题现象可能原因解决方案写入或检索速度变慢1. 记忆数量极大数十万条向量检索未优化。2. SQLite 数据库文件碎片化。3. 硬盘 I/O 性能瓶颈。1. 考虑对记忆进行分库或归档旧记忆。2. 定期对 SQLite 执行VACUUM;命令整理数据库需在服务停止时进行。3. 确保 Nexus 的数据目录在 SSD 上。磁盘空间占用增长较快1. SQLite WAL 文件 (-wal) 和共享内存文件 (-shm) 未正常清理。2. 存储了大型附件或长文本。1. 正常关闭 Nexus 服务 (./bubblefish stop) 会合并 WAL 文件。避免强制kill尽管数据安全但可能留下 WAL。2. 检查是否有客户端写入了异常大的内容可在写入前于客户端进行大小限制。bubblefish demo测试失败极少数情况下系统在kill -9后立即发生硬盘损坏或断电。1. 确保测试在正常的文件系统上进行。2. 重复运行测试如果持续失败检查磁盘健康状况 (fsck,chkdsk)。理论上SQLite 的 WAL 模式能承受 kill -9但无法抵御物理介质损坏。6.4 开发与构建问题问题现象可能原因解决方案go build失败提示找不到包Go 模块未正确下载或代理问题。1. 在项目根目录运行go mod download。2. 设置 GOPROXY 环境变量例如export GOPROXYhttps://goproxy.cn,direct在中国大陆可加速。竞态检测测试 (go test -race) 失败代码中存在数据竞态。1. 仔细阅读竞态检测报告定位冲突的变量和 goroutine。2. 使用sync.Mutex或sync.RWMutex保护共享数据或使用 channel 进行通信。交叉编译到其他平台失败某些依赖可能包含 C 语言部分CGO交叉编译更复杂。1. 对于纯 Go 依赖使用GOOS和GOARCH环境变量即可。2. 对于 CGO 依赖需要目标平台的 C 交叉编译工具链。建议在目标平台或 CI 中直接编译。7. 总结与个人体会回顾过去几个月从构思到实现再到集成七个客户端构建 Nexus 的过程更像是一次对“可靠性”的深度修行。最大的收获不是掌握了某一种 Go 的并发模式或者搞懂了 OAuth 2.1 的细节而是一种工程思维上的转变对“成功”的定义要前置到数据持久化的那一刻而不是请求处理的整个链条结束。很多系统会把“写入数据库”和“返回成功”以及“触发下游事件”放在一个模糊的事务里或者依赖消息队列的“至少一次”投递来保证最终一致性。但对于一个以“零数据丢失”为承诺的记忆服务这种模糊地带是致命的。Nexus 的架构强迫我明确划清界限HTTP 200 只代表“数据已安全落盘”后续的队列处理、索引更新都是锦上添花不影响核心承诺。这种清晰的责任划分让整个系统的可靠性变得可推理、可验证。另一个深刻的体会是关于“简单”的价值。最初我也考虑过用更“强大”的分布式数据库或者引入复杂的流处理框架。但最终回归到 SQLite 和简单的内存队列反而让核心的崩溃安全特性更容易实现和证明。在基础设施软件中每一份复杂性都需要用十倍的可信度来证明其必要性。对于 Nexus 1.0 的目标场景——个人或小团队在单机上运行——SQLite 提供的 ACID 保证和极致简洁性就是最优解。最后关于开源和 solo dev独立开发。AGPL-3.0 协议的选择是希望能在保护开源精神的同时为项目的可持续发展留下可能性。一个人维护一个涉及多协议集成、高可靠性要求的项目测试套件就是你的安全网。那 607 个测试每一个都是在替未来的你或者某位潜在的贡献者提前回答一个问题“我改了这里会不会把别的东西搞坏” 没有这个安全网增加新功能会变得畏首畏尾。如果你也在构建类似的基础设施类工具我的建议是尽早并持续地投资于你的测试和验证体系尤其是针对你最核心的卖点比如 Nexus 的崩溃安全。设计一个像bubblefish demo这样能自动化的、可重复的、针对核心特性的“演示攻击”它不仅是给用户看的更是给你自己的一颗定心丸。当你可以随时运行一个命令模拟最极端的故障并看到系统从容恢复时你对代码的信心会完全不一样。Nexus 还是一个年轻的项目但它在它要解决的问题上已经做到了我认为的“坚实”。接下来我会更关注如何让“记忆”变得更智能比如更好的去重、总结、关联以及探索更多有趣的应用场景。如果你试用了遇到了问题或者有更好的想法GitHub 的 issue 和讨论区始终开放。毕竟让 AI 更好地为我们工作这条路需要一起探索。