A script is the body of a JavaScript function. input is the job's input, and the value the script returns is the job's output.
// title.js
const page = http.get(input.url)
emit("fetched", { url: input.url, size: page.body.length })
return { title: html.parse(page.body).find("title").text }corbie test title.js --input '{"url": "https://example.com/news"}' --allow net:example.com output: {"title":"News"}
A script uses files, the network, programs and environment variables only by calling a plugin, and Corbie checks each call against the job's permissions. A call returns its result, so a script needs no callback and no await.
What a script has
inputthe job's input
job.namethe job's name; a restart adds its attempt, as in get_title@2
job.attemptthe number of this attempt, from 1
job.requestthe run's ID and the job's name, the same in every attempt
job.instancethe run's ID
emit(name, value)sends the machine a named value while the script runs
log(text, ...)writes a line to the run's record, which --watch prints
progress(done, total)writes { done, total } to the run's record; total is optional
http, fs, html, ...every plugin and library is a global name
require("http")the same plugin or library, by name
require("./lib.js")one of the job's files:, which assigns to exports
require("./data.yaml")the data of one of the job's files: of YAML or JSON
# double.yaml
flow: double
input: { n: number }
stages:
double: { job: { script: ./double.js, files: [./lib.js], input: { n: "${input.n}" } } }
output: ${stages.double.output}// double.js
return require("./lib.js").double(input.n)// lib.js
exports.double = (n) => n * 2
corbie test double.yaml --input '{"n": 21}' output: 42
A path given to require is read from the directory of the file that calls it. A loaded file has the plugins and require, and not input, job, emit, log or progress.
A file whose path ends in .yaml, .yml or .json gives its data, read as yaml says. It needs no permission.
A script cannot start a script or a machine: only a machine starts jobs.
Plugins
corbie manual lists the plugins, and each has a page, such as http. Every call is module.call(first, { option: value }): one value, then named options.
- A call refuses an option it does not know.
- Arguments and results are plain data: text, numbers, lists, maps, bytes (a
Uint8Array), blobs and handles to secrets. - A call that fails throws an error with a
code.
not_foundwhat the call asked for does not exist
refusedthe other side refused
timeoutthe call took too long
not_permittedthe operating system refused
bad_inputthe call was written wrong
failedanything else
allow: [fs.read:maybe.txt]
try {
fs.read("maybe.txt")
} catch (e) {
if (e.code !== "not_found") throw e
}A call that the job's permissions refuse stops the job with status denied. The script cannot catch it.
What a script returns
The returned value is the job's output. It is plain data, and so is a value sent with emit.
no valuenull
bytesa blob
a Datetext, such as 2026-10-13T20:00:00Z
a functionthe job fails with status error
a handle to a secretthe job fails with status error
an error not caughtthe job fails with status error, and the error names the line
A blob is a large value, such as a file, a long text or an image. To a script it is an object with type and size, and the blobs plugin reads and makes them. A blob that a script returns or emits is stored in the run's record, and a later job reads it from its input.
Limits
timethe job's timeout:, default 5m; past it the job fails with status timeout
memorythe job's memory:, default no limit; past it the job fails with status error
size4 MiB for a definition as it is stored; 16 MiB for a script in a blob
processeach job runs in its own process
directoryeach job has its own scratch directory, removed when the job ends; a path with no leading / is in it
Reference
- Definitions every field of a
job:block - Permissions how an
allow:entry is written, and how a refusal reads - blobs the calls that read and make a blob