mcpField
I
iii

iii

github.com/evolv3-aiOther140 installsUpdated 14w ago
Repository

About iii

A skill guide for building services on the iii cross-language backend engine. It explains function registration and calls, triggers, shared engine capabilities, and version-sensitive SDK conventions for developers.

Key features

  1. 01Registers callable functions
  2. 02Calls functions across services
  3. 03Supports HTTP, cron, queue, and log triggers
  4. 04Uses engine modules for state and streams
  5. 05Explains SDK version-specific conventions

Use cases

  1. 01Connecting services written in different languages
  2. 02Exposing functions as HTTP endpoints
  3. 03Scheduling functions or reacting to queued messages

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/evolv3-ai/vibe-skills --skill iii

After installing, type/iiiin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
api-reference.md
cross-language.md
docker-deployment.md
engine-modules.md

Details

From the project README

iii - Cross-Language Backend Engine

SDK 0.3.0-alpha Breaking Change (verified 2026-02-18): The registerTrigger field is trigger_type (NOT type). HTTP triggers use trigger_type: "api" (NOT "http"). Using type: "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/sdk with a Bridge class — this package does not exist on npm (404 as of 2026-02-17). Always install iii-sdk (no scope). The docs SDK uses different conventions (dot separators, trigger_type, function_path). See references/cross-language.md for translation guide.


Core Concepts

iii has two fundamental primitives:

  1. Register - Make a function callable by the engine
  2. 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.md for 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-name convention for function IDs (e.g., "client::health", "data-service::transform")
  • Set "type": "module" in package.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 /health on port 3111 to check engine health before connecting services
  • Use index (not scope) for KV Server function parameters — scope is for the State module only
Never Do
  • Never hardcode ws://localhost:49134 without env var fallback
  • Never use process.env in 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 (NOT type) in registerTrigger. Use "api" (NOT "http") for HTTP triggers. Use "queue" for queue triggers, "cron" for cron triggers
  • iii-sdk 0.2.0: Use type (NOT trigger_type). Use "http" for HTTP triggers
  • Never import from iii-sdk/state or iii-sdk/stream for basic state operations - use sdk.call("state::set", ...) instead. Removed in 0.3.0
  • Never npm install @iii-dev/sdk — this scoped package does not exist on npm (404). Use iii-sdk instead
  • Never use dot separators in engine function paths — use :: (double colon). "kv_server::get" not "kv_server.get"

SDK Entry Points

ImportPurpose
iii-sdkCore: init, getContext, withContext, Logger (+ types). init() returns ISdk with registerFunction, registerTrigger, call, callVoid, shutdown methods
iii-sdk/stateAdvanced: Direct IState interface (get/set/delete/list/update)
iii-sdk/streamAdvanced: Direct IStream interface with groups
iii-sdk/telemetryOpenTelemetry: 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: "[email protected]" } });

// 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 use trigger_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: "[email protected]" } });
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

NeedFile
Full SDK type definitions and state/stream APIsreferences/api-reference.md
Engine module configuration schemasreferences/engine-modules.md
Cross-language examples, OpenTelemetry, env varsreferences/cross-language.md
Docker deployment and project structurereferences/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.internal requires explicit extra_hosts mapping
  • 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 import and require now work
0.3.0-alpha Breaking Changes (confirmed working 2026-02-18)

These are current reality with [email protected] + iii engine 0.4.0:

  • trigger_type replaces type in registerTrigger — use trigger_type: "api" (NOT type: "http")
  • "api" replaces "http" as the HTTP trigger type name
  • iii-sdk/state export removed — use call("state::set", ...) and call("state::get", ...)
  • iii-sdk/stream renamed to iii-sdk/streams (plural)
  • ISdk type 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/sdk availability

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

ErrorCauseFix
ECONNREFUSED 127.0.0.1:49134Engine not runningStart with iii -c iii-config.yaml
Function call times out (30s)Target service not connectedStart the service; check engine::workers::list
Cannot find module 'iii-sdk'SDK not installednpm install iii-sdk
HTTP endpoint returns 404Wrong trigger field or typeWith 0.3.0-alpha: use trigger_type: "api" (NOT type: "http"). Verify api_path matches URL
Cron not firingWrong expression formatUse 7-field format: sec min hour dom mon dow year
State returns nullWrong scope or keyCheck scope/key strings match exactly
trigger_type_not_foundSDK/engine version mismatch0.2.0: use type. 0.3.0-alpha: use trigger_type
All routes 404 (silent)Used type: "http" with 0.3.0-alpha SDKChange to trigger_type: "api" — motia sends "http", iii engine 0.4.0 expects "api"
KV Server timeoutKV Server module not in configAdd modules::kv_server::KvServer to iii-config.yaml
module class not foundWrong singular/plural pathTry stream vs streams in class path; check engine version
@iii-dev/sdk npm 404Package not publishedUse iii-sdk (no scope). @iii-dev/sdk is docs-only
Queue messages not processingMissing queue trigger or wrong topicVerify registerTrigger({ type: "queue", config: { topic } }) matches enqueue topic

Comments

Markdown supported