Agent面试详解(中):记忆、工具与多用户并发
《Agent面试详解》系列:上篇:运行时与上下文 · 中篇:记忆、工具与并发 · 下篇:评测、安全与落地
上篇搭好了 Agent 的运行时骨架:状态机驱动循环,checkpoint 负责恢复,上下文按 Token 预算动态组装。接下来要把它放进真实的多用户环境。
这一步比接入一个向量数据库或 MCP Server 难得多。长期记忆会重复、冲突和过期;工具会超时、越权并产生不可逆副作用;两个 Worker 可能同时执行同一个步骤。中篇集中处理这些数据一致性和并发问题。
长期记忆不能只有 insert 和 search
把所有历史对话做 embedding,再按相似度取 Top K,不等于长期记忆。它很快会遇到重复、冲突、过期、越权和无限膨胀。
先把记忆分层
| 层次 | 生命周期 | 例子 |
|---|---|---|
| Working memory | 单次模型调用 | 当前 Prompt、最近 observation |
| Session state | 当前 run | plan、step、checkpoint、预算 |
| Episodic memory | 跨 run 的经历 | 某次部署失败及其原因 |
| Semantic memory | 稳定事实 | 用户偏好、系统约束、领域知识 |
| Procedural memory | 长期规则 | Skill、操作步骤、验收规范 |
LangChain 的上下文工程文档也区分 runtime context、会话 state 和跨会话 store,并建议通过生命周期中间件完成摘要、工具筛选和状态更新。Context Engineering
分层的价值不只是便于命名,而是让每类信息有不同的写入条件和淘汰策略。一次工具返回属于 observation,不应因为与用户问题相似就自动升级成长期事实;一次成功操作也不应直接变成永远有效的流程规则。
给记忆设计“遗忘”
真正的 Memory Service 至少要支持写入、检索、合并、修订和遗忘:
- 相同实体、相同事实去重。
- 新事实覆盖旧版本,同时保留审计链。
- 临时信息设置 TTL。
- 根据重要性、置信度和使用频率衰减。
- 多条 episode 定期合并成 semantic memory。
- 长期不命中的内容进入冷存储。
- 每个租户和用户设置容量配额。
- 用户可以查看、纠正和删除自己的记忆。

“删除”也不能只删向量索引。原始对象、派生摘要、向量、缓存和搜索索引都要通过同一条删除链路处理,否则用户删除了一条记忆,Agent 仍可能从旧摘要里把它找回来。
检索先过权限,再谈相似度
每条记忆应带上来源、时间、租户、用户、权限、置信度和版本。检索不能只看向量距离:
WHERE tenant_id = :tenant_id
AND permission_scope && :allowed_scopes
AND valid_from <= :now
AND (expires_at IS NULL OR expires_at > :now)
ORDER BY relevance_score DESC, confidence DESC, last_used_at DESC
LIMIT :budgeted_k

先做硬过滤,再做语义排序。否则“很相似但属于别的客户”的记忆,正是最危险的数据泄漏。检索结果还要受本轮 Token 预算约束,Top K 只是候选数量,不表示候选必须全部进入上下文。
工具层要和模型彻底解耦
一个生产工具不能只是 Python 函数名加 description。它需要明确的执行合同:
name: orders.refund
version: 2
input_schema: RefundRequest
output_schema: RefundResult
timeout_ms: 10000
retry_policy: no_automatic_retry
idempotency: required
permission: orders.refund
side_effect_level: irreversible
approval: required
error_taxonomy:
- validation_error
- permission_denied
- provider_timeout
- business_rejected
Tool Registry 负责描述和发现,Tool Gateway 负责控制执行。模型只提交结构化调用,不直接获得第三方 Token,也不直接连数据库。

