TencentDB Agent Memory 非回环绑定:文档要求配 key,代码只 warn

2026-08-16

TencentDB Agent Memory 这个仓库时,最容易被绊一下的不是路由表,而是一个词:required

README 的环境变量表里,TDAI_GATEWAY_API_KEY 那一行的说明写着 HTTP Bearer authentication; required for non-loopback binding。中文读过去就是「绑非回环地址时这个 key 是必填的」。但我们把 MemoryCore/ 下的 Gateway 源码顺着读下来,在这条路径上只找到一条 logger.warn——没有找到「未设 key 且绑了非回环地址就拒绝启动」的实现

先把限定条件说在前面:本文对应的是 TencentCloud/TencentDB-Agent-Memory 在 2026-08-16 的快照,分支是 feat/server_team(这个仓库的默认分支就是它,不是 main 也不是 master,贴路径时容易写错)。MemoryCore/package.json 里的 version2.0.0-beta.1,仍处于 beta 阶段,配置项与接口随版本变动,下面每一处行号都只对这一份快照负责。

差异到底在哪两处

按写「不一致」的纪律,只陈述位置与原文,不推断原因、不评价。

A 处(文档层)MemoryCore/README.md:204,环境变量表原文 TDAI_GATEWAY_API_KEY | Unset | HTTP Bearer authentication; required for non-loopback binding。默认值一栏写的是 Unset

B 处(代码层)MemoryCore/src/gateway/server.ts:739-746。当 !loopback && !authOn 时,走的是一条 logger.warn,文案末尾写着「Bind to 127.0.0.1, or set TDAI_GATEWAY_API_KEY, before continuing.」。

与之配套的是同一个文件里的鉴权函数 verifyAuthserver.ts:1116-1118):取 this.config.server.apiKey若该值未配置就直接 return "ok",旁边的注释原文写的是 auth disabled — default behaviour。我们在 server.ts 里找到的唯一一处 process.exit:3040,属于优雅退出路径。

再补一条同一快照里的事实:MemoryCore/Dockerfile:146ENV 段里默认写着 TDAI_GATEWAY_HOST=0.0.0.0。也就是说镜像本身给出的绑定地址默认值,与「非回环」这个前提是对得上的,而镜像里没有强制 API Key 的动作。

两处都摆出来了,说完就停。

同一件事在四层配置里的口径

这类问题真正麻烦的地方,是「apiKey 该不该填」这件事在这个仓库里散落在四层,每层写法都不一样。把它们并排看一遍,才知道自己手上那份配置属于哪层:

位置关于 apiKey 的写法
README.md:204(文档)默认 Unset,说明写 required for non-loopback binding
tdai-gateway.yaml:29(默认配置模板)server.apiKey 整行被注释掉,注文写「不填则所有 v2 接口默认开放(仅本机)」
tdai-gateway.standalone.yaml(单机配置模板)这个键完全不出现
openclaw.plugin.json:28-36(插件配置 schema)server.apiKey 有默认值,写的是 local
server.ts:1116-1118(代码)未配置 → verifyAuth 直接返回 "ok"

注意最后两行不是一回事:openclaw.plugin.json 是这个模块的插件配置 schema,它的 server 段除 apiKey 默认 local 外,还有 url 默认 http://127.0.0.1:8420instanceId 默认 default:28-36);而 server.tsverifyAuth 读的是 this.config.server.apiKey,走的是 Gateway 进程自己那份配置。同一个字段名出现在两份文件里,看配置前先分清自己在改哪一份。

怎么确认自己撞的是这一条

我们没有部署、也没有运行过这个项目的任何一个模块,所以下面全是在仓库副本里可执行的核对动作,不是运行验证。

  1. 先确认分支git branch --show-current 看是不是 feat/server_team。分支不同,下面的行号就别照抄了。
  2. 核 A 处。打开 MemoryCore/README.md,找到环境变量那张表里 TDAI_GATEWAY_API_KEY 那一行,看说明列是否仍写着 required for non-loopback binding
  3. 核 B 处。打开 MemoryCore/src/gateway/server.ts,在文件里检索 TDAI_GATEWAY_API_KEYnon-loopback,看命中的是 logger.warn 还是别的分支;再检索 process.exit,看这个文件里一共有几处、分别在什么位置。
  4. 核鉴权函数。在同一文件里定位 verifyAuth,看第一段是不是「expected 为空则 return "ok"」。这一步决定了「没配 key 时请求会怎样」这个语义。
  5. 核你实际用的那份配置。如果用的是 tdai-gateway.yaml,看 server.apiKey 那行是不是仍被注释;如果用的是 tdai-gateway.standalone.yaml,确认这份文件里根本没有这个键。
  6. 核容器侧。如果走的是 Dockerfile,看 :144-148ENV 段里 TDAI_GATEWAY_HOST 是什么值、TDAI_GATEWAY_CONFIG 指向哪个路径;同一文件 :150EXPOSE 8420

这六步走完,你手上会有一份「文档怎么写、配置怎么写、代码怎么写」的三方对照,而不是一句「好像不生效」。

