Custom Host Functions
Expose trusted host functions to guest programs in agentOS VMs as typed CLI commands and JavaScript globals.
Host functions expose JavaScript functions from your process to code running inside the VM. They run as in-process callbacks, so they are available in the library only, not on hosted VMs. Each function is defined with a Zod input schema and becomes an auto-generated CLI command at /bin/agentos-{name}, plus an async function that inline JavaScript can call directly.
Getting started
Pass AgentOs.create({ hostFunctions }) a record of collections. The keys name everything: the collection key becomes the CLI binary agentos-{name} and a guest global, and each function key becomes a subcommand. A function needs only an inputSchema and an execute handler. .describe() on the schema becomes the command’s help text.
import { AgentOs } from "@rivet-dev/agentos-core";
import { z } from "zod";
const vm = await AgentOs.create({
hostFunctions: {
weather: {
get: {
inputSchema: z
.object({
city: z.string().describe("City name (e.g. 'London')."),
})
.describe("Get the current weather for a city."),
execute: async ({ city }) => ({
city,
temperature: 18,
conditions: "partly cloudy",
humidity: 65,
}),
examples: [
{ description: "Get London weather", input: { city: "London" } },
],
},
},
calc: {
add: {
inputSchema: z
.object({ a: z.number(), b: z.number() })
.describe("Add two numbers."),
execute: ({ a, b }) => ({ result: a + b }),
},
},
},
permissions: {
fs: "allow",
network: "allow",
childProcess: "allow",
env: "allow",
hostFunction: "allow",
},
});
try {
const weather = await vm.process.exec("agentos-weather get --city London", {
output: { capture: "all" },
});
console.log("Weather:", (weather.stdout ?? "").trim());
const sum = await vm.process.exec("agentos-calc add --a 10 --b 32", {
output: { capture: "all" },
});
console.log("Sum:", (sum.stdout ?? "").trim());
} finally {
await vm.dispose();
}
Each host function can override its timeout in milliseconds. The default is 30 seconds and can be changed with limits.hostFunctions.defaultTimeoutMs.
Zod to CLI mapping
Zod schema fields are converted to CLI flags automatically. Field names are converted from camelCase to kebab-case.
| Zod type | CLI syntax | Example |
|---|---|---|
z.string() | --name value | --path /tmp/out.png |
z.number() | --name 42 | --limit 5 |
z.boolean() | --flag / --no-flag | --full-page |
z.enum(["a","b"]) | --name a | --format json |
z.array(z.string()) | --name a --name b | --tags foo --tags bar |
Optional fields (via .optional()) become optional flags. Required fields are enforced at validation time. Use .describe() on Zod fields to generate useful --help output, and .describe() on the whole schema to describe the function itself.
Calling host functions from the shell
Guest programs call host functions as shell commands:
# List all available host-function collections
agentos list-host-functions
# List the host functions in a specific collection
agentos list-host-functions weather
# Get help for a host function
agentos-weather forecast --help
# Call a host function with flags
agentos-weather forecast --city Paris --days 3
# Call a host function with inline JSON
agentos-weather forecast --json '{"city":"Paris","days":3}'
# Call a host function with JSON from a file
agentos-weather forecast --json-file /tmp/input.json
On success, the host function exits 0 and writes a JSON envelope to stdout:
{"ok":true,"result":{"temperature":22,"condition":"sunny"}}
On failure (validation or execution error), the host function exits non-zero and writes the error message to stderr:
Missing required flag: --city
Call host functions from JavaScript
Inline JavaScript and TypeScript get each collection as a frozen global, and each
host function as an async function, so generated code calls your tools like any
other API. Keys written in camelCase become kebab-case commands and camelCase guest
identifiers, so orderStore.listOrders is agentos-order-store list-orders on
the CLI and orderStore.listOrders(input) in guest JavaScript.
// Each collection is a global inside the VM, and each host function is an
// async function, so generated code calls your tools like any other API.
const llmGeneratedExpression = `(async () => {
const [sf, tokyo] = await Promise.all([
tools.weather({ city: "San Francisco" }),
tools.weather({ city: "Tokyo" }),
]);
return {
sanFrancisco: sf,
tokyo,
differenceF: Math.abs(sf.tempF - tokyo.tempF),
};
})()`;
The function resolves to the host function’s result and rejects on a schema
violation, a thrown error, or a timeout. A collection whose name is already a
global, such as process, is not defined, and a warning is written to stderr.
The hostFunction permission scope applies exactly as it does to the commands.
See Bash, Node.js, and Python for calling host functions from each language.
Security
Host function calls from guest code invoke your execute() functions on the host. Your functions run with full access to the host environment, so you can call databases, APIs, and services directly without proxying credentials into the VM. Guest code never sees the credentials, only the function’s input and output contract.
Because handlers run with full host access, do not expose functions that could compromise the host without appropriate safeguards. Use the hostFunction permission scope to restrict which collections guest code may call.
Embedded API
Host functions are passed to AgentOs.create() and their handlers run in the
embedding process.
import { AgentOs } from "@rivet-dev/agentos-core";
import { z } from "zod";
// Pass host functions to AgentOs.create(). `execute` runs in this host process,
// with its input typed by its own schema.
const vm = await AgentOs.create({
hostFunctions: {
weather: {
forecast: {
inputSchema: z
.object({ city: z.string().describe("City name") })
.describe("Get the weather forecast for a city"),
execute: async ({ city }) => ({ city, temperature: 22 }),
},
},
},
});
// The agent calls it as `agentos-weather forecast --city Paris`.
const result = await vm.process.exec("agentos-weather forecast --city Paris");
console.log(result.stdout);
await vm.dispose();
Read more in the embedded API quickstart.