Skip to content

feat(llmo): register recurring DRS prompt-suggestion schedules at onboarding#2824

Open
dzehnder wants to merge 3 commits into
mainfrom
feat/prompt-suggestion-schedules
Open

feat(llmo): register recurring DRS prompt-suggestion schedules at onboarding#2824
dzehnder wants to merge 3 commits into
mainfrom
feat/prompt-suggestion-schedules

Conversation

@dzehnder

@dzehnder dzehnder commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 3 of the DRS prompt-suggestion schedules + onboarding-triggers effort (local/drs-prompt-suggestions-schedules-onboarding-plan.md). Wires three new onboarding triggers into the V2 onboarding path so newly onboarded LLMO sites get a recurring DRS schedule (plus an immediate first run) for each prompt-suggestion pipeline.

New helpers next to triggerBrandalfOnboardingJob in src/controllers/llmo/llmo-onboarding.js, each guarded by drsClient.isConfigured():

Helper provider_id Cadence
triggerSemrushPromptJob prompt_generation_semrush twice_monthly
triggerCitationAttemptJob prompt_generation_agentic_traffic twice_monthly
triggerSyntheticPersonasJob prompt_generation_synthetic_personas quarterly
  • prompt_generation_agentic_traffic is the provider behind "Citation Attempts" (no standalone citation-attempt provider — the name won't grep, so there's a code comment).
  • Each uses a single drsClient.createSchedule({ ..., triggerImmediately: true }) (no separate submitJob + createSchedule).
  • Invoked from the V2 block after the Brandalf trigger (which chains base prompt generation on completion, LLMO-4258) — not fired in parallel before brand data exists (plan Phase 0.3). The immediate run is best-effort; the durable outcome is the recurring schedule row.
  • enableBrandPresence defaults false (plan Phase 0.4).

Error handling — split semantics

  • Immediate first run: best-effort. If it produces nothing (e.g. brand/base-prompt data not present yet) the next scheduled run self-heals.
  • createSchedule (schedule registration): NOT best-effort. A failure means the site never gets a recurring schedule and nothing self-heals, so it is logged at ERROR with site_id + provider_id + status and surfaced via say/ops — never silently swallowed. Onboarding still succeeds (mirrors the brand-activation side-effect handling in brands.js activateBrand, which logs full context before we mirrored it).

Cross-repo dependency (deploy ordering)

Depends on a new additive createSchedule(...) method in @adobe/spacecat-shared-drs-client (separate spacecat-shared PR, not yet released). This PR bumps the dependency to 1.14.0.

This PR must merge/release after the shared drs-client createSchedule PR is published as 1.14.0. Until then, a fresh npm ci / bundle build cannot resolve 1.14.0 and CI install will be red — this is the intended cross-repo sequencing, not a defect. Unit tests do not hard-depend on the real method: they mock the DRS client (as existing onboarding tests do).

Confirmed createSchedule signature (from the shared PR author)

drsClient.createSchedule({
  siteId,                       // required, string
  providerIds,                  // required, string[] non-empty (one element per single-provider pipeline)
  cadence,                      // required, SCHEDULE_CADENCES value: 'twice_monthly' | 'quarterly'
  description,                  // optional, length-capped (1024)
  enableBrandPresence = false,
  providerParameters,           // optional per-provider passthrough
  priority = 'HIGH',            // 'HIGH' | 'LOW'
  metadata,                     // optional
  triggerImmediately = false,
  timeout,                      // optional ms
}) // => Promise<{ scheduleId: string, alreadyExisted: boolean }>  (409 dedup => alreadyExisted:true)

Key contract points this PR honors:

  • providerIds is an array (the job_config.provider_ids envelope), so each helper passes a one-element array.
  • cadence enum values use underscores (twice_monthly / quarterly); the client derives the cron server-side (frequency:'cron', per-site hour jitter for twice_monthly) — callers cannot pass raw cron. Kept as local literals matching SCHEDULE_CADENCES so the module loads against the currently-installed client; can be swapped for the imported const once 1.14.0 is installed.
  • No orgId/imsOrgId params — DRS derives the tenant-isolation key from site_id server-side and rejects any caller-supplied imsOrgId (at any nesting/case). This PR deliberately does not thread imsOrgId into createSchedule.

Tests

test/controllers/llmo/llmo-onboarding.test.js (mock DRS client; no real createSchedule dependency):

  • each helper registers the correct providerIds (array) + cadence with triggerImmediately: true, enableBrandPresence: false, and no imsOrgId/orgId;
  • each helper is a no-op (no createSchedule) when isConfigured() is false;
  • a createSchedule failure propagates to the caller (not swallowed at the helper);
  • V2 activateBrandAndGeneratePrompts registers all three schedules after the Brandalf trigger;
  • a createSchedule failure is logged at ERROR with site_id + provider_id + status while onboarding still completes.

npm run lint clean. All 11 new tests pass. The full llmo-onboarding.test.js file is 142 passing / 1 failing, where the single failure is a pre-existing settleWithin "before all" hook timeout that also fails on clean main (environment-related, unrelated to this change).

🤖 Generated with Claude Code

dzehnder and others added 2 commits July 15, 2026 16:05
…oarding

Adds three onboarding triggers next to triggerBrandalfOnboardingJob that
register recurring DRS schedules (with an immediate first run) for the
prompt-suggestion pipelines, invoked from the V2 onboarding block after the
Brandalf trigger:

- triggerSemrushPromptJob        -> prompt_generation_semrush        (twice-monthly)
- triggerCitationAttemptJob      -> prompt_generation_agentic_traffic (twice-monthly)
- triggerSyntheticPersonasJob    -> prompt_generation_synthetic_personas (quarterly)

Each is guarded by drsClient.isConfigured() and uses a single
createSchedule({ ..., triggerImmediately: true }) call. Split error semantics:
the immediate first run is best-effort (self-heals on the next scheduled run),
but a createSchedule (schedule-registration) failure is logged at ERROR with
site_id + provider_id + status and surfaced to ops -- never silently swallowed,
since a swallowed failure means the site never gets a recurring schedule.
Onboarding still succeeds when a schedule registration fails.

Depends on a new additive createSchedule(...) method in
@adobe/spacecat-shared-drs-client (bumped to 1.14.0); must merge/release after
that shared PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dule signature

Match the shared drs-client createSchedule contract (confirmed):
- providerIds is an ARRAY (job_config.provider_ids envelope) — pass a
  one-element array per single-provider pipeline.
- cadence enum values use underscores: 'twice_monthly' / 'quarterly'
  (SCHEDULE_CADENCES); the client derives the cron server-side.
- drop orgId/imsOrgId entirely: DRS derives the tenant-isolation key from
  site_id server-side and rejects any caller-supplied imsOrgId, so it must not
  be threaded through createSchedule.
- add a length-capped description; priority defaults HIGH in the client.

Also harden the new Phase 3 before-hook with an explicit timeout for cold
esmock module loads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

Address PR review on the prompt-suggestion schedule registration:

- Replace the three near-identical exported trigger helpers and the inline
  per-provider array with a single PROMPT_SUGGESTION_PIPELINES table, iterated
  by registerPromptSuggestionSchedules. Each providerId+cadence is now declared
  once; adding a pipeline is a one-line change. Rename reflects the durable
  effect (register a recurring schedule, not trigger a one-shot job).
- Single-layer error ownership: iterate the pipelines under Promise.all with a
  per-pipeline try/catch that owns the failure (ERROR log + operator Slack
  signal) and never rethrows, replacing the redundant allSettled + inner catch.
- Correct the overstated sequencing comment: the immediate first run fires right
  after SUBMITTING the async Brandalf job, so it races Brandalf and typically
  no-ops for brand-new sites, self-healing on the next scheduled run. Note the
  worst-case quarterly (synthetic_personas) cold-start latency, and add a TODO
  for the cross-repo follow-up (DRS chaining off base-prompt-gen completion).
- Hoist the DRS client to a single DrsClient.createFrom(context) in the V2 block.
- Tests: iterate PROMPT_SUGGESTION_PIPELINES (with a drift guard), assert the
  schedules register after the Brandalf submit, cover the status=unknown and
  alreadyExisted:true branches, and assert the failure-path Slack warning fires.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant