没有过去的我,就不会有现在的我。
在最开始做这个项目的时候,我其实没有特别重视 Memory。 因为直觉上会觉得,当前系统已经给了司机状态、历史记录、偏好、可见货源,那把这些信息都塞给 LLM 不就好了嘛。
但是实际跑起来之后发现,这个想法很快就会出问题。
货运调度是一个连续的决策过程,每一步的动作都会影响后面的状态,比如:
- 刚刚搜过某个地方,但是没搜到什么好货,那下一步就不应该继续原地重复搜;
- 某个货源刚刚接单失败,或者已经过期了,那后面就不应该继续把它当成可接货源;
- 司机今天已经等了很多次但是没有接单,那主决策就应该知道“继续等”可能不是一个好策略;
- 有些偏好是按天、按月累计的,比如休息天数、到访次数、禁行区域、空驶限制,这些不是当前一步就能判断完的;
- 某些区域可能逐渐表现出货源密度高、收益高,这其实就是一种市场经验。
所以 Memory 模块是必须有的,我们不是为了去要一个 Memory 模块而去设计,而是 Memory 模块真的能帮助到我们这样一个动态决策的过程。
在这个项目中,由于这是一个 Multi-Agent 的项目,Memory 起到一个管理全局上下文的作用,给不同的 Agent 管理不同的 Memory。
1. 第一阶段-直接将所有信息塞入 Prompt
一开始我们就只是尝试了一个最朴素的做法,全部塞进 Prompt,包括当前可见的货源、司机的偏好、历史的动作这些。

这有很多严重的问题,上下文变得很臃肿,缺乏关键有效信息,模型决策困难。(想一下给你这么多信息,你也很难决策出最完美的那个答案)
第一,历史会越来越长。 司机连续跑很多天,每一步都有动作、有搜索、有失败、有校验结果。如果全部放进 prompt,token 很快就会爆掉,而且对于小模型来说非常容易 lost in middle。
第二,历史不是所有内容都同样重要。 比如“十步前某个货源已经过期”对 CargoAnalystAgent 很重要,但是对 PreferenceAgent 可能就没有那么重要。 反过来,“司机这个月已经休息了几天”对偏好执行很重要,但是对搜索市场热点不一定重要。
第三,原始历史本身不是可执行信息。 LLM 看到一堆日志,不代表它真的能稳定总结出一些特定的规律:
这个区域最近搜索收益很低,不要再搜
这个货源已经失败过,不要再推荐
司机最近连续 wait 但没有收益,应该主动 search 或 reposition
所以后面我们就把 Memory 从“历史文本”改成了“结构化状态 + 任务级视图”。 也就是说,不是让每个 Agent 自己从一大堆日志里捞信息,而是 MemoryAgent 先把信息整理好,再按不同 Agent 的需求发给它们。
2. 第二阶段 - MemoryAgent
我们的 Memory 整个架构主要分为三层(事实记忆、短期记忆、长期记忆):
第一层是结构化记忆,负责把运行过程中观察到的信息存起来。 这里主要有三类:
CargoMemory:记录货源状态,比如搜索到的货源价值、是否过期;MarketMemory:记录搜索过的市场网格和历史收益;DriverMemory:记录司机自己的计划、偏好进度和动作摘要。
第二层是MemoryAgent,负责把这些结构化记忆整理成不同 Agent 能用的 memory view,属于一个模拟进程内的短期记忆,它根根据不同 subagent 的职责分别生成:
- search view;
- preference view;
- cargo analysis view;
- main decision view;
然后从 Memory Stores 中记录的一些结构化事实按规则分发给不同的 Agent。(这也是下一版)
第三层是长期记忆,也就是真正能落入库中的一些经验 skill/lesson
- skill evolution view。(这一个是相关于自进化的,比较复杂,他主要是在整个模拟结束后才会有这个skill evolution,而不是实时进行压缩生成 skill 的)
整体流程大概是这样:

