Skip to main content

Crate improv_extfn

Crate improv_extfn 

Source
Expand description

External-language function runtime for Improv (Phase 6).

This crate is the analogue of the engine’s in-process scalar registry (improv_engine’s scalar_arity/scalar_func) for NON-builtin functions: it evaluates a registered external function on typed [improv_core_model::Value] arguments, deterministically and in a subprocess sandbox. The engine calls it host-side, outside the dataflow (improv_engine::external::refresh_external_measure), so external calls never run on the incremental hot path.

§Invariant (see AGENT_MASTER_STEERING.md §7)

External functions must be pure, return a typed value, and declare dimensionality, so they behave as ordinary operators and keep the engine deterministic. This runtime is deterministic given the same inputs + body; purity of the user’s function body is the user’s contract (the engine treats external calls as pure). Dimensionality is declared on the descriptor (arg_types / return_type); higher-arity dimension broadcasting is the engine’s job at the call site, not this runtime’s.

§Language runtimes

eval dispatches on f.language to a per-language runner:

  • Python / R / Julia / Pure — subprocess runners (shared plumbing in the runner module): the interpreter reads a generated program on stdin and prints one {"ok":..} / {"error":..} JSON envelope on stdout. A wall-clock timeout kills a runaway child. If the interpreter binary is absent, eval returns ExtFnError::LanguageUnavailable — it never panics.
  • Wasm — an in-process wasmi interpreter (no subprocess); numeric f64 ABI (see the wasm module).

§Sandboxing and its limits

Python is invoked as python3 -I -S - (isolated, no site, program on stdin); R as Rscript --vanilla -; Julia as julia --startup-file=no -; Pure as pure -q. In every case the program arrives on stdin, so the function body is never interpolated into a shell command line (no shell is spawned). A wall-clock timeout (default 5s) kills a runaway child.

On top of that, every subprocess runner is spawned through the sandbox module’s OS sandbox (see its docs): on Linux, a bwrap (bubblewrap) filesystem/network sandbox plus setrlimit ceilings (CPU time, address space, open files, process count). It fails closed: without bwrap (always the case on macOS and Windows) a Restricted body is refused with ExtFnError::Sandbox unless the operator sets IMPROV_EXTFN_ALLOW_UNSANDBOXED=1, which falls back to rlimits only. The Wasm runtime runs in-process via wasmi, which has no syscall access at all by construction, independent of any OS mechanism.

The host, never the model, picks the sandbox strictness: every body runs SandboxPolicy::Restricted unless the operator sets IMPROV_EXTFN_TRUSTED=1 (see sandbox::policy_for). ExternalFn::pure is model data and only affects caching.

Structs§

ExternalFn
A registered external function: everything needed to type-check a call and (via the improv_extfn runtime) invoke it deterministically.
Registry
Name -> descriptor. The engine will resolve a FuncId/name to a descriptor here for non-builtin calls.

Enums§

ExtFnError
Errors from resolving or evaluating an external function. The engine will convert these to a Value::Error at the call site (see error_value); this API returns Result so the caller decides.
Language
The runtime language an external function targets. Each variant has a runner in the improv_extfn crate (subprocess for the scripting languages, an in-process wasmi interpreter for Wasm).
SandboxPolicy
How strictly a subprocess runner should be confined.

Functions§

error_value
Convenience: build a Value::Error (used by callers mapping ExtFnError).
eval
Evaluate one descriptor on typed arguments with an explicit timeout.
value_to_json
Encode one Value as JSON for stdin.