locus

Workflows

Declaratively describe background and event-driven processes in your application: trigger something, run actions, branch, iterate, send an email, retry on failure, and plug in custom step kinds — all in one cohesive block.

Status: Experimental / MVP (workflow manifest version 1). Syntax and behavior may evolve; we aim to keep changes additive when possible.


At a Glance

workflow WelcomeEmail {
  trigger { on:webhook(secret: HOOK_SECRET) }
  steps {
    run hydrateUser(userId)
    branch { condition: userIsAdmin steps { run auditAccess(userId) } else { run recordStandardLogin(userId) } }
    send_email {
      to: userEmail
      subject: Welcome
      template: welcome_template.html
    }
  }
  retry { max: 2, backoff: exponential, factor: 2 }
  concurrency { group: welcomeGroup, limit: 5 }
}

Feature Matrix

Capability Implemented Notes
trigger (webhook + basic entity events) Yes Webhook secret captured in manifest (triggerMeta).
steps block & core step kinds Yes run, delay, branch, for_each, send_email, http_request (placeholder runtime).
run step arg parsing + simple expression capture Yes Single argument expression parsed for future analysis.
branch condition expression (simple) Yes (heuristic) Advanced boolean / nested parsing is incremental.
forEach loop Yes Iterates array-like value; binding injected per item.
send_email (to + subject/template validation) Yes Manifest includes structured fields. Future: richer templating.
http_request Placeholder Parsed & surfaced; execution logic not yet implemented. HTTPS enforced unless allow_insecure:true.
retry block Yes max 0..100, backoff fixed|exponential, factor>1 for exponential, optional delay.
concurrency block Yes FIFO queue simulation (no real async scheduling yet). Limit 1..10000 validated.
on_error block Yes (basic) Runs listed actions after failure before fallback.
on_failure block Yes Fallback if no on_error.
Workflow manifest JSON (v2) Yes Generated under generated/workflows/.
Custom step kinds via plugins Yes Register with registerWorkflowStepKinds().
Precise error spans for all constructs Partial Improved for send_email; more coming.
State/input schema serialization Not yet Raw text only for now.
Advanced scheduling / cancel policy / jitter Not yet Future phases.
Type inference / semantic resolution Not yet Planned after expression enrichment.

Core Concepts

Trigger Block

Defines what starts the workflow.

workflow OrderWebhook {
  trigger { on:webhook(secret: ORDER_HOOK) }
  steps { run processOrder() }
}

Supported today:

Mixing a webhook trigger with any entity trigger is currently disallowed (validator enforced).

Steps Block

Contains an ordered list of step statements. Each statement produces a stable manifest entry.

run

Executes a named action (resolved at runtime by your actions table / environment). Arguments are tokenized; a single bare argument is parsed as an expression for future semantic checks.

workflow ExampleRun { trigger { t } steps { run enrichUser(userId) } }

delay

Placeholder (simulated). Presently logs the step; future: configurable durations.

workflow ExampleDelay { trigger { t } steps { delay { } } }

branch

Conditional execution. Heuristically extracts condition: portion; minimal expressions supported.

workflow ExampleBranch { trigger { t } steps { branch { condition: userIsAdmin steps { run grantAccess() } else { run grantBasic() } } } }

forEach

Iterates a collection; sets loop binding each iteration.

workflow ExampleLoop { trigger { t } steps { forEach item in items { run process(item) } } }

send_email

Validates presence of to and at least one of subject or template.

workflow ExampleEmail { trigger { t } steps { send_email { to: userEmail, subject: AccountActivated, template: account_activation.html } } }

http_request (Placeholder)

Syntax recognized; runtime execution is not implemented yet. Safe to include for forward compatibility.

workflow ExampleHttp { trigger { t } steps { http_request { } } }

Retry Strategy

Attach a retry block at workflow root. Applied during execution for retryable steps (core steps use it today in a simplified loop).

workflow ExampleRetry { trigger { t } steps { run act() } retry { max: 3, backoff: exponential, factor: 2 } }

Validation rules:

Common Validation Errors (Examples)

Workflow 'WelcomeEmail' cannot mix webhook and entity triggers.
Workflow 'OrderRetry' retry.max must be 0..100.
Workflow 'BulkProcess' concurrency.limit must be 1..10000.
Workflow 'NotifyUser' has run step missing action name.
Workflow 'Invite' send_email missing 'to'.
Workflow 'Invite' send_email requires 'subject' or 'template'.
Workflow 'CallWebhook' http_request must use HTTPS (add allow_insecure: true to override).

