Skip to main content
Alpha

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

What you need before defining a task
NameTypeDescription
@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 executor

createDefaultTaskExecutor() builds a MultiRuntimeTaskExecutor wired with every built-in runtime adapter. Call executor.execute(task, options?) to resolve the matching adapter, spawn the subprocess, and get back one TaskResult. Per-call options (a TaskExecutionOptions) can add args, env, a timeout, an AbortSignal, a correlationId, and the onStdout/onStderr/onLog streaming callbacks.

// workers/run-score.ts
import { createDefaultTaskExecutor } from '@netscript/plugin-workers-core/executor';
import { scoreBatch } from './tasks/score-batch.ts';

const executor = createDefaultTaskExecutor();

const result = await executor.execute(scoreBatch, {
  // options.env merges OVER the task's env; trace headers are injected automatically.
  onStdout: (line) => console.log('[score]', line),
  onStderr: (line) => console.warn('[score:err]', line),
});

if (result.success) {
  // result.result is the parsed JSON object from the LAST stdout line, or null.
  console.log('scored', result.result, `in ${result.duration}ms`);
} else {
  // status is 'failed' | 'timeout' | 'cancelled'; exitCode is -1 when the process never ran.
  console.error('task failed', result.status, result.exitCode, result.error);
}

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

In-production pitfalls

See also

Polyglot tasks

Background jobs

workers