Run agent-authored TypeScript ESM against host-controlled tools on a pluggable, long-lived JavaScript runtime.
Code mode owns the complete program contract: whether per-execution input exists, its type when declared, tool declarations, checking, TypeScript erasure, host-side schema validation, tool routing, telemetry, and its internal wire protocol. Runtime drivers own where programs execute, how modules resolve, how the runner is booted, and how strongly execution can be isolated or cancelled.
npm install @torkbot/code-modeThe library requires Node.js 24 or newer on the host. Agent programs can run in any environment with a conforming runtime driver.
import {
createClient,
createProgramContract,
defineTool,
} from "@torkbot/code-mode";
import { createHostNodeRuntime } from "@torkbot/code-mode/host-node";
const contract = createProgramContract({
inputSchema: WeatherRequested,
tools: [
defineTool(
"getWeather",
{
description: "Get weather for a location.",
inputSchema: WeatherInput,
outputSchema: WeatherReport,
},
async ({ signal }, input) => {
signal.throwIfAborted();
return weatherService.get(input.location, { signal });
},
),
],
});
const runtime = await createHostNodeRuntime(
{
nodePath: process.execPath,
cwd: process.cwd(),
},
AbortSignal.timeout(5_000),
);
try {
const client = createClient({ runtime, contract });
const source = `
import { inspect } from "node:util";
export default async function ({ input, codemode, console }: AgentProgramScope) {
const weather = await codemode.getWeather({ location: input.location });
console.log(inspect(weather));
}
`.trimStart();
const validation = await client.validate(
source,
AbortSignal.timeout(5_000),
);
if (validation.kind === "invalid") {
throw new Error(validation.report);
}
const outcome = await client.run(source, {
input: {
type: "weather.requested",
location: "London",
},
signal: AbortSignal.timeout(30_000),
onTelemetry(event) {
if (event.kind === "program-output") {
process[event.stream].write(event.text);
}
},
});
} finally {
await runtime[Symbol.asyncDispose]();
}createHostNodeRuntime() starts one Node.js process and returns after its runner
is ready. Every subsequent client.run() is an execution request on that live
connection. Dispose the runtime when its owner is done with it.
A program contract has one of two explicit forms:
const inputless = createProgramContract({
tools,
});
const inputBearing = createProgramContract({
inputSchema: EventInput,
tools,
});inputSchema is not an optional value with a fallback. Its presence selects an
input-bearing contract; its absence selects an inputless contract. Accordingly,
client.run(source, { signal }) is the complete inputless execution call, while
an input-bearing client requires
client.run(source, { input: schemaInput, signal }).
A submitted program is a real ECMAScript module. It may use static imports and must have a callable default export assignable to its generated contract. An input-bearing contract generates:
interface AgentProgramScope {
readonly input: {
readonly type: "weather.requested";
readonly location: string;
};
readonly codemode: Tools;
readonly console: CodeModeConsole;
}
type AgentProgram = (scope: AgentProgramScope) => unknown;An inputless contract instead generates:
interface AgentProgramScope {
readonly codemode: Tools;
readonly console: CodeModeConsole;
}The runner evaluates a fresh root module for every execution. It calls the
default export with { input, codemode, console } for an input-bearing
execution and { codemode, console } for an inputless execution, awaits it, and
ignores its fulfilled value. Declared input is the host-validated, transformed
value for that execution. There is no continuity between root modules; a
runtime may still use its platform's normal cache for imported dependencies.
Only the console passed in AgentProgramScope is captured. Calls to an ambient
global console or an imported console are outside the runtime contract. The
captured console has this minimum surface:
interface CodeModeConsole {
debug(...values: unknown[]): void;
error(...values: unknown[]): void;
info(...values: unknown[]): void;
log(...values: unknown[]): void;
warn(...values: unknown[]): void;
}Runtimes format console arguments. Code mode only promises text chunks and their provenance:
type ProgramOutput = {
readonly stream: "stdout" | "stderr";
readonly text: string;
};The consumer is expected to interpret text. There is no structured-value, circular-reference, or console-formatting contract.
validate() checks the submitted module itself. run() uses Amaro's strip-only
transform to erase TypeScript in place, with no parser-owned wrapper and no
prepended source. Runtime stacks therefore retain submitted line and column
coordinates.
A declared program input schema and every tool input/output schema implement
both StandardSchemaV1 and StandardJSONSchemaV1:
type CodeModeSchema<Input, Output> =
& StandardSchemaV1<Input, Output>
& StandardJSONSchemaV1<Input, Output>;Standard Schema validates and transforms values on the host. Standard JSON Schema generates exact agent-facing declarations. Transforming schemas have six distinct type positions:
| Boundary | Type |
|---|---|
| Host supplies program input | SchemaInput<ProgramInputSchema> |
| Program receives validated input | SchemaOutput<ProgramInputSchema> |
| Program supplies tool input | SchemaInput<InputSchema> |
| Handler receives validated input | SchemaOutput<InputSchema> |
| Handler returns an output candidate | SchemaInput<OutputSchema> |
| Program receives validated output | SchemaOutput<OutputSchema> |
Tool names must be unique JavaScript identifiers. then and the inherited
Object property names are reserved. Descriptions must be non-empty because
they become user-facing JSDoc in generated declarations.
The declaration printer supports closed objects, arrays, strings, numbers,
integers, booleans, and null. String schemas may use const to emit a string
literal type. Object schemas must explicitly set additionalProperties: false.
anyOf and oneOf support one deliberately narrow form: at least two closed
object branches must share a required string-const property, and each branch
must give that property a distinct value. The result is emitted as a
discriminated TypeScript union. The distinct required values also make the
branches mutually exclusive, so a TypeScript union preserves oneOf semantics.
Other composition shapes and unsupported schema keywords fail instead of being
represented loosely.
contract.typeDefinitions contains the complete program declaration surface:
the input declaration when one exists, tools, console, and default-export
contract. Program input declarations use the schema's output JSON Schema
because that is the value supplied after host validation.
Runtime.loadTypeDefinitionFiles() supplies checker-only declarations for the
execution environment. Those files are mounted in an in-memory TypeScript
project and are never sent as part of an execution request.
This remains honest for contracts built dynamically. Host TypeScript may know a
ledger-built schema only as CodeModeSchema<unknown, unknown>, while its
concrete output JSON Schema still generates a strong discriminated union for
the submitted program.
validate() has no runtime side effects. run() does not implicitly typecheck.
For an input-bearing contract, it validates and transforms program input before
submitting an execution; invalid or non-JSON transformed input never reaches
the runtime. Inputless contracts perform no input validation and submit no
input field. Tool inputs and outputs are also validated on the host. Non-JSON
tool values fail that program without closing the shared runtime.
type RunOutcome =
| { readonly kind: "success" }
| {
readonly kind: "program-failed";
readonly error: TelemetryError;
};Module evaluation errors, a non-callable default export, thrown or rejected
programs, unknown tools, handler failures, and tool schema failures resolve as
program-failed. The default export's fulfilled value never affects the
outcome. Transport failures and execution cancellation reject the promise.
For an input-bearing contract, invalid host-supplied program input also rejects:
it is an embedder failure, not a failure caused by the submitted program. Its
execution-failed telemetry has program-input-validation details with a
bounded schema report.
An execution signal governs only that execution and its active tool handlers. It does not close the runtime or cancel other multiplexed executions.
Telemetry reports:
program-outputwithstreamandtext;- tool call start, completion, and failure;
- terminal execution completion or infrastructure failure.
Telemetry callbacks are observational. Their throws and rejected promises do not alter program execution.
The public runtime used by a client is already connected:
interface Runtime extends AsyncDisposable {
readonly description: string;
readonly finished: Promise<RuntimeFinished>;
loadTypeDefinitionFiles(
signal: AbortSignal,
): Promise<readonly TypeDefinitionFile[]>;
execute(request: RuntimeExecuteRequest): Promise<RunOutcome>;
[Symbol.asyncDispose](): Promise<void>;
}RuntimeExecuteRequest has input-bearing and inputless forms. The former
requires a host-validated, JSON-compatible input; the latter forbids the
property. The wire message and runner scope preserve that distinction rather
than representing absence as undefined, null, or {}.
description is opaque environment context an embedder may present to an
agent. finished always resolves when the runtime becomes unusable: closed
for an orderly end, or failed with the underlying error. Async disposal closes
the connection and waits for driver-owned resources to stop.
Scheduling belongs to the driver. Clients always receive promises; a driver may serialize them. The built-in Host Node driver is fully multiplexed.
Driver authors use @torkbot/code-mode/runtime:
interface RuntimeDriver<Options> {
readonly description: string;
loadTypeDefinitionFiles(
signal: AbortSignal,
): Promise<readonly TypeDefinitionFile[]>;
connect(
options: Options,
request: {
readonly runnerSource: string;
readonly signal: AbortSignal;
},
): Promise<RuntimeConnection>;
}
interface RuntimeConnection extends AsyncDisposable {
readonly channel: {
readonly readable: ReadableStream<Uint8Array>;
readonly writable: WritableStream<Uint8Array>;
};
readonly finished: Promise<RuntimeFinished>;
[Symbol.asyncDispose](): Promise<void>;
}Expose a user-facing factory by closing over the driver:
import { createRuntimeFactory } from "@torkbot/code-mode/runtime";
const driver: RuntimeDriver<MyOptions> = {
description: "My runtime",
loadTypeDefinitionFiles,
connect,
};
export const createMyRuntime = createRuntimeFactory(driver);The factory owns the version-matched runner, readiness handshake, internal execution IDs, wire protocol, and failed-boot cleanup. Its signal governs boot through readiness and then detaches. The caller that creates the runtime owns its later lifetime.
connect() boots the execution environment, wires one runner to the returned
byte channel, and returns the connection. The driver can use any process,
isolate, VM, worker, socket, provider API, or in-memory transport. Agent source,
tool calls, output, outcomes, cancellation, and opaque correlation are carried
by execution requests after boot; they are not driver options.
The byte framing and messages are internal to this package. Drivers transport bytes; they do not reproduce or interpret the protocol.
Platforms that can preinstall the package use the normal ESM export:
import { startRunner } from "@torkbot/code-mode/runner";
await startRunner({
channel,
schedule: (execute) => execute(),
importModule: ({ source, signal }) => importFreshRoot(source, signal),
createConsole: (emit) => createPlatformConsole(emit),
});Some runtimes cannot preinstall npm modules. Every connect() request therefore
also receives runnerSource: self-contained ESM source exporting the same
startRunner() implementation. A driver can evaluate or upload that source and
append only its platform glue. The equivalent artifact is exported from
@torkbot/code-mode/runner/source for driver tooling and inspection, but normal
runtime users never plumb it.
schedule() wraps the complete module-evaluation and default-export invocation.
Call its callback immediately to multiplex, or queue callbacks to serialize a
runtime. importModule() must evaluate each request as a fresh root ESM module
and use the target's native static-import resolution. Its signal provides
logical cancellation; runtimes with stronger interruption primitives can apply
them there. createConsole() must call emit with text plus stdout or
stderr.
Node.js 24 driver authors can reuse code-mode's version-matched declarations and guest execution semantics without adopting the built-in Host Node process lifecycle:
import {
assertNode24Version,
createNode24BootstrapSource,
loadNode24TypeDefinitionFiles,
} from "@torkbot/code-mode/node-runtime";
const driver: RuntimeDriver<MyNodeOptions> = {
description: "My Node.js 24 runtime",
loadTypeDefinitionFiles: loadNode24TypeDefinitionFiles,
async connect(options, { runnerSource, signal }) {
const version = await readRuntimeNodeVersion(options, signal);
assertNode24Version(version, "My Node runtime");
const bootstrapSource = createNode24BootstrapSource({
runnerSource,
channelFileDescriptor: 3,
});
return launchRuntimeNode(options, bootstrapSource, signal);
},
};The generated self-contained ESM attaches to the supplied full-duplex file
descriptor, multiplexes executions, evaluates fresh root modules with native
Node resolution from process.cwd(), and constructs the captured console with
node:console. The driver still owns how the source reaches Node, how the file
descriptor is connected, ambient stdio, boot cancellation, and process
lifecycle. This keeps Host Node, Sandbox Node, and other Node substrates aligned
without importing each other's launch mechanics.
import { createHostNodeRuntime } from "@torkbot/code-mode/host-node";
const runtime = await createHostNodeRuntime(
{ nodePath: process.execPath, cwd: process.cwd() },
bootSignal,
);The driver requires Node.js 24. It starts one long-lived child process per
runtime, connects its runner over fd 3, and multiplexes executions. Programs use
native Node ESM resolution rooted at cwd, including ordinary ESM/CJS package
interop. The child constructs the captured console with node:console; its
ambient stdout, stderr, and global console are not program-output channels.
Host Node is not a sandbox. Use it for trusted programs and conformance tests.
Sandbox vendors and @torkbot/code-mode-sandbox should implement their own
driver while reusing the standard factory and the Node runtime authoring
surface where applicable.
Node retains each unique root module record in its module map until the runtime process is disposed. Imported dependencies may also remain cached. Code mode does not pretend to evict native module records or add process recycling policy; runtime owners should choose a lifecycle appropriate to their workload. To observe the retained-memory profile locally:
npm run benchmark:host-node-memoryThe benchmark reports samples and does not impose a universal pass/fail limit.
Every driver should run the exported black-box suite:
import { testRuntime } from "@torkbot/code-mode/testing";
testRuntime({
name: "my runtime",
createRuntime(signal) {
return createMyRuntime(requiredOptions, signal);
},
});The suite creates and disposes arbitrary runtime instances. It exercises both
the public Runtime interface and the public client journey without observing a
driver, connection, process, or wire message.
The complete program contract replaces the tool-only primitive:
- replace
ToolboxwithProgramContractand let the constructor infer its input form; - replace
createToolbox(tools)withcreateProgramContract({ tools }); - add
inputSchemaonly for a genuinely input-bearing program contract; - replace
ToolSchemawithCodeModeSchema; - pass
contract, nottoolbox, tocreateClient(); - for input-bearing contracts, supply required
inputtoclient.run()and read requiredinputinAgentProgramScope; - for inputless contracts, do not pass
input; it is absent fromAgentProgramScope; - runtime authors must preserve whether
RuntimeExecuteRequest.inputis present when invoking the default export, without wrapping or rewriting submitted source.