多智能体协作这件事做 demo 的时候永远岁月静好一上真实业务就原形毕露——这个智能体改完用户的收货地址另一个智能体还拿着旧地址在算运费主智能体把任务拆成 5 个子任务交给 5 个 worker每个 worker 回传结果时都带着一份“我以为你是这个意思”的上下文。我最早处理这些问题时用的是共享 Redis 加全局变量结果比不用还惨变量污染、过期键堆积、tag 越起越乱最后连自己都分不清哪段上下文该给哪个智能体。后来换成了 pheromone-network 来管智能体上下文才算把这个问题从“自治”变成了“治”。它本质上不是又一个聊天记录存储工具而是一张带遗忘机制的共享上下文网络——每个智能体往网络里写入的信息都带着“信息素”标记其他智能体按标记去认领、聚合、消费。这篇文章就围绕 pheromone-network 的核心设计、接入步骤、实际场景里的完整实现和调参踩坑展开给正在被多智能体上下文搞到头秃的开发者一个可以直接照抄的落地方案。1. 为什么智能体的上下文不能靠“塞 prompt”解决1.1 单智能体里的上下文和多智能体里的上下文完全是两回事先看最常见的做法把历史消息拼成长文本一股脑塞进 system prompt。单智能体时这个办法勉强能用因为只有一个大脑、一条时间线、一种“当前任务”。但一旦切到多智能体协作问题就变了。多智能体的上下文至少有三个特征局部性每个智能体只关心和自己任务相关的信息、时效性库存信息、订单状态这类数据 10 秒前和 10 秒后的结论完全不同、交叉性一个智能体的输出会成为另一个智能体的输入而中间可能还夹着用户的新指令。用单一 prompt 塞给所有智能体的做法本质上是在用“广播”模拟“点对点”必然炸出三类问题信息过载每个智能体都收到全量上下文token 消耗线性上涨无关信息反过来干扰任务判断旧值覆盖A 智能体更新了“地址”B 智能体还持有旧地址两个上下文副本不一致丢失关联用户说“改成上次那个地址”到底“上次”是哪个地址取决于哪个智能体在哪个时间点记录过什么。这些都是典型的分布式状态一致性问题靠拼 prompt 拼不出答案。1.2 为什么需要一个专门的上下文网络我尝试过用消息队列Redis Stream / RabbitMQ来中转上下文思路是 A 发布、B 订阅但很快发现一个根本矛盾消息队列是“一次性消费”模型消息被读走就没了而智能体上下文是“持续可见、随需聚合”的模型B 不仅要读 A 的最新消息还要能回溯 A 一小时前的一个决策依据。也试过用向量数据库做记忆检索那套方案适合“长期事实记忆”但对“任务进行中的临时状态同步”反而太重——向量化有延迟相似度检索有误差状态同步要的是确定性。这就是 pheromone-network 这类框架解决的问题它把上下文建模成一张智能体共享的状态网络写入的信息带着标记在网络上扩散链路内的智能体按需读取同时所有信息带有生命周期过期自动衰减遗忘。类比一下就是蚂蚁用信息素标记路径某条路上蚂蚁走得多信息素就浓路线就清晰没人走了信息素慢慢挥发路径自然消失。智能体上下文在这张网络上天然就是有时效、有强度、有归属的。提示pheromone-network 的定位不是“记忆数据库”而是“上下文管道”。前者负责存事实后者负责让正确的上下文在正确的时间流到正确的智能体那里。两者可以配合使用但不要混为一谈。2. pheromone-network 的核心概念把上下文变成一张活网络2.1 通道、信息素与扩散规则pheromone-network 把上下文管理的核心抽象成了三个概念通道Channel、信息素Pheromone、扩散Propagation。通道Channel不同业务上下文之间的隔离边界。比如订单域的上下文写在order_ctx通道客服域的写在support_ctx通道智能体只能感知自己所在通道的网络状态。这很好理解就像不同的蚂蚁巢穴有不同的信息素网络各管各的。信息素Pheromone智能体写入网络的一条上下文记录。每条 Pheromone 由一个唯一 ID、一个标记键key、一段内容payload、一个强度intensity和一个剩余生命周期ttl组成。扩散Propagation写入的上下文不是静止的它会依据网络拓扑向关联通道扩散——这正是“网络”和“数据库”最大的区别。A 智能体在order_ctx写入一条“用户地址已变更为 X”这条信息会自动扩散到绑定了order_ctx的下游通道下游智能体可以主动嗅探到变化而无需轮询数据库。关键规则有两个同类标记信息素会聚合即同 key 的多次写入按时间线合并成最新状态强度随时间衰减即每次被读取都会被“加固”一点点长时间没人读强度直线下降直到归零。归零的信息素从网络中消失这就实现了上下文的自动遗忘。2.2 上下文网络里的“读写权”设计我一开始以为它就是一张大表读读写写结果发现它有一个很关键的约束写入是广播式的读取是声明式的。智能体写入一条信息素时不需要指定“给谁看”只需要声明这条信息属于哪个通道、标记是什么、生命周期多久。网络负责扩散。而读取时智能体必须显式声明自己关注哪些通道和哪些标记。网络只把智能体“认领”过的信息返回给它未认领的信息它完全看不见。这个设计和消息队列的 topic 订阅有些相似但多了关键一招读取不会消费掉信息。多个智能体可以同时认领同一条上下文各自按自己的用途去消费互不抢占。这对多智能体协作非常友好。原本用 MQ 时最头疼的“消息被 A 读了 B 就读不到”的问题在 pheromone-network 里不存在——上下文是共享状态不是一次性事件。2.3 上下文强度的衰减曲线衰减机制是实现“忘记”的核心工程。pheromone-network 的强度衰减遵循指数衰减模型S(t) S₀ * exp(-λ * t)S(t)t 时刻的信息素强度S₀初始写入强度默认 1.0λ衰减速率常数由信息素的初始 ttl 决定λ ln(2) / ttl_seconds这个设计妙在你可以用一个直觉化的方式控制记忆时长把 ttl 设成 600 秒那么这条信息素在 300 秒时掉到一半强度600 秒时只剩约 1/4每次被读取一次强度会回复 0.2可配置回复上限不超过初始强度 S₀。也就是说正在被持续引用的上下文可以一直“续命”不再被关心的上下文会自动蒸发。实际调参的时候我通常把不同业务的 ttl 拉开差距订单价格类的上下文 ttl 设 15 分钟用户偏好类的设 2 小时任务中间状态如“正在生成报告”只设 3 分钟。这样网络里自然形成记忆层次既不会被旧数据误导也不需要手动清缓存。3. 接入 pheromone-network环境准备与最小实现3.1 安装与初始化pheromone-network 本身是纯 Python 实现核心依赖只有pydantic和pyyaml可以用 pip 直接装pip install pheromone-network初始化一个网络实例需要指定一个网络 ID 和存储后端。框架支持内存后端和 Redis 后端单机调试用内存多进程/多实例部署用 Redisfrom pheromone_network import PheromoneNetwork # 单机调试内存后端 net PheromoneNetwork(network_iddemo-net, backendmemory) # 多实例部署Redis 后端 net PheromoneNetwork( network_idprod-net, backendredis, redis_urlredis://localhost:6379/5, ttl_scan_interval10, # 每 10 秒扫描一次过期信息素 )注意这个network_id不是装饰用的它用来隔离不同业务网络的上下文。我在同一条业务线里就吃过亏订单系统和客服系统共用一个 network_id结果客服智能体一“闻”到了订单域全量的上下文token 消耗直接翻倍。后来强制约定不同业务域必须用独立的 network_id宁可多起几个实例。3.2 最小可运行示例两个智能体共享一段上下文初始化网络后注册两个虚拟智能体角色演示最基本的“一侧写入、另一侧闻到”# 创建一个共享事件上下文通道 order_ctx net.create_channel(order_ctx, ttl900) # 注册角色 net.register_agent(address_svc_agent, channels[order_ctx]) net.register_agent(logistics_agent, channels[order_ctx]) # 智能体 A写入信息素声明一条订单上下文的更新 net.emit( channelorder_ctx, keyorder#10086.shipping_address, payload{province: Guangdong, city: Shenzhen, detail: Nanshan ...}, intensity1.0, ttl900, ) # 智能体 B读取其关注标记的上下文 ctx net.smell( agentlogistics_agent, channelorder_ctx, keys[order#10086.shipping_address], ) print(ctx) # 输出 # { # key: order#10086.shipping_address, # payload: {province: Guangdong, city: Shenzhen, ...}, # intensity: 0.98, # 被读取后强度回复了 0.2 → 从 0.81 回到 1.0 上限 # updated_at: 2026-05-11T14:32:01Z, # }这基本上就是最小闭环。但注意这只是一个“信息读写”的能力演示真正的上下文管理还涉及组合、聚合、链路追踪下面用完整的业务场景串起来看。4. 实战场景一个订单客服链路中三个智能体的上下文协作4.1 场景设定为了让内容不抽象我拿一条真实业务链路来拆解。假设有一个售前客服智能体 库存查询智能体 物流跟踪智能体三者协作处理用户请求“查一下我那个着急的订单到哪了顺便改一下收货地址”。这三个智能体要协同完成的任务意图解析智能体nlp_agent识别用户的真实意图是“查询物流 修改地址”两个动作库存/订单智能体order_agent定位订单号、确认可修改状态、更新配送地址物流智能体logistics_agent根据最新订单上下文查询实时物流轨迹。三个智能体如果各管各的 prompt一定会出现意图解析智能体解析出了“订单号 10086”但物流智能体不知道“10086”是从哪来的也不清楚地址修改是否已经生效。用 pheromone-network 管理这个链路的上下文方案是定义一条共享通道三个智能体通过通道交换“任务中间态”。4.2 定义通道与上下文标记规范先定义好通道和标记规范。这一步很关键我会把标记命名看成一组团队内“协议”定清楚什么人写什么、什么人读什么避免乱写乱读net PheromoneNetwork(network_idcs-order-net, backendmemory) # 三个智能体共享的协作通道 net.create_channel(cs_order_flow, ttl600) # 上下文标记协议 # task.intent - 用户意图解析结果nlp_agent 写入order/logistics 读取 # order.id - 当前正在处理的订单号nlp_agent 写入全链路共享 # order.address - 最新地址快照order_agent 写入logistics_agent 读取 # logistics.trace - 物流轨迹logistics_agent 写入nlp_agent 用于话术汇总 net.register_agent(nlp_agent, channels[cs_order_flow]) net.register_agent(order_agent, channels[cs_order_flow]) net.register_agent(logistics_agent, channels[cs_order_flow])4.3 链路执行与上下文流转现在模拟一次完整用户请求。步骤严格贴合上面定义的协议# Step 1: nlp_agent 完成意图解析把结果写进共享通道 net.emit(cs_order_flow, keytask.intent, payload{actions: [track_logistics, update_address], parsed_order_id: 10086}) # Step 2: order_agent 察觉到task.intent更新取用订单号并锁定处理上下文 intent net.smell(order_agent, cs_order_flow, keys[task.intent]) order_id intent[payload][parsed_order_id] # order_agent 回写确认信息附带它正在处理的订单号信息素 net.emit(cs_order_flow, keyorder.id, payload{order_id: order_id}) net.emit(cs_order_flow, keyorder.address, payload{new_address: Shenzhen Nanshan ..., verified: True})注意order_agent 全程没有“拿到用户原话”。它只拿到了 nlp_agent 解析后的结构化意图——这其实就是上下文网络最核心的价值把上下文从“人话”抽象成“结构化状态”让不同智能体使用同一份事实而不是各自从原话里再猜一遍。继续往下走# Step 3: logistics_agent 观察 order.address 被更新随后查询最新物流轨迹 addr_ctx net.smell(logistics_agent, cs_order_flow, keys[order.address]) trace query_logistics(order_id10086) # 把轨迹和当前地址快照一并回写供 nlp_agent 汇总成用户话术 net.emit(cs_order_flow, keylogistics.trace, payload{trace: trace, address_snapshot: addr_ctx[payload]})4.4 用上下文聚合生成最终回复最后 nlp_agent 要生成给用户的自然语言回复。它需要把整条链路的最终状态聚合起来# Step 4: nlp_agent 聚合所有上下文生成话术 final_ctx net.smell(nlp_agent, cs_order_flow, keys[order.id, order.address, logistics.trace]) reply generate_reply(final_ctx) print(reply)这个过程中无论如何调度三个智能体——哪怕 order_agent 和 logistics_agent 是被两个不同 worker 进程并行调用的——它们读取到的地址快照、订单号、意图标记都是同一份由网络维护的状态。不需要额外写分布式锁不需要传返回值不需要把整段会话历史塞进每个智能体的 system prompt。我在短信里把这段链路从“函数参数传递 塞 prompt”重构为“共享上下文网络”后最直观的变化是每个智能体的 prompt 平均长度下降了 60% 以上且不再需要在代码里手写“拼接上下文”逻辑。新增一个智能体工种比如加一个“优惠券计算智能体”时只需要注册一个 agent 声明关注的标记完全不改动其他智能体的调用链。5. 实际运行中踩过的坑衰减、串扰与可观测性5.1 上下文衰减过快导致的“决策丢失”第一次实验时我把所有通道的 ttl 都设成了 300 秒然后跑一条稍复杂的链路。结果链路里有一步异步处理耗时超过了 4 分钟等 worker 回来读取上下文时信息素的强度已经衰减到 0.1 以下——网络正常把它判定为了“不再活跃的上下文”返回的结果带上了intensity: 0.08的低置信度标记而我的代码没有对低强度做处理直接把它当作可靠上下文用了导致客服话术出现前后矛盾。排查之后我总结了两个经验根据链路最大耗时设定 ttl。一条链路的 ttl 至少要是“最长分支处理时长”的 2 倍。比如链路里最慢的仓库查询接口要 2 分钟上下文 ttl 至少设 5 分钟。宁可让旧信息多存活一会儿也不能让链路中途丢状态。读取时检查强度阈值。smell()返回的上下文中带intensity字段建议在业务代码里统一断言def get_reliable_content(ctx): if ctx.get(intensity, 0) 0.4: raise ContextStaleError(fctx {ctx[key]} intensity too low: {ctx[intensity]}) return ctx类似“读不到就重新写一份”的补偿机制要写在业务代码里。上下文网络负责尽量可靠但最终决策的鲁棒性还是要靠使用方。5.2 多任务并发时共享通道的“信息串扰”这是另一个非常隐蔽的坑。我最初以为只要不同通道隔离就能避免信息交叉但实际上同一个通道、同一个 key可能被多条任务线共用。比如用户 A 和用户 B 同时发起咨询都命中order.address这个 key。如果我只用“keyorder.address”而不带业务单号那么两个用户的地址信息就会互相覆盖用户 B 的更新会直接冲掉用户 A 的。信息素网络不会帮你区分“这个 key 属于哪个用户”它的隔离粒度是通道和 key而不是业务实体。正确的做法是把订单号/用户 ID 作为 key 的一部分形成业务级隔离key forder#{user_id}.address key forder#{user_id}.track.trace这种命名方式相当于在共享网络上划出了无数条互不干扰的“小车行道”。我第一版代码偷懒没用拼接 key结果压测时并发场景下大约 30% 的请求出现了张冠李戴换成带 ID 的 key 后问题完全消失。5.3 可观测性给上下文网络加“嗅探日志”多智能体系统最痛苦的事情就是查问题——你永远不知道某个错误结论是哪个智能体基于哪条上下文做出的。pheromone-network 提供了事件监听接口可以挂在关键节点上记录上下文读写流水net.on_pheromone_emitted lambda evt: log_pheromone(evt, EMIT) net.on_pheromone_smelled lambda evt: log_pheromone(evt, SMELL)我在每个 emit 和 smell 事件里记录了三个字段agent ID、key、intensity。排查问题时只要按订单号反查事件日志就能看到一张完整的上下文流转时序图——谁在什么时间点写了一版地址、谁在什么时间点读到了这版地址、读取时强度是多少。有了这层日志调试多智能体链路的效率至少翻倍。没有这层日志之前我全靠 print 和猜。5.4 后端选型进程内内存 vs Redis最后说一下后端选型。单机调试、链路测试阶段用backendmemory完全没有问题速度快还免运维。但一旦服务是多实例部署必须切 Redis 后端否则每个实例各持一份上下文网络智能体轮流命中不同的实例时上下文会出现严重的“左右手不一致”。切到 Redis 后有几个参数需要关注参数作用建议ttl_scan_interval过期信息素清扫频率默认为 10s链路时延敏感时调到 3sstale_threshold低强度信息素是否返回默认返回配合业务层自判key_prefixRedis 键前缀多环境共用队列时用前缀隔离如staging:/prod:另外提一句pheromone-network 的 Redis 后端建议配 Redis 6.2因为内部用到了ZADD ... GT和ZRANGEBYSCORE做强度排序。旧版本 Redis 也能兼容只是绕过一些原子更新优化高并发表现会差一点。6. 结合 LLM 智能体场景它的适用边界与我的选型建议6.1 什么时候该用什么时候别硬用这个框架并不是万能的。对我自己来说适用它的场景有一些共同特征多个智能体共享同一份业务状态而且状态会被多处实时修改协作链路较长中间有异步执行、分支等待不能一步到位用函数返回传参不需要长期保留的上下文任务结束即可遗忘。反过来如果你的场景是“长期记忆”比如让智能体记住用户上个月的偏好那更适合向量库 长期存储如果你的场景是“一次性的任务消息传递”那直接用任务队列更轻。简单总结就是长期记忆交给数据库和向量检索任务消息交给 MQ而进行中的协作状态交给 pheromone-network 这类上下文网络。6.2 在 Agent 框架里怎么结合使用如果你的智能体是基于 LangChain / LlamaIndex / 自研 Agent 框架跑的接入 pheromone-network 的思路很清晰在每次 LLM 调用前把网络里认领到的上下文拼进 system promptLLM 返回结果后解析其中的“对既有事实的修改”重新 emit 回网络。核心代码骨架大致长这样def run_agent_with_ctx(agent_name, task_prompt: str, keys: list[str]): # 1. 从网络读取该智能体关注的全部上下文 ctx_map collect_context(agent_name, keys) # 2. 拼进 prompt sys_msg build_system_message(ctx_map) # 3. 调用 LLM response llm_call(systemsys_msg, usertask_prompt) # 4. 解析响应里的状态变更写回网络 detected_updates parse_state_updates(response) for update in detected_updates: net.emit(update[channel], keyupdate[key], payloadupdate[payload]) return response这套模式的好处是LLM 本身不用感知“网络”的存在——它只需要看到拼好的上下文输出结构化的状态变更即可。所有一致性、时效性、扩散和遗忘交给网络处理。这也比较符合我个人的工程原则尽量让智能体自身的逻辑保持简单把复杂度压制到基础设施层。6.3 我对这种“上下文中间件”方向的总体看法从更宏观的角度说pheromone-network 代表了一个我观察到的明确趋势多智能体系统正在从“堆模型”走向“补基础设施”。模型负责理解与生成但这之外的记忆、通信、同步、容错、可观测性全都需要专门的中间件来支撑。上下文网络这层东西就像当年微服务落地时的注册中心与配置中心——没有它单体时代一切靠内存变量传递的隐性约定在分布式多智能体场景下全都不成立。我现在负责的几个 AI 应用里已经把 pheromone-network 作为多智能体协作的标准层凡是涉及两个以上智能体协同处理业务状态的地方都优先用上下文网络来编排。坦白说它并不是一个“重框架”代码量很小核心概念也不多但它提供了一个非常值得借鉴的思路上下文不应该是被“传递”的而应该是被“共享”的。最后分享一个我自己的使用习惯在新接一个智能体协作场景时不要急着写代码。先拿一张纸画一下“谁会产生什么状态、谁需要消费什么状态、状态需要存活多久”把这三点理清楚之后再落到 pheromone-network 的通道和标记上。绝大多数上下文混乱问题的根源其实不是工具不够强而是在设计阶段没有把状态边界和状态生命周期定义清楚。工具只是把你想明白的这套协议稳定地执行下去而已。