Use when registering iii functions, binding triggers, observing functions, selecting sync/void/enqueue invocation, creating workers, inspecting the live worker registry, installing registry workers, authoring event sources, moving channel data, or adapting external HTTP functions across TypeScript, Python, and Rust.
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
iii has three top-level primitives:
orders::validateSince 0.25 every function is also observable: iii.observe(source, target) invokes target
on each invocation of source that passes its filters. Observers replace trigger bindings over
time; the built-in providers (http, cron, queue, state, stream, pub/sub, database) still
use trigger types in 0.25, so keep registerTrigger for them. See iii-observe.
Use :: in function IDs, leading slashes in HTTP api_path, and expression for cron config.
Register local handlers when you control the implementation. Register HTTP-invoked functions when iii should call an existing external endpoint.
| Shape | Use for |
|---|---|
registerFunction(id, handler, options?) | Local worker code |
registerFunction(id, HttpInvocationConfig, options?) | Existing HTTP services |
registerTrigger({ type, function_id, config, metadata? }) | Binding a built-in event source (deprecated in 0.25, still required for providers that have not migrated) |
observe({ function_id, args?, filters? }, target, options?) | Running target after another function's invocations, or on an engine event (iii-observe) |
trigger({ function_id, payload, action?, timeout? }) | Calling any function by ID |
Functions and triggers can carry metadata for ownership, discovery, and generated skills. Do not put secrets in metadata.
A worker is any process that connects to the engine and registers functions or trigger types. There are two common paths:
| Task | Use |
|---|---|
| Create your own worker | Write SDK code that calls registerWorker, registerFunction, and registerTrigger or observe |
| Add an existing capability | Browse https://workers.iii.dev/, then call compose::add worker=<name> |
| Pin a worker version | compose::add worker=<name>@<version> |
| Declare a local worker | Add worker: path://./workers/my-worker under containers: |
| Reproduce a project | Commit the exact versions in worker-compose.yaml |
The public worker registry at workers.iii.dev is for installable workers such as HTTP, state,
queue, pub/sub, cron, database, shell, console, rbac-proxy, and other capability workers.
(iii-sandbox and iii-observability are engine-owned and are not added through the registry.) Those workers may ship their own function-level skills; do not duplicate every capability
as a top-level iii skill.
Use iii.worker.yaml when iii should start a local worker project:
name: math-worker
runtime:
kind: python
package_manager: pip
entry: math_worker.py
scripts:
install: "pip install -r requirements.txt"
start: "python math_worker.py"The manifest describes how to start the process. Once running, the WebSocket connection and function registrations are what make the worker part of iii.
The engine keeps a live registry of connected workers, registered functions, triggers, and trigger types. Read it through the built-in discovery functions:
| Function | Returns |
|---|---|
engine::workers::list | Connected workers and metrics |
engine::functions::list | Registered functions |
engine::triggers::list | Registered triggers |
engine::trigger-types::list | Advertised trigger types and schemas |
engine::observers::list | Observers you may see (function_id, namespace, worker filters) |
For topology changes, observe the engine-hosted sources engine::on-workers-changed or
engine::on-functions-changed with deliver: 'input' (see iii-observe). The older
engine::workers-available and engine::functions-available trigger types still fire.
The workers behind these types have not moved to source functions in 0.25, so bind them with
registerTrigger. Each call logs a one-time deprecation warning per process; it is informational
and the binding works as before. For log, the engine also hosts the observability::on-log
source (iii-observe).
| Trigger type | Registration config | Handler payload |
|---|---|---|
http | { api_path: "/orders/:id", http_method: "POST" } | { query_params, path_params, headers, path, method, body } |
cron | { expression: "0 0 9 * * * *" } | { trigger, job_id, scheduled_time, actual_time } |
durable:subscriber | { topic: "payments" } | The queued message payload |
subscribe | { topic: "orders.created" } | The published event payload |
state | { scope: "orders", key?: "order-123" } | { event_type, scope, key, old_value, new_value } |
stream | { stream_name, group_id, item_id? } | Stream event details |
log | { level: "warn" } | OpenTelemetry-style log data |
Add condition_function_id to built-in trigger config when the handler should only run if a boolean
condition function returns true. With observe, a condition you can state over the event becomes a
declarative filter; a condition that needs code stays on registerTrigger or moves into the target.
| Mode | Shape | Use when |
|---|---|---|
| Sync | trigger({ function_id, payload }) | The caller needs the result |
| Void | TriggerAction.Void() | Optional side effect, no result needed |
| Enqueue | TriggerAction.Enqueue({ queue }) | Reliable async work with queue policy |
Use enqueue for work that must complete with retries. Use void for analytics, notifications, and other non-critical side effects.
Every worker registers in a namespace: the namespace init option, else III_NAMESPACE, else the
engine's default. Compose sets III_NAMESPACE to its daemon's namespace for every worker it
starts, so all workers in one worker-compose.yaml share a namespace and call each other by bare
function id. Engine-owned functions (engine::*, configuration::*, stream::*) live in default;
engine::* resolves there implicitly, the others need namespace: "default" on the call from a
namespaced worker. Routing is strict and ids are never prefixed with a namespace. From the CLI,
iii trigger -n <namespace> <function> selects it. Details and per-language examples are in
iii-sdk-reference.
import { registerWorker, TriggerAction } from "iii-sdk";
const iii = registerWorker("ws://localhost:49134", { workerName: "orders-worker" });
iii.registerFunction("orders::validate", async (order) => {
if (!order.id) throw new Error("missing order id");
return { ...order, valid: true };
});
iii.registerFunction("orders::process", async (order) => {
const validated = await iii.trigger({ function_id: "orders::validate", payload: order });
await iii.trigger({
function_id: "orders::charge",
payload: validated,
action: TriggerAction.Enqueue({ queue: "payments" }),
});
return { accepted: true, orderId: validated.id };
});
iii.registerTrigger({
type: "http",
function_id: "orders::process",
config: { api_path: "/orders", http_method: "POST" },
});from iii import register_worker
iii = register_worker("ws://localhost:49134")
def validate(order):
if not order.get("id"):
raise ValueError("missing order id")
return {**order, "valid": True}
def process(order):
validated = iii.trigger({"function_id": "orders::validate", "payload": order})
iii.trigger({
"function_id": "orders::charge",
"payload": validated,
"action": {"type": "enqueue", "queue": "payments"},
})
return {"accepted": True, "orderId": validated["id"]}
iii.register_function("orders::validate", validate)
iii.register_function("orders::process", process)
iii.register_trigger({
"type": "http",
"function_id": "orders::process",
"config": {"api_path": "/orders", "http_method": "POST"},
})use iii_sdk::{register_worker, Error, InitOptions, RegisterFunction, TriggerAction};
use iii_sdk::protocol::{RegisterTriggerInput, TriggerRequest};
use serde_json::{json, Value};
let iii = register_worker("ws://127.0.0.1:49134", InitOptions::default());
iii.register_function("orders::validate", RegisterFunction::new(|order: Value| -> Result<Value, Error> {
if order["id"].is_null() {
return Err("missing order id".into());
}
Ok(json!({ "valid": true, "order": order }))
}));
let process_client = iii.clone();
iii.register_function("orders::process", RegisterFunction::new_async(move |order: Value| {
let iii = process_client.clone();
async move {
let validated = iii.trigger(TriggerRequest::new("orders::validate", order)).await?;
iii.trigger(TriggerRequest {
function_id: "orders::charge".into(),
payload: validated.clone(),
action: Some(TriggerAction::Enqueue { queue: "payments".into() }),
timeout_ms: None,
}).await?;
Ok::<_, Error>(json!({ "accepted": true, "order": validated }))
}
}));
// Deprecated since 0.25 (the http worker still provides a trigger type): compiles with a warning.
#[allow(deprecated)]
iii.register_trigger(RegisterTriggerInput::new(
"http",
"orders::process",
json!({ "api_path": "/orders", "http_method": "POST" }),
))?;observe descriptor
(delivery: 'owner', args_format, onObserverAdded / onObserverRemoved) and hand events to
observers with obs.emit or obs.call (see iii-observe). registerTriggerType({ id, description }, handler) is deprecated: it still works and logs one warning per process. Keep
listener setup in registerTrigger and cleanup in unregisterTrigger if you maintain one.createChannel() for binary or streaming data that should not be serialized into
JSON payloads. Pass readerRef or writerRef through a function payload.HttpInvocationConfig for legacy APIs, third-party endpoints, or
immutable services. Use environment variable names for auth fields, not raw secrets.schemars::JsonSchema; Python can use
type hints or Pydantic; Node can pass JSON Schema manually.registerTrigger (built-in providers in 0.25) and observe (any
function, engine events, migrated providers).iii.observe, filters, observable functions, local dispatch, and migrating off
registerTrigger / registerTriggerType, use iii-observe.rbac-proxy), and deployment config,
use iii-engine-config.iii-sdk-reference.iii-architecture-patterns.iii-error-handling.iii-hq/iii
618a6fb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.