Skip to main content

What “writing a function” means

A worker contributes capabilities to an iii system by registering functions. Each function has an id of the form service::name, a handler that receives the payload and returns a result, and optional JSON Schemas that describe the request and response shape. The function_id is the contract. Callers invoke the function, and observers run a target on each call; see Using iii / Functions.

Register a function

Inside the worker, register the function with the SDK. The id is what callers and observers use as a function_id.
A function that is an observer’s target receives what the observed function’s documentation describes. For example, a target of cron::on-tick receives the tick as its payload’s input, and a target of http::on-request receives an HTTP request with body, headers, and other properties. Each worker’s page at workers.iii.dev lists its functions, their input, and their args.

Attach request and response schemas

Attach JSON Schemas to the registration so the request and response shape are documented alongside the function. The schemas are stored with the function and surface in the iii console, iii call <function_id> --help, and elsewhere.
Runtime validation is not yet supported. Attached schemas are metadata only; the engine does not enforce a specific schema, reject payloads, nor handler return values that don’t match the schemas. Treat the schemas as contract documentation for function invocations, agents, and the console.

Attach metadata

metadata is an arbitrary JSON object stored with the function at registration. The engine never interprets it, it’s stored with the function so other parts of the system can act on it. It surfaces in engine::functions::info, the iii console, and is commonly used with iii-worker-manager / Role based access control. Because the shape is entirely yours, metadata is a facility that can enable and enhance other functionality. For example, worker-manager RBAC allowlists can expose functions by a metadata: selector, so tagging a function { public: true } or { tier: "premium" } lets you gate access on the fields you define.
All access should be gated system-side. You should never rely on an untrusted worker to define what they can or can’t have access to.

Receive per-invocation metadata

Each function can also receive optional per-invocation metadata: any JSON value. A caller sets it on the call, and an observer sets it once for every activation of its target, to give the receiving function execution context (see Policy and metadata). A call through an observer takes no metadata of its own: its target receives the observer’s. null is the same as no metadata. A call that carries no metadata reaches the handler with none, whether it runs in the same worker or goes through the engine.
Registration metadata is a different field from per-invocation metadata. It is set once on the registration and the engine stores it with the function; the per-invocation value travels on each call and reaches the handler.

HTTP-invoked functions

A worker can register a function that the engine invokes over HTTP. The registration carries an HttpInvocationConfig instead of a handler: the engine makes the HTTP call whenever the function is invoked, and the worker declares only the endpoint. This is the mechanism that surfaces an existing API gateway, webhook, serverless platform (Lambda, Azure Functions, Google Cloud Functions), or third-party API as a regular iii function. iii-http-functions is one worker that uses this mechanism; its Worker Docs describe its configuration and security controls.

HttpInvocationConfig fields

A handler function takes an id and a handler. An HTTP-invoked function takes an id and an HttpInvocationConfig, and no handler runs in the worker.
Auth fields (token_key, secret_key, value_key) are specified as the names of environment variables, not the secrets themselves. The engine resolves them from its own process environment at registration time, so secrets stay on the engine host and never travel over the SDK WebSocket.

HTTP error handling

The engine sends the invocation payload as the JSON request body. It treats every response with a status below 400 as a success, and every other response, or a network error, as an invocation failure that propagates back to the caller. A 1xx or 3xx response is a success with no function result. HTTP-invoked functions appear in engine::functions::list and are discoverable through the console exactly like in-process handlers.

Return values and errors

A handler returns a value that matches its documented response schema, or an error. An error raised in the handler reaches the caller as an invocation error, with the handler’s error.stack attached in Node. The engine does not swallow it. Return a structured value for an expected failure, and throw for an unexpected one.

Observers of a function

Every registered function is observable unless the registration sets observable: false, which makes the engine refuse every observer with NOT_OBSERVABLE; the function stays callable. private: true allows calls only from the connection that hosts the function, and other workers can still observe it. args_format is the JSON Schema of the args an observer passes. See Observable functions for the hooks that run around observation.

Unregister a function

registerFunction returns a handle with an unregister() method that removes the function from the engine at runtime. When the worker disconnects, all of its functions are removed automatically and pending invocations error out.