oJob YAML Reference Guide
oJob is OpenAF’s job orchestration framework that allows you to define, schedule, and execute jobs using YAML configuration files. This guide describes the implementation in owrap.oJob.js and the command-line entry point in ojob.js. Start with the runnable recipes; use the ojob.io examples map to find larger applications.
Table of Contents
- Basic Structure
- Help Section
- Initialization
- oJob Configuration
- Todo List
- Including Other oJobs
- Job Definitions
- Basic Job Structure
- Job Types — simple, periodic, shutdown, subscribe, jobs (external)
- Job Dependencies
- Code Separation
- Built-in Jobs
- Built-in Job Shortcuts —
(if),(parallel),(each), etc. - Built-in Jobs and Shortcut Correlation — mapping table
- Built-in Job Arguments Reference — per-job argument mappings
- Built-in Job Shortcuts —
- Command-line Usage
- State and Scheduling
- Examples
Basic Structure
An oJob YAML file consists of several main sections:
# Optional: Help information
help:
text : "Description of what this oJob does"
expects:
- name: arg1
desc: "Description of argument 1"
# Optional: Initialization values
init:
someValue: "default value"
# Optional: oJob configuration
ojob:
daemon : false
logToConsole: true
# Optional: Include other oJob files
include:
- another-ojob.yaml
# Job definitions (can come from includes or built-ins)
jobs:
- name: "My First Job"
exec: |
print("Hello World!")
# Execution order
todo:
- "My First Job"
Command-line Usage
Save a definition as hello.yaml and run ojob hello.yaml name=World. Arguments are available on args; convert numbers and booleans with check.in before using them. YAML and JSON definitions are supported.
| Command | Purpose |
|---|---|
ojob -h | CLI help (not the definition’s help). |
ojob hello.yaml -jobhelp | Definition help. |
ojob hello.yaml -jobhelp Greet | Help for the job named Greet; place its name last. |
ojob hello.yaml -i | Prompt for arguments from help.expects, then execute. |
ojob hello.yaml name=World -f params.yaml | Read additional arguments from YAML, JSON or SLON; put -f and its filename last. Explicit CLI values take precedence over file values. |
ojob hello.yaml -jobs | List available job names from the expanded definition. |
ojob hello.yaml -todo | List normalized todo job names. |
ojob hello.yaml -deps | Show dependency and composition paths. |
ojob hello.yaml -compile | Expand includes and print YAML. This does not compile JavaScript to native code. |
ojob hello.yaml -tojson | Expand includes and print JSON. |
ojob hello.yaml -exportcode dir=hello-code | Export local embedded source and inline job bodies with a manifest. |
ojob hello.yaml -importcode dir=hello-code output=updated.yaml | Import edited source into a new definition. |
ojob hello.yaml -which | Show local/oPack lookup result or an explicit URL. |
ojob -global | List definitions in OJOB_LOCALPATH. |
ojob -shortcuts | List registered shortcut mappings. |
ojob -shortcuts ojob output | Filter shortcut mappings by job/shortcut name. |
ojob -syntax | Show the bundled annotated YAML reference. |
ojob -reference / ojob -mdreference | Show the bundled Markdown reference, rendered or raw. |
ojob hello.yaml -json | Set args.__format=json for ow.oJob.output; ordinary print calls are unaffected. |
ojob hello.yaml -gb64json | Set args.__format=gb64json. |
ojob hello.yaml -nocolor | Disable ANSI job-status output. |
Inspection commands such as -compile use the definition loader: they resolve includes and can load oPacks and JavaScript libraries. They do not run the todo list, but they are not a side-effect-free YAML parser. -compile, -tojson, -jobs, -todo, and -deps refuse encrypted definitions or detected encrypted includes. See encrypted definitions.
The CLI counts failed entries in oJob::log after execution and exits nonzero when it finds failures. A catch handler that returns true marks an error as handled; logging an error alone does not do that. Disabling job logging also affects this final error-count mechanism.
-reference and -syntax read resources inside the installed JAR. Editing this checkout’s documentation changes those commands only after packaging a new JAR.
Help Section
The help section documents the definition and drives interactive prompting. Missing arguments explicitly marked mandatory: true cause help to be displayed before normal execution (unless ojob.showHelp: false). Use check.in for runtime validation and defaults; example, options, and moptions are help/prompt metadata, not validation rules. With -i, moptions selections become a comma-separated string.
help:
text : "Detailed description of the oJob functionality"
expects:
- name : inputFile
desc : "Path to the input file to process"
mandatory: true
example : "/path/to/file.txt"
- name : outputDir
desc : "Directory where results will be saved"
mandatory: false
example : "/tmp/output"
- name : verbose
desc : "Enable verbose logging"
options : ["true", "false"]
- name : mode
desc : "Processing mode"
moptions : ["fast", "thorough", "debug"]
- name : password
desc : "Authentication password"
secret : true
Help Properties
text: Main description of the oJobexpects: Array of expected argumentsname: Argument namedesc: Description of the argumentmandatory: Whether the argument is requiredexample: Example valueoptions: Array of valid values (single choice)moptions: Array of valid values (multiple choice)secret: Whether the argument should be hidden when prompted
Initialization
The init section provides default values that are available to all jobs as args.init.
init:
database:
host: "localhost"
port: 5432
apiEndpoint: "https://api.example.com"
retryCount: 3
timeout: 30000
oJob Configuration
The ojob section controls how the oJob executes and behaves. The following blocks illustrate configuration groups, not a single configuration to copy wholesale. See the recipes for minimal complete definitions.
Execution Control
ojob:
# Thread management
numThreads: 4 # Force specific number of threads
poolThreadFactor: 2 # Multiply cores by this factor
# Execution behavior
async: false # Execute jobs async instead of sequential
sequential: true # Force sequential execution
shareArgs: true # Pass results between sequential todos
daemon: false # Keep running as daemon
timeInterval: 50 # Daemon check interval in ms
# Dependencies
depsWait: false # Wait for dependencies even if others fail
depsTimeout: 300000 # Max time to wait for dependencies (ms)
depsOnFail: | # Code to execute when dependency fails
logErr("Dependency failed: " + job.name)
# Templates and arguments
templateArgs: true # Process {{}} handlebars in args
argsFromEnvs: true # Load environment variables as args
initTemplateEscape: false # Escape {{ in init values
# Timing
cronInLocalTime: false # false selects UTC; true selects local time
async defaults to false. async: true forces sequential: false. Otherwise sequential defaults to OJOB_SEQUENTIAL (shipped as true), and the engine fallback for shareArgs in sequential mode is OJOB_SHAREARGS (shipped as true). However, oJobRunFile supplies { shareArgs: false } when no options map is supplied, including the normal CLI path. Set these explicitly when a pipeline depends on argument sharing. shareArgs is the option name; sharedArgs is not an alias.
Logging Configuration
ojob:
# Console logging
logToConsole: true # Output job messages to stderr
logOJob: false # Use OpenAF logging for jobs
logJobs: true # Log job execution
logArgs: false # Log arguments before each job
logLimit: 3 # Number of executions to keep in log
logHistory: 10 # Factor of job logs to keep
ignoreNoLog: false # Ignore job-level nolog settings
# Console appearance
conAnsi: true # Enable ANSI terminal detection
conWidth: 128 # Force console width
# File logging (all options mirror ow.ch.utils.setLogToFile)
logToFile:
logFolder: "/var/log" # Where the current log file is written (default: '.')
filenameTemplate: "log-{{timedate}}.log" # Template for log filename (uses timedate)
fileDateFormat: "yyyy-MM-dd" # Date format for filename (daily); use 'yyyy-MM-dd-HH' for hourly
lineTemplate: "{{timedate}} | {{type}} | {{{message}}}\n" # Template per log line
lineDateFormat: "yyyy-MM-dd HH:mm:ss.SSS" # Date format used inside each line
HKRegExPattern: "log-\\d{4}-\\d{2}-\\d{2}\\.log" # Regex to identify log files for housekeeping
HKhowLongAgoInMinutes: 2880 # Minutes of logs to retain; older files are deleted (omit to disable)
dontCompress: false # Set true to skip gzip compression of older log files
backupFolder: "/var/log/backup" # Move older log files here (omit to keep in logFolder)
numberOfEntriesToKeep: 100 # In-memory OpenAF log channel entries to keep
setLogOff: false # Set true to suppress console logging
# Structured logging
log:
format: json # Use JSON format
level: INFO
External Dependencies
ojob:
# Include built-in oJob definitions
includeOJob: true
# OpenAF Packages
opacks:
- openaf: ">=20230601" # Minimum version
- S3: ">=20230401"
- oJob-common # Latest version
# OpenWrap libraries
owraps:
- Server
- Java
# JavaScript libraries
loads:
- anotherJS.js
loadLibs:
- utils.js
Process Control
ojob:
# Unique execution
unique:
pidFile: "myjob.pid" # PID file for uniqueness
killPrevious: false # Kill previous instance
# Error handling
catch: | # Global error handler
logErr("Global error: " + exception)
# Stall detection
checkStall:
everySeconds: 60 # Check interval
killAfterSeconds: 120 # Kill after this time
checkFunc: | # Custom stall check function
return false // Add your logic here
Channel Management
ojob:
channels:
recordLog : false # Record OpenAF logs
recordLogHistory: -1 # Number of log entries to keep (-1 unlimited)
# Create channels
create:
- name: myChannel
type: mvs
options:
file: data.db
# Expose channels via HTTP
expose: true
port: 8080
host: "0.0.0.0"
# Authentication
auth:
- login: user1
pass: pass1
permissions: "rw"
# Peer connections
peers:
- "https://peer.example.com:8080/chs/myChannel"
# Clustering
clusters:
- name: mainCluster
checkPeriod: 2500
host: "localhost"
port: 8080
Metrics and Monitoring
ojob:
metrics:
# Passive metrics (expose endpoint)
passive: true
port: 8080
uri: "/metrics"
openMetrics: true
openMetricsPrefix: "ojob"
# Active metrics (push to external systems)
active:
openmetrics:
url: "http://pushgateway:9091/metrics/job/test"
prefix: "myapp"
metrics: ["custom-metric", "mem"]
nattrmon:
url: "http://user:pass@nattrmon:7777/remote"
attrPrefix: "oJob/"
metrics: ["custom-metric", "mem"]
periodInMs: 5000
# Collect metrics to channel
collect:
ch: metricsChannel
period: 5000
some: ["mem", "cpu"]
# Custom metrics
add:
custom-metric: |
return { value: Math.random() * 100 }
Language Support
ojob:
langs:
- lang: mylang
shell: "mylang -"
pre: "var args = JSON.parse('{{args}}');\n"
pos: "\nconsole.log(JSON.stringify(args));\n"
returnRE: "\\s*#\\s+return (.+)[\\s\\n]*$"
returnFn: |
return "console.log(JSON.stringify({" + _args + "}));"
Security and Integrity
ojob:
# File integrity checking
integrity:
list:
- "external-ojob.yaml": "sha256:abc123..."
- "https://remote.com/ojob": "md5:def456..."
strict: true # Require a registered hash when checking is active
warn: false # Abort on a hash mismatch
# Debugging
debug: true # Enable debug mode for jobs
Todo List
The todo section defines what jobs to execute and in what order.
Simple Todo
todo:
- "Job 1" # Execute by name
- "Job 2"
- name: "Job 3" # Execute with custom args
args:
customParam: "value"
Conditional Execution
todo:
- name: "Conditional Job"
typeArgs:
when: "production" # Only run when state is "production"
args:
env: "prod"
See below state and scheduling for more on state management.
Multiple Arguments
todo:
- name: "Parallel Execution Job"
args:
- input: "file1.txt" # First execution
output: "result1.txt"
- input: "file2.txt" # Second execution (parallel)
output: "result2.txt"
Default Arguments in Todo
Todo entries also support default argument values using the "${key:-defaultValue}" syntax, allowing you to provide fallback values for arguments that may not be defined:
todo:
- name: "Job with Default Args"
args:
# Use "localhost" if "serverHost" is not defined
host: "${serverHost:-localhost}"
# Use 3306 if "dbPort" is not defined
port: "${dbPort:-3306}"
# Use existing value if "mode" is provided, otherwise use "default"
mode: "${mode:-default}"
# Works with nested paths - use "INFO" if config.log.level is not defined
logLevel: "${config.log.level:-INFO}"
Key features in todo entries:
- Runtime resolution: Default values are resolved when the todo entry is processed
- Inheritance: These processed arguments are passed to the target job
- Template integration: Works seamlessly with oJob’s template processing
- Nested support: Supports dot notation for nested object properties
- Type preservation: When a string value is exactly
"${key}"the resolved value keeps its original type (number, boolean, array, object, etc.). When a token appears inside a longer string (e.g."prefix-${key}-suffix") the result is always a string. - Inline interpolation: Tokens can appear anywhere inside a string value and multiple tokens may be combined — e.g.
"${host:-localhost}:${port:-8080}". Note that when multiple tokens are interpolated into a string context, each resolved value is converted viaString()before concatenation. - Escaping: Use a YAML single-quoted value such as ‘${key}’ to preserve literal
${key}. Odd backslash counts escape substitution; even counts allow it.
Usage examples:
# Todo entry with various default patterns
todo:
- name: "Database Migration Job"
args:
# Database connection defaults
dbHost: "${DB_HOST:-localhost}"
dbPort: "${DB_PORT:-5432}"
dbName: "${DB_NAME:-myapp}"
# Processing options with defaults
batchSize: "${BATCH_SIZE:-1000}"
timeout: "${TIMEOUT:-30000}"
# Feature flags with defaults
dryRun: "${DRY_RUN:-false}"
verbose: "${VERBOSE:-true}"
# Nested configuration defaults
config:
retryCount: "${config.retries:-3}"
backoffMs: "${config.backoff:-5000}"
Including Other oJobs
Include Complete oJobs
include:
- "common-jobs.yaml" # Local file
- "MyOPack::jobs.yaml" # From an oPack
- "ojob.io/common/utils" # Remote oJob
Include Jobs Without Their Todo
jobsInclude:
- "job-definitions.yaml"
- "ojob.io/db/operations"
include merges the included jobs, todo, configuration, init, code and help. Included todo entries precede the including file’s entries. jobsInclude suppresses the included todo and help, but still merges ojob, init, code, and jobs; it does not isolate configuration or library-loading effects. The including file’s map values take precedence during merging.
Local paths resolve from the process working directory, with lookup in installed oPack roots and OJOB_LOCALPATH. Use MyOPack::path/jobs.yaml to identify an oPack explicitly. A bare authorized domain such as ojob.io/... becomes HTTPS; an extensionless remote path normally receives .json. See remote loading and integrity.
Job Definitions
Jobs are the building blocks of oJob. Each job defines a unit of work.
Basic Job Structure
jobs:
- name: "Basic Job" # unique name (mandatory)
type: simple # Job type (default)
exec: | #js # Code to execute
print("Hello from " + job.name)
args.result = "success"
Job Types
Simple Jobs (default)
jobs:
- name: "Simple Job"
type: simple
exec: | #js
// Your JavaScript code here
log("Processing...")
Periodic Jobs
jobs:
- name: "Scheduled Job"
type: periodic
typeArgs:
cron: "0 */5 * * * *" # Every 5 minutes
# OR
timeInterval: 300000 # Every 5 minutes in ms
waitForFinish: true # Don't start new if previous still running
exec: | #js
log("Periodic execution at " + new Date())
Shutdown Jobs
jobs:
- name: "Cleanup Job"
type: shutdown
exec: | #js
log("Cleaning up before shutdown")
// Cleanup code here
Subscribe Jobs
jobs:
- name: "Channel Subscriber"
type: subscribe
typeArgs:
chSubscribe: "dataChannel"
exec: | #js
log("Channel operation: " + args.op + " on " + args.ch);
log("Key: " + stringify(args.k));
log("Value: " + stringify(args.v));
External oJob Jobs
jobs:
- name: "External Job Runner"
type: jobs
typeArgs:
file: "external-ojob.yaml"
# OR
url: "https://example.com/remote-ojob.yaml"
Job Dependencies
deps checks execution status; it does not enqueue the prerequisite. Put prerequisites in todo too. Use from/to when you want to compose reusable code into a job. In sequential mode, list prerequisites before dependents. A dependency handler receives args, job, and id; returning true from onFail permits proceeding despite the failed dependency.
jobs:
- name: "Dependent Job"
deps:
- "Prerequisite Job"
- name : "Another Prerequisite"
onSuccess: | #js
log("Prerequisite succeeded");
return true;
onFail : | #js
log("Prerequisite failed");
return false; // Do not proceed after failure
exec: | #js
log("All dependencies satisfied")
Job Arguments and Templates
args is the current invocation’s map; args.init contains top-level init. In sequential mode with shareArgs: true, the previous result stored as $get("res") is merged into the next invocation. $set("name", value) / $get("name") provide explicitly named shared values; these are separate from properties on args.
Job args are merged during execution and can overwrite caller/todo values. Prefer check.in with .default(...) for overridable defaults. An array of argument maps runs the same job once per element; typeArgs.single: true processes the elements serially. This is independent of ordering between todo entries.
ojob.templateArgs: true enables Handlebars expansion in string arguments, with args as the template root: use {{input}}, not {{args.input}}. A job can opt out with typeArgs.noTemplateArgs: true. ${...} argument substitution is separate and does not require Handlebars. Set initTemplateEscape: true when init contains templates intended for a later rendering step.
jobs:
- name: "Templated Job"
args:
defaultValue: "hello"
templateValue: "{{input}}-processed"
exec: | #js
log("Default: " + args.defaultValue)
log("Template result: " + args.templateValue)
Default Arguments
oJob supports default argument values using the "${key:-defaultValue}" syntax. This allows you to specify fallback values for arguments that may not be provided:
jobs:
- name: "Job with Default Args"
args:
# Use "defaultHost" if "serverHost" is not defined
host: "${serverHost:-defaultHost}"
# Use 8080 if "serverPort" is not defined
port: "${serverPort:-8080}"
# Use existing value if "environment" is provided, otherwise use "development"
env: "${environment:-development}"
# Works with nested paths - use "localhost" if config.database.host is not defined
dbHost: "${config.database.host:-localhost}"
exec: | #js
log("Connecting to: " + args.host + ":" + args.port)
log("Environment: " + args.env)
log("Database host: " + args.dbHost)
todo:
- name: "Job with Default Args"
args:
serverHost: "production.example.com"
config:
database:
host: "db.example.com"
A whole token such as ${port} preserves the referenced value’s type. A fallback such as ${missing:-8080} is a string; use toNumber.isNumber.default(8080) in check.in when a number is required. Embedded tokens, such as http://${host:-localhost}:${port:-8080}, produce strings. Dot paths are supported.
An unresolved whole token becomes undefined; an unresolved embedded token contributes an empty string. A token referring to its own destination key is left unchanged with a warning, even if it specifies a fallback. This is a direct self-reference check, not a general dependency resolver for chains of references.
Use YAML single quotes for escaped tokens: '\${name}' produces literal ${name}. An odd number of preceding backslashes escapes substitution; an even number permits it and retains those backslashes.
Error Handling
A job’s catch takes precedence over ojob.catch; onerror is an alias when catch is absent. Handlers receive exception, args, job, id, and deps. Return true to recover and retain modified arguments. Returning false, returning nothing, or throwing leaves the failure unhandled. to is ordinary composed code, not a finally block: it is skipped if earlier code throws. Use JavaScript try/finally for per-invocation cleanup.
jobs:
- name : "Error Prone Job"
catch: | #js
logErr("Job failed: " + exception)
// Handle error, return false to propagate
return true // Error handled
exec : | #js
if (Math.random() > 0.5) {
throw "Random failure"
}
Job Languages
oJob supports external language runners as well as OpenAF JavaScript. Install the corresponding interpreter before using a runner. The fragments below are alternatives; SSH also needs an args.ssh connection map (often populated by secget).
jobs:
# Python
- name: "Python Job"
lang: python
exec: | #python
import json
print("Python is running")
args['pythonResult'] = 'success'
# Shell/Bash
- name: "Shell Job"
lang: shell
exec: | #shell
echo "Running shell command"
# To use input args
# echo $aInputArgs
# OR
# echo {{aInputArgs}}
export RESULT="shell-success"
# To output args
echo '{"shellResult": "'$RESULT'"}'
# SSH Remote
- name : "Remote SSH Job"
lang : ssh
exec : | #shell
echo "Running on remote server"
hostname
typeArgs:
shell: "/bin/bash"
# PowerShell
- name: "PowerShell Job"
lang: powershell
exec: | #powershell
$_args | Add-Member -NotePropertyName psResult -NotePropertyValue "success" -Force
# Alternative Python execution
- name: "Python File Job"
typeArgs:
execPy: "/path/to/script.py"
exec: |
# Python script will be executed
# Go
- name: "Go Job"
lang: go
exec: | #go
args["goResult"] = "success"
# Ruby
- name: "Ruby Job"
lang: ruby
exec: | #ruby
args['rubyResult'] = 'success'
# Node.js
- name: "Node Job"
lang: node
exec: | #js
args.nodeResult = "success"
Without lang, jobs run as OpenAF JavaScript (oaf, js, or javascript), with OpenAF APIs in the current JVM. lang: node runs Node.js in a separate process: it has Node APIs and JSON arguments, not OpenAF globals. There is no TypeScript adapter.
Node, Go, Ruby, Perl, Swift, PowerShell, and Java exchange args through UTF-8 JSON files. stdout and stderr are logs and no longer interfere with results. A successful job must produce a JSON object; the generated wrapper does this automatically. Results merge using the existing OpenAF merge behavior (including array concatenation). Failed jobs never merge their result. JSON cannot represent arbitrary runtime objects or exact integers beyond JavaScript’s safe range.
jobs:
- name: Java example
lang: java
typeArgs:
langTimeout: 30000
langArgs:
javaImports: [java.time.Instant]
javaClasspath: []
exec: |
System.out.println("Java is running");
args.put("javaResult", Instant.now().toString());
Java uses a JDK 21+ source launcher, defaulting to the current Java installation, and Gson from the OpenAF distribution. exec (or file) is a Java method body with Map<String,Object> args. JSON numbers are Long or Double; use Number when reading them. Imports omit the final semicolon; classpath entries are paths, not a platform-separated string. No dependencies are downloaded. Java source is literal by default; set noTemplate: false to opt into source templating. Other existing language defaults remain unchanged.
For these local structured runners:
| Option | Meaning |
|---|---|
typeArgs.langExecutable | Executable name or path, overriding the adapter default; e.g. pwsh instead of powershell. |
typeArgs.langExecutableArgs | Array of extra arguments before the script arguments. |
typeArgs.langTimeout | Positive milliseconds covering compilation and execution; absent means unlimited. Separate from job-level timeout. |
typeArgs.pwd | Child working directory. Paths and executable arguments containing spaces are supported. |
typeArgs.shellPrefix | Prefix buffered stdout/stderr log lines after execution. |
typeArgs.noTemplate | Keep source literal when true. Pass data through args rather than interpolating it into code. |
Temporary input, source, and result files are removed on success, failure, and timeout. Timeouts terminate the direct process and tracked descendants. OS restrictions can prevent descendant enumeration, and deliberately detached children are not a containment guarantee. The process helper reports descendantTracking so callers can distinguish that limitation.
ow.oJob.getLanguages() lists registered languages, prerequisites, and supported process options. getLanguages(true) additionally runs local probes with a five-second limit; it does not install software. available: null means the adapter has no local probe. Python retains OAF_PYTHON and its existing bridge configuration; new process options are rejected for Python, shell/SSH, and legacy adapters that do not advertise support.
Migration: built-in structured runners now require the generated result file; printing JSON alone or exiting before the wrapper finishes no longer returns arguments. stdout is always logging. Arguments are no longer copied into environment variables for these runners; use their existing args variable ($_args in PowerShell, $args in Perl). Source runs from a temporary file; Node’s require resolves from the child working directory. Python retains its existing bridge. sh, shell, SSH and Kubernetes retain their current conventions, including shell # return support.
Custom language adapters
Existing ojob.langs entries using shell, pre, pos, withFile, returnRE, returnFn, or langFn retain their contract, including overrides of built-in names. To use file-based argument exchange, explicitly register protocol: json-file-v1 with an executable, extension, optional scriptArgs/versionArgs, and pre/pos wrappers. The wrapper reads OJOB_ARGS_FILE and writes a JSON object to OJOB_RESULT_FILE. pre receives template variables argsFile, resultFile, and langArgs; pos is literal source. Paths are also available through the environment, avoiding source quoting issues.
For adapters implementing their own protocol, ow.oJob.runLanguageProcess(argv, {pwd, env, timeout}) returns {stdout, stderr, exitcode, timedOut, descendantTracking} with concurrent UTF-8 output capture. env overlays the inherited environment; stdin is closed. Set processOptions: true only when the adapter implements the documented process options. Register cleanup with ow.oJob.registerLanguageCleanup(name, fn); it runs after oJob workers stop.
The optional Rust oPack retains complete Rust programs, environment arguments, and JSON stdout results, with compilation caching and bounded execution. Prolog and Kubernetes remain optional adapters with their existing interfaces.
Job Execution Control
jobs:
- name : "Controlled Job"
typeArgs:
timeout : 30000 # Max execution time (ms)
single : true # Don't parallelize array args
async : false # Force synchronous execution
noLog : true # Don't log this job
pwd : "/tmp" # Working directory
when : ["init", "ready"] # Only run in these states
stopWhen: | # Stop condition
return $get("shouldStop") === true;
lock: "myLock" # Mutual exclusion lock
lockCh: "oJob::locks" # Channel for locks
exec : |
// Job code here
stopWhen is compiled as a separate function, with no injected args, job, id, or deps. Use explicit shared state such as $get("shouldStop"). It is evaluated by the thread-box controller; it is not a cleanup callback. timeout is in milliseconds. pwd applies to supported external language runners, not JavaScript’s working directory.
Job Validation
oJob provides comprehensive input and output validation through the check section:
jobs:
- name : "Validated Job"
check:
in: # Input validation
inputFile: isString # Must be a string
port : toNumber.isNumber.default(8080) # Convert to number, default 8080
enabled : toBoolean.isBoolean.default(false) # Convert to boolean, default false
config : isMap # Must be an object/map
items : isArray.default([]) # Must be array, default empty
level : isString.oneOf(['debug', 'info', 'warn', 'error']).default('info')
timeout : isNumber.between(1000, 60000).default(30000) # Between 1-60 seconds
out: # Output validation
result: isString.oneOf(['success', 'failure']) # Must be one of these values
count : isNumber.default(0) # Must be number, default 0
data : isMap.default({}) # Must be map, default empty object
exec : | #js
// Input validation happens automatically before this code runs
// args.inputFile is guaranteed to be a string
// args.port is guaranteed to be a number (converted from string if needed)
// args.enabled is guaranteed to be a boolean
// Process the validated inputs
log("Processing file: " + args.inputFile + " on port: " + args.port);
// Set outputs - these will be validated too
args.result = "success"
args.count = 42
args.data = { processed: true, timestamp: new Date() }
// Output validation happens automatically after this code runs
Validation order and supported checks
For a composed job, the order is:
check._in → from / earlier → check.in → exec → check.out → to / then → check._out
The before/after jobs also retain their own compiled checks. Checks use methods from OpenAF’s _$ validator; see the sigil reference. Use isString, isNumber, isMap, isArray, toNumber, toBoolean, oneOf(...), match(...), and default(...) as appropriate. A chain without default(...) ends in a required-value assertion. Unknown method names are filtered by the compiler, so do not assume familiar validation methods from another library work here. Use explicit JavaScript assertions for checks not provided by _$.
jobs:
- name: Validate request
check:
_in:
config: isMap
in:
config.port: toNumber.isNumber.default(8080)
mode: isString.oneOf(['fast', 'full']).default('fast')
optionalLabel: isString.default(__)
out:
result: isMap
_out:
result.ok: isBoolean
exec: |
if (args.config.port < 1 || args.config.port > 65535) {
throw "port must be between 1 and 65535";
}
args.result = { ok: true };
Quote an entire YAML scalar if the JavaScript expression includes : , or use a block scalar. default(__) allows a missing value. The check declarations validate/change args; they do not validate the YAML document itself.
Job Inheritance
from (alias earlier) and to (alias then) compose code around the current exec. They accept names or todo-style maps, including shortcuts. Only the composed job needs to be in todo; scheduling its helper jobs separately runs those helpers again. These helpers are not separate scheduled invocations, and their job types do not turn composed code into periodic or shutdown work.
jobs:
- name: "Base Job"
exec: | #js
log("Base functionality")
- name: "Extended Job"
from:
- "Base Job" # Execute before main job
to :
- "Cleanup Job" # Execute after main job
exec: | #js
log("Main functionality")
Job Help
jobs:
- name: "Documented Job"
help:
text : "This job processes data files"
expects:
- name: inputFile
desc: "Path to input file"
- name: format
desc: "Output format"
exec: |
// Job implementation
Code Separation
Use a module when you want to test the same JavaScript independently and package it inside an oJob. Use a raw body when it needs the job execution context.
| Source | Behavior |
|---|---|
jobs[].exec | Inline source in the selected lang, defaulting to OpenAF JavaScript. |
execFile / typeArgs.file | Uses the matching top-level code entry first, otherwise reads a filesystem file. |
execRequire / typeArgs.execRequire | Calls the module export whose name exactly matches the job name, passing args. Mutate args inside that function. |
ojob.execRequire | Default module for eligible JavaScript jobs with empty inline bodies. |
typeArgs.execJs / typeArgs.execPy | Reads a filesystem body and selects OpenAF JavaScript / Python. |
jobs[].file | Reads a filesystem body directly, overriding earlier source selection; it does not resolve embedded code. |
| YAML anchors / aliases | Reuses YAML values, including exec strings, inside one document. |
Avoid combining source selectors. In the loader, execJs is applied before the empty-body execRequire fallback, then execPy, then typeArgs.file (including execFile), then jobs[].file. An inline body suppresses the module fallback. type: jobs uses file options for nested oJob definitions instead of executable bodies.
Filesystem body paths use the process working directory; require also searches installed oPack paths. Embedded keys are matched literally. Extracting a file does not make it override an embedded copy. Run a standalone test from the export directory, then re-import before testing the packaged workflow. Start a fresh process to avoid cached source from an earlier load.
The code map embeds source by filename. JavaScript entries are retained as bodies and, when the existing module heuristic matches, registered in the require cache. An IIFE around exports is the established module form:
todo:
- Normalize
jobs:
- name: Normalize
check:
in:
text: isString.default("hello")
execRequire: handlers.js
code:
handlers.js: |-
(function() {
exports.Normalize = function(args) {
args.text = args.text.toUpperCase();
};
})();
For a raw body, use execFile: normalize.js and put the body itself under code["normalize.js"]. For other languages, retain lang, for example lang: python with execFile: normalize.py. Extracted bodies may rely on args, job, id, composition, or language adapters; provide that context in a test harness. The command exports source, without generating execution wrappers.
Export, test, and import source
The local source operations support .yaml, .yml, and .json. They parse one document without executing jobs, evaluating tagged code, loading modules, fetching URLs, or expanding include / jobsInclude. Process included definitions separately. Encrypted definitions and executable YAML tags are unsupported.
# Export code entries and inline jobs[].exec bodies.
ojob workflow.yaml -exportcode dir=workflow-code
# Export one inline body to a chosen file.
ojob workflow.yaml -exportcode job=Transform file=transform.js
# Select another string property using a JSON pointer.
ojob workflow.yaml -exportcode path=/code/handlers.js dir=handlers-code
# Import into a new definition; omit output to write the definition to stdout.
ojob workflow.yaml -importcode dir=workflow-code output=workflow.updated.yaml
# Explicitly replace the original definition.
ojob workflow.yaml -importcode dir=workflow-code inplace=true
Source operations accept one operation flag and only their documented options; do not combine them with -compile, -f, or execution options.
The default export/import directory is <definition-stem>-code; specify dir to use another location. Export writes .ojob-code.json alongside the source files. It records the canonical input path, property pointers, job identities, and source hashes. Keep it with the exported files. Import checks those mappings against the original input; unrelated edits are allowed, but conflicting source edits or renamed/reordered jobs are rejected. An unchanged export/import returns the original definition text. Re-importing the same edits is allowed.
code filenames are retained. Inline bodies use jobs/<index>-<sanitized-name>.<extension>. JavaScript uses .js, Python .py, shell .sh, and other known languages their usual extension; custom languages use .txt. Set extension=ext on export to choose an extension for generated filenames. Explicit file requires one selected string; when dir is omitted, the file’s parent becomes the export directory. JSON pointers escape / as ~1 and ~ as ~0; for example /code/scripts~1task.sh.
Existing files are refused unless force=true is set. This permits file replacement, but does not bypass definition-source conflicts. Import with output keeps the input format. Output files are replaced using an atomic move after all mappings and destinations are checked. A multi-file export is not a filesystem transaction.
Import replaces only selected scalar ranges. It preserves surrounding YAML text, comments, and untouched properties; changed block scalars use literal style with explicit indentation. Quoted/flow scalars use quoted strings; source containing carriage returns or control characters also uses quoted strings. JSON formatting outside changed strings is retained. Editing an alias directly, or changing an anchor in a way that modifies unselected properties, fails rather than silently rewriting shared values. Use separate scalar values when independent editing is required.
See the standalone module workflow for complete runnable commands. A JSON data file is not an executable job body; read it with io.readFileJSON or ojob file get.
State and Scheduling
Set state explicitly with (state) or ow.oJob.setState("ready"); use typeArgs.when for jobs restricted to that state. Do not rely on an implicit initial state. when directly on a todo entry is not read by the dispatcher.
Periodic, subscribe, and shutdown definitions must appear in todo to register them. Use ojob.daemon: true to keep a service running after registration. ojob.daemonFunc is a global callback run at ojob.timeInterval (default 100 ms); returning true requests termination. It is not a job-level property and receives no job arguments.
Periodic jobs use either typeArgs.timeInterval in milliseconds or a cron expression; a positive interval takes precedence if both are supplied. cronInLocalTime: true selects local time, while false selects UTC. waitForFinish: true prevents overlapping scheduled executions. See the complete periodic recipe.
cronCheck is a map with active, ch, retries, retryWait, and optional cron (defaults to the job’s cron). The channel holds { name, last, status, retries }. The default channel is in memory: retaining missed-run information across restarts requires a suitable persistent channel. The failure counter starts at 1; retries continue while it is less than retries, and only when retryWait is provided. Thus retries: 3 allows up to three attempts, including the initial attempt. This mechanism is not a general durable queue.
Built-in Jobs
ojob.includeOJob defaults to true and loads packaged built-in jobs. Additional shortcuts such as (httpdStart) or (stdioMCP) come from included libraries (for example oJobHTTPd.yaml or oJobMCP.yaml); declaring an oPack alone does not include its job definitions.
Built-in Job Shortcuts
A shortcut expands to an ordinary { name, args, typeArgs } todo entry. Spaces before closing parentheses are allowed. Put one primary shortcut in each map; secondary keys use double parentheses. Only registered secondary keys are mapped. If a built-in has an argument without a shortcut mapping, pass it under ordinary args.
ojob:
sequential: true
shareArgs: true
logToConsole: false
todo:
- (pass):
name: World
- (log): "Hello {{name}}"
- (output): args
((path)): name
((format)): json
Shortcuts also work inside from and to. (run) and (runfile) both run external definitions; use a job name directly, (todo), or $job("Name", args) to invoke a job in the current definition.
Built-in Jobs and Shortcut Correlation
The following mappings follow parseTodo in js/owrap.oJob.js. The CLI’s -shortcuts output is the authority for your installed version. Built-in job metadata in ojob.yaml can contain additional argument names; it does not override an existing parser mapping.
| Shortcut | Job | Primary argument |
|---|---|---|
(if) | ojob if | __condition |
(parallel) | ojob parallel | todo |
(pass) | ojob pass | __args |
(wait) | ojob wait | time |
(optionOn) | ojob options | __optionOn |
(fail) | ojob exit | code |
(fn) | ojob function | __fn |
(check) | ojob check | _checks |
(query) | ojob query | __query |
(output) | ojob output | __key |
(repeat) | ojob repeat | __times |
(secget) | ojob sec get | secKey |
(each) | ojob repeat with each | __path |
(state) | ojob set state | __state |
(stateOn) | ojob state | stateOn |
(template) | ojob template | template |
(templateFolder) | ojob template folder | — |
(jobdebug) | ojob job debug | job |
(jobsdebug) | ojob job debug | jobs |
(log) | ojob log | msg |
(printmd) | ojob print md | __text |
(print) | ojob print | msg |
(ch) | ojob channel | __name |
(runfile) | ojob run file | __job |
(get) | ojob get | __key |
(set) | ojob set | __key |
(unset) | ojob unset | __key |
(fileget) | ojob file get | __file |
(todo) | ojob todo | todo |
(findReplace) | ojob find/replace | __key |
(debug) | ojob debug | — |
(run) | ojob run file | __job |
(convert) | ojob convert | __inKey |
(ask) | ojob ask | __answers |
(questions) | ojob questions | __questions |
(oafp) | ojob oafp | __params |
(llm) | ojob llm | __llmPrompt |
Built-in Job Arguments Reference
Each entry lists the registered shortcut attributes and the job arguments they populate. For descriptions, inspect ojob your.yaml -jobhelp ojob output (substitute the full built-in job name), or consult ojob.yaml.
(if) → ojob if
If the provided “condition” is evaluated as true it will execute the “then” jobs otherwise it will execute the “else” jobs
((then)) → __then; ((else)) → __else; ((debug)) → __debug.
(parallel) → ojob parallel
Executes an ojob sub-todo in parallel.
((isolateArgs)) → isolateArgs; ((isolateJob)) → isolateJob; ((templateArgs)) → templateArgs; ((shareArgs)) → shareArgs; ((debug)) → __debug.
(pass) → ojob pass
Placeholder/pass job to allow for arguments injection
((debug)) → __debug; ((templateArgs)) → __templateArgs.
(wait) → ojob wait
Waits for a specific amount of time
No secondary shortcut attributes.
(optionOn) → ojob options
Adds new “todo” entries depending on the value of a provided args variable.
((lowerCase)) → __lowerCase; ((upperCase)) → __upperCase; ((todos)) → __todos; ((default)) → __default; ((async)) → __async.
(fail) → ojob exit
Ends all processing with an exit code
((force)) → force.
(fn) → ojob function
Executes the provided function mapping any args to the function arguments using the odoc help available for the provided function.
((key)) → __key; ((path)) → __path; ((fnPath)) → __fnPath.
(check) → ojob check
((actions)) → _actions.
(query) → ojob query
Performs a query (using ow.obj.filter) to the existing args.
((type)) → __type; ((from)) → __from; ((to)) → __to; ((toKey)) → __toKey; ((key)) → __key.
(output) → ojob output
Prints the current arguments to the console.
((path)) → __path; ((format)) → __format; ((title)) → __title; ((internal)) → __internal; ((function)) → __function.
(repeat) → ojob repeat
Repeats sequentially, for a specific number of “times”, the provided list of “jobs” (one or more)
((todo)) → __jobs.
(secget) → ojob sec get
This job will get a SBucket secret and map it to oJob’s args
((secRepo)) → secRepo; ((secBucket)) → secBucket; ((secPass)) → secPass; ((secOut)) → secOut; ((secMainPass)) → secMainPass; ((secFile)) → secFile; ((secDontAsk)) → secDontAsk; ((secIgnore)) → secIgnore; ((secEnv)) → secEnv.
(each) → ojob repeat with each
Repeats the configured “jobs” (one or more jobs) sequentially for each element of the provided “key” list.
((key)) → __key; ((todo)) → __jobs.
(state) → ojob set state
Changes the current state.
No secondary shortcut attributes.
(stateOn) → ojob state
Changes the current state depending on the value of a provided args variable.
((lowerCase)) → lowerCase; ((upperCase)) → upperCase; ((validStates)) → validStates; ((default)) → default.
(template) → ojob template
Applies the OpenAF template over the provided data producing an output.
((templateFile)) → templateFile; ((data)) → data; ((dataFile)) → dataFile; ((outputFile)) → outputFile; ((key)) → __key; ((tpath)) → __tpath; ((dpath)) → __dpath; ((outPath)) → __outPath; ((out)) → __out.
(templateFolder) → ojob template folder
The current parser has a misspelled primary attribute for this shortcut. Use the full job name with args.templateFolder instead of relying on the shortcut value.
Given a templateFolder it will execute ‘ojob template’ for each (recursively), with the provided data, to output to outputFolder. Optionally metaTemplate can be use where each json/yaml file in templateFolder all or part of the arguments for ‘ojob template’.
((templatePath)) → __templatePath; ((data)) → data; ((dataFile)) → dataFile; ((outputFolder)) → outputFolder; ((key)) → __key; ((dpath)) → __dpath; ((logJob)) → logJob; ((metaTemplate)) → metaTemplate.
(jobdebug) → ojob job debug
Provides an alternative to print based debug.
((lineColor)) → lineColor; ((textColor)) → textColor; ((theme)) → theme; ((emoticons)) → emoticons; ((signs)) → signs; ((includeTime)) → includeTime.
(jobsdebug) → ojob job debug
Provides an alternative to print based debug.
((lineColor)) → lineColor; ((textColor)) → textColor; ((theme)) → theme; ((emoticons)) → emoticons; ((signs)) → signs; ((includeTime)) → includeTime.
(log) → ojob log
Logs a message line given an OpenAF template
((key)) → __key; ((path)) → __path; ((level)) → level; ((options)) → options.
(printmd) → ojob print md
Parses an input text as simple ascii markdown
((outputMD)) → __outputMD.
(print) → ojob print
Prints a message line given an OpenAF template
((key)) → __key; ((path)) → __path; ((level)) → level.
(ch) → ojob channel
Provides a set of operations over an OpenAF channel
((op)) → __op; ((key)) → __key; ((kpath)) → __kpath; ((k)) → key; ((ks)) → keys; ((v)) → value; ((vs)) → values; ((vpath)) → __vpath; ((extra)) → extra.
(runfile) → ojob run file
Executes an external YAML/JSON ojob file or a remote URL with the provided args.
((args)) → __args; ((out)) → __out; ((key)) → __key; ((inKey)) → __inKey; ((usePM)) → __usePM; ((inPM)) → __inPM; ((templateArgs)) → __templateArgs; ((debug)) → __debug.
(get) → ojob get
Retrieves a specific map key (or path) using $get
((path)) → __path.
(set) → ojob set
Sets a “key” with the current value on a “path”, or provided data, using $set
((path)) → __path.
(unset) → ojob unset
Unsets a “key” using $unset
No secondary shortcut attributes.
(fileget) → ojob file get
Retrieves a specific map key (or path) from an YAML or JSON file provided.
((path)) → __path; ((cache)) → __cache; ((ttl)) → __ttl; ((out)) → __out; ((key)) → __key.
(todo) → ojob todo
Executes an ojob sub-todo.
((isolateArgs)) → isolateArgs; ((isolateJob)) → isolateJob; ((templateArgs)) → templateArgs; ((shareArgs)) → shareArgs; ((debug)) → __debug.
(findReplace) → ojob find/replace
Performs an in-memory find/replace on a provided string or file and outputs to args.output or, optionally, to a file.
((path)) → __path; ((inputKey)) → inputKey; ((inputPath)) → inputPath; ((inputFile)) → inputFile; ((outputFile)) → outputFile; ((useRegExp)) → useRegExp; ((flagsRegExp)) → flagsRegExp; ((logJob)) → logJob.
(debug) → ojob debug
Outputs the current args and res values to help debug an ojob flow.
No secondary shortcut attributes.
(run) → ojob run file
Executes an external YAML/JSON ojob file or a remote URL with the provided args.
((args)) → __args; ((out)) → __out; ((key)) → __key; ((inKey)) → __inKey; ((usePM)) → __usePM; ((inPM)) → __inPM; ((templateArgs)) → __templateArgs; ((debug)) → __debug.
(convert) → ojob convert
Converts string content into an internal object (map/array)
((inPath)) → __inPath; ((inFormat)) → __inFormat; ((outPath)) → __outPath; ((outKey)) → __outKey.
(ask) → ojob ask
Asks for user input and stores the result into args if the args value is not yet defined.
((question)) → __question; ((force)) → __force.
(questions) → ojob questions
Asks a list of questions and stores the answers into args.
No secondary shortcut attributes.
(oafp) → ojob oafp
No secondary shortcut attributes.
(llm) → ojob llm
“Executes a LLM (Local Language Model) prompt using $llm”
((context)) → __llmContext; ((inPath)) → __llmInPath; ((outPath)) → __llmOutPath; ((options)) → __llmOptions; ((inKey)) → __llmInKey; ((env)) → __llmEnv; ((debug)) → __llmDebug.
The parser does not register (options), (split), (replace), or (setenvs). Use (optionOn) for option dispatch and (findReplace) for replacement, with the argument shapes documented by their jobs. (oafp) takes an oafp parameter map, for example { data: '[1,2]', in: json, out: yaml }; it has no ((from)), ((to)), or ((outPath)) attributes. (convert) has ((inFormat)), not ((outFormat)). (debug) prints diagnostic data; it is not a debugger breakpoint.
Custom Shortcuts
todo:
- (greet): Ada
((prefix)): Hello
jobs:
- name: Greet
typeArgs:
shortcut:
name: greet
keyArg: person
args:
prefix: greeting
check:
in:
person: isString
greeting: isString.default("Hi")
exec: |
print(args.greeting + " " + args.person);
Custom shortcut names must be distinct from built-ins. Use typeArgs.shortcut.nolog for shortcut metadata and typeArgs.noLog for a job’s execution logging.
Examples
The recipe collection provides complete examples for arguments, composition, query/output, fan-out, templates, periodic jobs, subscriptions, and error recovery. The ojob.io examples map connects those patterns to real YAML definitions and their prerequisites.