From c2bcc4ec2f783c59a62a590d571e88aa23904c72 Mon Sep 17 00:00:00 2001 From: jack-finance-able Date: Mon, 29 Jun 2026 20:43:42 -0500 Subject: [PATCH] feat(continuous-learning-v2): make observer model configurable via ECC_OBSERVER_MODEL (#2390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(continuous-learning-v2): make observer model configurable via ECC_OBSERVER_MODEL The observer hardcoded `--model haiku`. Parameterize as "${ECC_OBSERVER_MODEL:-haiku}": the haiku default is preserved (no behavior change for existing users), but users can opt into a stronger model — e.g. `ECC_OBSERVER_MODEL=opus` — for higher-quality instinct extraction. Useful on subscription plans where model cost isn't the limiting factor. * fix(continuous-learning-v2): address review — update wiring test + docs - Update source-inspection test to assert the ${ECC_OBSERVER_MODEL:-haiku} defaulting behavior (was matching the literal `claude --model haiku`, which this PR changed). All 31 tests pass. - Add guidance to raise ECC_OBSERVER_TIMEOUT_SECONDS for slower models (e.g. opus) so the 120s watchdog doesn't kill analysis mid-run. - Fix now-stale 'Haiku session' comment -> 'observer session' (model is configurable). --- skills/continuous-learning-v2/agents/observer-loop.sh | 8 ++++++-- tests/hooks/observer-memory.test.js | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/skills/continuous-learning-v2/agents/observer-loop.sh b/skills/continuous-learning-v2/agents/observer-loop.sh index 1ac1c56c..fa50c1f6 100755 --- a/skills/continuous-learning-v2/agents/observer-loop.sh +++ b/skills/continuous-learning-v2/agents/observer-loop.sh @@ -241,11 +241,15 @@ PROMPT # on all platforms, not just when the observer happens to be launched from the project root. cd "$PROJECT_DIR" || { echo "[$(date)] Failed to cd to PROJECT_DIR ($PROJECT_DIR), skipping analysis" >> "$LOG_FILE"; rm -f "$analysis_file"; return; } - # Prevent observe.sh from recording this automated Haiku session as observations. + # Prevent observe.sh from recording this automated observer session as observations. # Pass prompt via -p flag instead of stdin redirect for Windows compatibility (#842). # prompt_content is already loaded in-memory so this no longer depends on the # mktemp absolute path continuing to resolve after cwd changes (#1296). - ECC_SKIP_OBSERVE=1 ECC_HOOK_PROFILE=minimal claude --model haiku --max-turns "$max_turns" --print \ + # Model is configurable via ECC_OBSERVER_MODEL (defaults to haiku for cost efficiency); + # e.g. ECC_OBSERVER_MODEL=opus for higher-quality instinct extraction. Heavier models are + # slower — consider raising ECC_OBSERVER_TIMEOUT_SECONDS (default 120s) so the watchdog + # doesn't kill the analysis mid-run. + ECC_SKIP_OBSERVE=1 ECC_HOOK_PROFILE=minimal claude --model "${ECC_OBSERVER_MODEL:-haiku}" --max-turns "$max_turns" --print \ --allowedTools "Read,Write" \ -p "$prompt_content" >> "$LOG_FILE" 2>&1 & claude_pid=$! diff --git a/tests/hooks/observer-memory.test.js b/tests/hooks/observer-memory.test.js index 597c7df2..9bfbdde0 100644 --- a/tests/hooks/observer-memory.test.js +++ b/tests/hooks/observer-memory.test.js @@ -454,8 +454,13 @@ test('claude invocation still includes ECC_SKIP_OBSERVE and ECC_HOOK_PROFILE gua const content = fs.readFileSync(observerLoopPath, 'utf8'); // Find the claude execution line(s) const lines = content.split('\n'); - const claudeLine = lines.find(l => l.includes('claude --model haiku')); - assert.ok(claudeLine, 'Should find claude --model haiku invocation line'); + const claudeLine = lines.find(l => l.includes('claude --model')); + assert.ok(claudeLine, 'Should find claude --model invocation line'); + // Model is configurable via ECC_OBSERVER_MODEL but must still default to haiku. + assert.ok( + claudeLine.includes('${ECC_OBSERVER_MODEL:-haiku}'), + `claude --model should default to haiku and honor ECC_OBSERVER_MODEL, got: ${claudeLine}` + ); // The env vars are on the same line as the claude command const claudeLineIndex = lines.indexOf(claudeLine); const fullCommand = lines.slice(Math.max(0, claudeLineIndex - 1), claudeLineIndex + 3).join(' ');