学习笔记 · Obsidian

Managed Deep Agents TypeScript:扩展能力与安全边界

LangChainTypeScriptMCP

结论先行

TypeScript 版把 authored tools、MCP、middleware、memory、sandbox 和 identity 分成不同的扩展面。它们可以组合,但不能互相替代:sandbox 提供隔离环境,不提供业务授权;identity 证明调用者,不约束工具副作用;memory 跨线程持久化知识,却必须被视为不可信输入。

生产安全必须同时覆盖身份、线程归属、工具审批、文件权限、远端工具 allowlist、超时、幂等、秘密路由和共享状态隔离。

Authored tools 与 HITL

TypeScript authored tool 使用 LangChain tool 和 Zod schema 定义,放在 tools/ 中,由 agent.ts 显式导入并加入 tools 数组。

export const lookupCustomer = tool(
  async ({ customerId }) => lookup(customerId),
  {
    name: "lookup_customer",
    description: "按 ID 查询客户",
    schema: z.object({ customerId: z.string() }),
  },
);

工程边界:

  • authored tool 适合私有 API、数据库和项目内业务逻辑。
  • 工具名必须清晰且唯一,避免与其他本地工具或 MCP 前缀后的名称碰撞。
  • 外部写操作必须做输入校验、权限检查、幂等和可审计错误处理。
  • 每次运行的用户、组织、feature flag 或请求 metadata 应通过 LangChain runtime context 传递,不应默认拼进模型 prompt。
  • 部署 secret 可从环境变量读取,但不能把 key 记录到返回值、日志或 trace 内容中。

对敏感调用,在 agent definition 使用 interruptOn,让人类批准、编辑或拒绝;文件系统再叠加 permissions。托管 runtime 自带 durable checkpointer,暂停/恢复不需要项目自行配置状态存储。

本地中断通过 Studio 处理;部署后通过 LangGraph Server resume payload 恢复。当前 public beta 仍是 CLI-first,自有应用的完整 programmatic invocation 未正式文档化,不能在未确认接口契约时设计关键业务流程。

MCP connectors

connectors/mcp.ts 必须导出命名 connector,通过 connectors.mcp({ mcpServers: ... }) 声明远端服务器。

维度TypeScript 规则
TransportStreamable HTTP 与 legacy SSE;不支持 stdio
工具选择默认全量;生产优先 includeTools allowlist,可再用 excludeTools
名称选择时使用原始 MCP 名,加载后默认加 {server}__ 前缀
失败策略throwOnLoadError 默认 true,即 fail closed
HTTP 兼容可配置 automaticSSEFallback
调用超时defaultToolTimeout 的 TypeScript 单位是毫秒
凭据从环境变量构造 headers,禁止硬编码

与 Python 的高风险差异是超时单位:同一个数值在 Python 表示秒,在 TypeScript 表示毫秒。迁移配置时必须按单位换算,否则会出现极短超时或意外长时间占用资源。

文档表格同时列出 snake_case 与 camelCase 别名;TypeScript 新代码应使用 includeTools、excludeTools、defaultToolTimeout、automaticSSEFallback、prefixToolNameWithServerName 和 throwOnLoadError,避免跨语言配置混杂。

远端 MCP 是供应链和授权边界:只暴露必要工具,限制请求时间,验证 endpoint 与 TLS,决定连接失败是阻断启动还是显式降级,并对每个有副作用的 MCP tool 施加业务级授权。

Middleware

TypeScript 可以直接使用 LangChain 预构建 middleware,也可以用 createMiddleware 定义 wrapToolCall 等 hook。数组顺序就是组合顺序,应把数据清洗、限额、重试、fallback、审计和监控的先后关系显式化。

export const logToolCalls = createMiddleware({
  name: "LogToolCalls",
  wrapToolCall: async (request, handler) => {
    const result = await handler(request);
    return result;
  },
});

TypeScript wrapper 应正确 await 下游 handler,保持异常和取消语义;不能在日志中输出完整参数、模型输入或 secret。运行时仍由 MDA 管理 backend、store、checkpointer、memory、skills 和 system prompt,middleware 不应重新实现这些能力。