Concurrency Control

Limits concurrent executions by group. Queues overflow instead of dropping (simple FIFO in-memory simulation).

workflow ExampleConcurrency { trigger { t } steps { run act() } concurrency { group: welcomeGroup, limit: 5 } }

Error Paths: on_error vs on_failure

workflow ExampleErrors {
  trigger { t }
  steps { run risky() }
  on_error { notifyAdmin rollback }
  on_failure { cleanup }
}

Execution Log

Runtime produces an ordered log; each entry includes kind and v: 1 (schema version). Useful for debugging & future tracing tooling.

Manifest Output

Each workflow generates workflows/<Name>.json (relative to your build output directory, default generated/). Example (trimmed):

{
  "name": "WelcomeEmail",
  "version": 1,
  "triggerMeta": { "type": "webhook", "secretRef": "HOOK_SECRET" },
  "steps": [
    { "kind": "run", "action": "hydrateUser" },
    { "kind": "branch", "condition": "userIsAdmin" },
    { "kind": "send_email", "email": { "to": "userEmail", "subject": "Welcome", "template": "welcome_template.html" } }
  ],
  "retry": "max: 2, backoff: exponential, factor: 2"
}

Custom Step Kinds (Plugins)

Define a plugin (locus.plugins.js) and register new kinds:

export default [{
  name: 'myWorkflowExt',
  registerWorkflowStepKinds() {
    return [{
      kind: 'publish_event',
      run(step, { ctx }) {
        // custom execution logic (MVP: synchronous)
        return { published: true };
      }
    }];
  }
}];

Registering a step kind exposes its kind name to validation. An unrecognized kind in a workflow will raise an error until a plugin registers it.

Hooks: onWorkflowParse, onWorkflowValidate let plugins inspect or warn. Slow hooks trigger performance warnings (>50ms).

Reserved & Inferred Bindings

Loop variables (forEach item in ...) and future input/state declarations become reserved names within the workflow scope. Redeclaring or shadowing a reserved binding will raise a validation error once those blocks are introduced.

Performance (JIT vs Interpreter)

If LOCUS_WORKFLOW_JIT=1 is set, the engine builds a JavaScript function that inlines simple step dispatch. Pros: lower per-run overhead for large workflows. Cons: small upfront compile cost; experimental (APIs may adjust). For benchmark methodology and variance thresholds see Performance & Budgets.

Reserved & Inferred Bindings

The validator reserves names introduced via input / state and ensures loop or const bindings are unique. Shadowing or redeclaration raises an error.


Performance

Optional JIT path (LOCUS_WORKFLOW_JIT=1) compiles the step graph to a JS function. For small workflows the interpreter is fine; large graphs may see reduced per-execution overhead. Benchmarks are experimental; monitor release notes for stability guarantees.


Limitations & Roadmap

Not Yet Planned Direction
Rich async scheduling / real delays Integrate simulated clock & timers.
State mutation (update_state) Declarative state graph + diff generation.
Cancel policies / on_cancel Cooperative cancellation + compensation hooks.
HTTP request runtime Fetch + retries + auth injection.
Expression type inference Static validation of identifiers & members.
Enhanced error spans Token-level highlights for every step keyword & field.

Additional Examples

Entity Trigger

workflow NewUserAudit {
  trigger { on:create(User) }
  steps { run seedProfile(userId) }
}

Email Only

workflow SimpleEmail {
  trigger { on:webhook(secret: MAIL_HOOK) }
  steps { send_email { to: adminEmail, subject: Ping } }
}

Retry + Failure Path

workflow CriticalJob {
  trigger { on:webhook(secret: JOB) }
  steps { run performRisky() }
  retry { max: 5, backoff: exponential, factor: 2 }
  on_failure { alertOps }
}

Parsing Notes (MVP Safety Rails)

To keep error messages crisp we intentionally stop at the first syntax issue. Complex nested braces inside steps may be rejected until higher-fidelity parsing lands. If you hit an unexpected parse error, simplify the step or open an issue with the snippet.


Summary

Workflows give you a single, inspectable source of truth for background logic. The current surface focuses on clarity and deterministic output. Add them incrementally; the validator will guide you when you wander into not‑yet‑implemented territory.