fix(923): watchdog counts only tool-using turns; narration-only turns no longer burn budget

Observed: stories 917, 918, 920, 910 all turn-limit-killed despite producing
real commits. Tally across their session logs shows 30–55% of assistant
turns were pure narration ("I'll read X next", "Now let me check Y") with
no tool_use. At 80 max_turns the effective work budget was ~44 tool calls,
not enough for a typical bug fix's edit + test + check_criterion cycle.

Changes:
- New optional AgentConfig field max_tool_turns. When set the watchdog
  uses it instead of max_turns; only assistant messages whose
  data.message.content has at least one tool_use block count.
- count_turns_in_log in agents/pool/auto_assign/watchdog/limits.rs
  filters on tool_use. Existing test helper write_fake_session_log now
  emits tool_use blocks; added write_fake_mixed_session_log for the
  narration regression test.
- agents.toml: coders/coder-opus get max_turns=200 (claude-code's own
  --max-turns cap, sized to never bite before the watchdog) and
  max_tool_turns=80. qa: 120 / 40. mergemaster: 250 / 100. Budgets
  unchanged — the dollar cap remains the runaway-loop backstop, with
  ~$3-5 worst-case waste if an agent narrates indefinitely.
- Two new regression tests:
  * watchdog_does_not_count_narration_only_turns: 5 tool + 30 narration
    under max_tool_turns=10 stays Running.
  * watchdog_max_tool_turns_overrides_max_turns: 4 tool turns at
    max_tool_turns=3 / max_turns=200 still terminates with TurnLimit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-05-12 17:25:11 +01:00
parent ce07c4d7b7
commit 6feb68f3e3
5 changed files with 197 additions and 22 deletions
+10
View File
@@ -242,6 +242,15 @@ pub struct AgentConfig {
pub disallowed_tools: Option<Vec<String>>,
#[serde(default)]
pub max_turns: Option<u32>,
/// Watchdog cap on **tool-using** assistant turns (story 923).
///
/// Narration-only turns (assistant messages with no `tool_use` block)
/// don't count against this. When unset, the watchdog falls back to
/// `max_turns`. Set this lower than `max_turns` so claude-code's own
/// `--max-turns` cap (which counts narration) doesn't fire before the
/// watchdog.
#[serde(default)]
pub max_tool_turns: Option<u32>,
#[serde(default)]
pub max_budget_usd: Option<f64>,
#[serde(default)]
@@ -325,6 +334,7 @@ impl Default for ProjectConfig {
allowed_tools: None,
disallowed_tools: None,
max_turns: None,
max_tool_turns: None,
max_budget_usd: None,
system_prompt: None,
stage: None,