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

99 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "Gate without caller silently disables feature"
date: 2026-07-06
module: experts/orchestrator, core/reflexion
tags: [gate, trigger_reason, dead-code, design-pattern, code-review]
problem_type: logic_error
severity: P1
resolution_type: code_fix
category: 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"`,使重新规划时检索历史反思:
```python
# 修复前 — 两个调用方都不传 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. 测试覆盖生产调用路径
```python
# 不要只测试门控逻辑本身
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` 如果从未被调用,是死代码的信号。
## Related
- `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 — 本次修复