TencentDB Agent Memory 的 MemoryProxy 配置:哪些字段真的会读
翻一个陌生项目的配置文件时,最容易犯的错是把 config.example.yaml 当成权威清单:以为里面列的就是全部可配项,以为里面的默认值就是不写时程序会用的值。TencentDB Agent Memory 的 MemoryProxy 模块恰好是个反例——截至 2026-08-16,我们在 feat/server_team 分支的快照上实读,这份示例配置有 648 行、23 个顶层 section,而代码里读的字段集合与它两个方向都对不上:示例里有代码不读的,代码里读的示例又没有。
先交代边界:这个仓库的默认分支就是 feat/server_team(不是 main、也不是 master),下文所有路径都以该分支为准。MemoryProxy 目录的 package.json 里包名是 context-proxy、版本是 0.1.0——目录名和包名不是一回事,别拿目录名去写安装命令。整个项目主模块尚处 beta 阶段,参数与接口随版本变动,下面所有字段名和取值都请以仓库最新内容为准。我们没有部署、也没有启动过这个模块,本文写的全部是文件里的静态事实。
一、先搞清楚三层优先级
src/config.ts 的 buildConfig 注释里把优先级写死成一句话:CLI overrides > YAML config file > defaults(src/config.ts:260-261)。配置文件路径的默认值是 "config.yaml"(src/config.ts:263)。
这里第一个容易忽略的点:命令行能覆盖的参数只有 9 个(src/config.ts:539-575)——--config、--host、--port、--upstream、--log-file、--opik-url、--opik-api-key、--opik-enabled、--verbose/-v。除此之外的任何配置项都只能走 YAML 或代码内建默认值,命令行上没有对应开关。
第二个点更关键:快照里没有 config.yaml,它被 MemoryProxy/.gitignore 排除了。也就是说,我们能看到的只有 config.example.yaml(一份示例)和 src/config.ts 里的 DEFAULT_CONFIG(一份代码内建值),真实部署上生效的那份配置,我们一个值都不知道。npm scripts 里 start 是 node --import tsx/esm src/index.ts,start:config 才带 --config config.yaml(MemoryProxy/package.json:6-14)——不带 --config 时走的是默认路径 config.yaml,文件不在就落到 DEFAULT_CONFIG。
二、同一个字段,两层的值不一样
这是本模块最需要提前知道的一件事。DEFAULT_CONFIG 与 config.example.yaml 取值不同的字段,我们对照下来至少有这些(左为代码默认,右为示例配置):
| 字段 | src/config.ts | config.example.yaml |
|---|---|---|
redis.enabled | false(34) | true(116) |
injection.enabled | false(77) | true(435) |
sessionInit.enabled | false(89) | true(498) |
tdai.enabled | false(104) | true(546) |
auth.enabled | false(136) | true(322) |
tdai.endpoint | ""(105) | http://127.0.0.1:8420(547) |
auth.url | ""(137) | http://kernel.example.com:8420(323) |
tdai.memory.timeoutMs | 3000(116) | 5000(558) |
clickhouse.ttlDays | 0(31) | 30(313) |
log.file | ""(13) | logs(101) |
还有 upstream.url、tdai.apiKey、coreSkill.serviceToken、knowledge.serviceToken、storage.cos.shark.baseUrl、opik.url、以及 tdai.memory 下 enabled/inject/writeL0/recallL1/injectL2L3 五个布尔位(代码里全 false、示例里全 true)也在这张差异表里。
这张表怎么读?它说明「不给配置文件直接跑」和「照抄示例配置跑」得到的是两套形态完全不同的东西:前者鉴权关闭、会话初始化关闭、注入关闭、记忆链路关闭;后者全部打开。这只是两层取值的陈述,我们没有部署过,不对任何一种形态的实际行为下判断。
这里也顺带回答一个高频问题:某个开关到底该设成什么?项目没有给通用值,具体该开哪些、超时设多少,取决于你的部署形态与上游,我们不做推荐。
三、代码在读、示例里根本没有的段
比「值不一样」更隐蔽的是「示例里压根没这一段,代码却在读」。至少有三处:
costGuard 整段。 src/config.ts:65-70 定义了它的默认值(enabled:false、markerOptIn:false、agentProfile:"auto"、options:{}),:201-232 有专门的 parseCostGuard 解析函数,src/server.ts 的 :35、:93、:188、:265 四处直接读 config.costGuard.*。而 config.example.yaml 里没有 costGuard: 段,全文只有 3 处提及且全在注释行(:58、:60、:450)。
这一段还有连锁效应:src/server.ts:35-50 有一道门控中间件,costGuard.markerOptIn 为 false 时,任何路径含 /cost-guard/ 段的请求直接返 404,错误体是 {error:"cost_guard_marker_disabled", ...}。也就是说这个在示例配置里找不到的字段,直接决定一类 URL 能不能走通。类似地,injection.assetReflection.markerOptIn 为 false 时含 /analyse/ 段的请求同样返 404,错误码 analyse_marker_disabled(src/server.ts:60-76)——这个字段示例里倒是有(config.example.yaml:463,默认 false)。
workbuddyRequestRouting。 src/config.ts:144 定义 { enabled: true },解析在 :506-510。它是我们在这个模块里看到的唯一一个默认为 true 的 routing 开关,而对称的 ccRequestRouting 在示例配置里是 enabled: false(config.example.yaml:643-644)。示例配置中没有 workbuddyRequestRouting 这一段。
badcaseCollector。 parseCostGuard 会把顶层的 yaml.badcaseCollector 折叠进 costGuard.options,注释原文写它是 without being interpreted(src/config.ts:196-199、:214-216)。同一段注释还写明 parseCostGuard 只识别四个通用字段(enabled / markerOptIn / agentProfile / anthropicUpstream),其余键「every other key is kept opaque in options and handed to the extension untouched」——原样交给扩展。
需要补一句:@context-proxy/cost-guard 这个模块本身不在快照里。tsconfig.json:11 和 vitest.config.ts:11 都有指向 ./packages/cost-guard/src/index.ts 的 alias,src/storage/factory.ts:102 动态 import 它,而 packages/ 目录并不存在。代码里对此有处理说明:src/storage/factory.ts:93-96 写「cost-guard 不可用(开源用户无 submodule…)时静默跳过」。所以这些 options 里的键最终被怎么用,我们看不到,也不作推断。
其余只在代码里出现的小字段还有 redis.keyPrefix: "cg:sess:"(src/config.ts:40)、langfuse.debug(:20)、opik.stripRequestLogContent(:19)、sessionInit.defaultTaskId(:401-403)、sessionInit.debugVerboseLogging(:427)。
四、示例里写着、但当前不生效的字段
反方向同样存在。示例配置自己就用 [必]/[可]/[死] 三态标了一张存储字段速查表(config.example.yaml:155-173),明确标为 [死] 的包括:storage.ttlDays 在 cos/fs/memory 后端下、storage.cos.rootPrefix 在所有后端下、cos.endpointDomain 在非 cos 下、sqlite.dbPath 在非 sqlite 下、fs.fsRoot 在非 fs 下。其中 cos.rootPrefix 的理由写得很直白:「shark 侧硬编码 “proxy_cache”,proxy 侧任何值都无效」(:172、:222-223)。
另一处值得单独拎出来的是 tdai.memory.recallL1。config.example.yaml:554 仍写着 recallL1: true # 是否启用 L1 召回(从对话中检索相关记忆);而 src/injection/index.ts:310-312 的注释原文写「L0/L1 不再每轮自动召回注入到 user prompt(会破坏 KV/prompt cache)……L1 recall injector 已下线,recallL1 配置保留但不再注册」。对应的 TdaiL1RecallInjector 仍从 src/injection/index.ts:76 导出,但 buildPipelineBundle 里没有 import、没有 register。这两处说法不一致,我们只陈述差异、标明位置,不推断原因。
还有一个跨层的例子:config.example.yaml:27 给 forwardTimeoutMs: 600000 并注释「0 = 不超时」。src/handler.ts:353-355 与 src/systemUserPassthrough.ts:530-532 确实用 if (forwardTimeoutMs > 0) 包住了;而 src/anthropicHandler.ts:452 与 :493 两处是无条件调用 AbortSignal.timeout(forwardTimeoutMs) 的;src/codexHandler.ts 与 src/workbuddyHandler.ts 里 grep 不到这个字段,它们用的是硬编码 5 分钟的 setTimeout(src/codexHandler.ts:1150-1156、src/workbuddyHandler.ts:673-679)。同一条注释语义在不同 handler 路径上覆盖面不同,这也是只陈述、不延伸。
五、开关为真 ≠ 组件被装上
injection.injectors 在示例里是 ["skill","knowledge","tdai-memory"](config.example.yaml:436),但列表里写了名字只是必要条件之一。src/injection/index.ts 里的注册前置条件是叠加的:
knowledge需要同时满足injectors含"knowledge"、knowledge.enabled、knowledge.serviceToken非空三项(src/injection/index.ts:453-457的shouldRegisterKnowledgeInjector)。而knowledge.enabled在示例里是false(config.example.yaml:590),knowledge.serviceToken的代码默认是空字符串(src/config.ts:128)。tdai-memory要求injectors含"tdai-memory"且tdai.enabled且tdai.memory.enabled且tdai.memory.inject四者全真(src/injection/index.ts:288);其中TdaiProfileMemoryInjector还额外要求tdai.memory.injectL2L3(:307-309)。skill相对简单:injectors含"skill"就注册SkillInjector与SkillToolsInjector两个(:262-275)。
所以排查「某个注入块没出现」时,只看 injectors 数组是不够的,要沿着这几个布尔位一路查下去。
六、两个写法上的坑
段名兼容。 config.example.yaml:571 的注释写:skill: 段也可写作 coreSkill:(旧名,仍被读取);两者同时存在时 skill: 优先。也就是说,你可能在不同来源的配置片段里见到两个名字,它们指的是同一段。
环境变量展开的作用域极窄。 ${VAR} / ${VAR:-default} 这种写法只对 systemUsers 段生效,src/config.ts:514-528 的注释原文是「Deliberately scoped narrowly (only called from systemUsers today)」。别指望在其它段里写 ${VAR} 能被展开。示例配置里 systemUsers 那条 name: memory 的条目也确实是用 ${VAR} 占位写 userId/displayName/userKey 的(config.example.yaml:362-365);而 src/config.ts:479-486 会把 userId 为空的条目静默丢弃。
另外,src/ 里以字面量形式出现的环境变量有 PROXY_DB_PATH、PROXY_DEBUG_DUMP_OUTBOUND_MD5、PROXY_DEBUG_DUMP_INBOUND、PROXY_DEBUG_DUMP_BODY、TDAI_PROXY_ADMIN_API_KEY、PROXY_DEBUG_ASSET_REFLECTION、CC_FORM_MODE。其中 PROXY_DEBUG_DUMP_BODY、PROXY_DEBUG_DUMP_INBOUND、PROXY_DEBUG_DUMP_OUTBOUND_MD5、PROXY_DEBUG_ASSET_REFLECTION、CC_FORM_MODE 在 README.md 与 config.example.yaml 里 grep 均为 0 次,README 的环境变量小节只列了 4 个(MemoryProxy/README.md:233-238)。CC_FORM_MODE 自己的注释还明标 EXPERIMENTAL(src/session/form.ts:388-402)。
管理端密钥的优先级示例配置写得很清楚:env TDAI_PROXY_ADMIN_API_KEY > yaml admin.apiKey > 默认 ""(config.example.yaml:347、:349)。真实密钥请走你自己的托管方式,配置示例里请保留 <YOUR_API_KEY> 这类占位。
七、自己怎么核这份配置
不必相信本文的结论,给三个可执行动作:
- 打开
MemoryProxy/src/config.ts,把DEFAULT_CONFIG(:9-145)整段与config.example.yaml并排看一遍,凡两边都有、值不同的,记下来。 - 在
MemoryProxy/src/下检索config.开头的读取点,凡读到的键在config.example.yaml里搜不到的,就是「代码读、示例不给」的那一类(costGuard是最大的一块)。 - 反过来,把
config.example.yaml里的字段名逐个拿去src/检索,搜不到的要么是被[死]标注过的,要么就落进本文第四节那类情况。
最后提醒一句:这个模块会经手团队与 Agent 的对话内容,涉及敏感数据;配置里的上游地址、鉴权开关、日志与追踪后端如何设置,请结合自身合规要求评估,本文只是把仓库里的字段读了一遍。
延伸阅读
- 从头读起:TencentDB Agent Memory 是什么:团队级 Agent 记忆中枢怎么读
- 本专题共 40 篇,完整分组目录见专题页
- TencentDB Agent Memory 被引用的 cost-guard 目录并不存在
- TencentDB Agent Memory 的 MemoryProxy:一条请求经过它发生了什么
本文依据 TencentDB Agent Memory 官方仓库(github.com/TencentCloud/TencentDB-Agent-Memory)
feat/server_team 分支上的 README、INSTALL、CHANGELOG、ROADMAP 与四个模块的源码整理,
核对日 2026-08-16,对应仓库快照 97f9465。该仓库的默认分支即为 feat/server_team。
本文内容为仓库源码与文档口径,我们没有部署、也没有运行过该项目的任何一个模块,
因此不涉及运行效果、检索质量与性能的任何描述。
该项目主模块处于 beta 阶段、其余模块版本号仍为 0.1.0,参数与接口随版本变动,请以仓库最新内容为准。
该项目会采集并存储团队的对话、文档与代码,属于敏感数据,是否使用请结合自身合规要求评估。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。