学习笔记 · Obsidian
Managed Deep Agents TypeScript:扩展能力与安全边界
结论先行
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 规则 |
|---|---|
| Transport | Streamable 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 不应重新实现这些能力。
常见推荐顺序不是固定公式,但必须验证:
- 身份/上下文校验。
- 输入 PII 处理和 guardrail。
- 调用限额与预算。
- 重试或模型 fallback。
- 工具前授权与审计。
- 输出清洗和可观测性。
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 / snapshotId | sandbox 创建来源 | 两者只能选一个 |
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。