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
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 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.
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 |
| 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.
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 |
For topology changes, bind triggers to engine::workers-available or
engine::functions-available.
Built-In Trigger Shapes
| 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.
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.
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, 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,
})?;
Advanced Primitive Patterns
- Custom triggers: use
registerTriggerType({ id, description }, handler) when the event source is
not built in. Keep listener setup in registerTrigger and cleanup in unregisterTrigger.
- 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 this when a task spans TypeScript, Python, or Rust examples for the same iii primitive.
Boundaries
- For engine ports, adapters, queue retry policy, worker manager, RBAC listeners, 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.
1---2name: iii-core-primitives3description: 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.4---5
6# Core Primitives
7
8iii has three top-level primitives:
9
10- **Function**: a named unit of work such as `orders::validate`
11- **Trigger**: an event source bound to a function
12- **Worker**: a process that connects to the engine and executes functions
13
14Use `::` in function IDs, leading slashes in HTTP `api_path`, and `expression` for cron config.
15
16## Function Registration
17
18Register local handlers when you control the implementation. Register HTTP-invoked functions when
19iii should call an existing external endpoint.
20
21| Shape | Use for |
22| --- | --- |
23| `registerFunction(id, handler, options?)` | Local worker code |
24| `registerFunction(id, HttpInvocationConfig, options?)` | Existing HTTP services |
25| `registerTrigger({ type, function_id, config, metadata? })` | Binding an event source |
26| `trigger({ function_id, payload, action?, timeout? })` | Calling any function by ID |
27
28Functions and triggers can carry metadata for ownership, discovery, and generated skills. Do not put
29secrets in metadata.
30
31## Workers and Registry
32
33A worker is any process that connects to the engine and registers functions or trigger types. There
34are two common paths:
35
36| Task | Use |
37| --- | --- |
38| Create your own worker | Write SDK code that calls `registerWorker`, `registerFunction`, and `registerTrigger` |
39| Add an existing capability | Browse `https://workers.iii.dev/`, then run `iii worker add <name>` |
40| Pin a worker version | `iii worker add <name>@<version>` |
41| Add an OCI worker | `iii worker add ghcr.io/org/worker:tag` |
42| Add a local worker during development | `iii worker add ./workers/my-worker` |
43| Replay installed workers | Commit `iii.lock`, then run `iii worker sync` |
44
45The public worker registry at `workers.iii.dev` is for installable workers such as HTTP, state,
46queue, pub/sub, cron, observability, sandbox, database, shell, console, and other capability
47workers. Those workers may ship their own function-level skills; do not duplicate every capability
48as a top-level iii skill.
49
50### Worker Manifest
51
52Use `iii.worker.yaml` when iii should start a local worker project:
53
54```yaml
55name: math-worker
56runtime:
57 kind: python
58 package_manager: pip
59 entry: math_worker.py
60scripts:
61 install: "pip install -r requirements.txt"
62 start: "python math_worker.py"
63```
64
65The manifest describes how to start the process. Once running, the WebSocket connection and function
66registrations are what make the worker part of iii.
67
68### Live Engine Registry
69
70The engine keeps a live registry of connected workers, registered functions, triggers, and trigger
71types. Read it through the built-in discovery functions:
72
73| Function | Returns |
74| --- | --- |
75| `engine::workers::list` | Connected workers and metrics |
76| `engine::functions::list` | Registered functions |
77| `engine::triggers::list` | Registered triggers |
78| `engine::trigger-types::list` | Advertised trigger types and schemas |
79
80For topology changes, bind triggers to `engine::workers-available` or
81`engine::functions-available`.
82
83## Built-In Trigger Shapes
84
85| Trigger type | Registration config | Handler payload |
86| --- | --- | --- |
87| `http` | `{ api_path: "/orders/:id", http_method: "POST" }` | `{ query_params, path_params, headers, path, method, body }` |
88| `cron` | `{ expression: "0 0 9 * * * *" }` | `{ trigger, job_id, scheduled_time, actual_time }` |
89| `durable:subscriber` | `{ topic: "payments" }` | The queued message payload |
90| `subscribe` | `{ topic: "orders.created" }` | The published event payload |
91| `state` | `{ scope: "orders", key?: "order-123" }` | `{ event_type, scope, key, old_value, new_value }` |
92| `stream` | `{ stream_name, group_id, item_id? }` | Stream event details |
93| `log` | `{ level: "warn" }` | OpenTelemetry-style log data |
94
95Add `condition_function_id` to built-in trigger config when the handler should only run if a boolean
96condition function returns `true`.
97
98## Invocation Modes
99
100| Mode | Shape | Use when |
101| --- | --- | --- |
102| Sync | `trigger({ function_id, payload })` | The caller needs the result |
103| Void | `TriggerAction.Void()` | Optional side effect, no result needed |
104| Enqueue | `TriggerAction.Enqueue({ queue })` | Reliable async work with queue policy |
105
106Use enqueue for work that must complete with retries. Use void for analytics, notifications, and
107other non-critical side effects.
108
109## Code Examples
110
111### TypeScript
112
113```typescript
114import { registerWorker, TriggerAction } from "iii-sdk";
115
116const iii = registerWorker("ws://localhost:49134", { workerName: "orders-worker" });
117
118iii.registerFunction("orders::validate", async (order) => {
119 if (!order.id) throw new Error("missing order id");
120 return { ...order, valid: true };
121});
122
123iii.registerFunction("orders::process", async (order) => {
124 const validated = await iii.trigger({ function_id: "orders::validate", payload: order });
125 await iii.trigger({
126 function_id: "orders::charge",
127 payload: validated,
128 action: TriggerAction.Enqueue({ queue: "payments" }),
129 });
130 return { accepted: true, orderId: validated.id };
131});
132
133iii.registerTrigger({
134 type: "http",
135 function_id: "orders::process",
136 config: { api_path: "/orders", http_method: "POST" },
137});
138```
139
140### Python
141
142```python
143from iii import register_worker
144
145iii = register_worker("ws://localhost:49134")
146
147def validate(order):
148 if not order.get("id"):
149 raise ValueError("missing order id")
150 return {**order, "valid": True}
151
152def process(order):
153 validated = iii.trigger({"function_id": "orders::validate", "payload": order})
154 iii.trigger({
155 "function_id": "orders::charge",
156 "payload": validated,
157 "action": {"type": "enqueue", "queue": "payments"},
158 })
159 return {"accepted": True, "orderId": validated["id"]}
160
161iii.register_function("orders::validate", validate)
162iii.register_function("orders::process", process)
163iii.register_trigger({
164 "type": "http",
165 "function_id": "orders::process",
166 "config": {"api_path": "/orders", "http_method": "POST"},
167})
168```
169
170### Rust
171
172```rust
173use iii_sdk::{register_worker, InitOptions, RegisterFunction, TriggerAction};
174use iii_sdk::protocol::{RegisterTriggerInput, TriggerRequest};
175use serde_json::json;
176
177let iii = register_worker("ws://127.0.0.1:49134", InitOptions::default());
178
179iii.register_function(RegisterFunction::new("orders::validate", |order: serde_json::Value| {
180 if order["id"].is_null() {
181 return Err("missing order id".into());
182 }
183 Ok(json!({ "valid": true, "order": order }))
184}))?;
185
186let process_client = iii.clone();
187iii.register_function(RegisterFunction::new_async("orders::process", move |order: serde_json::Value| {
188 let iii = process_client.clone();
189 async move {
190 let validated = iii.trigger(TriggerRequest::new("orders::validate", order)).await?;
191 iii.trigger(TriggerRequest {
192 function_id: "orders::charge".into(),
193 payload: validated.clone(),
194 action: Some(TriggerAction::Enqueue { queue: "payments".into() }),
195 timeout_ms: None,
196 }).await?;
197 Ok(json!({ "accepted": true, "order": validated }))
198 }
199}))?;
200
201iii.register_trigger(RegisterTriggerInput {
202 trigger_type: "http".into(),
203 function_id: "orders::process".into(),
204 config: json!({ "api_path": "/orders", "http_method": "POST" }),
205 metadata: None,
206})?;
207```
208
209## Advanced Primitive Patterns
210
211- **Custom triggers**: use `registerTriggerType({ id, description }, handler)` when the event source is
212 not built in. Keep listener setup in `registerTrigger` and cleanup in `unregisterTrigger`.
213- **Channels**: use `createChannel()` for binary or streaming data that should not be serialized into
214 JSON payloads. Pass `readerRef` or `writerRef` through a function payload.
215- **HTTP-invoked functions**: use `HttpInvocationConfig` for legacy APIs, third-party endpoints, or
216 immutable services. Use environment variable names for auth fields, not raw secrets.
217- **Schemas**: Rust can derive request/response schemas with `schemars::JsonSchema`; Python can use
218 type hints or Pydantic; Node can pass JSON Schema manually.
219
220## When to Use
221
222- Use this skill for function registration, trigger binding, trigger payload shapes, invocation mode
223 decisions, worker creation, worker registry access, trigger conditions, custom trigger types,
224 channels, and HTTP-invoked functions.
225- Use this when a task spans TypeScript, Python, or Rust examples for the same iii primitive.
226
227## Boundaries
228
229- For engine ports, adapters, queue retry policy, worker manager, RBAC listeners, and deployment
230 config, use `iii-engine-config`.
231- For SDK-specific package exports and language caveats, use `iii-sdk-reference`.
232- For complete backend designs such as workflows, CQRS, agentic systems, and reactive apps, use
233 `iii-architecture-patterns`.
234- For failed invocations, timeouts, RBAC denials, and retryability, use `iii-error-handling`.
235- Worker-backed capability details live with the worker docs, not as top-level iii skills.