[integrations] Enrichment worker: in-place LLM metadata for un-enriched thoughts + Readwise stamping - #454
Conversation
…a error reasons Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
… breaker) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
…orget enrich Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
Key is passed via the x-brain-key header rather than a ?key= URL param so the secret does not persist in cron.job, cron.job_run_details, or request URL logs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
…eps key out of URL logs only)
Drain thoughts_needing_enrichment by repeatedly invoking the worker. Stops cleanly only when remaining==0; treats remaining<0 (count-query sentinel) or non-numeric as transient and retries; exits 1 on circuit break; retries on curl failure. Progress line every 10 iterations. No set -x — the access key is never echoed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
The repo copy predated the live Tier-1 hygiene block. Mirror the deployed source exactly: import the canonical derived-thought-writer, add the lint_hygiene_summary fetch + hygiene/llm_error surface, switch to claude-haiku-4-5 with JSON-fence stripping, and consolidate auth onto MCP_ACCESS_KEY. Also vendor the _shared/derived-thought-writer.ts the function bundles so the repo is deployable in isolation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
Fold the Task 1 enrichment-backlog views into the auditor's Tier-1 hygiene surface. fetchHygiene() now head-counts thoughts_needing_enrichment and thoughts_enrichment_stuck (before the LLM pass, cheap, always computed) and merges needs_enrichment / enrichment_stuck (-1 on null) into the hygiene object. The mechanical hygiene line gains, in the same middot style: un-enriched: N (M stuck-at-max). Deployed as auditor v14; verified live the rendered line reads 'un-enriched: 8540 (1 stuck-at-max)'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
… breaks (5-strike cooldown) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
…EY + fix '?' render Finding 1: auditor code authenticates on MCP_ACCESS_KEY (x-brain-key header) but the recipe docs still told installers to use the retired AUDITOR_ACCESS_KEY via ?key= URL param — a fresh install would 401 forever. Update README env list, credential tracker, smoke-test + troubleshooting invocations, and schedule.sql to the header form; refresh the required-env header comment in index.ts. Finding 2: enrichment head-counts are stored as -1 on failure (never null), so the `?? "?"` render fallback never fired — failures rendered as -1. Add a showN() helper that renders '?' for negative values on the two enrichment fields, and correct the stale comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
deno.json was copied from enhanced-mcp and shipped @hono/mcp, @modelcontextprotocol/sdk, hono, and zod — none of which the worker imports. The @supabase/supabase-js map entry was also dead: index.ts imports the full npm:@supabase/supabase-js@2 specifier, so the bare-key pin never resolved. Empty the imports map. Tests unchanged (11/11). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
…-row transport failures
Fix B (native list preservation): buildCompletePatch now keeps the native
base list WHOLE (never capped/dropped) and appends LLM extras only into the
room left under the cap of 12. Adds coerceStringList() to handle string,
string[], and Readwise {name}/{tag} object shapes, failing closed (raw base
kept verbatim, no merge) on any unrecognizable value. Applied uniformly to
topics/tags/people/action_items. TDD: 4 new tests + updated cap test.
Fix C (single-row transport failures): a lone claim has no circuit probe, so
a transport-class fallback (fatal_provider_error / transient_failures_exhausted
/ all_providers_failed) on the only row now trips the breaker — clears the
claim, burns zero attempts, reports circuit_broken — instead of consuming an
attempt. Multi-row batches keep per-row fallback semantics (probe proves
provider health; cron batches provide the stuck-escalation path).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
…e queue (schema v2) Fix A (defense-in-depth): both the thoughts_needing_enrichment view predicate and the claim_thoughts_for_enrichment RPC predicate now add `AND NOT (metadata ? 'generator')`. Synthesis artifacts (audit reports, briefings, wiki pages) carry a generator key and must never be re-classified; writers also stamp status 'exempt'. This is the schema-independent belt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
…upsert Fix A (writer + auditor): storeDerivedThought now forces metadata.enrichment_status='exempt' (belt) and stamps derivation_layer='derived' + derivation_method='synthesis' on the row (suspenders, via a best-effort follow-up UPDATE so a missing provenance column can't fail a synthesis run). The auditor's audit_report metadata also carries enrichment_status='exempt' explicitly. Fix D (portability): the writer attempts the optional 3-arg upsert_thought(content,payload,embedding); on PGRST202 / 42883 it falls back to the canonical 2-arg upsert_thought and sets the embedding in the follow-up UPDATE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
…on + backfill Adds a "Synthesis artifacts (never enrich these)" README section explaining the three-layer exemption (exempt status, derived provenance columns, generator-key predicate) and a documented-not-run enumerated-or-predicate backfill UPDATE for pre-existing synthesis rows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
OB1 PR Gate✅ Folder structure — All files are in allowed directories
✅ Contribution dependencies — All declared skill and primitive dependencies exist and are linked in README
Result: 13/15 checks passed. Please fix the issues above and push again. Post-Merge TasksThese don't block merge — they're reminders for admins after this PR lands.
|
…adata Legacy rows storing metadata as a JSON string/array were array-ified by the claim RPC's jsonb || (scalars concat as single-element arrays) and then spread into numeric-keyed objects by the merge (2026-07-15 incident: 48 rows mangled, all repaired; 50 string-shaped rows normalized). Guard both layers: the RPC wraps non-object metadata verbatim under legacy_metadata, and asObjectMetadata() does the same in every merge builder. 17/17 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
Summary
enrichment-workerEdge Function: drains thoughts withenrichment_statusNULL/'pending'/'fallback'/'skipped' through the canonical extractMetadata cascade, in place, via an atomicFOR UPDATE SKIP LOCKEDclaim RPC with a 10-min leasesource_typeimmutable; Readwise rows keeptype='reference'(classifier opinion →metadata.classified_type); native tags/topics/people/action_items preserved whole with LLM additions capped to the room under 12 (fail-closed coercion for non-string list shapes); classifier fallback writes tracking keys only — never fallbackMetadata contentgenerator-keyed metadata) are excluded from the queue and derived-thought writers stampenrichment_status='exempt'+ provenancereadwise-capturestampsenrichment_status='pending'+ fire-and-forgets a single-id enrich;import-readwise.pystamps the sameextractMetadatagains additive_enrichment_errorreasons so callers can distinguish run-level from row-level failuresbackfill.shdrain loop with 5-strike outage cooldown; auditor reportsun-enriched: N (M stuck-at-max)in its hygiene line (auditor repo copy also reconciled with live source and its install docs fixed to the MCP_ACCESS_KEY header pattern)Review
Test plan
deno test --allow-read— 15/15 (merge policy incl. preservation/coercion edge cases + vendored-_shared drift guard)🤖 Generated with Claude Code
https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH