Skip to content

User-Specific AI Jobs

The user_specific_contents_ai/ directory contains five PM2-managed AI workers that run continuously in the background, polling the database for queued work and producing per-account content. They power the post-onboarding pipeline that turns a corporate profile into actionable brand positioning, objectives, and content.

For the synchronous onboarding agents that run inside HTTP requests, see Conversational AI Agents.


Overview

File PM2 name Cron Reads Writes
job_brand_positioning_ai.js brandPositioning-AI-Response every 30s tCompany_Website_Info tBrandPositioning, tCorporateLogs
job_objective_ai.js objective-AI-Response every 30s tCompany_Website_Info + brand positioning tObjective
job_recom_subjects_ai.js recomSubjects-AI-Response every 30s brand + objective tRecommendedSub, tCorporateLogs
job_content_translation.js (job-queue daemon, type TCON) every 15s tJobs tRephrased_Translated_Content, tUserLibrary
job_translate_and_rephrase.js (job-queue daemon, type RTCON) every 30s tJobs tRephrased_Translated_Content, tUserLibrary, enqueues RGEN job

All five are registered in ecosystem.config.js (lines ~88–101) and run as standalone Node processes managed by PM2.

Note: ecosystem.config.js references a sixth file user_specific_contents_ai/job_content_ai.js that does not exist on disk. See Jobs Inventory for the full list of stale PM2 references.


Common Pattern

Each job follows a consistent shape:

┌─────────────────────────────────────────────────────────┐
│  setInterval / cron tick                                │
│      │                                                  │
│      ▼                                                  │
│  SELECT row(s) FROM source_table WHERE status IN (0, 2)│
│      │                                                  │
│      ▼                                                  │
│  fetch AI parameters (getAiParametersFunction)         │
│      │                                                  │
│      ▼                                                  │
│  invoke model (Gemini / Llama / Bedrock)                │
│      │                                                  │
│      ├─ success ──► UPDATE row SET ai_response, status=1│
│      └─ failure ──► UPDATE row SET status = 2 (retry)   │
│                                              or = 3 (give up after 2nd attempt) │
└─────────────────────────────────────────────────────────┘

Status flow

The first three jobs (brand / objective / recom) share a status convention on their source row:

Status Meaning
0 Pending — pick up on next tick
1 Completed successfully
2 Errored on first attempt — retry once
3 Errored on second attempt — give up

The two job-queue daemons (TCON, RTCON) use the standard tJobs status machine documented in Content Pipeline → Status states.

Concurrency

Each daemon uses a module-level isOnProcess flag so cron ticks don't overlap themselves. If a tick fires while the previous run is still working, it returns early.


job_brand_positioning_ai.js

Trigger: every 30s, scans tCompany_Website_Info for rows where brandPositioned IN (0, 2).

Inputs: - extracted_data — JSON blob of company details from the onboarding research - languageId, accountId — for prompt localization and ownership

Model: Gemini, via getGeminiResult() in helper/aiLogics.js. AI parameters are fetched via getAiParametersFunction({ languageId, ... }) — see RAG Pipeline.

Output shape: JSON array of { question_id, group_id, question, answer } matched to rows in tBrandPositioningQuestions.

Persistence: - Inserts or updates tBrandPositioning.ai_response keyed on (accountId, question_id) - Logs the run in tCorporateLogs with type = 2 - Sets tCompany_Website_Info.brandPositioned = 1


job_objective_ai.js

Trigger: every 30s. Joins tCompany_Website_Info with tBrandPositioning and only picks rows where the brand response exists and isAiObj_created IN (0, 2).

Why the join: objectives depend on already-approved brand positioning. If brand Q&A isn't done, this job skips the row.

Inputs: orgProfile + brand Q&A passed into the prompt template.

Model: Gemini via getGeminiResult().

Output: JSON array of objective entries.

Persistence: - Inserts or updates tObjective.ai_objective - Sets tCompany_Website_Info.isAiObj_created = 1


job_recom_subjects_ai.js

Trigger: every 30s. Requires brand positioning + objective both approved, with isAiRecSub_created IN (0, 2).

Inputs: company info + brand Q&A + objective, all pulled from tCorporateLogs.

Model: Llama via LlamaFunction({ id: 45, ... }) (AWS Bedrock Claude — see Integration Inventory).

Output: array of recommended content topics.

Persistence: - Inserts one row per topic into tRecommendedSub - Logs the run in tCorporateLogs with type = 4 - Sets tCompany_Website_Info.isAiRecSub_created = 1


job_content_translation.js (TCON)

Trigger: every 15s. Polls tJobs WHERE type = 'TCON' AND status IN (0, 3).

Job detail field (JSON): { member_id, content_id, language_id }.

Model: Llama via LlamaFunction({ id: 49, ... }).

Output: translated content JSON { Header, Body, Caption, Title, Footer }.

Persistence: - Inserts into tRephrased_Translated_Content - Updates tUserLibrary with translated fields and language_id, sets isApproved = 0 (pending user review) - Sets tJobs.status = 2 (done) or 4 (final failure after retry)


job_translate_and_rephrase.js (RTCON)

Trigger: every 30s. Polls tJobs WHERE type = 'RTCON' AND status IN (0, 3).

Job detail field (JSON): { member_id, accountId, content_id, tone_id, language_id }.

Model: Gemini via getGeminiResult(). Prompt parameters include source content + tone of voice + target language.

Output: rephrased + translated { Header, Title, Body, Footer, Caption }.

Persistence: - Inserts into tRephrased_Translated_Content - Updates tUserLibrary with new tone/language IDs, sets isApproved = 2 - Enqueues a follow-up job with type = 'RGEN' so the design pipeline regenerates the visuals to match the new copy - Sets tJobs.status = 2 (done) or 4 (final failure)


Operational Notes

Retries

The first three jobs implement retry inline (status 0 → 2 → 3). The two queue-driven jobs rely on the tJobs.status = 3 retry slot picked up by the next tick before being marked 4 (terminal failure).

AI provider selection

Job Provider Reason
brand positioning, objective, RTCON Gemini 2.5 Flash Structured-JSON outputs, cheap and fast
recommended subjects, TCON AWS Bedrock (Llama / Claude) Higher quality for free-form copy and translation

The provider per task is configured in the tLanguageModels table — see getAiParametersFunction for how each job pulls its model config at runtime.

Idempotency

These jobs are idempotent only on the source row's status flag. If the status is reset back to 0 manually, the job will run again and overwrite any prior AI response. Be careful when re-running.

Failure modes

  • Gemini quota exceeded — job catches the error, sets status to retry. No backoff is implemented; the next 30s tick will hit Gemini again.
  • Malformed JSON from the model — caught and treated as a transient error (retry).
  • Missing AI parameters in tLanguageModels — job logs and skips the row. The status flag is left untouched, so the row will be re-picked next tick (potential for a tight loop if the language config is permanently broken).

Ordering

Brand → objective → recom-subjects forms a strict dependency chain. The three jobs run independently, but the JOIN on prerequisite-approved rows ensures each only picks up work once the prior step is complete.

TCON and RTCON are independent of the chain — they fire whenever a translation/rephrase request is enqueued from the UI.


  • Content Pipeline — how RGEN and other downstream job types fit into the full content lifecycle
  • Jobs Inventory — the complete list of background workers
  • Data Model — schemas for tBrandPositioning, tObjective, tRecommendedSub, tRephrased_Translated_Content, tUserLibrary, tJobs
  • Integration Inventory — Gemini, Bedrock, Vertex configuration