这里有一个容易混淆的边界:Tool Registry 回答“有哪些工具、参数是什么”,Tool Gateway 回答“当前租户能不能调用、这次调用是否需要审批、失败能不能重试”。把两者合在模型侧,会让工具描述、凭证和策略同时暴露给一个不完全确定的决策者。
大结果不要原样返回模型
工具执行后可以同时生成四种结果:
- 原始结果:存对象存储,供审计和下载。
- 结构化结果:供程序继续处理。
- 模型摘要:只包含下一步决策需要的信息。
- 类型化错误:驱动重试、改参、重规划或停止。
例如数据库查询返回 20 万行时,模型不需要看到全部内容。它需要字段、行数、统计摘要、异常样本和原始结果引用。
Anthropic 在工具工程实践中强调,工具应该定义清楚、能够组合、谨慎占用上下文,并通过评测反复改进。Writing Effective Tools for AI Agents
MCP 解决了工具如何暴露给不同客户端的问题,但不自动解决租户隔离、审批、幂等、执行沙箱和企业审计。这些仍然属于 Harness。
多用户并发冲突怎么处理
Agent 一旦支持多用户,就会遇到三类冲突:
- 同一用户打开两个会话,同时修改同一 run 或同一文档。
- 不同用户操作同一个业务对象,例如工单、代码分支或订单。
- 两个 Worker 误以为自己都拥有同一个 run,重复产生副作用。
这三类冲突不能只靠“每个用户一个 session”解决。session 隔离的是交互上下文,业务对象、run 所有权和外部副作用仍然可能共享。
先让每一层都认识租户
Run、step、message、memory、tool call、trace 和 artifact 都必须带租户边界:
tenant_id / user_id / session_id / run_id / step_id / tool_call_id
不能只在 API 入口检查一次,然后让内部队列和记忆库丢掉 tenant_id。租户隔离必须贯穿入口、状态、队列、工具、存储和日志。
状态冲突用版本,不用长锁
更新 run 时携带版本号:
UPDATE agent_runs
SET state = :new_state,
version = version + 1
WHERE id = :run_id
AND tenant_id = :tenant_id
AND version = :expected_version;
更新行数为 0,说明状态已经变化。此时重新读取并判断是否重规划,而不是覆盖别人的结果。
数据库悲观锁只应该包围毫秒级的原子更新,不能跨模型调用和网络调用。让事务等模型思考 30 秒,会直接把连接池和并发拖垮。
Worker 使用租约和心跳
Worker 获取的是有过期时间的 lease,不是永久所有权。执行期间续租;崩溃后 lease 到期,其他 Worker 才能接管。接管者从最后一个已提交 checkpoint 继续,而不是猜测前一个 Worker 做到哪里。

租约时间需要覆盖一次正常心跳抖动,但不能长到故障后迟迟不能接管。比较实用的起点是:租约时长为心跳间隔的三到五倍,续租失败后立即停止领取新步骤;真正执行副作用前,再确认租约和状态版本仍然有效。
副作用必须幂等
idempotency_key = run_id + step_id + tool_call_id
同一个 key 的退款、发送、创建和写入只能成功一次。Agent 的“恰好一次执行”通常无法端到端保证,更现实的做法是 at-least-once 调度,加业务侧幂等。

如果数据库状态更新和事件发布必须同时成功,可以使用 transactional outbox:事务内只写业务状态和 outbox,独立 relay 再把事件送入队列。
Planning 不等于先写一份漂亮清单
静态计划在第一步执行后就可能过时。一个有效 plan 至少包含:
- 最终可观察目标。
- 步骤依赖。
- 每一步验收证据。
- 当前状态和未解决阻塞。
- 失败后的恢复策略。
当工具返回与假设冲突、依赖不可用、预算明显不足或连续两次没有产生新证据时,应触发重规划。重规划不是把原计划再说一遍,而是明确撤销哪些假设、保留哪些证据、为什么换路径。
单 Agent 是默认选择
Subagent 适合并行检索、独立 review、上下文隔离和不同领域分析。Multi-Agent 只适合任务真正可分、角色确实不同、结果可验证的场景。
多 Agent 最常见的问题不是智力不足,而是:
- 重复搜索和重复调用工具。
- 状态版本不同步。
- 子 Agent 互相强化错误。
- 通信 Token 超过实际工作。
- 最终没有人对结果负责。
委派协议应明确输入、输出 schema、预算、截止时间和完成条件。子 Agent 返回的不是“我研究完了”,而是一份可验证的结果包。
中篇留下的状态
到这里,Agent 的记忆有生命周期,工具有执行合同,多租户状态有版本,Worker 有租约,外部副作用有幂等键。它已经不容易因为重试或并发直接写坏数据。
但“没有写坏”还不等于“做对了”。下篇要回答最后一组问题:怎样还原一次 run,怎样测试不确定的执行轨迹,安全边界放在哪里,以及哪些 Agent 场景值得真正投入。
继续阅读:下篇:评测、安全与落地
《Agent面试详解》系列:上篇:运行时与上下文 · 中篇:记忆、工具与并发 · 下篇:评测、安全与落地
微信公众号
欢迎关注「字与码」
如果这篇文章对你有用,也欢迎在微信里继续关注后续更新。
X / Twitter
关注 @ax2_zicode
更即时的技术观察、新文章提醒和一些短想法会发在 X 上。