Skip to main content

Discover workers, functions, and triggers

Use this guide when you need to inspect which workers are connected, which functions are registered, which triggers are active, or react to functions coming online at runtime — without relying on SDK convenience wrappers.
For the architectural background on why discovery is built into the engine, see Concepts: Discovery.
The Node and browser SDKs export EngineFunctions and EngineTriggers constants for the built-in discovery function IDs and trigger types. The examples below use those constants in Node, but the underlying identifiers are the same across all SDKs.

Goal 1: List registered functions

engine::functions::list returns the current function registry. By default it filters out internal engine::* functions. Set include_internal: true when you want the engine’s own functions in the result.
import {
  EngineFunctions,
  registerWorker,
  type FunctionInfo,
} from 'iii-sdk'

const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134')

const { functions } = await iii.trigger<
  { include_internal?: boolean },
  { functions: FunctionInfo[] }
>({
  function_id: EngineFunctions.LIST_FUNCTIONS,
  payload: {},
})

const { functions: allFunctions } = await iii.trigger<
  { include_internal: boolean },
  { functions: FunctionInfo[] }
>({
  function_id: EngineFunctions.LIST_FUNCTIONS,
  payload: { include_internal: true },
})

Goal 2: List connected workers

engine::workers::list returns the workers currently connected to the engine. Pass worker_id when you want to inspect a single worker entry.
import {
  EngineFunctions,
  registerWorker,
  type WorkerInfo,
} from 'iii-sdk'

const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134')

const { workers } = await iii.trigger<
  { worker_id?: string },
  { workers: WorkerInfo[]; timestamp: number }
>({
  function_id: EngineFunctions.LIST_WORKERS,
  payload: {},
})

const { workers: onlyOneWorker } = await iii.trigger<
  { worker_id: string },
  { workers: WorkerInfo[]; timestamp: number }
>({
  function_id: EngineFunctions.LIST_WORKERS,
  payload: { worker_id: 'worker-123' },
})

Goal 3: List registered triggers

engine::triggers::list returns all active triggers. Like functions, internal engine triggers are filtered unless you opt in with include_internal: true.
import {
  EngineFunctions,
  registerWorker,
  type TriggerInfo,
} from 'iii-sdk'

const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134')

const { triggers } = await iii.trigger<
  { include_internal?: boolean },
  { triggers: TriggerInfo[] }
>({
  function_id: EngineFunctions.LIST_TRIGGERS,
  payload: { include_internal: false },
})

Goal 4: List available trigger types

engine::trigger-types::list returns the trigger types registered with the engine. Each item includes trigger_request_format and call_request_format JSON schemas, which you can use to validate configs and call payloads before registration.
import {
  EngineFunctions,
  registerWorker,
  type TriggerTypeInfo,
} from 'iii-sdk'

const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134')

const { trigger_types } = await iii.trigger<
  { include_internal?: boolean },
  { trigger_types: TriggerTypeInfo[] }
>({
  function_id: EngineFunctions.LIST_TRIGGER_TYPES,
  payload: { include_internal: false },
})

Goal 5: Subscribe to function availability changes

Use a normal function plus a normal trigger registration on engine::functions-available. This is the same primitive the old wrapper methods hid. The engine polls its function registry about every five seconds. Give the handler function ID a UUID suffix so it does not collide with another worker instance. Cleanup means unregistering the trigger and, if you no longer need the handler, unregistering that function too.
import {
  EngineTriggers,
  registerWorker,
  type FunctionInfo,
} from 'iii-sdk'
import crypto from 'node:crypto'

const iii = registerWorker(process.env.III_URL ?? 'ws://localhost:49134')

const topologyHandlerId = `my_app::topology::function-availability::${crypto.randomUUID()}`

const functionRef = iii.registerFunction(
  topologyHandlerId,
  async ({ functions }: { functions: FunctionInfo[] }) => {
    console.log('available functions', functions.length)
    return null
  },
  {},
)

const triggerRef = await iii.registerTrigger({
  type: EngineTriggers.FUNCTIONS_AVAILABLE,
  function_id: topologyHandlerId,
  config: {},
})

// later
await triggerRef.unregister()
functionRef.unregister()

Result

You get the same discovery data the SDK wrappers used to expose, but directly through the stable engine primitives: trigger() for registry reads and register_function plus register_trigger for live topology updates. For the exact response types, see the Node SDK reference, Rust SDK reference, and Python SDK reference.