name: iii description: | Build cross-language backends with the iii engine. Register functions in TypeScript, Python, or Rust callable from any connected service via WebSocket. Covers iii-sdk, state, KV Server, streams, triggers (HTTP/cron/queue/log), Bridge Client mesh, OpenTelemetry, Docker deployment, iii Console, and Prometheus metrics. Use when: setting up iii engine, cross-language function calls, iii-sdk integration, registerTrigger configuration, KV Server storage, queue/log triggers, Docker deploy, or debugging "ECONNREFUSED 49134", "type_not_found", "@iii-dev/sdk 404".
iii - Cross-Language Backend Engine
SDK 0.3.0-alpha Breaking Change (verified 2026-02-18): The
registerTriggerfield istrigger_type(NOTtype). HTTP triggers usetrigger_type: "api"(NOT"http"). Usingtype: "http"causes silent 404s on all routes. This was confirmed in the tac-4-go rewrite from motia to direct iii-sdk.
SDK Note: The iii.dev docs reference
@iii-dev/sdkwith aBridgeclass — this package does not exist on npm (404 as of 2026-02-17). Always installiii-sdk(no scope). The docs SDK uses different conventions (dot separators,trigger_type,function_path). Seereferences/cross-language.mdfor translation guide.
Core Concepts
iii has two fundamental primitives:
- Register - Make a function callable by the engine
- Call - Invoke any registered function regardless of language or process
Services connect to the iii engine over WebSocket. The engine handles routing, retries, observability, state, streams, cron, and HTTP API exposure.
Quick Start
1. Install the Engine
curl -fsSL https://install.iii.dev/iii/main/install.sh | sh
iii --version
Or run via Docker:
docker pull iiidev/iii:latest
docker run -p 3111:3111 -p 49134:49134 \
-v ./iii-config.yaml:/app/config.yaml:ro \
iiidev/iii:latest
2. Create Engine Config (iii-config.yaml)
modules:
- class: modules::api::RestApiModule
config:
port: 3111
host: 127.0.0.1
default_timeout: 30000
concurrency_request_limit: 1024
cors:
allowed_origins: ["*"]
allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
- class: modules::state::StateModule
config:
adapter:
class: modules::state::adapters::KvStore
config:
store_method: file_based
file_path: ./data/state_store.db
- class: modules::shell::ExecModule
config:
exec:
- bun run --enable-source-maps worker.js
See
references/engine-modules.mdfor full module configuration including StreamModule, OtelModule, QueueModule, PubSubModule, CronModule, KvServer, LoggingModule, and BridgeClientModule.
3. Start the Engine
iii -c iii-config.yaml
4. Create a TypeScript Service
mkdir my-service && cd my-service
npm init -y
npm install iii-sdk
Set "type": "module" in package.json.
// src/worker.ts
import { init, getContext } from "iii-sdk";
const { registerFunction, registerTrigger, call, callVoid } = init(
process.env.III_BRIDGE_URL ?? "ws://localhost:49134"
);
// Register a function callable by any connected service
const health = registerFunction({ id: "my-service::health" }, async () => {
const { logger } = getContext();
logger.info("Health check OK");
return { status: 200, body: { healthy: true, timestamp: Date.now() } };
});
// Expose as HTTP endpoint via the engine's REST API module
registerTrigger({
trigger_type: "api",
function_id: health.id,
config: { api_path: "health", http_method: "GET" },
});
console.log("Service started - listening for calls");
Run with: npx tsx src/worker.ts
Test: curl http://localhost:3111/health
Critical Rules
Always Do
- Use
service-name::function-nameconvention for function IDs (e.g.,"client::health","data-service::transform") - Set
"type": "module"inpackage.json(SDK uses ESM) - Use
process.env.III_BRIDGE_URL ?? "ws://localhost:49134"for the engine address - Start the iii engine before starting services
- Use
getContext()for logging inside function handlers (provides structured OTEL logging) - Use
Promise.allSettled()when calling multiple remote functions in parallel (graceful partial failure) - Use
GET /healthon port 3111 to check engine health before connecting services - Use
index(notscope) for KV Server function parameters —scopeis for the State module only
Never Do
- Never hardcode
ws://localhost:49134without env var fallback - Never use
process.envin function IDs (they must be static strings) - Never assume all services are available - handle missing services gracefully
- Never use 6-field cron expressions - iii supports 7 fields (seconds included):
"*/30 * * * * * *" - iii-sdk 0.3.0-alpha: Use
trigger_type(NOTtype) inregisterTrigger. Use"api"(NOT"http") for HTTP triggers. Use"queue"for queue triggers,"cron"for cron triggers - iii-sdk 0.2.0: Use
type(NOTtrigger_type). Use"http"for HTTP triggers - Never import from
iii-sdk/stateoriii-sdk/streamfor basic state operations - usesdk.call("state::set", ...)instead. Removed in 0.3.0 - Never
npm install @iii-dev/sdk— this scoped package does not exist on npm (404). Useiii-sdkinstead - Never use dot separators in engine function paths — use
::(double colon)."kv_server::get"not"kv_server.get"
SDK Entry Points
| Import | Purpose |
|---|---|
iii-sdk |
Core: init, getContext, withContext, Logger (+ types). init() returns ISdk with registerFunction, registerTrigger, call, callVoid, shutdown methods |
iii-sdk/state |
Advanced: Direct IState interface (get/set/delete/list/update) |
iii-sdk/stream |
Advanced: Direct IStream interface with groups |
iii-sdk/telemetry |
OpenTelemetry: initOtel, withSpan, getTracer, getMeter |
For most use cases, use the core SDK and call("state::set", ...) / call("state::get", ...) for state.
Function Registration
const fn = registerFunction(
{
id: "service::action", // Required: unique function ID
description: "What it does", // Optional: for discovery
request_format: { /* schema */ }, // Optional: input schema
response_format: { /* schema */ }, // Optional: output schema
metadata: { version: "1.0" }, // Optional: custom metadata
},
async (payload) => {
const { logger } = getContext();
logger.info("Processing", { payload });
return { result: "done" }; // Return value sent back to caller
}
);
// fn.id = "service::action"
// fn.unregister() - removes the function
Calling Functions
// Awaitable call (returns result)
const result = await call<InputType, OutputType>("other-service::action", { data: "hello" });
// Fire-and-forget (no return value)
callVoid("log-service::audit", { event: "user_login" });
// Parallel calls with graceful failure
const [a, b] = await Promise.allSettled([
call("service-a::process", payload),
call("service-b::process", payload),
]);
Built-in Functions
// State management (uses 'scope')
await call("state::set", { scope: "shared", key: "VERSION", value: 1 });
const val = await call("state::get", { scope: "shared", key: "VERSION" });
const items = await call("state::list", { scope: "shared" });
// KV Server (uses 'index' — NOT 'scope')
await call("kv_server::set", { index: "default", key: "user:123", value: { name: "Alice" } });
const user = await call("kv_server::get", { index: "default", key: "user:123" });
await call("kv_server::delete", { index: "default", key: "user:123" });
const keys = await call("kv_server::list_keys_with_prefix", { prefix: "user:" });
// Queue — enqueue a message to a topic
await call("enqueue", { topic: "user.created", data: { id: "123", email: "user@example.com" } });
// Engine introspection
const functions = await call("engine::functions::list", {});
const workers = await call("engine::workers::list", {});
// Health check (HTTP, not via SDK)
// curl http://localhost:3111/health
// Graceful shutdown (new in 0.2.0)
await sdk.shutdown(); // Closes WebSocket, cleans up resources
Note:
shutdown()was added in 0.2.0. Always call it during graceful process termination.
Triggers
HTTP Trigger (API)
Exposes a function as an HTTP endpoint on the engine's REST API (default port 3111).
// iii-sdk 0.3.0-alpha (current working — use this)
registerTrigger({
trigger_type: "api",
function_id: "service::handler",
config: {
api_path: "users/:id", // Path params supported
http_method: "POST", // GET, POST, PUT, DELETE, OPTIONS
},
});
// Accessible at: http://localhost:3111/users/123
Warning: Using
type: "http"(0.2.0 syntax) with iii engine 0.4.0+ causes silent 404s on all routes. Always usetrigger_type: "api"with 0.3.0-alpha SDK.
The handler receives an ApiRequest object:
registerFunction({ id: "service::handler" }, async (req: ApiRequest) => {
const { path_params, query_params, body, headers, method } = req;
return {
status_code: 200,
headers: { "Content-Type": "application/json" },
body: { id: path_params.id, data: body },
};
});
Cron Trigger
Supports 7-field cron expressions (seconds granularity):
registerTrigger({
trigger_type: "cron",
function_id: "service::cleanup",
config: { expression: "*/30 * * * * * *" }, // Every 30 seconds
});
// Fields: seconds minutes hours day-of-month month day-of-week year
Queue Trigger
Invokes a function when a message is enqueued to a specific topic. Requires QueueModule in engine config.
const consumer = registerFunction(
{ id: "events::on-user-created" },
async (data) => {
console.log("User created", data);
return { ok: true };
}
);
registerTrigger({
trigger_type: "queue",
function_id: consumer.id,
config: { topic: "user.created" },
});
// Enqueue from anywhere:
await call("enqueue", { topic: "user.created", data: { id: "123", email: "user@example.com" } });
Log Trigger
Invokes a function when log events match a level. Requires LoggingModule in engine config.
registerFunction({ id: "monitoring::on-error" }, async (logEntry) => {
// logEntry: { trace_id, message, level, function_name, date }
const { logger } = getContext();
logger.info("Error captured", logEntry);
// Send alert, store in database, etc.
});
registerTrigger({
trigger_type: "log",
function_id: "monitoring::on-error",
config: { level: "error" }, // Optional: info, warn, error, debug (omit for all)
});
References
| Need | File |
|---|---|
| Full SDK type definitions and state/stream APIs | references/api-reference.md |
| Engine module configuration schemas | references/engine-modules.md |
| Cross-language examples, OpenTelemetry, env vars | references/cross-language.md |
| Docker deployment and project structure | references/docker-deployment.md |
Known Issues
- Alpha software: API may change between releases
- Port 49134 conflicts: The engine's WebSocket port is fixed at 49134; ensure nothing else uses it
- Docker networking: On Linux Docker <20.10,
host.docker.internalrequires explicitextra_hostsmapping - Reconnection: SDK auto-reconnects with exponential backoff (1s initial, 30s max, infinite retries by default)
- CJS support: 0.2.0 adds CommonJS exports alongside ESM - both
importandrequirenow work
0.3.0-alpha Breaking Changes (confirmed working 2026-02-18)
These are current reality with iii-sdk@0.3.0-alpha.20260210122502 + iii engine 0.4.0:
trigger_typereplacestypeinregisterTrigger— usetrigger_type: "api"(NOTtype: "http")"api"replaces"http"as the HTTP trigger type nameiii-sdk/stateexport removed — usecall("state::set", ...)andcall("state::get", ...)iii-sdk/streamrenamed toiii-sdk/streams(plural)ISdktype no longer exported from the main entry point- SDK transition: The docs-only
@iii-dev/sdk(Bridge class, dot separators) may become the official SDK. Monitor npm for@iii-dev/sdkavailability
Verified: tac-4-go project uses direct iii-sdk 0.3.0-alpha with
trigger_type: "api"and all 4 HTTP endpoints + queue + cron work correctly.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
ECONNREFUSED 127.0.0.1:49134 |
Engine not running | Start with iii -c iii-config.yaml |
| Function call times out (30s) | Target service not connected | Start the service; check engine::workers::list |
Cannot find module 'iii-sdk' |
SDK not installed | npm install iii-sdk |
| HTTP endpoint returns 404 | Wrong trigger field or type | With 0.3.0-alpha: use trigger_type: "api" (NOT type: "http"). Verify api_path matches URL |
| Cron not firing | Wrong expression format | Use 7-field format: sec min hour dom mon dow year |
| State returns null | Wrong scope or key | Check scope/key strings match exactly |
trigger_type_not_found |
SDK/engine version mismatch | 0.2.0: use type. 0.3.0-alpha: use trigger_type |
| All routes 404 (silent) | Used type: "http" with 0.3.0-alpha SDK |
Change to trigger_type: "api" — motia sends "http", iii engine 0.4.0 expects "api" |
| KV Server timeout | KV Server module not in config | Add modules::kv_server::KvServer to iii-config.yaml |
module class not found |
Wrong singular/plural path | Try stream vs streams in class path; check engine version |
@iii-dev/sdk npm 404 |
Package not published | Use iii-sdk (no scope). @iii-dev/sdk is docs-only |
| Queue messages not processing | Missing queue trigger or wrong topic | Verify registerTrigger({ type: "queue", config: { topic } }) matches enqueue topic |