Skip to main content
Use this guide when one workflow is easier or safer to split by runtime, such as Bash for ingestion, Python for analysis, and Node for final formatting.

Diagram: Stateless pipeline across runtimes

Runtime roles and extension mapping

RuntimeTypical role in workflowDefault extension
Pythondata transforms, analytics, scientific packages.py
NodeJSON shaping, ecosystem tooling, JS scripts.mjs (.js and .cjs aliases)
Bunfast TS/JS steps.ts
DenoTypeScript with explicit Deno runtime.mts
Bashshell glue and orchestration helpers.sh
Runtime auto-detection is extension-based. .ts maps to Bun, while Deno uses .mts to avoid ambiguity.

Choose an orchestration mode

ModeWhen to useConstraint
Stateless chaining (ephemeral)each step independent, clean sandbox per steppass state through stdin/files between calls
Per-runtime persistent sessionssame runtime needs shared state across stepskeep one session per runtime; do not runtime-switch in one session
Run each step in an isolated execution and pass structured output forward via stdin.
import { DockerIsol8 } from "@isol8/core";

const engine = new DockerIsol8({
  mode: "ephemeral",
  network: "none",
  timeoutMs: 30000,
});
await engine.start();

// Step 1: generate rows with Bash
const gen = await engine.execute({
  runtime: "bash",
  code: `
for i in $(seq 1 20); do
  echo "$i,$((RANDOM % 1000))"
done
`,
});

// Step 2: aggregate with Python
const agg = await engine.execute({
  runtime: "python",
  stdin: gen.stdout,
  code: `
import csv, io, json, sys
rows = list(csv.reader(io.StringIO(sys.stdin.read())))
vals = [int(r[1]) for r in rows]
print(json.dumps({
  "count": len(vals),
  "avg": sum(vals)/len(vals),
  "max": max(vals)
}))
`,
});

// Step 3: format report with Node
const report = await engine.execute({
  runtime: "node",
  stdin: agg.stdout,
  code: `
const fs = require("node:fs");
const stats = JSON.parse(fs.readFileSync("/dev/stdin", "utf8"));
console.log("=== Report ===");
console.log("count:", stats.count);
console.log("avg:", Number(stats.avg).toFixed(2));
console.log("max:", stats.max);
`,
});

console.log(report.stdout);
await engine.stop();

Pattern 2: same request shape across interfaces

Use tabs when demonstrating equivalent CLI, library, and API usage.
isol8 run -e "console.log(JSON.stringify({ step: 'format', ok: true }))" \
--runtime node \
--mode ephemeral

Pattern 3: compare implementations across runtimes

Useful for performance comparisons or parity testing.
const cases = [
  {
    runtime: "python" as const,
    code: `
import json
def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)
print(json.dumps({"result": fib(20)}))
`,
  },
  {
    runtime: "node" as const,
    code: `
function fib(n){ return n < 2 ? n : fib(n-1) + fib(n-2); }
console.log(JSON.stringify({ result: fib(20) }));
`,
  },
  {
    runtime: "bun" as const,
    code: `
function fib(n:number):number{ return n < 2 ? n : fib(n-1) + fib(n-2); }
console.log(JSON.stringify({ result: fib(20) }));
`,
  },
];

const outputs = await Promise.all(
  cases.map(async (c) => {
    const r = await engine.execute({ runtime: c.runtime, code: c.code });
    return { runtime: c.runtime, out: JSON.parse(r.stdout).result };
  })
);

Diagram: Persistent sessions per runtime

Pattern 4: persistent workflows by runtime

Persistent containers are runtime-bound. For stateful polyglot systems, use one persistent engine/session per runtime.
const pySession = new DockerIsol8({ mode: "persistent", timeoutMs: 30000 });
const nodeSession = new DockerIsol8({ mode: "persistent", timeoutMs: 30000 });

await pySession.start();
await nodeSession.start();

await pySession.execute({
  runtime: "python",
  code: `
open("/sandbox/data.json","w").write('{"x": 10}')
print("py ready")
`,
});

await nodeSession.execute({
  runtime: "node",
  code: `
require("node:fs").writeFileSync("/sandbox/info.json", JSON.stringify({ y: 20 }));
console.log("node ready");
`,
});

await pySession.stop();
await nodeSession.stop();
Do not switch runtimes in a single persistent container. Create one persistent session per runtime.

Runtime selection and overrides

isol8 run step1.sh      # bash
isol8 run step2.py      # python
isol8 run step3.js      # node
isol8 run step4.ts      # bun
isol8 run step5.mts     # deno

Cross-runtime code generation pattern

Generate code in one runtime and execute in another.
const generated = await engine.execute({
  runtime: "bun",
  code: `
const lines = [
  "data = [1,2,3,4]",
  "print(sum(data))",
];
console.log(lines.join("\\n"));
`,
});

const executed = await engine.execute({
  runtime: "python",
  code: generated.stdout,
});

console.log(executed.stdout); // 10

Remote multi-runtime orchestrator

For centralized orchestration, use dedicated remote sessions per runtime role.
import { RemoteIsol8 } from "@isol8/core";

const pyRemote = new RemoteIsol8(
  {
    host: "http://localhost:3000",
    apiKey: process.env.ISOL8_API_KEY!,
    sessionId: "job-42-python",
  },
  { network: "none", timeoutMs: 30000 }
);

const nodeRemote = new RemoteIsol8(
  {
    host: "http://localhost:3000",
    apiKey: process.env.ISOL8_API_KEY!,
    sessionId: "job-42-node",
  },
  { network: "none", timeoutMs: 30000 }
);
Reuse a stable sessionId per runtime role when you need cross-call state in remote workflows. Rotate session IDs per job for strict isolation.

Design rules for polyglot systems

  • keep cross-runtime handoff format explicit (JSON, CSV, newline-delimited text)
  • keep each step deterministic and small
  • isolate heavyweight dependencies to the runtime that needs them
  • pre-bake dependencies for stable production latency
  • use explicit runtime selection when extension detection is ambiguous