Skip to content
围炉聊科技微信公众号二维码关注公众号,获取新文章推送
添加我为微信朋友扫描二维码,添加我为朋友

TencentDB Agent Memory 架构梳理:读源码看清 Codex 接记忆的硬冲突

TencentDB-Agent-Memory 是腾讯开源的智能体记忆框架。最近我在尝试把 Codex CLI(Coding Plan 模式)接入它,由于官方文档对这种 Coding Plan 的对接方式讲述得并不详细,我逐行读了一遍项目源码,一方面确认能否对接,另一方面也梳理一下它的架构设计。这篇文章就是梳理的结果:先讲清楚它是什么、由什么组成,再讲它最核心的身份模型与请求流水线,最后落到实战——两种接法怎么选、怎么配。

一、它解决什么问题

TencentDB Agent Memory 的起点很实在:怎样减少使用 Agent 时的重复工作。它的核心判断是——

这里的 Memory 不只是"记住对话"。凡是能让下一个 Agent 少走弯路的信息,都应该被保存、组织并复用。

它把"信息"收敛成一句话的价值链路:

mermaid
flowchart LR
    A[已有信息] --> B[可复用记忆资产]
    B --> C[更少 Turns]
    C --> D[更少返工]
    D --> E[更稳定的结果和更高的效率]

所以它是一台把"经验"沉淀成资产、再按身份装配给 Agent 的团队记忆控制板,而非又一个聊天记录仓库。资产有四类:Chat Memory(对话记忆)、Skill(可执行经验)、LLM-Wiki(文档知识)、Code-Graph(代码图谱)。

官方给了一个对照,能快速理解它和普通 RAG / 聊天历史到底差在哪:

聊天历史标准 RAGTencentDB Agent Memory
跨会话理解用户✅ Chat Memory
蒸馏出的可执行经验✅ Skill
文档结构与关联△ 切块检索✅ Wiki + 链接图谱
代码调用图与影响面△ 文本匹配✅ CodeGraph
归属 / 版本 / 状态
团队共享与 Agent 配装
私有 / 团队 / ACL

RAG 回答"能搜到什么",Team Memory 还回答"谁能用到、哪个版本有效、该配给哪个 Agent"。

二、整体架构:三件套 + 一个知识模块

一键部署拉起三个容器:memory-core、memory-hub(内含 Panel + Knowledge 合并镜像)、proxy。它们各自守一段职责:

服务目录默认端口定位
memory-coreMemoryCore/8420记忆核心:存储原始对话 + 异步提炼 L0→L3,提供 Memory Core v3 API 与 SDK
memory-hub(Panel)MemoryPanel/8125人类掌控的团队记忆面板:组队、审查/分享/配装资产、管理 Owner/版本/ACL(源码目录即 MemoryPanel/;一键部署里与 Knowledge 打包成 tdai-memory-hub 合并镜像,见下)
proxyMemoryProxy/8096兼容 OpenAI / Anthropic 协议的智能代理:零代码接入,做会话绑定与记忆注入
knowledge(MemoryKnowledge)MemoryKnowledge/8424Wiki 与 Code-Graph 的摄取、索引与查询服务(独立 npm run dev 默认 8421,一键部署合并进 tdai-memory-hub 镜像并映射到 8424

它们的协作关系,可以用下面这张图串起来:

mermaid
flowchart TB
    subgraph Clients[各 Agent 客户端 · base URL 全部指向 Proxy]
        WB[WorkBuddy]
        CC[Claude Code]
        CX[Codex]
    end
    Clients -->|user_key 身份凭证| Proxy

    Proxy["Proxy 8096<br/>auth → sessionInit → injection → 转发上游 LLM<br/>绑定 team / agent / task<br/>每轮注入 L2/L3 + Skill + Knowledge"]
    Proxy --> Core
    Proxy --> Hub

    Core["Core 8420 · L0→L3 存储 + 异步提炼"]
    Hub["Hub 8125 · 团队 / 资产 / ACL"]
    Knowledge["Knowledge 8424 · Wiki + CodeGraph"]

    Core --> Knowledge
    Hub --> Knowledge

概括三者定位:core 是脑(存与炼),hub 是操作台(人管治理),proxy 是网关(拦流量、绑会话、注记忆)。三者加 knowledge,组成完整的"沉淀 → 治理 → 装配"闭环。

这里先给 proxy 一个准确定位(来自 MemoryProxy/README.md):它是 transparent LLM request proxy——对客户端和上游模型都是"透明"的,协议不改,把 OpenAI /v1/chat/completions 和 Anthropic /v1/messages 原样转发,只在进出的路上多做几件事:鉴权、会话初始化、记忆注入、对话回写、用量上报。proxy 自身不持久化记忆内容本身(那是 MemoryCore 的职责),它只缓存运行态(session binding、注入缓存、Skill 状态等)到 Redis / ProxyStorage——这些缓存丢了能重建、不影响已沉淀的资产;所有记忆内容读写都走 MemoryCore Gateway(默认 :8420)。一句话:proxy 是"接入与转发"层,不是"存储与处理"层。

proxy 是全文的主角,也是对接时最需要理解的组件。接下来三节依次讲清它的三件事:它用什么凭证认人(§三)、一次请求在它内部走什么流水线(§四)、它怎么处理上游模型凭证(§五)。这三节铺完,§六的硬冲突结论就是水到渠成的事。

三、基础概念:两把 key 与 proxy 的身份模型

官方文档默认读者已经懂"user_key"这些概念,但没有解释。这一节把 proxy 的身份模型一次讲清——它是理解后面所有内容(流水线、鉴权模式、透传硬冲突)的钥匙。

3.1 两把 key:门禁卡与饭票

proxy 处在"客户端"和"上游 LLM"中间,它经手两种完全不同的凭证,名字相近但用途相反:

凭证交给谁用来干嘛长什么样
记忆身份 key(user_keyTencentDB Agent Memory proxy证明"我是哪个用户 / 哪个团队",决定我能读 / 写哪份记忆、用量如何上报归因sk-mem-xxxxxxxx
LLM 上游 key真正的模型服务商(OpenAI / ChatGPT / TokenHub…)付费调用模型OpenAI key、ChatGPT OAuth token、TokenHub key 等

一句话:user_key 是进 proxy 的"门禁卡"(管记忆归属),LLM 上游 key 是出 proxy 的"饭票"(管调模型)。§五讲的"服务端 key 模式 vs 客户端 key 透传",本质就是讨论"这两把 key 由谁提供、proxy 替不替你换"。

容易晕的点:ChatGPT 的 OAuth token 既是"LLM 上游 key"(用来调 chatgpt.com 的 Coding Plan),又不是合法的 user_key(它不是记忆系统的 sk-mem-xxx)。§六的硬冲突就源于此。

3.2 user_keyuser_id:proxy 为什么要"验一次"

proxy 不信任客户端自报"我是谁"。它每个请求都拿 user_key 去 MemoryCore 的 /v3/meta/auth/verify 验一次,换回一个稳定的内部身份 user_id(代码里写作 userId)。

user_id 解决三件事:

  • 归属:记忆资产(team / agent / task / Skill)记在谁名下;
  • 隔离 / ACL:只有该 user_id own 或 team 共享的资产才对它可见;
  • 计费归因:用量记到哪个账户(ClickHouse / Langfuse / Opik 等上报通道按 user_id 归因;但 Credit 上报体只有 SpaceId、无 user_id——MemoryProxy/src/credit-reporter.ts:10-15,即 Credit 维度按实例而非按人)。

对应的行为(MemoryProxy/src/auth.ts):

  • auth.enabled=true 时,proxy 强制验 user_key,验不过(包括"把上游 key 当 user_key 来验")直接 401;
  • auth.enabled=false 时跳过验证,userId 恒为空串 ""——这个"恒空"是 §六硬冲突的伏笔。

四、一次请求的完整流水线

先解释一个 URL 里反复出现的概念:spaceId(记忆实例 id)。各客户端接入时的 base URL 形如 http://<proxy>/codex/<spaceId>(不同客户端前缀不同)。它回答的问题是"这次请求的记忆,打到哪一个记忆实例(kernel)"。从源码看(MemoryProxy/src/routes/whitelist.ts),路由层只把 /codex/<spaceId>通配前缀剥离,不校验它是否真实存在;真正把它当实例标识用,是在两处:codexHandler.tscreateCodexTdaiClientserviceId: spaceId || config.tdai.serviceId,以及 getInstanceUpstreamConfigs(config.coreSkill, spaceId) 按实例拉取专属上游。

两种部署下 spaceId 的含义不同,这是新手最容易懵的地方:

  • 单实例(本文的本地 Docker 实验就是这种):整个栈只有一个 MemoryCore(本地内核),proxy 自动生成的配置里硬编码 tdai.serviceId: default。所以填 default(或不填回退 default)都指向这唯一一个实例;填任意其它字符串也会被当成 serviceId 去问本地内核——但本地内核就一个实例,所有 spaceId 实际都落到它身上。换句话说,单实例下 spaceId 本质是命名空间 / 前缀,没有"选不同库"的意义,照文档填 default 最省事。
  • 云上多实例(即腾讯云 Agent Memory 服务):腾讯云已把这项能力作为正式商业化服务(云数据库 Agent Memory)提供,在控制台「新建 Memory」即可开通实例,每个实例有云分配的唯一 ID(控制台「实例详情 → API 接入」里查访问地址与密钥)。此时 spaceId 填那个实例 ID 即可。一个 proxy 进程就能按 spaceId 路由到不同实例:serviceIdspaceId || 默认 指向对应 kernel 实例,getInstanceUpstreamConfigs(config.coreSkill, spaceId) 还能按 spaceId 拉到该实例专属上游——源码里的多实例路由就是为这种云部署准备的。

一句话:spaceId = 选哪台"记忆库"。本地单实例时 default 就是那台库;云上多实例时它就是各实例在控制台里的 ID。它不是面板里 team / agent / task 任何一项——那些是实例内部的会话级概念(见下文的"三元组绑定")。

一次带 spaceId 的主模型调用,在 proxy 内部经过这些阶段:

mermaid
flowchart LR
    A["auth(校验 user_key,换 user_id)"] --> B["sessionInit(弹 team / agent / task 选择表单)"]
    B --> C["injection(注入 L2/L3 记忆、Skill、Knowledge 到 system prompt)"]
    C --> D["rateLimit(spaceId × 模型 TPM/QPM 限流)"]
    D --> E["forward(转发到上游 LLM)"]
    E --> F["extract(每轮结束异步回写对话 + 写 L0)"]
    F --> G["report(ClickHouse / Langfuse / Opik / Credit 上报)"]

上图是通用主模型调用流水线,两点细化:① Codex 走 /responses 协议时,codexHandler.ts 不含 rateLimit 阶段,限流对这条链路不生效;② L2/L3 记忆在 session 注册后由各 injector 以 session_init 策略缓存一次(非每轮重拉上游),效果等价于"每轮注入"。

三个关键点:

  • ==三元组绑定:用记忆必须落到具体 team / agent / task==。sessionInit 阶段客户端会收到一次选择表单,选完 proxy 记住本次会话的绑定,后续每一轮自动注入该 Agent 的 L2/L3 记忆 + Skill + Knowledge,直到会话结束。
  • 身份隔离:表单里能看到哪些 team / agent / task,由 §3.2 的 user_id 决定——只有该用户 own 或同队的才出现。共享靠"同队成员",隔离靠"身份"。

一个关键前提:这条完整流水线只在 proxy 能拿到 userId 时才跑全,即 auth.enabled=true、客户端发合法 sk-mem-... user_key 的模式。在客户端 key 透传 + auth.enabled=false 下(§5.2),请求会在 sessionInit 一步直接 passing through unintercepted——表单不弹、注入/回写整体休眠。这是 §六要展开的核心问题。

五、上游鉴权的两种模式

回到我最关心的问题:客户端自带的模型订阅(比如 Codex 的 Coding Plan),走代理后还能不能用? 答案藏在 proxy 的上游鉴权模式里。proxy 同时支持两种模式,且"透传客户端自己的 key"是代码里的一等公民(依据见 src/handler.ts 的注释与 codexHandler.ts 的实现)。

5.1 服务端 key 模式(默认)

  • 部署方在 upstream.apiKey 里填入 proxy 自己的上游凭证。
  • 客户端发来的 Authorization记忆系统的身份凭证 sk-mem-xxx,proxy 校验通过后,upstream.apiKey 替换掉它,再转发到 upstream.url
  • 此时"实际调哪个模型"由 proxy 的上游配置决定,客户端自带的那把订阅 key 不参与上游调用。

这是官方一键部署的默认模式(start-all.sh 让你填"memory group + proxy group 两组 LLM 参数"):proxy 拿着统一的上游 key(如 TokenHub),客户端只交 sk-mem-xxx 做身份,集中计费、集中治理。

5.2 客户端 key 透传模式

  • upstream.apiKey 留空(或某 agent 只配 url、不配 apiKey)。
  • proxy 原样保留客户端带来的 Authorization 转发到上游,模型名也照客户端请求转发。
  • 此时客户端自带的模型订阅(Coding Plan / 自己的 key)继续生效,proxy 只负责加记忆 + 抓对话。

5.3 兜底规则:进了 agents 表就切断全局兜底

per-agent 的 key 解析有三种情况(MemoryProxy/src/handler.ts 源码注释):

ts
// Per-agent apiKey resolution — three cases:
//   (a) no entry in agents map           → global upstream.apiKey (兜底)
//   (b) entry present, apiKey empty      → "" (passthrough, keep client key)
//   (c) entry present, apiKey non-empty  → agent.apiKey (server-side key)
// The presence of an entry (case b/c) is what cuts the global fallback.

即:想让某个客户端走透传,就把它加进 agents 表且不写 apiKey;没进表的客户端继续走全局兜底。两种模式可以按 agent 混用。

5.4 透传的凭证二义性

透传能保住客户端自带模型,但有一个必须知道的代价:proxy 的身份鉴权闸(§3.2 的 verifyUserKey)和"透传客户端上游 key"抢的是同一个 Authorization

ts
// MemoryProxy/src/handler.ts(early auth:body 解析之前就校验)
const earlyAuthHeader = c.req.header("authorization") ?? c.req.header("Authorization") ?? "";
const earlyApiKey = extractBearerToken(earlyAuthHeader);
const earlySpaceId = extractSpaceIdFromPath(c.req.path) ?? "";
const earlyVerify = await verifyUserKey(earlyApiKey, earlySpaceId);
if (earlyVerify.rejected) {
  return c.json({ error: `Authentication failed: ${earlyVerify.rejectReason ?? "unknown"}` }, 401);
}
  • 服务端 key 模式下没问题:客户端发 sk-mem-xxx,proxy 拿它做身份校验,再换成上游 key——身份与上游凭证各用各的。
  • 透传模式下客户端发的是自己的上游 key(比如 ChatGPT OAuth token),proxy 的 auth 闸读同一把头去内核校验——这把上游 key 不是合法的记忆 user_keyauth.enabled=true 时会直接 401。

所以现实里透传模式的取舍是:

"保留客户端自带模型 / 订阅" 与 "开启 proxy 的用户级身份与计费" 在同一把 Authorization 头上是互斥的——除非把凭证分开架构。实操上,透传 agent 通常配合 auth.enabled=false(proxy 不做用户级闸门)。

而 auth 一关,就触发了下一节那个更深的问题。

六、透传模式的硬冲突:保住 Coding Plan,就激活不了记忆

§5.4 讲的是"身份闸门"和"透传上游 key"抢同一把头的 401 张力——关掉 auth 似乎就绕过去了。但实测(Windows Codex 走 WSL docker proxy)+ 源码证明了一个更深的、代码级的死锁:==在 OAuth 透传模式下,记忆激活(session-init 表单)根本不会触发,proxy 退化为纯透传管道==。

证据链(基于 feat/server_team @ 0468a2a 分支源码,非推测):

① 表单状态机要求 userId,缺则直接放行、不发表单MemoryProxy/src/session/codebuddy/init.ts,uninitialized 分支):

ts
if (!state || state.status === "uninitialized") {
  if (!userId) {
    console.warn(`[session-init:cb] ... no userId, passing through unintercepted`);
    return { intercepted: false };   // 表单根本没发,请求直接透传
  }

userId 的唯一来源是 verifyUserKey(§3.2)。codexHandler.tshandleSessionInit(sessionKey, userId || null, ...) 传进去——这个参数没有 anonymous 回退("anonymous" 只用在会话存储的 identity 标签上,不是表单的 userId)。auth 关 → verifyUserKey 恒返 { userId: "" }userId 恒空。

③ 死锁根源是同一把头的二义性(§5.4 那段代码):codexHandler.tsextractBearerToken(authorization) ?? x-api-key ?? "" 把同一个值同时当"身份凭证"去 verifyUserKey 和"上游凭证"去转发(?? 短路,bearer 存在时 x-api-key 救不了):

  • requires_openai_auth=true(透传 Coding Plan)→ bearer 是 ChatGPT OAuth token → 不是合法 user_key → auth 开必 401(Coding Plan 断);
  • auth 关 → userId 恒空 → 表单永不发(记忆休眠)。

两个分支合起来,"客户端 key 透传"与"记忆注入 / Recall / 归档"在同一套 proxy 配置下互斥

你要的结果proxy auth上游凭证后果
保住 Coding Plan(OAuth 透传)ChatGPT OAuth → chatgpt.com/backend-api/codex✅ 模型照常;❌ 记忆休眠(proxy = 纯透传)
激活记忆(team / agent / task 绑定)sk-mem-... user_key → 托管上游✅ 记忆全功能;❌ Coding Plan 不生效

简言之:==Codex 走 proxy,"保 Coding Plan" 与 "用记忆" 当前只能二选一==。想两者兼得,只能改 proxy 源码让 userId 从 config / system 身份出(绕开 user_key 验证),或等官方把 Codex 做成"记忆一等公民"时解决这个凭证冲突。§九的实战决策会据此给出两条具体路线。

七、客户端接入:一套 proxy 共享记忆

核心原理只有一句话:一套 Proxy,协议不变,所有客户端把 base URL 指向它,带上各自的 user_key。因为大家打到的是同一个 Proxy、同一个 Core,所以"共享"本质上是——同一 Team 下的不同客户端,读到的是同一份 Chat Memory / Skill / Wiki / CodeGraph。

各客户端的差异只在"配置写在哪里",接入机制完全一致:

客户端配置落点接入本质
Claude Code环境变量 或 ~/.claude/settings.jsonbase URL → Proxy + user_key
CodeBuddy~/.codebuddy/models.json同上
WorkBuddy~/.workbuddy/models.json同上
Codex~/.codex/config.toml同上;透传模式保留自带模型(见 §5.2 / §六)
DeepSeek Harness~/.dsh/settings.yaml + .credentials.yaml同上
OpenCode~/.config/opencode/opencode.json同上
Hermes / OpenClaw配置文件 + Header 预选(x-team-id/x-agent-id/x-task-id同上,但需静态指定 x-conversation-id

注:官方支持矩阵(README 图标矩阵)明确列出 7 个客户端——DeepSeek Harness(矩阵第一格,INSTALL.md 有专节)、Claude Code、Codex、CodeBuddy、WorkBuddy、Hermes、OpenClaw;OpenCode 虽不在图标矩阵,但 INSTALL.md 有 agents/opencode/ 正式接入条目。任何兼容 OpenAI / Anthropic 协议的 harness 同理可接——只要能把 base URL 指向 proxy 即可。

以 Claude Code 为例,start-all.sh 启动完成后会打印一段可复制的命令(服务端 key 模式写法,客户端 apiKey 用记忆凭证 sk-mem-xxx):

bash
export ANTHROPIC_BASE_URL=http://127.0.0.1:8096/claude-code/default
export ANTHROPIC_AUTH_TOKEN='sk-mem-<随机32位>'
claude --model <上游模>

⚠️ 这个 ANTHROPIC_AUTH_TOKEN 实际是 start-all.sh.admin-key 文件读出的 admin keydeploy/global-images/start-all.sh:67-68),不是给业务用户签发的 user_key。本地一键体验无妨;真要多人共用,应在面板里给每人各自签发 sk-mem-... user_key,别把 admin key 当 user_key 分发。

首次开新会话,Proxy 会用 AskUserQuestion 弹出 Team → Agent → Task 连续选择表单,选完即绑定并自动注入记忆。

注意 MemoryProxy/README.md 里那句"keep the rest (apiKey, model, ...) unchanged"要结合上下文看:默认拓扑下 proxy 持有上游 key,所以客户端 apiKey 实际被换成了 sk-mem-xxx(记忆身份),客户端原来自带的上游 key 在这个模式下不参与上游调用。如果你想保留客户端自带模型,就走 §5.2 的透传模式——这时客户端的 apiKey 才是它自己的上游 key,且要面对 §5.4 的凭证二义性与 §六的记忆休眠。

八、资产的管理与使用

四类资产(Chat Memory / Skill / Wiki / CodeGraph)分属不同服务,但治理哲学一致:默认私有 → 明确动作才共享 → 按需装配。本节分别看它们怎么被管理、怎么被使用。

8.1 Skill:可执行经验

Skill 不止是一段 Prompt。 文档对 Skill 的定义是:它有版本、资源文件、触发边界、执行步骤、验证规则——是可版本化、可审计的资产,而非塞进 system prompt 的一句话。Agent 做完复杂工作后,可以从对话和工具调用里提炼 Skill(通过 conversation/add 这条主链路),再在需要时导入指定 Agent 上下文。

生命周期:默认私有 → 审核 → 团队分享 → 配装

mermaid
flowchart TB
    S0[个人 Skill(默认 private)] --> S1[Hub 里人工审查]
    S1 --> S2[分享给团队(visibility = team)]
    S2 --> S3[Hub 给目标 Agent 配装(Loadout / 绑定)]
    S3 --> S4[其他 Agent 也能用]

Skill 数据面(POST /api/v1/skill/*)有独立存储,ID 前缀 skl-,规则是团队内可读、owner agent 可写,身份字段放在 body 而非 Header。其支持的能力以 SKILL_ACTIONS 常量数组为准(MemoryPanel/src/panel/api/skill-actions.tsfeat/server_team @ 0468a2a):

ts
export const SKILL_ACTIONS = [
  'create', 'update', 'patch', 'delete', 'get', 'list', 'search', 'versions',
  'files/write', 'files/remove', 'files/read', 'listing', 'extract',
  'export', 'conversation/add',
] as const;

共 15 个 action(注意 files/* 用斜杠命名)。exportconversation/add 都在这个数组里:export 在内核数据面已有 handleExport 路由(MemoryCore/src/gateway/skill-handlers.ts:1167),conversation/add 是提炼 Skill 的提取主链路。数组就是数据面能力的权威清单,某个 action 是否对面板开放取决于具体部署版本——不要凭注释或 Roadmap 文字反推代码状态。

在 Hub 里如何"装备"给团队使用

  • 资产池聚合/agent-overview/bootstrap 一次性返回 Agent 概览所需的全部资产引导数据(skill / code-graph / wiki / chat-memory 资产池 + 各 agent 挂载计数)。
  • 绑定资产到 Agent(作用对象是 chat_memory 类资产,详见 §8.2):set-agent-fixed 批量把 chat_memory 资产绑成某 Agent 的固定资产;allocate 把记忆借入 Agent(≤2 条校验)。
  • 级联清理:删 Agent 时 delete-cascade 先级联删其名下所有 active Skill,再归档 Agent(archive 内部顺手清 chat_memory)。
  • 可见性语义private 严格属 Owner(团队管理员也看不到);team 面向全队;restricted 通过 User/Role/Agent ACL 精确授权;agent 用于同团队 Agent 的定向装配。

关键点是:分享是一个明确动作,而非默认泄漏。Skill 练会一次,全队可用,但每一步都过 Hub 的人工审查和权限开关。

这些 Skill 怎么到达客户端(以 Claude Code 为例)

理解了 Skill 是什么、怎么在 Hub 里装备,剩下的问题是:客户端(比如 Claude Code CLI)是怎么"拿到"这些 Skill 的?答案是——客户端几乎零配置,Skill 由 proxy 在转发前注入请求的 system prompt

Claude Code 一侧只需把 API 指向 proxy、带上 user_key,其余交给 proxy:

json
// ~/.claude/settings.json 的 env 字段(持久化)
{
  "env": {
    "ANTHROPIC_BASE_URL": "http://127.0.0.1:8096/claude-code/default",
    "ANTHROPIC_AUTH_TOKEN": "<面板 API Key 页取的 sk-mem-... user_key>",
    "ANTHROPIC_MODEL": "claude-opus-4.7"
  }
}

请求打过去后,proxy 依序做 auth(校验 user_key)→ sessionInit(选 team/agent/task 表单)→ injection(把 L2/L3 记忆、Skill、Knowledge 注入 system prompt)→ 转发上游 LLM。整个过程客户端无感:它不知道 Skill 是"文件"还是"文本",只知道 system prompt 多了一段。

注入形态(由多个独立 injector 拼进请求的 body.systemfeat/server_team @ 0468a2a):Skill、记忆、知识各由一个 injector 负责,落到不同锚点:

markdown
## Skills (mandatory)
<available_skills>...</available_skills>   # SkillInjector:本 agent 名下云端 skill 列表

<tdai_profile_memory>                      # TdaiProfileMemoryInjector:L2/L3 画像
  ...
</tdai_profile_memory>

<knowledge_tools>                          # KnowledgeToolsInjector:团队 wiki/code-graph 资源
  ...
</knowledge_tools>

不同客户端的标签略有差异:Claude Code 走 Anthropic 协议时,记忆块标签是 <tdai_profile_memory><user_memory> 是 Codex / WorkBuddy 用的标签。## Skills (mandatory) 这个 header 文案来自 skill-injector.ts:62

模型读到 Skill 描述,就能在对话里调用对应能力。此外会话内还支持 mem:sync / mem:create-skill / mem:session-reset 等命令,直接操作记忆库。

一个和 §六的呼应:上面这条注入链依赖 auth 校验通过 + sessionInit 跑全(拿到 sessionInfo 后才拉 assetCapabilities、才把资产放进注入管道)。所以透传模式(auth 关)下,Skill 同样注入不到客户端——它和"记忆休眠"是同一套门控。想让 Claude Code 用上 Skill,必须走服务端 key / team-proxy 模式(auth 开 + user_key),代价是放弃 Coding Plan 透传。

服务端注入 vs 本地 slash command:本质区别

这里容易和"在客户端本地用 slash command 加载 Skill"搞混。两者最终都让大模型看到一段 Skill 文本,但发送到接口的"装配方式"有本质不同

维度服务端 proxy 注入(本文机制)本地 slash command / .claude/skills/*.md
Skill 文本落在请求哪system prompt 内联全文(每次请求都带 ## Skills 段)system 里只注册名称 + 描述(frontmatter 摘要);模型调用该 Skill 时,才把 SKILL.md 正文读进 messages / tool 结果
谁决定注入哪些proxy 按 user_id + 绑定的 team/agent/task + assetCapabilities 动态装配用户手动 /xxx,或 Claude Code 启动时扫描目录按需加载
内容来源远端 MemoryCore(跨会话/用户沉淀,随记忆更新而变)本地磁盘文件(手写 / git 同步,静态
跨机器 / 跨用户共享天然(同一中心记忆库)不天然(每台机器各自一份文件)
对客户端侵入:只改 system 字段,客户端无感需在客户端布置 skills 目录 / plugin,且模型多一层"技能调用"抽象
大模型的可见性每次都看到完整 Skill 正文(被动喂给默认只看到目录摘要,正文按需加载(模型主动选用)

一句话概括本质差异:服务端注入 = 把"团队知识库"当上下文,中心化、按身份动态装配、客户端无感;本地 slash command = 客户端本地的"能力模块",按需加载、可带执行动作、单机文件。 前者解决"团队经验自动随身份沉淀并复用",后者解决"个人/项目把常用工作流固化成本地命令"。两者不冲突,甚至可并存:proxy 注入团队级 Skill,本地 slash command 管个人级工具。

8.2 Chat Memory:对话记忆

分层生长 + 每 Agent 独立记忆。 每个 Agent 创建时自动获得独立记忆,下次对话不必再从自我介绍开始。记忆按 L0→L3 逐层生长。文档里那句"别重构旧鉴权模块,移动端还在用"——这种代价很高的上下文,就不该靠人每次提醒,而该留在 Chat Memory 里。

Hub 侧的审查与治理。 Chat Memory 在 Panel 有一整套 POST /api/v1/chat-memory/* 接口,覆盖"审查 + 治理":

  • 资产盘点team-assets(团队已共享,visibility=team)、agent-fixed(Agent 名下固定资产,仅 Owner 可见)、my-agents(我的资产分配)、mine(我名下的)。

  • 建与导入create 建独立记忆(mem-xxx);import 把历史对话导入某 Agent 的 L0,实现冷启动。单次上限 100 条,面板层与内核 schema 双重拦截(MemoryPanel/src/panel/http/routes/chat-memory.ts):

    ts
    // 权限:agent.owner = me(只能往自己的 agent 导入)
    if (!Array.isArray(rawMessages) || rawMessages.length === 0) {
      return respondControlError(c, 400, "MISSING_MESSAGES");
    }
    // tdai conversationAddRequestSchema 上限 100,超了内核会挡回来 → 面板层也 100
    if (rawMessages.length > 100) {
      return respondControlError(c, 400, "TOO_MANY_MESSAGES");
    }
    // ...走数据面 conversation/add;user_id 用 agent.owner_user_id(数据面按 owner 隔离)
    const addEnv = await deps.kernelHttp.postEnvelope("/v3/conversation/add", {
      team_id: teamId, user_id: agent.owner_user_id, agent_id: agentId,
      session_id: sessionId, messages,
    }, cred);
  • 共享与配装patch-scope 在 team ↔ private 间改可见性;set-agent-fixed 批量绑到 Agent;allocate 借入、unbind 解绑(自己绑自己会被拒)。

  • 分层操作layer 懒加载 L0/L1/L2/L3;layer-update 编辑 L1/L2/L3(仅 Owner);layer-delete 删 L0/L1(仅 Owner);clear 一键清空内容但保留资产壳。

  • 检索search 做 L0/L1 关键词检索。

  • 读权限:Owner / visibility=team(且 caller 是同 team 成员)/ 已被借入,三者满足其一才可读,否则 403 ASSET_NOT_ACCESSIBLE

冷启动别从零开始。 Panel 支持直接导入三类已有资产——代码仓库(CodeGraph 自动索引)、文档文件(Wiki 自动生成结构化页)、历史对话 session(自动抽取 Skill 与 Chat Memory,即上面的 import)。把"已经付过的学习成本"变成团队的"存档",新 Agent 第一天就能继承经验,而非从零开始学你的项目。

8.3 Wiki 与 Code Graph:按需查询的知识工具

这两个属于 MemoryKnowledge 服务(8424),共同点是:平时只是可用的工具,只有真正需要时,经 /v3/tools/list + /v3/tools/call 才进入上下文,绝不整库注入。

Wiki 知识库。 生命周期(建壳 → 写源 → 触发抽取 → 页面就绪):

mermaid
flowchart LR
    A["wiki/create(draft,幂等)"] --> B["wiki/raw/write(必须先写源文件,单文件 ≤512KB、总 ≤5MB)"]
    B --> C["wiki/ingest(draft → pending → processing → ready / failed)"]
    C --> D["page/ls · page/read · page/write · page/rm(管理抽取后的页面)"]
  • 链接图谱(Link Graph)wiki/graph 返回 { nodes, edges, communities };未 ready 时返回空图(不是报错)。搜索 wiki/search 支持 hop(图谱扩展跳数 0–5)和 decay(衰减系数 0–1),响应带 links 用于沿链接下钻。

  • Agent 怎么用/v3/tools/list 暴露 7 个只读 Wiki 工具,再 /v3/tools/call 取具体内容(MemoryKnowledge/src/routes/tools.ts):

    ts
    const WIKI_TOOLS: HttpToolDef[] = [
      { name: "get_info",   description: "获取 wiki 元信息(名称、状态、页面数等)。" },
      { name: "search",     description: "BM25 全文搜索 wiki 页面内容。" },
      { name: "list_pages", description: "列出所有页面引用(id + title + path)。" },
      { name: "read_page",  description: "读取指定页面完整内容。" },
      { name: "get_graph",  description: "获取知识图谱结构(nodes, edges, communities)。" },
      { name: "list_raw",   description: "列出原始上传文件。" },
      { name: "read_raw",   description: "读取指定原始文件内容。" },
    ];
  • 管理list / get / update-meta / delete(批量且容错,单个失败写进 failed 不整体报错),另有 auto-sync 调度。

Code Graph 代码图谱。 生命周期(建即构建):

mermaid
flowchart TB
    Create["code-graph/create(pending,自动触发 build;同 repo_url + branch 幂等)"] --> Process[processing]
    Process --> Ready["ready / failed"]
    Sync["code-graph/sync(手动重建索引)"] -. "手动重建索引" .-> Process
  • 索引内容:代码符号、文件、调用关系、影响路径。

  • Agent 怎么用/v3/tools/list 暴露 9 个只读工具(MemoryKnowledge/src/routes/tools.ts,工具名的单一真相源):

    ts
    const CODE_GRAPH_TOOLS = [
      "get_info", "search", "explore", "callers", "callees",
      "impact", "node", "status", "files",
    ];
    // 对外暴露的 codegraph 查询工具名(不含 get_info):
    export const CODEGRAPH_QUERY_TOOL_NAMES = [
      "search", "explore", "callers", "callees", "impact", "node", "status", "files",
    ];

    其中 get_info 返回仓库元信息(不需 ready);其余 8 个查询工具需索引 ready 才生效(非 ready 时返回 { text:"", isError:false },HTTP 200,不是错误)。explore 是首选——一次调用按文件分组返回相关符号完整源码;impact 做影响分析(depth 默认 2,可配);files 支持 tree / flat / grouped。

  • 当前约束:优先支持公开 HTTPS 仓库,私有仓库 / SSH 凭证接入仍在完善。

设计渊源(官方致谢):CodeGraph 模块复用了 colbymchenry/codegraph 的代码;Skill 资产部分复用了 Hermes Agent 的相关实现;Wiki 的"让 LLM 增量维护、可持续复利的知识产物"思路来自 Karpathy 的 LLM Wiki。

这些知识工具怎么到达客户端(以 Claude Code 为例)

上半节讲的是"服务端有什么工具"(MemoryKnowledge 经 /v3/tools/list + /v3/tools/call 暴露的 7+9 个只读工具)。客户端(Claude Code / Codex)要真正调到它们,有两条通道——且都是 agent 自主决策,你不用手动触发:

  • 通道 A — Proxy 被动注入(透传态下休眠)。proxy 的 KnowledgeToolsInjectorMemoryProxy/src/injection/injectors/knowledge-tools-injector.ts)在会话 prewarm 时向 system prompt 注入 <knowledge_tools> 块,列出团队 wiki / code-graph 资源的 idurlname,以及 wiki 资源about(摘要,code-graph 不注入 summary),并给"两步自发现"配方:先 tools/list 拿工具目录,再 tools/call 取内容。注册门控只有三条(injection/index.ts:499 shouldRegisterKnowledgeInjector):injectorsknowledge + knowledge.enabled + serviceToken 非空——本身不查 userId。真正让这条链路休眠的是透传态下 session-init 整体被 bypass:auth.enabled=falseverifyUserKey 恒返空 userId → session-init 直通(init.tsno userId 分支),anthropicHandler.ts:864 据此 bypassed → skipping all injection,整条注入链(含 knowledge)一起跳过。这正呼应 §六:保 Coding Plan 的代价是知识注入一起休眠。

  • 通道 B — MCP server 直连(不依赖 proxy,你配置下能真用)。MemoryKnowledge 自带 stdio MCP server(src/mcp/server.ts + tools.ts),把知识查询暴露成 12 个原生 MCP 工具:Code Graph 8 个(code_search / code_explore / code_callers / code_callees / code_impact / code_node / code_status / code_files)、Wiki 4 个(wiki_search / wiki_read / wiki_list / wiki_graph)。起法:

    bash
    KNOWLEDGE_API_URL=http://127.0.0.1:8421 \
    KNOWLEDGE_API_TOKEN=<面板给的 service token> \
    node dist/mcp/server.js

    在 Claude Code 侧用 .mcp.jsonclaude mcp add 注册即可。注册后这 12 个工具直接进函数调用 schema,agent 自主调用,完全不碰 proxy,与保 Plan 的透传配置无冲突。注意两点:① MCP 工具每次调用要传 wiki_id / code_graph_id,需你显式告诉 agent 这些资源 ID;② 上面 KNOWLEDGE_API_URL8421 是 MemoryKnowledge 独立 npm run dev 的默认端口,一键部署里它被映射到 8424(见 §2 端口表)——按你实际监听端口填,两个端口指向同一个服务、只是部署方式不同。

场景对照:Code Graph 用于理解实现 / 架构 / 定位 bug(code_explore 首选)、重构前评估影响面(code_impact)、查调用链(code_callers / code_callees)、看项目结构(code_files);Wiki 用于查设计意图 / runbook / onboarding(wiki_searchwiki_readwiki_graph)。两条 ingestion 链路:Wiki 来自上传或拉取文档经 LLM 抽取,Code Graph 来自 git clone 建索引(MemoryKnowledge/README.md:10)。

一句话:要真让 Claude Code 用上 Wiki / Code Graph,最干净的做法是通道 B 单独挂 MCP server;通道 A 需在 §六的硬冲突里二选一(开 auth 牺牲 Plan)。

8.4 一个值得点赞的设计细节:L0/L1 不进 prompt,做成工具

很多人担心"每轮往 system prompt 灌记忆会把上下文撑爆、还把上游 KV cache 打挂"。这个项目的解法是分层的:

  • L2/L3(Agent Profile / Team 记忆)直接注入 system prompt,做"快速进入语境";
  • L0/L1(原始对话 / 会话级关键信息)走工具化路线,暴露成只读工具(<tdai_memory_tools>)而非注入 prompt,让模型主动按需查询——官方注释明确写"避免上游 KV-cache 失效"。

回退链路是:平时用 L2/L3 快速 bootstrap;需要具体事实时,BM25 + 向量检索 + RRF 回退到 L1/L0;再叠加条数 / 字符预算 / 超时三重限制兜住。这个"注入 vs 工具化"的取舍,是对上游缓存经济性的尊重,值得在自家做记忆系统时抄作业。

九、实战:Codex 怎么接 proxy(两种路线怎么选、怎么配)

机制讲完,落到部署。本节两套配置目标客户端都是 Codex CLI——即把 Codex 的 Coding Plan 接到 Memory Proxy 上。其它客户端(Claude Code / CodeBuddy / WorkBuddy)改 base URL 的接法一样,只是 Codex 走 OpenAI Responses 协议、带 ChatGPT OAuth token,有几处特有坑,下文单独点出。基于 §五、§六,Codex 实际只有两条路线:

9.1 决策表

诉求选哪种模式客户端 apiKey 填什么上游模型由谁决定注意点
想一键部署、集中计费治理、要记忆全功能服务端 key(默认)sk-mem-xxx(记忆身份)proxy 的 upstream.apiKey客户端自带订阅 key 不参与上游调用
必须用自己订阅里的模型(如 Codex 的 Coding Plan)客户端 key 透传客户端自带凭证(ChatGPT OAuth token / 自己的上游 key)客户端请求里的 modelauth 必须关;关掉后记忆激活(session-init)不触发,记忆注入/回写休眠(§六),proxy 退化为纯透传管道
多人多 Agent 共用同一份记忆两种都可,统一走 Proxy各自 sk-mem-xxx取决于选哪种模式共享靠 team 维度,隔离靠 user_key

9.2 路线 A:保 Coding Plan(透传模式,实测可用)

proxy 侧配置(源码部署写法):

yaml
# MemoryProxy/config.yaml(节选)
upstream:
  url: https://chatgpt.com/backend-api/codex      # Codex 的 ChatGPT Coding Plan 后端
  apiKey: ""                           # 留空 → 透传客户端自己带的 ChatGPT OAuth token
  agents:
    codex:                             # 也可只在这里配,其它 agent 走全局兜底
      url: "https://chatgpt.com/backend-api/codex"
      # 不写 apiKey → 透传 Codex 的 ChatGPT OAuth token
auth:
  enabled: false                      # 透传下必须关掉 proxy 用户级闸门,否则 OAuth token 被当无效 user_key 拦 401

Codex 客户端侧配置~/.codex/config.toml,这是整套方案成立的前提):

toml
model = "gpt-5.6-luna"                # 你 Coding Plan 里实际可用的模型
model_provider = "team-proxy"

[model_providers.team-proxy]
name = "TDAI proxy"
wire_api = "responses"                             # Codex 走 OpenAI Responses 协议
base_url = "http://127.0.0.1:8096/codex/default"   # proxy 地址 + /codex/<spaceId>
requires_openai_auth = true                        # 关键:让 Codex 发 ChatGPT OAuth token
# 不要写 experimental_bearer_token —— 那会把 sk-mem-... 发给 chatgpt.com 导致 401
disable_response_storage = true

为什么这样能通(逐条源码依据,基于 feat/server_team @ 0468a2a 分支)

  • proxy 原生支持 Codex 的 Responses 协议:白名单 whitelist.ts/responses 登记 upstreamEndpoint: "/responses"protocol: "openai",由 codexHandler.ts 处理——Codex 的 wire_api="responses" 正走这条,不会 404。
  • 路径改写规则:proxy 收到 …/codex/<spaceId>/responses 后剥掉 /codex/<spaceId> 前缀,再拼 upstream.url + "/responses"。故 upstream.url 必须填 https://chatgpt.com/backend-api/codex(Coding Plan 真实端点,OAuth token 合法);填 https://api.openai.com/v1 会拼成 /v1/responses 且带 ChatGPT OAuth token → 平台 API 不认,401。
  • apiKey 为空 → 原样透传客户端 Authorizationauth 关 → 不 401handler.ts 写明空 key 透传,auth.tsinitAuth!enabled 时置 config=nullverifyUserKey 直接放行。
  • 模型保真 + Token 刷新:docker 配置不含 creditPricing,价目表空时 model gate 跳过,body.model 原样转发;ChatGPT OAuth token 由 Codex CLI 自管(~/.codex/auth.json 自动刷新),proxy 不介入。

Docker 一键部署怎么填:proxy 的 config.yamlstart-proxy.sh.envPROXY_UPSTREAM_* 生成后挂进容器(/data/config.yaml,只读、每次启动重新生成),非手写;PROXY_UPSTREAM_URL → upstream.urlPROXY_UPSTREAM_API_KEY → upstream.apiKey,且仅含全局 upstream。

  • 服务端 key 模式:两个变量都填非空。
  • 透传模式:PROXY_UPSTREAM_URL=https://chatgpt.com/backend-api/codex不能留空,它是转发目标),PROXY_UPSTREAM_API_KEY= 留空。坑:stock start-proxy.shrequire_vars 把空值当缺失 exit 1——需把该变量从清单移除(或放宽空值判断)才能用留空触发透传。
  • auth 默认关(PROXY_ENABLE_AUTH=0);若开 PROXY_FULL_STACK=1(auth 开),透传就得另发记忆 user_key
  • 精细控制(如仅 codex 透传、其余服务端 key):.env 不支持,需自备含 upstream.agents.codexconfig.yaml 挂到 /data/config.yaml,绕过自动生成。

9.3 路线 B:用记忆(team-proxy 全栈)

proxy auth.enabled=true,Codex 侧改用记忆身份凭证、上游改托管地址:

toml
[model_providers.team-proxy]
wire_api = "responses"
base_url = "http://127.0.0.1:8096/codex/default"
experimental_bearer_token = "sk-mem-..."   # 从面板取 user_key

这是 agents/codex/README.md 给 Codex CLI 的现成配置(上游如 copilot.tencent.com)。结果:session-init 激活、Team→Agent→Task 表单、记忆注入/回写/归档全功能;代价是 Coding Plan 不生效(上游不是 chatgpt.com,且 sk-mem-... 不是 ChatGPT 凭证)。

9.4 为什么两条路不可兼得

一句话:记忆激活要 userIduserId 只能由 auth 验 user_key 产生,而透传时 bearer 是 ChatGPT OAuth(非合法 user_key)——完整的代码级证据链见 §六。想要"记忆 + Coding Plan 两全"只能改 proxy 源码(让 userId 从 config / system 身份出),或等官方解决该冲突。

十、边界与总结

几个边界与坑

  • proxy 是协议层拦截,不是魔法:生效前提是客户端把 LLM 调用的 base URL 指向它。模型进程内本地跑、或 endpoint 写死不可改 base URL 的客户端接管不了——只能走 Core 的 conversation/add 直连。WorkBuddy / CodeBuddy / Claude Code / Codex 都支持改 base URL,都接得上。
  • 模型与身份在同一把 Authorization 头上有张力(§5.4):开 auth 用记忆就牺牲透传模型,关 auth 保模型就牺牲记忆(§六)。想两全得在凭证层额外设计。
  • proxy 不存记忆:读写都走 MemoryCore Gateway(默认 :8420)。proxy 挂了不影响已沉淀资产,只影响本轮注入/回写。
  • Wiki / CodeGraph 异步构建:建完等 ready 才能被工具检索;CodeGraph 当前优先公开仓库,私有 / SSH 仍在完善。
  • 计费语义:仅 TokenHub 上游走 Credit 定价上报;你透传到自己 key 的上游 CreditDelta=0,用量直接体现在你自己的上游账单,proxy 不重复计费。
  • 别神话 benchmark:官方 PersonaMem 48%→76%(+59%),测的是"长交互后 Agent 能否正确理解并应用用户信息",不等于接上就所有任务暴涨,效果取决于你往 Hub 喂了什么。Codex CLI 转发已 shipped(README 图标矩阵已列、CHANGELOG 有记录),但 §六的凭证冲突使它无法同时激活记忆;README 的 v2.0.1 Roadmap 提到 "Codex (IDE Plan mode) support"——当前边界就是 §9.1 决策表:接转发 ✅ 现在就能,接记忆 ❌ 与 Coding Plan 互斥(§六)。

一句话总结

架构哲学:记忆沿 L0→L3 分层生长,文档与代码转成 Wiki / CodeGraph 这类可复用资产(L0/L1 还刻意做成工具而非注入,尊重上游 KV cache);所有资产统一登记为 Memory Asset,靠 Fixed Binding + ACL 在 Team / User / Agent / 可见性维度收口;Agent 按需调 /v3/tools/list + /v3/tools/call,而非整库注入。

工程上真正让它"跨 Agent 共享"的,是 proxy 这层统一网关 + 统一身份 + 统一资产存储:客户端形态各异,只要 base URL 指向同一 Proxy、带同一 team 的 user_key,记忆就在团队维度汇流;Hub 把"分享"做成可审计的人工闸门——这是它和个人笔记工具最本质的区别。

而对接层面最重要的判断:透传和记忆是两件事。透传只保住模型与传输;记忆激活依赖 session-init,而它要的 userId 由 auth 验 user_key 产生,与 OAuth 透传在同一把 Authorization 头上互斥。选接法前先想清楚要哪头——这是 §六论证过的结论。