iii-hq/iii

iii-core-primitives

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…

View source

How to use

  1. Copy the installation command and review the source package before installing it in your own agent environment.
  2. Check the source document for required tools, credentials and supported hosts.
  3. Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/iii-hq/iii --skill iii-core-primitives

Before you start

  • Installation does not include a model subscription, API allowance or permission to access your files.
  • Repository stars and installation counts describe activity, not an independent quality or security test.
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

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.

ShapeUse 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:

TaskUse
Create your own workerWrite SDK code that calls registerWorker, registerFunction, and registerTrigger or observe
Add an existing capabilityBrowse https://workers.iii.dev/, then call compose::add worker=<name>
Pin a worker versioncompose::add worker=<name>@<version>
Declare a local workerAdd worker: path://./workers/my-worker under containers:
Reproduce a projectCommit 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:

yaml
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:

FunctionReturns
engine::workers::listConnected workers and metrics
engine::functions::listRegistered functions
engine::triggers::listRegistered triggers
engine::trigger-types::listAdvertised trigger types and schemas
engine::observers::listObservers 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 typeRegistration configHandler 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

ModeShapeUse when
Synctrigger({ function_id, payload })The caller needs the result
VoidTriggerAction.Void()Optional side effect, no result needed
EnqueueTriggerAction.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

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

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

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

  • 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 HttpInvocationConfig for 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) and observe (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.
from this repository

More skills

All skills