fischer-agentkit/docs/solutions/logic-errors/gate-without-caller-silentl...

3.9 KiB
Raw Blame History

title date module tags problem_type severity resolution_type category
Gate without caller silently disables feature 2026-07-06 experts/orchestrator, core/reflexion
gate
trigger_reason
dead-code
design-pattern
code-review
logic_error P1 code_fix logic-errors

Gate without caller silently disables feature

Problem

_decompose_task 新增了 trigger_reason: str | None = None 参数和门控逻辑,用于控制 retrieve_prompt_reflection 的调用。但两个生产调用方都不传 trigger_reason,导致门控条件恒为 False功能被静默禁用。

Symptoms

  • retrieve_prompt_reflection 在生产中永远不会被调用(死代码)
  • 无错误日志,无异常 — 功能只是"消失"了
  • 单元测试全部通过(因为测试直接传入 trigger_reason="verify_retry",未覆盖生产调用路径)
  • 修复前是"每次分解都检索"(浪费 token修复后变成"永不检索"(功能丢失)

What Didn't Work

  1. 单元测试无法发现:测试直接调用 _decompose_task(..., trigger_reason="verify_retry"),验证门控逻辑本身正确,但未覆盖生产调用路径(两个调用方都不传 trigger_reason
  2. 功能测试无法发现retrieve_prompt_reflection 返回空时不报错,只是降级到默认 prompt行为差异不明显
  3. 类型检查无法发现trigger_reason 有默认值 None,类型检查通过

Solution

_rebalance 路径传入 trigger_reason="loop_detection",使重新规划时检索历史反思:

# 修复前 — 两个调用方都不传 trigger_reason
phases = await self._decompose_task(lead, task)  # 正常分解
new_phases = await self._decompose_task(lead, augmented_task)  # 重分解

# 修复后 — _rebalance 路径传入 trigger_reason
new_phases = await self._decompose_task(
    lead, augmented_task, trigger_reason="loop_detection"
)

并在正常分解路径添加注释说明 verify/schema 重试路径待接入。

Why This Works

门控设计的核心假设是"有调用方会传入触发条件"。当这个假设不成立时,门控会静默禁用功能:

  • trigger_reason: str | None = None — 默认 None
  • None not in frozenset({"verify_retry", ...}) — 门控条件恒为 False
  • 无错误、无日志、无异常 — 功能只是"消失"了

修复方式是确保至少有一个调用方传入触发条件。_rebalance 是重新规划路径,传入 loop_detection 语义合理(检测到规划问题后重新规划)。

Prevention

1. 门控设计检查清单

设计门控/开关时,必须:

  • 列出所有调用方,确保至少有一个会传入触发条件
  • 添加断言或日志:如果门控条件从未为 True发出警告
  • 在 docstring 中明确说明"调用方必须传入 X 才能触发 Y"

2. 测试覆盖生产调用路径

# 不要只测试门控逻辑本身
async def test_gate_with_trigger():
    await decompose_task(lead, task, trigger_reason="verify_retry")
    assert retrieve_called  # 这只验证门控逻辑

# 还要测试生产调用路径
async def test_production_call_path_does_not_silently_disable():
    # 模拟生产调用(不传 trigger_reason
    await decompose_task(lead, task)
    # 如果功能应该被禁用assert 它被禁用
    # 如果功能不应该被禁用assert 它仍然工作

3. 代码审查重点

审查门控逻辑时,追踪所有调用方:

  • 门控条件是否可达?
  • 是否有调用方传入触发条件?
  • 默认值是否会导致功能被静默禁用?

4. 死代码检测

定期检查从未执行的代码路径。retrieve_prompt_reflection 如果从未被调用,是死代码的信号。

  • docs/solutions/runtime-errors/async-generator-yield-before-await-deadlock.md — 同一 IQ-boost 工作中的另一个非显而易见 bug
  • PR #27 (feat/agent-iq-boost) — 原始 trigger_reason 门控引入
  • PR fix/agent-iq-boost-gaps — 本次修复