Skip to content

fix: 父级 intent 覆盖不再原地写共享缓存子 IR,改写 per-config metadata - #82

Open
openjiuwen-release-bot[bot] wants to merge 1 commit into
openJiuwen-ai:studio-2.0-devfrom
openjiuwenai:gitcode-mr-1642-199c4e1
Open

fix: 父级 intent 覆盖不再原地写共享缓存子 IR,改写 per-config metadata#82
openjiuwen-release-bot[bot] wants to merge 1 commit into
openJiuwen-ai:studio-2.0-devfrom
openjiuwenai:gitcode-mr-1642-199c4e1

Conversation

@openjiuwen-release-bot

Copy link
Copy Markdown
Contributor

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"翻转为"未识别到意图");重启清缓存对照一致 ✓

_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>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@openjiuwen-release-bot

Copy link
Copy Markdown
Contributor Author

变更摘要

此 PR 修复了多智能体编排中父级 Controller 引用同一共享子 Agent IR 时,转换器 _recursive_create 闭包原地修改共享缓存子 IR 对象导致跨父级意图(intent)污染的问题。修复方案是新增模块级函数 _resolve_intent_fields,将父级 intent 覆盖仅写入各配置独立的 AgentMetaData 元数据,不再触碰由 async_ir_load 返回的共享缓存对象,从而确保同一子 IR 被多个父级引用时互不串扰。

主要改动

  • 新增 _resolve_intent_fields 函数: 抽取 intent 字段解析逻辑为独立纯函数,通过 or 短路实现"非空才覆盖"语义(等价于旧实现的 if child_intent.get(...) 判断),返回 (intent_name, intent_description) 元组,不修改任何传入对象。
  • _recursive_create 闭包新增 parent_intent 参数: 闭包签名增加 parent_intent: Optional[Dict[str, Any]] = None,在构建 AgentMetaData 时调用 _resolve_intent_fields(current_ir_data, parent_intent) 获取最终 intent 值,替代原先从 child_ir_data 直接读取并原地回写的做法。
  • 删除 child_ir_data 原地写入逻辑: 移除了循环中 child_ir_data["intent_name"] = ...child_ir_data["intent_description"] = ... 的原代码,改为从 child 引用中提取 child_intent 并通过 parent_intent 参数下传给递归调用,杜绝共享缓存对象被污染。
  • 新增单元测试 test_ir_converter_parent_intent_isolation.py: 覆盖 13 个测试用例,包含 _resolve_intent_fields 参数化语义测试(7 例)、共享 IR 缓存不变性验证、父级 metadata 确定性覆盖、跨父级不串线及顺序无关性验证。

@openjiuwen-release-bot

Copy link
Copy Markdown
Contributor Author

代码审查

我已经完成了对两个变更文件的全面审查。让我来总结我的发现。


审查结论

变更文件 1:agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py

已审,未发现问题。

变更内容:

  • 新增模块级私有函数 _resolve_intent_fields(L794-809),实现父级 per-reference intent 覆盖语义,使用 or 短路运算符保证"非空才覆盖",语义等价于旧实现中的 if child_intent.get("name"): ...
  • _recursive_create 闭包中新增可选参数 parent_intent: Optional[Dict[str, Any]] = None(L1060),将 intent 覆盖写从"原地污染共享缓存子 IR 对象"改为"下传到 per-config current_metadata"。
  • 移除了旧的直接写入 child_ir_data 的代码(原 L1074-1083),替换为在 L1098 提取 child_intent 并以 parent_intent=child_intent 下传(L1103)。
  • 所有边界情况(parent_intent=None、空 dict、非 dict、空字符串字段、无 intent 字段等)均正确回退到子 IR 原值。
  • OptionalDict 等类型已从 typing 导入(L13),类型标注有效。
  • 根调用 _recursive_create(root_ir_data, parent_metadata=None)(L1157)未传 parent_intent,默认 None,根 Agent 的 intent 正确取自自身 IR。
  • create_agent_group_config 中的另一个调用点(L1189)也未传 parent_intent,行为一致。

变更文件 2:agent-runtime/tests/unit_tests/serve/test_ir_converter_parent_intent_isolation.py

已审,未发现问题。

变更内容:

  • 新增 236 行测试文件,覆盖三大类场景:
    1. 覆盖语义参数化(7 个参数化用例):直接测试 _resolve_intent_fields 纯函数,覆盖 None、非 dict、空 dict、空/非空字段、单字段覆盖、双字段覆盖。
    2. 转换器隔离(4 个用例):验证 shared IR 缓存不被修改 + 各父级 metadata 按各自 child-ref 正确覆盖/回退。
    3. 跨父级不串线 + 顺序无关(2 个用例):在同一 shared IR 对象上顺序运行两父级,验证无污染且结果有序无关。
  • Mock 策略合理:async_ir_load 返回同一 shared_ir 对象模拟 memory cache 共享;create_agent_config 每次返回新 AgentConfig() 避免 metadata 互相覆盖;AgentIrUtils / AgentIrValidator 被旁路。
  • 所有 imports 正确,_resolve_intent_fields 作为模块级私有函数被测试导入,无问题。
  • 测试用例命名清晰,断言精确。

总体风险评估:低风险

本次变更是针对性修复,核心逻辑是:将 intent 覆盖从"原地写缓存 IR 对象"改为"通过参数下传到 per-config metadata 构建"。新的 _resolve_intent_fields 函数对旧实现的语义保持精确等价,所有 7 个参数化测试用例均验证了等价性。测试覆盖了缓存不变性、跨父级隔离、顺序无关性等关键回归点。对外接口 create_all_agents_config_list 签名不变,parent_intent 仅为内部闭包参数。无 breaking change、无安全风险、无性能退化。

⚠️ 已识别出整体风险,但无法提取行内评论,请参考整体评估。

@openjiuwen-release-bot

Copy link
Copy Markdown
Contributor Author
TASK STATUS DETAILS
CodeCheck ❌FAILED Click here
AntiPoison ✅SUCCESS Click here
Software Composition Analysis ✅SUCCESS Click here
Npm Build ✅SUCCESS See CHECK tab
Ruff Check ✅SUCCESS See CHECK tab
UT测试 ✅SUCCESS 点此跳转

@openjiuwen-release-bot

Copy link
Copy Markdown
Contributor Author
TASK STATUS DETAILS
CodeCheck ✅SUCCESS Click here
AntiPoison ✅SUCCESS Click here
Software Composition Analysis ✅SUCCESS Click here
Npm Build ✅SUCCESS See CHECK tab
Ruff Check ✅SUCCESS See CHECK tab
UT测试 ✅SUCCESS 点此跳转

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants