Skip to main content

Runtime API

The Runtime API provides the execution engine for Prism programs, handling AST interpretation, value management, confidence tracking, and context management.

Overview​

The runtime system includes:

  • AST interpretation and execution
  • Value types with confidence tracking
  • Environment management for variable scoping
  • Context management for uncertainty handling
  • Built-in functions and LLM integration
  • Error handling with detailed error messages

Interpreter Class​

Constructor​

class Interpreter {
constructor()
}

Creates a new interpreter instance with an empty environment and context manager.

Methods​

interpret()​

async interpret(node: ASTNode): Promise<Value>

Interprets an AST node and returns the resulting value.

Parameters:

  • node: The AST node to interpret

Returns: A Promise resolving to the execution result

Example:

import { parse, Interpreter } from '@prism-lang/core';

const interpreter = new Interpreter();
const ast = parse('x = 10; y = x * 2;');
const result = await interpreter.interpret(ast);
console.log(result.toString()); // "20"

registerLLMProvider()​

registerLLMProvider(name: string, provider: LLMProvider): void

Registers an LLM provider for use in llm() function calls.

Parameters:

  • name: Unique identifier for the provider
  • provider: LLMProvider implementation

setDefaultLLMProvider()​

setDefaultLLMProvider(name: string): void

Sets the default LLM provider for llm() calls.

Parameters:

  • name: Name of a registered provider

Example:

import { MockLLMProvider } from '@prism-lang/llm';

const interpreter = new Interpreter();
const mockProvider = new MockLLMProvider();

interpreter.registerLLMProvider('mock', mockProvider);
interpreter.setDefaultLLMProvider('mock');

Runtime Class​

While the Interpreter gives you low-level control, most applications use the higher-level Runtime, which wires an interpreter together with the module system, built-ins, and helpers for cache management.

createRuntime​

function createRuntime(options?: RuntimeOptions): Runtime

Creates a fully configured runtime.

  • options.moduleSystem (optional): Provide a preconfigured ModuleSystem (custom file readers, virtual files, etc.). When omitted, a new one is created automatically.
  • options.confidence (optional): Configure confidence combination rules and provenance tracking.
interface RuntimeOptions {
moduleSystem?: ModuleSystem;
confidence?: {
strategy?: {
arithmetic?: 'min' | 'max' | 'product' | 'average';
comparison?: 'min' | 'max' | 'product' | 'average';
logicalAnd?: 'min' | 'max' | 'product' | 'average';
logicalOr?: 'min' | 'max' | 'product' | 'average';
chain?: 'min' | 'max' | 'product' | 'average';
ternary?: 'min' | 'max' | 'product' | 'average';
functionCall?: 'min' | 'max' | 'product' | 'average';
parallel?: 'min' | 'max';
coalesceThreshold?: number;
thresholdGate?: {
threshold?: number;
reduceFactor?: number;
onFail?: 'reduce' | 'returnNull' | 'returnLeft';
};
};
trackProvenance?: boolean;
};
}

Example: custom confidence strategy

import { createRuntime } from '@prism-lang/core';

const runtime = createRuntime({
confidence: {
strategy: {
arithmetic: 'min',
logicalOr: 'max',
ternary: 'product',
thresholdGate: { threshold: 0.8, reduceFactor: 0.4 }
},
trackProvenance: true
}
});

Runtime Methods​

execute()​

await runtime.execute(ast: Program): Promise<Value>

Runs a parsed program.

registerLLMProvider() / setDefaultLLMProvider()​

Proxy to the interpreter helpers; register or select an LLM provider shared across all modules.

getModuleSystem()​

const moduleSystem = runtime.getModuleSystem();

Returns the ModuleSystem instance backing the runtime. Useful if you need direct access for advanced tooling.

invalidateModule()​

runtime.invalidateModule('/path/to/module.prism', {
invalidateDependents: true // default
});

Drops a module (and, by default, all modules that depend on it) from the cache. The next import will re-execute it.

reloadModule()​

const module = await runtime.reloadModule('/shared/state.prism');

Convenience helper that invalidates and immediately reloads a module, returning the updated Module descriptor (exports, dependency graph, etc.). Dependents are automatically invalidated so subsequent imports see the new exports.

Example: hot reload loop

import { createRuntime } from '@prism-lang/core';

const runtime = createRuntime();
await runtime.getModuleSystem().loadModule('/app/main.prism', runtime);

// when a file changes:
await runtime.reloadModule('/app/theme.prism');

This reuses the same interpreter, so existing globals, registered LLM providers, and environments remain intact between reloads.

The prism run --watch command uses this helper internally to refresh the edited module and all dependents without restarting the CLI.

streamLLM()​

const stream = runtime.streamLLM("Draft today's update", {
provider: 'claude',
structuredOutput: false,
temperature: 0.3,
});

for await (const chunk of stream.chunks) {
if (chunk.type === 'text' && chunk.content) {
process.stdout.write(chunk.content);
}
}

const final = await stream.response;
console.log(`\nConfidence: ${(final.confidence * 100).toFixed(1)}%`);

Returns a RuntimeLLMStream with:

  • chunks: AsyncIterable<LLMStreamChunk> of text/metadata events
  • response: Promise resolving to the final LLMResponse
  • cancel(reason?): stop streaming (best-effort for providers without native streaming)

If a provider does not implement streaming, Prism falls back to a single chunk once the request finishes. Providers that rely on structured responses should set structuredOutput: false when streaming plain text.

Value Types​

All runtime values extend the abstract Value class:

abstract class Value {
abstract type: string;
abstract value: unknown;
abstract equals(other: Value): boolean;
abstract isTruthy(): boolean;
abstract toString(): string;
}

NumberValue​

class NumberValue extends Value {
constructor(value: number)
type: 'number'
value: number
}

Represents numeric values.

Truthy: When not zero

StringValue​

class StringValue extends Value {
constructor(value: string)
type: 'string'
value: string
}

Represents string values.

Truthy: When not empty

BooleanValue​

class BooleanValue extends Value {
constructor(value: boolean)
type: 'boolean'
value: boolean
}

Represents boolean values.

Truthy: When true

NullValue​

class NullValue extends Value {
constructor()
type: 'null'
value: null
}

Represents null values.

Truthy: Always false

ArrayValue​

class ArrayValue extends Value {
constructor(elements: Value[])
type: 'array'
value: Value[]
elements: Value[]
}

Represents array values.

Truthy: Always true

Example:

const arr = new ArrayValue([
new NumberValue(1),
new StringValue("hello")
]);

ObjectValue​

class ObjectValue extends Value {
constructor(properties: Map<string, Value>)
type: 'object'
value: Map<string, Value>
properties: Map<string, Value>
}

Represents object values with string keys.

Truthy: Always true

Example:

const obj = new ObjectValue(new Map([
['name', new StringValue('Alice')],
['age', new NumberValue(30)]
]));

FunctionValue​

class FunctionValue extends Value {
constructor(
name: string,
value: (args: Value[]) => Promise<Value>,
arity?: number
)
type: 'function'
name: string
value: (args: Value[]) => Promise<Value>
arity?: number
}

Represents function values, including built-ins and lambdas.

Truthy: Always true

ConfidenceValue​

class ConfidenceValue extends Value {
constructor(
value: Value,
confidence: ConfidenceLib
)
type: 'confident'
value: Value
confidence: ConfidenceLib
}

Wraps any value with a confidence score.

Truthy: Based on wrapped value

Example:

const confident = new ConfidenceValue(
new StringValue("prediction"),
new ConfidenceLib(0.85)
);
console.log(confident.toString()); // "prediction (~0.85)"

Environment Management​

Environment Class​

class Environment {
constructor(parent?: Environment)
define(name: string, value: Value): void
get(name: string): Value
set(name: string, value: Value): void
getAllVariables(): Map<string, Value>
}

Manages variable scoping with lexical parent chaining.

Methods:

  • define(): Creates a new variable in current scope
  • get(): Retrieves variable value (searches parent scopes)
  • set(): Updates existing variable (searches parent scopes)
  • getAllVariables(): Returns all variables in current scope

Example:

const global = new Environment();
global.define('x', new NumberValue(10));

const local = new Environment(global);
local.define('y', new NumberValue(20));

console.log(local.get('x')); // NumberValue(10) - from parent
console.log(local.get('y')); // NumberValue(20) - from local

Error Handling​

RuntimeError​

class RuntimeError extends Error {
constructor(
message: string,
node?: ASTNode,
location?: { line: number; column: number }
)
line?: number
column?: number
node?: ASTNode
}

Thrown during runtime execution errors.

Example:

try {
await interpreter.interpret(ast);
} catch (error) {
if (error instanceof RuntimeError) {
console.error(`Runtime error at line ${error.line}: ${error.message}`);
}
}

LoopControlError​

class LoopControlError extends Error {
constructor(type: 'break' | 'continue')
type: 'break' | 'continue'
}

Internal error used for loop control flow (break/continue).

Built-in Functions​

The interpreter provides several built-in functions:

llm()​

llm(prompt: string, options?: LLMCallOptions): Promise<ConfidenceValue<string>>

Calls an LLM provider using the current runtime configuration.

Parameters:

  • prompt: The prompt string
  • options: Optional object to override provider behavior
interface LLMCallOptions {
provider?: string;
model?: string;
temperature?: number;
maxTokens?: number;
topP?: number;
timeout?: number;
structuredOutput?: boolean;
includeReasoning?: boolean;
confidenceExtractor?: (responseText: string) => Promise<{ value: number }>;
extractor?: (response: {
content: string;
confidence: number;
model: string;
tokensUsed: number;
provider: string;
prompt: string;
options: Record<string, unknown>;
metadata?: Record<string, unknown>;
}) => number | ConfidenceValue<any>;
}

confidenceExtractor is forwarded to the provider implementation (e.g. @prism-lang/llm) so it can derive a confidence score when structured output isn't available. extractor receives an object with { content, confidence, model, tokensUsed, provider, prompt, options, metadata } and lets you override the returned confidence by returning a number or confident value.

Returns: Confident string response using either the provider-reported confidence or the extractor override.

Example:

let response = llm("What is 2+2?");
// => "4" (~95%)

let custom = llm("Summarize this doc", {
provider: "claude",
model: "claude-3-sonnet",
temperature: 0.2,
extractor: info => info.confidence * 0.8
});

stream_llm()​

let handle = stream_llm("Draft a summary", { structuredOutput: false })

let chunk = await handle.next()
while (chunk) {
console.log(chunk.text)
chunk = await handle.next()
}

let final = await handle.result()

Returns a stream handle containing:

  • next(): Promise<Chunk | null> – resolves to the next LLMStreamChunk with fields such as type, text, reasoning, error.
  • result(): Promise<ConfidenceValue<string>> – resolves to the final confident string (extractor rules apply).
  • cancel(): void – aborts the stream.

If the provider doesn’t support streaming, Prism buffers the response and returns it as a single chunk once complete.

Array Functions​

map()​

map(array: Array, fn: Function): Array

Transforms array elements.

Example:

let numbers = [1, 2, 3];
let doubled = map(numbers, x => x * 2);
// Returns: [2, 4, 6]

filter()​

filter(array: Array, predicate: Function): Array

Filters array elements.

Example:

let numbers = [1, 2, 3, 4, 5];
let evens = filter(numbers, x => x % 2 == 0);
// Returns: [2, 4]

reduce()​

reduce(array: Array, reducer: Function, initial?: any): any

Reduces array to single value.

Example:

let numbers = [1, 2, 3, 4];
let sum = reduce(numbers, (acc, x) => acc + x, 0);
// Returns: 10

Math Functions​

max()​

max(...values: number[]): number

Returns maximum value.

Example:

let largest = max(10, 5, 20, 15);
// Returns: 20

min()​

min(...values: number[]): number

Returns minimum value.

Example:

let smallest = min(10, 5, 20, 15);
// Returns: 5

Async Functions​

delay()​

delay(milliseconds: number): Promise<void>

Pauses execution for the specified number of milliseconds. Returns a promise that resolves after the delay.

Example:

async function slowOperation() {
console.log("Starting...")
await delay(2000) // Wait 2 seconds
console.log("Done!")
}

// Retry with exponential backoff
async function retryWithBackoff(operation, maxRetries) {
for let i = 0; i < maxRetries; i++ {
try {
return await operation()
} catch (e) {
await delay(1000 * (2 ** i)) // 1s, 2s, 4s...
}
}
}

sleep()​

sleep(milliseconds: number): Promise<void>

Alias for delay(). Pauses execution for the specified duration.

Example:

await sleep(500)  // Same as await delay(500)

debounce()​

debounce(waitMs: number): Function

Creates a debounced wrapper that delays invoking a function until after waitMs milliseconds have elapsed since the last call. Useful for rate-limiting user input handlers.

Example:

// Create a debounced search function
let searchDebouncer = debounce(300)

// In event handler
let onSearchInput = (query) => {
searchDebouncer(() => {
let results = performSearch(query)
updateUI(results)
})
}

// Only executes 300ms after the user stops typing

Collection Functions​

sortBy()​

sortBy(key: string, direction?: "asc" | "desc"): Function

Creates a function that sorts an array of objects by the specified key. Returns a higher-order function for use in pipelines.

Parameters:

  • key: The property name to sort by
  • direction: Sort direction - "asc" (default) or "desc"

Example:

let users = [
{name: "Alice", score: 85},
{name: "Bob", score: 92},
{name: "Charlie", score: 78}
]

// Create sorter functions
let byScoreDesc = sortBy("score", "desc")
let byName = sortBy("name")

// Apply sorting
let rankedUsers = byScoreDesc(users)
// [{name: "Bob", score: 92}, {name: "Alice", score: 85}, {name: "Charlie", score: 78}]

let alphabetical = byName(users)
// [{name: "Alice", ...}, {name: "Bob", ...}, {name: "Charlie", ...}]

// Use in pipeline
let result = users |> sortBy("score", "desc")(_) |> map(u => u.name)
// ["Bob", "Alice", "Charlie"]

groupBy()​

groupBy(key: string | Function): Function

Creates a function that groups an array of objects by the specified key or function. Returns an object with keys for each group.

Parameters:

  • key: Property name to group by, or a function that returns the group key

Example:

let items = [
{name: "Apple", category: "fruit", price: 1.5},
{name: "Banana", category: "fruit", price: 0.5},
{name: "Carrot", category: "vegetable", price: 0.8},
{name: "Broccoli", category: "vegetable", price: 1.2}
]

// Group by property
let byCategory = groupBy("category")
let grouped = byCategory(items)
// {
// fruit: [{name: "Apple", ...}, {name: "Banana", ...}],
// vegetable: [{name: "Carrot", ...}, {name: "Broccoli", ...}]
// }

// Group by computed value
let byPriceRange = groupBy(item => item.price > 1 ? "expensive" : "cheap")
let priceGrouped = byPriceRange(items)
// {
// expensive: [{name: "Apple", ...}, {name: "Broccoli", ...}],
// cheap: [{name: "Banana", ...}, {name: "Carrot", ...}]
// }

// Use in pipeline
let result = items |> groupBy("category")(_)

Confidence Functions​

confidence()​

confidence(threshold: number): Function

Creates a function that wraps another function's return value with the specified confidence level. Useful for creating confidence-aware processing pipelines.

Parameters:

  • threshold: Confidence value between 0 and 1 to attach to results

Example:

// Wrap a function to add confidence to its output
let highConfidence = confidence(0.95)
let mediumConfidence = confidence(0.7)

let processWithConfidence = highConfidence(data => data * 2)
let result = processWithConfidence(10)
// 20 ~> 0.95

// Create calibrated processors
let sensorProcessor = confidence(0.85)
let userInputProcessor = confidence(0.6)

let sensorReading = sensorProcessor(() => readSensor())
let userValue = userInputProcessor(() => getUserInput())

threshold()​

threshold(minConfidence: number): Function

Creates a filter function that only passes values meeting the minimum confidence threshold. Values below the threshold are filtered out.

Parameters:

  • minConfidence: Minimum confidence value (0-1) required to pass

Example:

// Filter data by confidence level
let highConfidenceOnly = threshold(0.9)
let moderateConfidence = threshold(0.5)

let data = [
10 ~> 0.95,
20 ~> 0.7,
30 ~> 0.92,
40 ~> 0.4
]

let reliable = highConfidenceOnly(data)
// [10 ~> 0.95, 30 ~> 0.92]

let usable = moderateConfidence(data)
// [10 ~> 0.95, 20 ~> 0.7, 30 ~> 0.92]

// Use in pipeline for confident decision making
let result = predictions
|> threshold(0.8)(_)
|> map(p => p.recommendation)
|> first

Execution Features​

Confidence Propagation​

Confidence values are propagated through operations:

let x = 10 ~> 0.9;
let y = 20 ~> 0.8;
let z = x + y; // Result has confidence based on inputs

Context Management​

The runtime maintains execution contexts for uncertainty handling:

in context "analysis" {
// Code executes in analysis context
let result = processData(input);
}

Loop Control​

Supports break and continue statements:

for let i = 0; i < 10; i++ {
if (i == 5) {
break; // Exit loop
}
if (i % 2 == 0) {
continue; // Skip even numbers
}
// Process odd numbers
}

Destructuring Assignment​

Supports array and object destructuring with confidence:

// Array destructuring
let [a, b, c] = [1, 2, 3];
let [x, ...rest] = array;

// Object destructuring
let {name, age} = person;
let {x: coordX, y: coordY} = point;

// With confidence thresholds
let a = null;
let b = null;
[a, b] ~> 0.8 = uncertainArray;

Performance Considerations​

  1. Async Execution: All operations are async to support LLM calls
  2. Environment Lookup: Variable lookup is O(depth) where depth is scope nesting
  3. Confidence Tracking: Adds minimal overhead to operations
  4. Memory Management: Values are garbage collected when out of scope

Example: Custom Function Registration​

import { Interpreter, FunctionValue, NumberValue, Value } from '@prism-lang/core';

const interpreter = new Interpreter();

// Add custom sqrt function
interpreter.environment.define('sqrt', new FunctionValue(
'sqrt',
async (args: Value[]) => {
if (args.length !== 1) {
throw new RuntimeError('sqrt() requires exactly one argument');
}

const arg = args[0];
if (!(arg instanceof NumberValue)) {
throw new RuntimeError('sqrt() requires a number');
}

return new NumberValue(Math.sqrt(arg.value));
},
1 // arity
));

// Use the custom function
const result = await interpreter.interpret(parse('sqrt(16)'));
console.log(result.toString()); // "4"