2.1 Memory 模块拆分
| 模块 | 主要记录什么 | 给谁用 | 解决的问题 |
|---|---|---|---|
CargoMemory | 搜到过的货源、货源是否过期、是否接单失败、是否已经不可用 | CargoAnalystAgent、MainDecisionAgent | 避免重复分析过期货源、失败货源、无效货源 |
MarketMemory | 搜索过的地理网格、返回货源数量、历史最好收益、低收益连续次数 | SearchAgent、MainDecisionAgent | 让搜索不再完全盲搜,能够记住热点和低效区域 |
DriverMemory | 最近计划、动作摘要、偏好进度 | PreferenceAgent、MainDecisionAgent、MemoryConsolidatorAgent | 跟踪司机长期状态和偏好履约情况 |
MemoryAgent | 按任务生成不同 memory view | 所有下游 Agent | 避免所有 Agent 都塞入完整历史,降低上下文噪声 |
MemoryStore | 存储Memory的地方,一般存储在Python进程内存中,可选Redis加强 | Memory 内部 | 让记忆存储和业务逻辑解耦 |
MemorySkill | 从真实模拟的轨迹记录一些可复用skill/lesson,帮助后续的迭代进化 | 所有可能用到的Agent | 形成一个自进化的体系,使得每一次模拟越来越好 |
4. CargoMemory:货源记忆
CargoMemory 主要解决的是货源状态管理的问题。这个 Memory 主要给主决策 Agent 用。
这个项目里货源是随世界状态而改变的,它有 create_time、remove_time 和 load_time。 也就是说,一个货源被搜到,不代表它之后一直可用。 如果 Agent 不记得货源的状态,就很容易出现这种情况:
第 1 步搜到 cargo_520
第 2 步还在分析 cargo_520
第 3 步继续推荐 cargo_520
但这个货源其实已经过期 / 装货窗口错过 / 上次接单失败
这也是我们早期 Single Agent 里很明显的问题之一:模型会一直接到过期的货源。
所以 CargoMemory 会维护一个货源状态。 它会记录:
cargo_id;- 第一次看到这个货源的时间;
- 最近一次看到这个货源的时间;
- 这个货源来自哪次搜索;
- 搜索时的经纬度;
- 这个货源是否已经失败、过期、不可用、被接过。
同时它还会根据当前时间和货源窗口判断货源是否仍然 active。 如果某个货源已经过期、被接过、接单失败、装货窗口无效,就会转入不可用列表。
这样后面的 CargoAnalystAgent 在分析候选货源时,不会只看“这个货源收益高不高”,还会看到:
failed_cargo_ids
stale_cargo_ids
unavailable_cargo_ids
discarded_cargo_summary
blacklist_summary
这其实很重要。 因为货源收益计算再好,如果候选池里面混入了一堆已经失效的货源,那最后决策还是会翻车。
5. MarketMemory:市场记忆
MarketMemory 解决的是“搜哪里”的问题。这个 Memory 主要给搜货 Agent 使用。
一开始 SearchAgent 如果完全依赖当前状态,它其实不知道广东哪些地方货源多,也不知道哪些地方刚刚搜过但是没结果。 这样就会出现两个问题:
- 重复搜索低收益区域;
- 明明历史上有更好的市场热点,却没有利用起来。
所以我们在每次搜索之后,会把搜索位置映射成一个 grid,然后记录这个 grid 的历史表现。 主要包括:
- 这个 grid 被查询了几次;
- 平均返回了多少货源;
- 历史上最好的路线净收益是多少;
- 有没有连续低收益搜索;
- 这个表现发生在哪个小时段。(先验知识得出:白天的货源普遍比夜间货源跟密集、机会更多)
这里还有一个比较轻量的市场价值估计:
\text{MarketValue}
=
\text{BestRouteNet}
+
2 \cdot \text{AvgItems}
-
50 \cdot \text{LowYieldStreak}这主要是对一个 grid 内市场的粗糙估计,为了能粗略看出哪一个地方的货源会更好:
BestRouteNet越高,说明这个区域曾经出现过比较好的货源;AvgItems越高,说明这个区域货源密度更高;LowYieldStreak越高,说明这个区域最近连续搜不到好东西,需要降权。(搜不到好东西的定义是:本次搜索没货,或者最好净收益低于阈值)
这样 SearchAgent 在做决策时,就会有一个对于市场价值的初步判断,从而能做出更好的搜货选择。
也就是说,它不再是完全凭 LLM 猜“去哪里搜”,而是能结合历史搜索经验,知道哪些地方值得再试,哪些地方刚刚搜过效果不好。
6. DriverMemory:司机记忆
DriverMemory 更偏向司机自身状态。
它主要记录三类东西:
第一类是最近的短计划,比如主决策之前给出的 short_plan。 short_plan只是对下一步的一个大致规划,用来给下一次决策提供参考建议。
第二类是动作摘要。记录司机最近的决策动作:
take_order
wait
reposition
这对 MainDecisionAgent 很有用。 如果一个司机最近一直 wait,但是没有成功接单,那系统就应该意识到“继续等”可能不是最好的选择。
第三类是偏好进度。 有些偏好比如时间累计型、月度累计型、有多步骤的动作的这种偏好,需要记录司机履约的进度。 比如:
这个月要休息几天
今天是否已经满足连续休息
要先去哪里干什么、再去哪里干什么
最近一次偏好相关事件发生在什么时候
这些信息如果不放进 DriverMemory,后面其实很难判断偏好是否已经完成。 这也是为什么我觉得 Memory 和 Preference 不能完全分开讲。 PreferenceAgent 负责把自然语言偏好解析成结构化约束,而 DriverMemory 负责在运行过程中记录这些偏好的执行进度。
7. MemoryAgent:每个 Agent 只看自己该看的记忆
MemoryAgent 是这一层里最关键的接口。这一层也进行了一些尝试。
第一版的 MemoryAgent 是按照规则(就是按照事实,比如搜到什么货了,最近干了啥,然后把这些事实分发给不同的 Agent),给不同的 Agent 派发属于它们的 view。这一版有很大的缺点,因为在事实攒到一定量之后,每一步耗费的 token 数和 latency 是会变得越来越高的,所需的成本也特别大,甚至会触发 input_length_error,也就是输入的token太多了,显存直接撑不住了。
第二版的 MemoryAgent 才是真正意义上的 Agent 。我们把他接入了 LLM,在事实攒到一定的量之后,会自动触发压缩整合重点,比如自动整理出市场热点、低收益区域、偏好进度等等,然后分发给不同职责的 Agent。
这一版虽然增加了一次 LLM 调用,但是并不是每一 step 都会压缩,比如我搜到的货源 Memory 里的事实达到了200条,超过了某个阈值之后,就会自动触发一次LLM压缩,整理关键信息比如说市场的热点。
经过实测,第二版比第一版不仅大量减少了每step token的消耗(41%,规则版本是每次事实都会全量加入每一个Agent,所以每一步token会偏高,但是LLM压缩版本,每经过一段时间,才会执行一次LLM调用,压缩后的重点分发给不同的Agent),并且大大降低了latency(51%,14.3s->7.1s,但是这也可能受到很多方面的影响,比如模型推理速度受服务器预算限制(4x4090,Qwen3.5-35B-A3B)
| Memory View | 给谁用 | 里面主要有什么 |
|---|---|---|
view_for_search | SearchAgent | 当前 grid 表现、同小时热点、整体热点、低收益区域、偏好锚点、搜索 skill/lesson |
view_for_preference | PreferenceAgent | 偏好进度、已知地理锚点、偏好相关 skill/lesson、偏好事件 |
view_for_cargo_analysis | CargoAnalystAgent | 目的地市场价值、弱市场、失败货源、过期货源、不可用货源、货源 skill/lesson |
view_for_main_decision | MainDecisionAgent | 最近短计划、动作 skill/lesson、坏模式、风险记忆、当前市场、每日动作摘要、偏好进度 |
view_for_skill_evolution | 后续自进化模块 | 已沉淀 lesson 和 skill candidate |
举一个 search view 的例子,我们的 Prompt 大概是
You are MemoryPromptAdapter for SearchAgent.
Compress the given search_memory_view into short actionable search hints.
Focus on:
1. which grids or anchors are worth searching;
2. which grids should be avoided because of repeated low yield;
3. whether query budget is already high;
4. any search lessons that should affect the next query.
Rules:
- Do not invent unseen cargo or markets.
- Keep grid_id / coordinates if provided.
- Output at most 5 bullets.
- Each bullet should be directly useful for deciding whether/where to search.
这比直接把所有历史塞给 SearchAgent 好很多。 因为 SearchAgent 其实只需要知道:
哪里值得搜
哪里刚刚搜过但是效果差
有没有偏好锚点需要靠近
最近有没有关于搜索的经验
同理,MainDecisionAgent 需要的是更综合的上下文。 它需要知道当前市场、候选货源、偏好进度、最近动作模式、风险记忆,来决定是否接货/等待下一次机会。 所以 main decision view 会更偏向“最后决策的上下文压缩”。
我觉得这个设计的重点就是:
Memory 不是越多越好,而是要按任务给到刚好有用的信息。
这也是多 Agent 架构里很重要的一点。这更让我理解到了,Agent架构里一个很重要的 Harness 就是 Context。很多的工程就是围绕上下文如何精简、如何更有用的指导模型应该干啥。