Come usare
- Copia il comando e controlla il pacchetto prima di installarlo nel tuo ambiente agente.
- Controlla strumenti, credenziali e ambienti richiesti nel documento originale.
- Descrivi input e risultato desiderato; confronta l’output con le fonti.
npx skills add https://github.com/iii-hq/iii --skill iii-core-primitivesPrima di iniziare
- L’installazione non include abbonamenti, quote API o accesso ai file.
- Stelle e installazioni indicano attività, non un test indipendente di qualità o sicurezza.
Contenuto dal repository con titoli, esempi, codice, tabelle, link e immagini preservati.
Core Primitives
iii has three top-level primitives:
- Function: a named unit of work such as
orders::validate - Trigger: an event source bound to a function
- Worker: a process that connects to the engine and executes functions
Since 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.
Function Registration
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.
Workers and Registry
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.
Worker Manifest
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.
Live Engine Registry
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.
Built-In Trigger Shapes
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.
Invocation Modes
| 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.
Namespaces
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.
Code Examples
TypeScript
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" },
});Python
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"},
})Rust
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" }),
))?;Advanced Primitive Patterns
- Custom event sources: since 0.25, register a function with an
observedescriptor
(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.
- Channels: use
createChannel()for binary or streaming data that should not be serialized into
JSON payloads. Pass readerRef or writerRef through a function payload.
- HTTP-invoked functions: use
HttpInvocationConfigfor legacy APIs, third-party endpoints, or
immutable services. Use environment variable names for auth fields, not raw secrets.
- Schemas: Rust can derive request/response schemas with
schemars::JsonSchema; Python can use
type hints or Pydantic; Node can pass JSON Schema manually.
When to Use
- Use this skill for function registration, trigger binding, trigger payload shapes, invocation mode
decisions, worker creation, worker registry access, trigger conditions, custom trigger types, channels, and HTTP-invoked functions.
- Use it to decide between
registerTrigger(built-in providers in 0.25) andobserve(any
function, engine events, migrated providers).
- Use this when a task spans TypeScript, Python, or Rust examples for the same iii primitive.
Boundaries
- For
iii.observe, filters, observable functions, local dispatch, and migrating off
registerTrigger / registerTriggerType, use iii-observe.
- For engine ports, adapters, engine-owned workers, RBAC (
rbac-proxy), and deployment config,
use iii-engine-config.
- For SDK-specific package exports and language caveats, use
iii-sdk-reference. - For complete backend designs such as workflows, CQRS, agentic systems, and reactive apps, use
iii-architecture-patterns.
- For failed invocations, timeouts, RBAC denials, and retryability, use
iii-error-handling. - Worker-backed capability details live with the worker docs, not as top-level iii skills.

