Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when registering iii functions, binding triggers, selecting sync/void/enqueue invocation, creating workers, inspecting the live worker registry, installing registry workers, authoring custom triggers, moving channel data, or adapting external HTTP functions across TypeScript, Python, and Rust.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 149% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 67% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 94% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 27% | 0% |
iii has three top-level primitives:
orders::validateUse :: 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 an event source | | 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 | | Add an existing capability | Browse https://workers.iii.dev/, then run iii worker add <name> | | Pin a worker version | iii worker add <name>@<version> | | Add an OCI worker | iii worker add ghcr.io/org/worker:tag | | Add a local worker during development | iii worker add ./workers/my-worker | | Replay installed workers | Commit iii.lock, then run iii worker sync |
The public worker registry at workers.iii.dev is for installable workers such as HTTP, state, queue, pub/sub, cron, observability, sandbox, database, shell, console, and other capability workers. 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:
yamlname: 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 |
For topology changes, bind triggers to engine::workers-available or engine::functions-available.
| 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.
| 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.
typescriptimport { 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" }, });
pythonfrom 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"}, })
rustuse iii_sdk::{register_worker, InitOptions, RegisterFunction, TriggerAction}; use iii_sdk::protocol::{RegisterTriggerInput, TriggerRequest}; use serde_json::json; let iii = register_worker("ws://127.0.0.1:49134", InitOptions::default()); iii.register_function(RegisterFunction::new("orders::validate", |order: serde_json::Value| { 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(RegisterFunction::new_async("orders::process", move |order: serde_json::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(json!({ "accepted": true, "order": validated })) } }))?; iii.register_trigger(RegisterTriggerInput { trigger_type: "http".into(), function_id: "orders::process".into(), config: json!({ "api_path": "/orders", "http_method": "POST" }), metadata: None, })?;
registerTriggerType({ id, description }, handler) when the event source isnot built in. Keep listener setup in registerTrigger and cleanup in unregisterTrigger.
createChannel() for binary or streaming data that should not be serialized intoJSON payloads. Pass readerRef or writerRef through a function payload.
HttpInvocationConfig for legacy APIs, third-party endpoints, orimmutable services. Use environment variable names for auth fields, not raw secrets.
schemars::JsonSchema; Python can usetype hints or Pydantic; Node can pass JSON Schema manually.
decisions, worker creation, worker registry access, trigger conditions, custom trigger types, channels, and HTTP-invoked functions.
config, use iii-engine-config.
iii-sdk-reference.iii-architecture-patterns.
iii-error-handling.Other measured skills in the registry, with their headline benchmark lift.