源码语义给出的处置,以及处置后怎么核

README 自己给出的安全建议在 README.md:251,原文口径是绑非回环地址时务必配置 TDAI_GATEWAY_API_KEY。这是仓库文档自己的说法,不是我们的建议。

配置层面能核到的落点有两个:环境变量 TDAI_GATEWAY_API_KEY(README 那张表里登记的就是这个名字),以及 tdai-gateway.yaml:29 那行被注释掉的 server.apiKey。改完之后能做的核对,同样是读文件比对:配置里 server.apiKey 是否有非空值、绑定地址是不是你以为的那个。

这里还有一条容器侧的细节值得抄下来。Dockerfile:154-155 有一段注释,解释为什么 TDAI_GATEWAY_PORT 被刻意不写进 ENV:一旦它成为镜像环境变量,就会盖掉挂载配置里的 server.port。而同一份 Dockerfile:146 却把 TDAI_GATEWAY_HOST 写进了 ENV,值是 0.0.0.0。两条并排放着,不必替它解释原因,但你在核「我挂的 yaml 到底说不说了算」时,得知道这两个变量在镜像里的待遇不一样。

有两条相关行为也值得一并知道,免得核错地方:

  • GET /health 是无条件可达的server.ts:1046 处理 /health 的分支排在鉴权门之前,紧邻的注释写的就是它无条件可达。Dockerfile:156-157HEALTHCHECK 打的也正是 http://127.0.0.1:${TDAI_GATEWAY_PORT:-8420}/health。换句话说,/health 能通不代表鉴权生效。
  • CORS 的默认是「不发头」applyCorsHeadersserver.ts:1176-1180)在允许列表长度为 0 时直接返回、一个 CORS 头都不发,注释称之为 strict default。这与 apiKey 是两条独立的开关,别把「浏览器跨域被拦」当成鉴权在起作用。

什么情况说明你遇到的不是这一条

排查文章最该写清的是排除项:

  • 你根本没起 GatewayMemoryCore/index.ts 那条路径是作为 OpenClaw 插件在宿主进程内注册工具与钩子(api.registerTool(...) 三处、api.on("before_prompt_build", ...)api.on("agent_end", ...)),不经过这条 HTTP 鉴权路径。
  • 你绑的是回环地址tdai-gateway.yaml:27-28tdai-gateway.standalone.yaml:12-13server.port / host 默认值都是 8420 / 127.0.0.1README.md:44 也写 Listens on 127.0.0.1:8420 by default。前提不成立,这条差异就与你无关。
  • 你收到的 401 文案是 Missing x-tdai-service-id header。那来自 v2/v3 侧的 parseV2Authv2-router.ts:350-360),是缺少隔离头,不是 apiKey 这条线。同一函数在缺 Bearer 时返回的文案是 Missing or invalid Authorization header. Expected: Bearer {api_key}
  • 你的问题出在 /v3 的三元组上/v3 侧要求 team_id / agent_id / user_id,由 collectV3Missingv2-router.ts:130-150)收集,可从 body 或 x-tdai-team-id / x-tdai-agent-id / x-tdai-user-id 头取,其中 session_id 不强制。这属于参数校验,与启动时那条告警不是一回事。

另外说明我们核到哪为止:上面这些都是 dispatch 层的判定,至于 parseV2Auth 取到的 key 之后在每个 handler 里如何比对,我们没有逐个 handler 通读,这一段不下结论。

最后一句必须说的话

这个项目会采集并存储团队的对话、文档与代码——MemoryCore 侧落盘的目录结构里包含 conversations/(L0 每日 JSONL 分片)、records/(L1 每日 JSONL 分片)、scene_blocks/(L2 Markdown)、persona.md(L3 画像)与 vectors.db。仓库自己的诊断导出文档里,memory-tdai/ 这一栏的隐私风险标的是「高」,说明写的是「包含用户对话原文」。

所以「apiKey 到底是 warn 还是拒绝启动」这件事,落到你身上不是一个语义细节,而是一份配置该不该合并的判断依据。至于防火墙、密钥托管、网络隔离怎么做——那些属于通用运维做法,不是该项目文档里的内容,本文不展开,也不构成安全方案建议。

延伸阅读


本文依据 TencentDB Agent Memory 官方仓库(github.com/TencentCloud/TencentDB-Agent-Memoryfeat/server_team 分支上的 README、INSTALL、CHANGELOG、ROADMAP 与四个模块的源码整理, 核对日 2026-08-16,对应仓库快照 97f9465。该仓库的默认分支即为 feat/server_team。 本文内容为仓库源码与文档口径,我们没有部署、也没有运行过该项目的任何一个模块, 因此不涉及运行效果、检索质量与性能的任何描述。 该项目主模块处于 beta 阶段、其余模块版本号仍为 0.1.0,参数与接口随版本变动,请以仓库最新内容为准。 该项目会采集并存储团队的对话、文档与代码,属于敏感数据,是否使用请结合自身合规要求评估。 安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。