Ask: Make src/lib/lesson-feed.ts read DISPATCH_LLM_API_KEY / DISPATCH_LLM_BASE_URL and a DISPATCH_LESSON_FEED_MODEL env var (with OPENAI_* as documented legacy fallback), and update .env.example accordingly so the lesson feed actually fires in deployments that only configure DISPATCH_LLM_*.
Expected files: src/lib/lesson-feed.ts, .env.example, docs/*.md (only files mentioning OPENAI_API_KEY for the lesson feed)
Problem: The hosted groomer config (src/lib/groomer/config.ts) and the LLM lesson feed (src/lib/lesson-feed.ts) read different env vars for the same thing. The groomer uses DISPATCH_LLM_API_KEY and DISPATCH_LLM_BASE_URL; the lesson feed uses OPENAI_API_KEY and OPENAI_BASE_URL (.env.example explicitly calls OPENAI_* the "Legacy / lesson-feed fallback"). In a deployment that only sets DISPATCH_LLM_* (which is what every recent runbook and the .env.example top section push operators toward), the lesson feed silently no-ops — extractLessonFromFixOutcome returns { kind: 'no_lesson' } because apiKey is the empty string and the if (!apiKey) early-return fires. There is no startup warning, no log line, no metric. The lesson-feed trigger that closes the loop from pr-fix/tombstone outcomes back into AGENTS.md (introduced for issue #754 / windowstead#321-style "assert_eq is 2-arg" lessons) is therefore dead in any non-OpenAI deployment, and no one would notice until they checked the lesson-feed trigger logs.
Evidence:
- src/lib/lesson-feed.ts:78–80 reads OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL. The only dispatch-namespaced env var the file reads is none.
- src/lib/groomer/config.ts:67–68 reads DISPATCH_LLM_BASE_URL and DISPATCH_LLM_API_KEY exclusively — there is no OPENAI_* fallback path there.
- .env.example lines 159–171 document OPENAI_* as the lesson-feed fallback and never mention DISPATCH_LLM_* for the lesson feed.
- src/lib/pr-fix-queue.ts:249–253 swallows lesson-feed errors with .catch(() => {}), so a missing API key is invisible at runtime.
- The silent-fallback pattern is what AGENTS.md calls a "completed but useless" outcome — the lifecycle succeeds, the lesson never lands.
Acceptance: src/lib/lesson-feed.ts reads DISPATCH_LLM_API_KEY (with OPENAI_API_KEY as documented fallback), DISPATCH_LLM_BASE_URL (with OPENAI_BASE_URL as documented fallback), and either DISPATCH_LESSON_FEED_MODEL or DISPATCH_GROOMER_MODEL (with OPENAI_MODEL as documented fallback). .env.example is updated to mark OPENAI_* as the lesson-feed legacy fallback and to point operators at DISPATCH_LLM_* first. A test in src/lib/lesson-feed.test.ts pins the precedence (DISPATCH_* > OPENAI_) and the missing-key early-return. A startup log line or one-time warning fires when OPENAI_ is used while DISPATCH_* is set, so the legacy fallback path is observable.
Ask: Make src/lib/lesson-feed.ts read DISPATCH_LLM_API_KEY / DISPATCH_LLM_BASE_URL and a DISPATCH_LESSON_FEED_MODEL env var (with OPENAI_* as documented legacy fallback), and update .env.example accordingly so the lesson feed actually fires in deployments that only configure DISPATCH_LLM_*.
Expected files: src/lib/lesson-feed.ts, .env.example, docs/*.md (only files mentioning OPENAI_API_KEY for the lesson feed)
Problem: The hosted groomer config (src/lib/groomer/config.ts) and the LLM lesson feed (src/lib/lesson-feed.ts) read different env vars for the same thing. The groomer uses DISPATCH_LLM_API_KEY and DISPATCH_LLM_BASE_URL; the lesson feed uses OPENAI_API_KEY and OPENAI_BASE_URL (.env.example explicitly calls OPENAI_* the "Legacy / lesson-feed fallback"). In a deployment that only sets DISPATCH_LLM_* (which is what every recent runbook and the .env.example top section push operators toward), the lesson feed silently no-ops — extractLessonFromFixOutcome returns { kind: 'no_lesson' } because apiKey is the empty string and the
if (!apiKey)early-return fires. There is no startup warning, no log line, no metric. The lesson-feed trigger that closes the loop from pr-fix/tombstone outcomes back into AGENTS.md (introduced for issue #754 / windowstead#321-style "assert_eq is 2-arg" lessons) is therefore dead in any non-OpenAI deployment, and no one would notice until they checked the lesson-feed trigger logs.Evidence:
Acceptance: src/lib/lesson-feed.ts reads DISPATCH_LLM_API_KEY (with OPENAI_API_KEY as documented fallback), DISPATCH_LLM_BASE_URL (with OPENAI_BASE_URL as documented fallback), and either DISPATCH_LESSON_FEED_MODEL or DISPATCH_GROOMER_MODEL (with OPENAI_MODEL as documented fallback). .env.example is updated to mark OPENAI_* as the lesson-feed legacy fallback and to point operators at DISPATCH_LLM_* first. A test in src/lib/lesson-feed.test.ts pins the precedence (DISPATCH_* > OPENAI_) and the missing-key early-return. A startup log line or one-time warning fires when OPENAI_ is used while DISPATCH_* is set, so the legacy fallback path is observable.