Product Guide How It Works Administration Support Guide Owner Guide Engine Core

Engine Core - Computation, Overrides & Scripting

The core engine that powers Campaign Manager - computation graph, dependency resolution, override system, scripting

1. The Computation Engine

This is the heart of Campaign Manager. When you trigger a computation, here is what happens internally:

Full Computation Flowchart

flowchart TD
    Trigger["User triggers computation"] --> Load["Load all data from DB:
campaign -> phases -> steps -> CC items + env vars"] Load --> BuildGraph["Build dependency graph
(analyze foreign keys)"] BuildGraph --> ResolveEnv["Compute environment variables
(in order: strings, JSON, functions)"] ResolveEnv --> TopoSort["Determine execution order
(topological sort by dependencies)"] TopoSort --> ThreadPool["Allocate thread pool
(up to 30 threads per campaign)"] ThreadPool --> Execute{"Execute each variable"} Execute -->|AI prompt| LLM["Call LLM provider
(rate-limited, with retries)"] Execute -->|Function| JS["Execute JavaScript
(with full context)"] Execute -->|Asset| Render["Render document template
+ store file to S3"] Execute -->|Static| Direct["Use stored value"] LLM --> Store["Store result, update state"] JS --> Store Render --> Store Direct --> Store Store --> WS["Send WebSocket update
(real-time progress)"] WS --> Next{"More variables?"} Next -->|Yes| Execute Next -->|No| Done["Computation complete"] Done --> AutoDeploy{"Auto-deploy configured?"} AutoDeploy -->|Yes| Deploy["Deploy assets to microsite"] AutoDeploy -->|No| Finish["Done"]

Dependency Resolution - How Variables Connect

Each variable declares what other variables it depends on (its "foreign keys"). The system supports three dependency styles:

StyleExampleHow it works
No dependencies declared(null foreign keys)Depends on ALL previous items in sequence - implicit dependency
Simple key referenceheadlineSearches backwards through the execution order for a variable with key "headline"
Path-based referencePhase1/Step2/headlineFinds the variable at the exact path. Supports relative paths like ../headline

Gotcha: Broken references don't always fail immediately

If a variable references a key that doesn't exist, the system marks it as having "broken references" and logs a warning. With the "Continue on error" strategy, computation proceeds and only this variable errors. With "Stop on error", the entire computation halts.

Computation Scopes

ScopeWhat gets computed
Full campaignEvery phase, every step, every variable
PhaseAll steps in the selected phase only
StepAll variables in the selected step only
Single variableJust one variable + its downstream dependents
Custom scopeA user-selected set of variables

Computation Modes

ModeWhat gets computed
FullForce-recompute every variable regardless of current state
Not Computed OnlyOnly variables that haven't been computed yet
Not Computed + AssetsUncomputed variables PLUS all asset variables (assets always regenerated)

Downstream Propagation

When you recompute a single variable, you choose how far the effects ripple:

PropagationWhat gets recomputed
Me onlyJust this variable - nothing else changes
Step downstreamThis variable + everything that depends on it within the same step
Phase downstreamThis variable + all dependents across the entire phase
Campaign downstreamFull cascade across the entire campaign

Reset Options (How to Handle Existing Results)

OptionWhat it doesManual overrides
Reset all (status only)Clear states -> "created", keep valuesKept
Reset allClear states AND valuesCleared
Skip overridden (stop hierarchy)Don't recompute variables with overrides, stop cascading at overridden itemsKept, cascade stops
Skip overridden (with hierarchy)Don't recompute variables with overrides, but continue cascading through themKept, cascade continues

Error Handling During Computation

StrategyWhat happens when a variable fails
Continue with errorMark this variable as "error", continue processing the rest
Stop on errorHalt the entire computation immediately, all threads stop

Thread Pool Management

Dynamic Thread Allocation

The system starts with 1 thread per campaign and dynamically scales up to 30 threads based on workload. The total across all active campaigns is capped at 150 threads. Thread pool sizes are redistributed every 5 seconds among running computations. This means large campaigns with many AI prompts get more threads automatically.

Auto-Publishing After Computation

Publishing StageWhen assets are deployed
No deployNever auto-deployed
On demandEach asset deployed immediately after it's computed (one by one)
On execution done (all)All assets deployed after full computation completes
On execution done (not generated)Only non-generated assets deployed after computation

Asset Generation Pipeline (7 Steps)

When an asset-type variable is computed, here is what happens:

  1. The system loads the associated document template (XSL, FreeMarker, or static HTML)
  2. It collects all computed variable values from the step as template context
  3. The template is rendered into a final document (HTML, PDF, etc.)
  4. The generated file is stored in the company's public S3 storage
  5. If the output is HTML, a PDF version is also generated automatically
  6. An Asset record is created with the file reference and deployment path
  7. Based on the publishing stage, the asset may be auto-deployed to the microsite

Real-Time WebSocket Updates

During computation, the system sends real-time events via WebSocket:

EventWhat it reports
Computation StatusOverall progress: total items, active, on-hold, finished/error
Item UpdatedA specific variable finished computing - result available
Item ErrorA variable failed - error message
Asset Published/UnpublishedAn asset was deployed to or removed from the microsite
Post-Processing ResultIntermediate result from post-processing chain
Patched ItemA variable received a patched result
Log EventWarning or info message (e.g., broken reference detected)

The Customer App polls computation status every 2 seconds and auto-refreshes step data when computation completes.

Startup Recovery

Automatic Recovery After Restart

If the system restarts during an active computation, all "in-progress" variables are automatically reset to "created" state. No data is lost, but you need to re-trigger the computation.

2. Override & Precedence System

One of the most complex and least visible aspects of the system. Understanding who overrides whom is critical for product decisions.

CC Item Value Hierarchy

Every CC Item can have up to three values. The resultType field determines which value is returned:

flowchart TD
    Compute["AI/Function computes value"] --> Result["result
(original computed value)"] User["User manually overrides"] --> ResultOvr["resultOvr
(user override)"] AI2["AI merges original + override"] --> Patched["patched
(AI-merged version)"] Result --> Decision{"resultType?"} ResultOvr --> Decision Patched --> Decision Decision -->|ORIGINAL| UseResult["Return: result"] Decision -->|OVERRIDE| UseOvr["Return: resultOvr
(if exists, else result)"] Decision -->|PATCHED| UsePatched["Return: patched"] style UsePatched fill:#EEEEFD,stroke:#623CEA style UseOvr fill:#F4FBFF,stroke:#22B5FF style UseResult fill:#F7F7F7,stroke:#D4DAE0
ValueSourceWhen it winsPriority
patchedAI merges original + user override into a combined versionresultType = PATCHEDHighest
resultOvrUser manually entered a value that replaces the AI outputresultType = OVERRIDEMedium
resultOriginal computed value (AI or function output)resultType = ORIGINAL (default)Lowest

How patching works

When a variable has both an original AI result AND a user override, and a patch template is configured, the system can ask the AI to intelligently merge both versions. The AI receives the original and the override, and produces a "patched" version that incorporates both. This is useful when you want to keep the AI's structure but inject specific user changes.

Override protects against recomputation

When a variable has resultType = OVERRIDE and a valid override value, the system skips recomputing it entirely. It also respects the "Skip overridden" reset option - meaning downstream resets can stop at overridden items. This is how users protect their manual edits from being wiped by recomputation.

Chat Override Flow

sequenceDiagram
    participant User
    participant Chat as Chat Interface
    participant System
    participant CCItem as CC Item (Variable)

    User->>Chat: Asks AI to suggest values
    Chat->>System: AI generates suggestions
    System->>System: Create ChatOverride (status: READY)
    System-->>Chat: Show suggestions to user

    alt User accepts
        User->>Chat: Clicks "Apply"
        Chat->>System: Set ChatOverride status = APPLIED
        System->>CCItem: Set resultOvr = suggestion value
        System->>CCItem: Set resultType = OVERRIDE
        Note over CCItem: Variable now returns
user-approved AI suggestion else User rejects User->>Chat: Clicks "Cancel" Chat->>System: Set ChatOverride status = CANCELED Note over CCItem: Variable unchanged end

Chat overrides are the bridge between conversational AI and campaign data. The key insight: the AI suggestion doesn't change anything until the user explicitly applies it. This is a safety mechanism.

AI Model Resolution Chain

flowchart LR
    V["Variable-specific
model"] -->|if null| E["Env var prompt
model"] E -->|if null| C["Campaign default
model"] C -->|if null| S["System default
model"] S -->|if null| Err["Error:
No model found"] style V fill:#EEEEFD,stroke:#623CEA style Err fill:#FFF2F2,stroke:#DC2626

Each level is optional. The system walks the chain until it finds a model. If nothing is configured at any level, computation fails with "AI model not defined".

Downstream Reset & Override Protection

When the system resets variables for recomputation, it respects overrides:

Reset OptionOverridden variableVariables below it
Reset allCleared (override removed)All cleared
Reset all (status only)State reset, override keptAll reset
Skip overridden (stop)Skipped entirelyAlso skipped (cascade stops)
Skip overridden (continue)SkippedContinue resetting below

3. Scripting Engine

Function-type CC items and environment variables execute JavaScript (Nashorn engine, ECMAScript 5.1). Scripts have access to the full computation context through injected wrapper objects.

Available API Objects

llmApi - AI Model Access

MethodWhat it does
request(campaignId, promptKey, arg1, arg2, ...)Single LLM call. Loads prompt template from env var, substitutes %s placeholders with arguments, sends to AI model.
requestWithRetry(campaignId, promptKey, retries, arg1, ...)Same as request() but retries on failure up to N times.
request(campaignId, promptKey, jsonSchemaKey, arg1, ...)LLM call with JSON schema constraint. Schema loaded from env var. AI output validated against JSON Schema v4.
requestWithRetry(campaignId, promptKey, jsonSchemaKey, retries, arg1, ...)JSON-schema-constrained call with retries.

agentsApi - AI Agents (A2A)

MethodWhat it does
ask(agentName, campaignId, message)Single-shot call to an A2A agent. Creates new conversation, sends message, returns response. Agents: execution_inspector, campaign_planner, writing_orchestrator, writing_manager.
session(agentName, campaignId)Creates a persistent multi-turn session with an agent. Returns an AgentSession object that maintains conversation state (contextId) between calls.

AgentSession usage:

var session = agentsApi.session("writing_orchestrator", campaignId);
var first = session.send("Write a blog post about AI");
var refined = session.send("Make it shorter and add examples");
// Session remembers previous messages

campaignOpsApi - Campaign Operations

Query methods (read-only)
MethodWhat it does
listMyCompanies()All companies accessible to current user
listCampaigns(companyId)All campaigns in a company
listTemplates(companyId)Published templates in a company
listPhases(campaignId)All phases in a campaign
listItems(stepId)All CC items in a step, ordered by sequence
findPhasesByClasses(campaignId, classes[])Find phases with specific class tags
findStepsByClass(campaignId, classNames[])Find steps with specific classes
findCCItemInPhase(phaseId, key)Find all items with a key in a phase
externalKey(companyName, campaignName, path)Access items from OTHER campaigns by name (cross-campaign reference)
findItemByPath(campaignId, "Phase/Step/Key")Find item by full path
Mutation methods (write operations)
MethodWhat it does
startNewCampaign(templateId, companyId, name, btTitle)Create campaign from template (deep clone)
copyCampaign(sourceCampaignId, name, btTitle)Clone campaign within same company
updateCampaignKey(campaignId, path, content, btTitle)Update a CC item value + reset downstream dependents
updateCampaignKey(campaignId, path, content, resetDownstream, btTitle)Same with optional downstream reset control
updateCampaignKeys(campaignId, {path: value, ...}, btTitle)Bulk update multiple items atomically + cascade reset
updateCampaignKeysAndCompute(campaignId, {path: value}, upToPath, callback, btTitle)Update items, then async compute downstream to a target path. Callback receives "SUCCESS"/"ERROR"/"CANCELLED".
updateResultCampaignKey(campaignId, path, content, btTitle)Update raw result (not override). Uses SKIP_OVERRIDDEN_RESET_HIERARCHY.
updateCampaignApplicationState(campaignId, stateJson)Set the Customer App's application state
dropAndCreateStepByTemplate(graph, templatePath, targetPath, defaults, btTitle)Delete a step and replace it with a clone from a template, pre-filling values
createPhaseByTemplate(graph, templateName, targetName, classes, btTitle)Clone a phase with a new name
createPhasesByTemplateBatch(campaignId, graph, configs, classes, btTitle)Batch create multiple phases from templates
renameAndRecomputePhase(campaignId, oldName, newName, btTitle)Rename a phase and trigger full recomputation
updateStepClasses(stepId, classes[])Update step metadata tags

graph - Computation Graph Control

Graph computation methods
MethodWhat it does
loadGraph(campaignId)Load campaign computation graph from DB
findItemByPath(graph, path)Find item in loaded graph
computeCampaign(graph, btTitle)Compute entire campaign (async)
computeCampaign(graph, callback, btTitle)Same with JS callback on completion
computePhase(graph, phaseName, btTitle)Compute single phase
computeStep(graph, type, execType, scope, stepId, btTitle)Compute step with full control over type/scope
computeItem(graph, type, execType, scope, path)Compute single item with full control
computeCampaignUpToPath(graph, upToPath, btTitle)Compute from start up to a specific item path
computeCampaignUpToPath(graph, scope, upToPath, callback, btTitle)Same with custom scope and callback

log - Debug Logging

MethodWhat it does
enable() / disable()Toggle debug logging for this execution
info(message) / error(message)Log messages (only if logging enabled via user preference)
event(item, message)Publish a UI-visible event for a specific CC item

Other injected context

ObjectWhat it provides
Parent valuesAll parent variable results injected by their key name as JS variables
Environment functionsEnv vars of type "prompt" become callable functions: myPrompt(arg1, arg2) substitutes %s placeholders
JS librariesAll js_lib env vars loaded and executed before the script runs
itemThe current CC item being computed

Deferred Computation

Automatic Retry for Missing Dependencies

If a JavaScript function references a variable that hasn't been computed yet (ReferenceError), the system doesn't fail - it defers this item and tries again later. This handles implicit dependencies in script code that aren't captured by the declared dependency graph.

Cross-Campaign References

Scripts can access data from other campaigns using campaignOpsApi.externalKey(companyName, campaignName, "Phase/Step/Key"). This enables:

Async Computation with Callbacks

Scripts can trigger computations and receive a callback when they complete:

campaignOpsApi.updateCampaignKeysAndCompute(
    campaignId,
    {"Phase/Step/headline": "New headline value"},
    "Phase/Step/final_document",   // compute up to this path
    function(status) {
        if (status === "SUCCESS") log.info("Downstream computed!");
        if (status === "ERROR") log.error("Computation failed");
    },
    "Updated headline and recomputed"
);

4. Variable Type Options (CC Item Configuration)

Each CC item type has a hidden configuration object (options) that controls its behavior. These options aren't always visible in the UI but fundamentally change how the variable works.

Prompt Variables (prompt, json_prompt, image_prompt)

OptionTypeWhat it controls
aiModelIdLongOverride the campaign's default AI model for this specific variable
promptKeyStringEnvironment variable key containing the prompt template
argumentsString[]Arguments for %s placeholder substitution in the template
resultTypeEnumORIGINAL (AI output), OVERRIDE (user value), PATCHED (AI merge of both)
postProcessingParamsJSONChain of additional prompts applied after the main result (see post-processing below)

json_prompt adds: jsonSchemaKey - env var key for JSON Schema v4 that constrains AI output.

image_prompt adds: imageUrl - URL of an image for vision model analysis (png, jpg, gif, webp).

Session Prompt (session_prompt)

OptionWhat it controls
sessionNameConversation topic identifier (default: ${phase.name}-${step.name}). Items with the same session name share conversation history.
sessionInstructionSystem prompt / instructions for the conversation. Sets the AI's behavior and context.
promptKeyPrompt template for user messages
argumentsTemplate arguments

DALL-E Image Generation (dalle)

OptionWhat it controls
imageGenModelOnly DALL_E_3 currently supported
imageSize1024x1024 (square), 1024x1792 (portrait), 1792x1024 (landscape)
imagePromptText description of the image to generate
assetTitleName for the generated asset (supports ${variable} placeholders)
microSiteTargetFolderDeployment path on microsite

Asset Generation (asset)

OptionWhat it controls
documentTemplateIdWhich document template to render
assetTitleAsset filename (supports ${variable} placeholders)
buildPdfAssetIf true, also generates a PDF from the HTML output
pdfPageSizePage dimensions: "210x297 mm" (A4 default), "8.27x11.69 in", or ${variable}
initializeFullContextIf true, loads ALL campaign data into template context (expensive). Default: only dependencies.
overwriteIfExistsWhether to overwrite existing asset file (default: true)
microSiteTargetFolderDeployment path (supports ${company}, ${campaign}, ${phase}, ${step} placeholders)

Web Scraper (web_scraper)

OptionWhat it controls
webUrlTarget URL (supports ${variable} placeholders)
requestTimeoutTimeout in ms (default: 10000)
scrapHtmlIf true, parses HTML and extracts content via XPath
xpathSelectorXPath expression for targeted content extraction
serviceVESCRAPER (internal), SCRAPINGBEE (external), SCRAPINGBEE_RENDER_JS (with JavaScript rendering)
waitingDurationMilliseconds to wait for JS rendering (ScrapingBee only, 0-10000)

Function (function)

OptionWhat it controls
scriptContentJavaScript code to execute
cacheResultIf true, caches the result and skips re-execution on subsequent runs

File Content (file_content)

OptionWhat it controls
fileUrlURL to fetch file from (supports ${variable} placeholders). Formats: txt, csv, pdf, doc, docx, xls, xlsx, ppt, pptx.

5. Prompt Post-Processing & AI Patching

Post-Processing Chain

After a prompt variable produces its initial result, post-processing can apply a chain of additional LLM prompts to refine it:

flowchart TD
    Initial["Initial LLM result"] --> PP1["Post-processing prompt 1
(e.g., improve grammar)"] PP1 --> PP2["Post-processing prompt 2
(e.g., summarize)"] PP2 --> Final["Final result stored"] Initial -.->|"Available as ${0}"| PP1 PP1 -.->|"Available as ${1}"| PP2

Each post-processing step can reference:

Source typeHow it works
ccUse another CC item's value as input
envUse an environment variable as input
textUse literal text as the prompt

Previous results in the chain are available as ${0}, ${1}, etc. This enables multi-stage refinement pipelines.

AI Patching (Semantic Merge)

When a prompt variable has resultType = PATCHED, the system performs a two-step AI merge:

  1. Compute Patch - AI analyzes the semantic difference between the original result and the user's override:
    Prompt: "What changed between: [original] and: [override]?"
    Result stored in: item.patch
  2. Apply Patch - AI merges the original with the semantic patch to create a final version:
    Prompt: "Starting with: [original], apply this change: [patch]"
    Result stored in: item.patched

The patching prompts are loaded from environment variables:

When to use patching vs. override

Override: Use when you want to completely replace the AI output with your own text. The original is ignored.
Patching: Use when you want the AI to intelligently incorporate your changes into its original output. Good for tweaking tone, adding specific details, or correcting facts while keeping the AI's structure.