Skip to content

Intelligence

Intelligence is the recommendations feature that surfaces stale items, possible duplicates, grouping suggestions, and other gentle nudges to keep lists clean. It runs an LLM under the hood, so it depends on a configured AI provider.

The user-facing version of this feature is documented at Suggestions. This page is the operator’s reference.

  1. Configure an AI provider.
  2. Go to Admin > Intelligence.
  3. Toggle Enabled on.
  4. (Optional) Pick an intelligenceModelOverride if you want a different model than the global AI config uses elsewhere.
  5. Let the daily cron tick (0 4 * * *) populate recs, or run pnpm intelligence:run-once to generate immediately for testing.

Four analyzers ship in v1:

AnalyzerWhat it doesCalls the LLM?
Primary listPicks the user’s primary “active” wishlist and surfaces it on the dashboard. Pure heuristic.No
Stale itemsFlags items that haven’t been touched in a long time.Yes
DuplicatesDetects probable duplicate items within a list (or across the user’s lists).Yes
GroupingSuggests or / order item groups from clusters of similar items. The only analyzer that mutates state via an apply action.Yes

Each analyzer can be individually enabled/disabled via intelligencePerAnalyzerEnabled. Adding a new analyzer ships enabled by default; the per-analyzer map only stores admin overrides.

SettingDefaultWhat it does
intelligenceEnabledfalseMaster kill switch.
intelligenceRefreshIntervalDays7Cron regenerates a user’s recs when their last successful run is at least this old AND no active recs are un-reviewed.
intelligenceManualRefreshCooldownMinutes60Per-user rate limit on manual “refresh recs” clicks.
intelligenceCandidateCap50Max items per analyzer prompt. Caps token usage on power users.
intelligenceConcurrency3Max parallel users in one cron tick. Avoid hitting provider rate limits.
intelligenceUsersPerInvocation25Distinct users processed per cron tick before bailing. Lower if cron times out.
intelligenceStaleRecRetentionDays30How long to keep recommendations rows before the sweep deletes them.
intelligenceRunStepsRetentionDays30How long to keep recommendation_run_steps (per-step debug rows).
intelligenceDryRunfalseWhen true, runs go through the full pipeline (model calls, step logging) but persist no recommendations rows. Run + run-step rows still write, so admins can debug without affecting users.
intelligenceModelOverridenullOptional model ID override. Provider/key/baseUrl still come from the global AI config; only the model name swaps.

Every run path (cron, manual, CLI) records a row in recommendation_runs with a skip_reason if it didn’t actually generate recs. Common values:

skip_reasonWhen
disabledMaster toggle off.
no-providerAI config not resolvable.
unread-recs-existCron only. User has active recs from a prior batch; we don’t churn tokens before they engage with them.
unchanged-inputCron only. The combined per-analyzer input hash matches the last successful run AND we’re inside the refresh window.
cooldownManual only. User hit intelligenceManualRefreshCooldownMinutes.
lock-heldAnother run is in flight for this user.

The Postgres advisory lock is keyed by user, so concurrent triggers for the same user collide rather than double-spending tokens.

Each rec has a stable fingerprint (analyzer + kind + sorted target IDs). When a new batch produces a rec with the same fingerprint as a prior dismissed (or applied) rec, the runner carries the status forward instead of creating it as active.

So if you dismiss a “duplicate item pair” rec, that exact pair stays dismissed across regenerations until one of the items is deleted or renamed enough that the fingerprint changes.

Intelligence honors GiftWrapt’s spoiler-protection rule:

  • giftedItems (claim) data never appears in any prompt sent to a model.
  • Rec body text is human-readable rationale only; never references claim data.
  • items.isArchived = true items are excluded from candidate sets (those are revealed gifts, not list hygiene).
  • giftideas and todos lists are filtered out at the SQL level - gift ideas are spoiler surfaces for someone else, todos have no gift fields to analyze.

Three ways to run the analyzer pipeline; pick whichever fits your stack. They all share the same runner and can run alongside each other safely thanks to the per-user advisory lock.

ShapeWhere it runsWhen to pick it
Bundled HTTP cron/api/cron/intelligence-recommendations, CRON_SECRET-gatedDefault. Works with Vercel Cron, Render Cron, system crontab, the bundled docker compose sidecar.
External schedulerSame endpoint, hit by your own schedulerWhen you want central scheduling outside the app. Same secret.
Standalone workerpnpm intelligence:workerWhen you want a separate Docker service that owns the analyzer drain. Keep the HTTP cron running too if you like - the lock prevents double-spend.