常见推荐顺序不是固定公式,但必须验证:

  1. 身份/上下文校验。
  2. 输入 PII 处理和 guardrail。
  3. 调用限额与预算。
  4. 重试或模型 fallback。
  5. 工具前授权与审计。
  6. 输出清洗和可观测性。

Durable memory

默认只有 thread/session 级状态。跨线程 durable memory 需要根目录 memory.ts 显式启用:

export const memory = defineMemory({ scope: "agent" });

也可以用 scope: "none",或删除声明关闭。启用后,Context Hub 的 memories/agent 挂载为 /memories/agent/:

  • /memories/agent/AGENTS.md 是每次运行加载的 hot memory,必须短小。
  • 同一树中的其他文件是按需读取的 cold memory。
  • 只有该树内的写入会持久化;写到别处不等于 durable memory。
  • 部署同步 instructions/skills 时,不会覆盖已经保存的 durable memory。

最高风险在于 scope: "agent" 是 deployment-wide:每个调用者都能影响后续调用者读取的内容。Memory 必须当作不可信笔记,不能授予权限、修改审批、覆盖 agent definition 或存储个人数据、客户私有数据、Token、API key 和其他秘密。调用者不应互相影响时,不要启用这种共享 memory。

Sandbox

TypeScript sandbox 位于 sandbox/index.ts,用 defineSandbox 声明。目录存在才启用;删除 sandbox/ 即退出该能力。

字段含义生产建议
scope: "thread"每个 durable thread 一个 sandbox,并跨该线程运行复用默认选择
scope: "agent"Agent 进程处理的线程共享文件仅显式共享场景
idleTtlSeconds空闲多久后允许回收控制成本与复用窗口
defaultTimeout单条命令最大执行时间限制资源占用
templateName / snapshotIdsandbox 创建来源两者只能选一个

Agent 可使用文件工具和 execute。Instructions 应限定工作目录和禁止修改的路径,但 prompt 约束不能替代平台隔离与应用权限。

Sandbox 不是授权模型,也不是跨用户数据隔离的自动证明。仍需路径权限、工具审批、资源限制、网络策略、输入校验和审计。agent scope 会让不同线程读写同一文件,默认应视为跨租户泄露风险。

Identity

目标TypeScript 声明安全边界
SDK、脚本、服务访问auth.langsmithApiKey()只控制能否访问,不能自动隔离最终用户线程
登录用户私有会话auth.supabase({ projectRef })用 verified JWT 建立 thread ownership

默认模式要求客户端发送 x-api-key。任何持有 workspace key 的人都可访问 deployment,因此它是服务凭据,不是 end-user identity。

Supabase 模式支持 projectRef 或自定义 auth url。浏览器只应持有 Supabase publishable key,并在每次请求发送 Authorization: Bearer <access_token>;绝不能把 LangSmith API key 下发到客户端。MDA 根据项目 JWKS 验证 JWT。

已有 deployment 后切换 Supabase,不会自动为历史 thread 补 owner metadata。必须设计迁移,并验证:

  • 无凭据或无效 token 返回 401。
  • 同一用户能继续自己的 thread。
  • 跨用户访问返回 403。
  • 历史 thread 不会因缺少 owner 而被错误共享或永久锁死。

上线安全清单

  • Authored tool 对副作用实现幂等、业务授权、输入校验和 HITL。
  • MCP 使用 allowlist、毫秒级超时、唯一前缀和明确的加载失败策略。
  • Middleware 顺序、Promise/异常传播、PII 脱敏和日志字段经过测试。
  • Memory 明确 caller 隔离模型,禁止把共享内容当作可信指令。
  • Sandbox 默认 thread scope;本地 fallback 与线上 managed sandbox 分别验证。
  • Supabase 覆盖 401、同用户、跨用户 403 与历史 thread 迁移。
  • 浏览器、日志、trace、Context Hub 和 memory 中都不出现 LangSmith key 或 provider secret。

关联