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.jsreferences a sixth fileuser_specific_contents_ai/job_content_ai.jsthat 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.
Related¶
- Content Pipeline — how
RGENand 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