fix(observer): auto-scale max_turns by analysis batch size (#2062)

* fix(observer): auto-scale max_turns by analysis batch size (#2035)

The hardcoded default of MAX_TURNS=20 is insufficient when
MAX_ANALYSIS_LINES=500 (also the default). Claude exhausts its turn
budget before it can write all discovered instinct files, producing:

  Error: Reached max turns (20)

Fix: when ECC_OBSERVER_MAX_TURNS is not explicitly set, compute
max_turns proportionally to the actual analysis batch size:
  max_turns = clamp(analysis_count / 10, 20, 100)

This gives:
  - 20–199 lines → 20 turns  (existing floor, unchanged)
  - 500 lines    → 50 turns  (resolves the reported failure)
  - 1000 lines   → 100 turns (cap)

Explicitly setting ECC_OBSERVER_MAX_TURNS still overrides the
auto-scaled value, preserving the existing escape hatch.

* test(observer): update max_turns test for auto-scaling; document validation

The max-turns budget test in tests/hooks/hooks.test.js still asserted the removed literal max_turns="${ECC_OBSERVER_MAX_TURNS:-20}", which would fail against the new auto-scaling logic. Assert the auto-scale formula and the 20/100 clamp bounds instead.

Also add the explanatory comment CodeRabbit requested above the max_turns sanitization block, clarifying it guards the explicit ECC_OBSERVER_MAX_TURNS override path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zucchini
2026-06-07 10:25:29 +05:00
committed by GitHub
parent d8a84b5f7b
commit a3d8d8ab92
2 changed files with 17 additions and 2 deletions

View File

@@ -3137,7 +3137,9 @@ async function runTests() {
const observerLoopSource = fs.readFileSync(path.join(__dirname, '..', '..', 'skills', 'continuous-learning-v2', 'agents', 'observer-loop.sh'), 'utf8');
assert.ok(observerLoopSource.includes('ECC_OBSERVER_MAX_TURNS'), 'observer-loop should allow max-turn overrides');
assert.ok(observerLoopSource.includes('max_turns="${ECC_OBSERVER_MAX_TURNS:-20}"'), 'observer-loop should default to 20 turns');
assert.ok(observerLoopSource.includes('max_turns=$(( analysis_count / 10 ))'), 'observer-loop should auto-scale max_turns from the analysis batch size when no override is set');
assert.ok(observerLoopSource.includes('if [ "$max_turns" -lt 20 ]; then max_turns=20; fi'), 'observer-loop should clamp the auto-scaled budget to a floor of 20 turns');
assert.ok(observerLoopSource.includes('if [ "$max_turns" -gt 100 ]; then max_turns=100; fi'), 'observer-loop should clamp the auto-scaled budget to a cap of 100 turns');
assert.ok(!observerLoopSource.includes('--max-turns 3'), 'observer-loop should not hardcode a 3-turn limit');
assert.ok(observerLoopSource.includes('ECC_SKIP_OBSERVE=1'), 'observer-loop should suppress observe.sh for automated sessions');
assert.ok(observerLoopSource.includes('ECC_HOOK_PROFILE=minimal'), 'observer-loop should run automated analysis with the minimal hook profile');