Read-only streak detector was a dumb counter that killed agents exploring
unique files (12 reads = kill regardless of uniqueness). Team tasks were
then falsely auto-completed with the kill message as "result."
Changes:
- Add uniqueness ratio to read-only streak: exploration mode (>60% unique)
uses relaxed thresholds (warn=24, kill=36) vs stuck mode (warn=8, kill=12)
- Classify team_tasks by action: list/get/search=read-only, progress=neutral,
create/complete/cancel/comment/etc.=mutating
- Add LoopKilled flag to RunResult, propagated through all 3 kill paths
- Consumer routes LoopKilled to FailTask (not CompleteTask) with clear signal
- 40 tests covering uniqueness detection, team_tasks classification,
exact #506 trace replay, boundary values, and edge cases
MCP tools (mcp_*) are user-defined external integrations where
read-heavy workflows are expected and legitimate. The read-only
streak detector was designed for filesystem tool loops but caught
MCP tools in the default fallback, triggering false positives on
workflows like "query inbox + read 10 emails + summarize."
Treat mcp_* tools as neutral (same as exec/bash) since GoClaw
cannot determine whether an MCP tool is read or write.
Closes#399
Detect agents stuck in read-only loops (consecutive non-mutating tool
calls) and same-result patterns (identical outputs from different args).
Warns at threshold, force-stops at critical.
- Update go.mod and Dockerfile to Go 1.26
- Apply `go fix ./...` stdlib modernizations across 170+ files
- Add `go fix` to post-implementation checklist in CLAUDE.md
- Fix go fix misapplied rewrite in loop_history.go
- Add tool loop detection (toolloop.go): tracks repeated no-progress tool calls
using SHA256 hashing of args+results. Warning at 5 identical calls, force stop
at 10. Prevents Gemini models from burning tokens in infinite loops.
- Inject AVAILABILITY.md negative context when agent has no team/delegation
targets, so models don't waste iterations probing unavailable capabilities.
- Fix Dockerfile: create /app/.goclaw directory so Docker volume initializes
with correct goclaw:goclaw ownership instead of root:root.
- Update collapseToolCallsWithoutSig comments for clarity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>