Run a polyglot task
Define a non-TypeScript script — a Python program, a shell script, a .NET tool, or any
executable — as a NetScript task, give it a permission sandbox, and run it through the
multi-runtime executor with its stdout/stderr captured and its result parsed.
alpha
This is the task-focused DO companion to the polyglot tasks
hub — read that first for the WHY (what a task is, how the subprocess seam works, the full
TaskResult shape). This page wires one up.
Prerequisites
| Name | Type | Description |
|---|---|---|
@netscript/plugin-workers-core |
package (alpha) |
Provides defineTask (./builders) and createDefaultTaskExecutor (./executor). |
The target toolchain |
host binary |
The interpreter the task spawns must exist on the worker HOST: python3 (or a venv / py on Windows), bash, pwsh/powershell, the .NET SDK, or your prebuilt binary. |
An entrypoint script |
file path |
The script or executable to run, e.g. ./scripts/score.py. The task passes it input as argv + env, never stdin. |
(deno tasks only) a permission set |
BuilderPermissions |
net/read/write/env/run/ffi/import. Enforced ONLY for the deno runtime — see the sandbox note below. |
Steps
1. Define the task
defineTask(id) returns a typestate builder; the default runtime is 'deno', so call
.runtime(type) to target another language. Input reaches the script as argv
(.args(...)) and environment variables (.env({...})). .build() is only callable
once an .entrypoint(path) (subprocess) or .handler(fn) (in-process) has been set.
// workers/tasks/score-batch.ts
import { defineTask } from '@netscript/plugin-workers-core/builders';
// python runtime, script entrypoint, explicit inputs.
export const scoreBatch = defineTask('score-batch')
.runtime('python')
.entrypoint('./scripts/score.py')
.env({ MODEL_PATH: './models/scorer.pkl' })
.args('--threshold', '0.8')
.timeout(120_000) // ms; defaults to 300_000
.build();
// Spawns: python3 -u ./scripts/score.py --threshold 0.8
// workers/tasks/rotate-logs.ts
import { defineTask } from '@netscript/plugin-workers-core/builders';
// shell runtime runs the entrypoint under bash (no -c).
export const rotateLogs = defineTask('rotate-logs')
.runtime('shell')
.entrypoint('./scripts/rotate-logs.sh')
.args('--keep', '7')
.timeout(30_000)
.build();
// Spawns: bash ./scripts/rotate-logs.sh --keep 7
// workers/tasks/score-batch.ts — prefer a pinned venv over $PATH discovery
import { defineTask } from '@netscript/plugin-workers-core/builders';
export const scoreBatch = defineTask('score-batch')
.runtime('python')
.entrypoint('./scripts/score.py')
// Resolution order: pythonConfig.pythonPath -> venvPath -> NETSCRIPT_PYTHON_PATH -> python3/py
.metadata({ pythonConfig: { venvPath: './.venv' } })
.build();
2. Write the script so its result crosses the process boundary
The subprocess returns structured data by printing one JSON object as the last line of
stdout. Everything else on stdout/stderr is captured as logs. Print diagnostics to
stderr; emit the JSON result last, with no trailing output.
# scripts/score.py
import json, os, sys
# Input arrives as argv + env (NOT stdin).
threshold = float(sys.argv[sys.argv.index('--threshold') + 1])
model_path = os.environ['MODEL_PATH']
# ... do the work ...
scored = {'kept': 42, 'dropped': 3, 'threshold': threshold}
# Diagnostics go to stderr; the result is the LAST stdout line and must be a
# single JSON OBJECT (not an array) to populate result.result.
print('scoring complete', file=sys.stderr)
print(json.dumps(scored))
#!/usr/bin/env bash
# scripts/rotate-logs.sh
set -euo pipefail
keep="${2:-7}"
# ... rotate ...
echo "rotated logs, keeping ${keep}" >&2 # diagnostics -> stderr
# Last stdout line = JSON object result. Non-python runtimes are not -u'd, so
# flush by emitting the JSON as the final write with nothing after it.
printf '{"rotated": true, "kept": %s}\n' "$keep"
3. Run it through the workers CLI
ns-workers run-task resolves the generated task metadata and delegates to the same
MultiRuntimeTaskExecutor used by the worker runtime. Pass argv and environment as JSON; stdout and
stderr stream while the subprocess runs, followed by the complete TaskResult.
ns-workers run-task score-batch \
--args='["--threshold","0.8"]' \
--env='{"MODEL_PATH":"./models/scorer.pkl"}' \
--timeout=120000 \
--json
The programmatic createDefaultTaskExecutor() API remains available for runtime integrations and
custom adapters; ordinary task execution no longer needs a hand-written runner module.
4. Sandbox a deno task with explicit permissions
The deno runtime is the only one NetScript sandboxes: the .permissions({...}) set is
translated into --allow-* flags on the deno run command line. For python, shell,
powershell, cmd, dotnet, and executable, those keys are ignored — the subprocess
inherits the worker process's OS-level access. Gate those at the OS layer instead.
// workers/tasks/parse-feed.ts — a sandboxed deno task (least privilege)
import { defineTask } from '@netscript/plugin-workers-core/builders';
export const parseFeed = defineTask('parse-feed')
.runtime('deno') // the default; shown here for clarity
.entrypoint('./scripts/parse-feed.ts')
.permissions({
net: ['api.example.com'], // -> --allow-net=api.example.com
read: ['./feeds'], // -> --allow-read=./feeds
write: false,
env: ['FEED_TOKEN'], // -> --allow-env=FEED_TOKEN
})
.build();
// Spawns: deno run --allow-net=api.example.com --allow-read=./feeds --allow-env=FEED_TOKEN ./scripts/parse-feed.ts