Skip to main content
Extensions

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 typeCLI syntaxExample
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.