_recursive_create 把父级 child-ref 的 intent 覆盖原地写到 async_ir_load 返回的
缓存共享子 IR 对象上,而 cache_ir_queue memory 层返回引用不拷贝,导致跨父级/跨请求
污染、意图路由串线(R-04)。编译器对每个子 agent 引用无条件 setIntent,故每个带子
agent 的 controller 执行都会触发。
- 新增模块级纯函数 _resolve_intent_fields:非空才覆盖(or 短路),
语义等价旧 if child_intent.get(name/description)
- _recursive_create 加 parent_intent 参数,覆盖应用到 current_metadata,
删除对 child_ir_data 的原地写
- 新增 test_ir_converter_parent_intent_isolation.py(13 例):
覆盖语义参数化(免 mock)+ 缓存不变性 + 父B 真实形态回退 +
跨父级不串线 + 顺序无关;改前红/改后绿已验证
不改 IR 协议/事件流;jiuwen 应用包源码,重启 agent-runtime 即生效。
Co-Authored-By: Claude <noreply@anthropic.com>
What type of PR is this?
/kind bug
Self-checklist:
设计:方案已经过评审(R04修复方案设计修改建议.md 全部意见已答复并吸收,产出「代码核对 + 评审修订版」R04修复方案设计.md);如需正式 Maintainer 评审可再走一轮
测试:新增 test_ir_converter_parent_intent_isolation.py(13 例:覆盖语义参数化 7 例 + 缓存不变性 + 父B 真实形态回退 + 跨父级不串线 + 顺序无关),已随本 PR 上库;改前红/改后绿已验证
验证:见下方「验证结果」(G1-G4,含单测 236 passed 与真平台运行时翻转)
接口:不涉及对外接口变更(_recursive_create 为内部闭包,新增 parent_intent 为可选参数;_resolve_intent_fields 为新增模块级私有函数;不改 IR 协议/事件流/checkpoint)
文档:不涉及官网文档(内部缺陷修复)
Special notes for your reviewers:
前向兼容:兼容。parent_intent 默认 None,create_all_agents_config_list 外部签名不变;intent 覆盖语义等价(非空才覆盖,经 _resolve_intent_fields 的 or 短路 ≡ 旧 if child_intent.get(...));唯一行为变化是「共享缓存子 IR 不再被原地污染」——这正是修复目标,非破坏性变更
三方库依赖:不涉及(本 PR 仅 ir_converter.py + 新增测试文件;本地开发用的 pyproject.toml(openjiuwen 源 git→file://)与 uv.lock 改动为本地覆盖,未纳入本 PR)
背景 / 根因
多智能体编排中,两个父级 Controller 引用同一共享子 Agent IR 时,转换器 _recursive_create(agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py)把父级 child-ref 的 per-reference intent 覆盖原地写到 async_ir_load 返回的缓存对象上;而 cache_ir_queue memory 层返回对象引用不拷贝(open_utils.py CacheUtils._get_from_memory_cache,should_serialize 只兜底 redis 层)→ 父 A 的 intent 残留在缓存对象上,同进程内父 B 读同一 ir_path 拿到父 A 写过的对象 → 跨父级意图路由串线。
编译器 ControllerManagementService.java:1140 对每个子 agent 引用无条件 setIntent → 运行时 if "intent" in child 恒真 → 每个带子 agent 的 Controller 执行都触发写入,默认配置(IR_CACHE_ENABLE=true、AGENT_GROUP_CACHE_ENABLE=false)下即满足。缺陷无异常抛出,表现为静默的错误路由;P1 真平台已坐实(详见缺陷 issue)。
修复方案(方案 A:per-reference 覆盖写进 per-config metadata)
把 intent 覆盖从「原地写共享缓存子 IR 对象」改为「写到 per-config current_metadata」,不碰缓存对象:
新增模块级纯函数 _resolve_intent_fields(current_ir_data, parent_intent):非空才覆盖(or 短路),语义等价旧 if child_intent.get(name/description);parent_intent 非 dict 或字段为空时回退子 IR 原值。
_recursive_create 新增可选参数 parent_intent,current_metadata 改用 helper 取 intent_name/intent_description。
删除对 child_ir_data["intent_name"]/["intent_description"] 的原地写,改为递归调用时参数下传 parent_intent=child_intent。
正确性依据:intent 下游消费点 _process_child_agents(intention_detect_module.py)读的是 AgentMetaData 字段(非 IR dict),覆盖搬到 metadata 后路由行为不变;_recursive_create 后续对 IR dict 的使用不依赖这两个键。
改动清单
文件 改动
agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py 新增 _resolve_intent_fields;_recursive_create 加 parent_intent 参数 + 删原地写(+31/−14)
agent-runtime/tests/unit_tests/serve/test_ir_converter_parent_intent_isolation.py 新增,13 例回归测试
验证结果
项 结果
G1 原地写删除 grep 'child_ir_data["intent' ir_converter.py 为空 ✓
G2 改前红/改后绿 临时恢复旧原地写 → 缓存不变性 + 跨父级串线两测试红(B 读到 'A注入');撤回 → 绿 ✓
G3 单测 新增 13 例 + 现有 test_ir_converter_global_var_cow / test_ir_converter_node_defs 共 34 passed;全 tests/unit_tests/serve/ 236 passed,无回归 ✓
G4 真平台运行时翻转 同进程顺序跑父 A/父 B:父 A 仍路由到 SHARED(R04_E2E_OK,覆盖能力未破坏);父 B 不再路由到 SHARED(由"被污染→R04_E2E_OK"翻转为"未识别到意图");重启清缓存对照一致 ✓