Skip to content

Embedding Arrival and capabilities

The @inhuman.tools/arrival package exposes an asynchronous JavaScript entry point for running Scheme. Each top-level Scheme form produces one result, converted to a plain JavaScript value. The base roster (R7RS, SRFI, and polyglot forms) is assembled automatically; pass additional capabilities to expose host functionality.

import { exec } from "@inhuman.tools/arrival";
const [result] = await exec(`(filter (lambda (x) (> x 5)) (list 1 3 7 9 2))`);
// result: [7, 9]

exec is convenient when you need the values. For persistent definitions or tracing, use execState; a caller can supply and reuse a LexicalScope across calls:

import { execState, LexicalScope, toJS } from "@inhuman.tools/arrival";
const scope = LexicalScope.fresh("agent-session");
await execState(`(define (square x) (* x x))`, { scope });
const { values: [value] } = await execState(`(square 7)`, { scope });
toJS(value); // 49

A capability declares Scheme-visible symbols and their typed input/output contract. Define a capability, then pass it through the capabilities option:

import { exec, EnvCapability } from "@inhuman.tools/arrival";
const weather = EnvCapability.define("demo/weather", {
symbols: (symbol, z) => ({
"forecast-for": symbol.rosetta`forecast-for: the current forecast for a city`(
{ input: [z.string], output: [z.string], provenance: "source" },
async (city) => `cloudy in ${city}`,
),
}),
});
const [forecast] = await exec(
`(forecast-for "berlin")`,
{ capabilities: [weather] },
);

The contract is checked at the membrane boundary. A declared symbol also has a provenance role: source marks newly introduced external data, while a pure transform generally uses pipe to forward input lineage. source is the default role.

Capabilities are the explicit boundary for effects. Arrival does not provide ambient JavaScript globals to Scheme; host functions are available only when the host roots the corresponding capability. A capability can also declare dependencies on other capabilities rather than reaching into their internals.

Use define/overridable to declare a typed program parameter with a default. Supply the runtime value in config.params; the declared shape is validated:

import { exec } from "@inhuman.tools/arrival";
import { overridableCapability } from "@inhuman.tools/arrival/capabilities/overridable";
const [, answer] = await exec(
`(define/overridable users
(s/array (s/object (s/field/string "id") (s/field/number "priority")))
'())
(filter (lambda (u) (> (@ u :priority) 10)) users)`,
{
capabilities: [overridableCapability],
config: { params: { users: [{ id: "alice", priority: 15 }] } },
},
);
// [{ id: "alice", priority: 15 }]

If no value is supplied, the declared default is used. Invalid values are rejected with an error identifying the parameter and expected shape.

Module loading is not ambient and is not included automatically. To use (require ...), root arrivalLoaderCapability from @inhuman.tools/arrival-modules and provide either fs or a configured loader and dirname. Its built-in resolver formats are .scm, .json, .ndjson, and .txt; YAML, TOML, and Handlebars require their optional subpath capability and peer dependency.

  • arrival/packages/arrival/README.md (Quick Start; Tools; Data; Sessions; Membrane and provenance).
  • arrival/packages/arrival-modules/README.md (loader capability and optional resolvers).