Function Invocation

The funcs module calls registered functions synchronously or asynchronously. An executor can propagate request context, security identity, and implementation-specific call options. This page is an API reference; target IDs, arguments, and application data represent surrounding code.

Loading

local funcs = require("funcs")

call

Calls a registered function synchronously and waits for its result.

local result, err = funcs.call("app.api:get_user", user_id)
if err then
    return nil, err
end
print(result.name)
Parameter Type Description
target string Function ID in format "namespace:name"
...args any Arguments passed to the function

Returns: result, error

The target uses the namespace:name format.

async

Starts a function call and returns a Future immediately. Futures allow other work to continue while the call runs and support multiple concurrent calls.

-- Start heavy computation without blocking
local future, err = funcs.async("app.process:analyze_data", large_dataset)
if err then
    return nil, err
end

-- Do other work while computation runs...

-- Wait for result when ready
local ch = future:response()
local _, open = ch:receive()
if not open then
    return nil, errors.new("future response channel closed")
end

local payload, result_err = future:result()
if result_err then
    return nil, result_err
end
local result, data_err = payload:data()
if data_err then return nil, data_err end
Parameter Type Description
target string Function ID in format "namespace:name"
...args any Arguments passed to the function

Returns: Future, error

new

Creates an Executor for calls that need custom context, security identity, or call options.

local exec = funcs.new()

Returns: Executor

Executor

An executor stores call context and options. Its configuration methods return new executor instances, allowing a base configuration to be reused.

with_context

Adds request-scoped values that will be available to the called function, such as trace IDs, session data, or feature flags.

local ctx = require("ctx")

-- Propagate request context to downstream services
local request_id, ctx_err = ctx.get("request_id")
if ctx_err then return nil, ctx_err end

local exec, err = funcs.new():with_context({
    request_id = request_id,
    feature_flags = {dark_mode = true}
})
if err then return nil, err end

local user, err = exec:call("app.api:get_user", user_id)
if err then return nil, err end
Parameter Type Description
values table Key-value pairs to add to context

Returns: Executor, error

with_actor

Sets the security actor used for authorization checks in the called function.

local security = require("security")
local actor = security.actor()  -- Get current user's actor

-- Call admin function with user's credentials
local exec, err = funcs.new():with_actor(actor)
if err then return nil, err end
local result, err = exec:call("app.admin:delete_record", record_id)
if err and err:kind() == errors.PERMISSION_DENIED then
    return nil, errors.new({kind = errors.PERMISSION_DENIED, message = "User cannot delete records"})
end
Parameter Type Description
actor Actor Security actor (from security module)

Returns: Executor, error

with_scope

Sets the security scope for called functions. The scope defines the permissions available to the call.

local security = require("security")
local scope = security.new_scope()

local exec, err = funcs.new():with_scope(scope)
if err then return nil, err end
Parameter Type Description
scope Scope Security scope (from security module)

Returns: Executor, error

with_options

Sets call options such as the retry policy or the overlay network. Options are merged over any preset options of the target function entry.

-- Retry transient failures up to 5 times with exponential backoff
local exec = funcs.new():with_options({
    retry = { max_attempts = 5, initial_delay = 100 }
})
local result, err = exec:call("app.external:fetch_data", query)
if err then
    -- All attempts failed, or the error was not retryable
end
Parameter Type Description
options table Call options
Option Type Description
retry.max_attempts int Maximum attempts including the first (1 disables retry)
retry.initial_delay int/duration Delay before first retry (ms or duration string), default 100
retry.max_delay int/duration Upper bound for the backoff delay (ms or duration string), default 10s
retry.backoff_factor number Multiplier applied to the delay after each attempt, default 2.0
retry.jitter number Random jitter fraction applied to each delay, default 0.1
retry.retry_kinds string[] Only retry errors of these kinds; by default every kind except Invalid, PermissionDenied and Internal is retried
retry.skip_kinds string[] Never retry errors of these kinds
network string Registry ID of an overlay network to route the call's outbound traffic through; requires the network.select permission

Only retryable errors trigger retries; non-retryable errors surface immediately. Temporal activity options are described in Activities.

The runtime-defined option is:

Recognized option Type Description
network string Registry ID of the outbound network.* entry

Returns: Executor, error

Selecting a network requires network.select permission on that network ID.

call and async

The executor versions of call and async use its configured context and options.

-- Build reusable executor with context
local exec = funcs.new()
    :with_context({trace_id = "abc-123"})
    :with_options({retry = {max_attempts = 3}})

-- Make multiple calls with same context
local users, users_err = exec:call("app.api:list_users")
if users_err then return nil, users_err end
local posts, posts_err = exec:call("app.api:list_posts")
if posts_err then return nil, posts_err end

Future Invocation Summary

async() returns a future representing an in-progress invocation. The methods below cover the caller-facing steps for receiving, inspecting, or canceling that invocation. See Future for the Future object reference.

response and channel

Returns the channel used to receive the result.

local time = require("time")

local future, err = funcs.async("app.api:slow_operation", data)
if err then
    return nil, err
end
local ch = future:response()  -- or future:channel()

local timeout, err = time.after("5s")
if err then
    return nil, err
end

local result = channel.select {
    ch:case_receive(),
    timeout:case_receive()
}

Returns: Channel

The response channel signals completion. After it becomes ready, call future:result() to obtain the cached value or the called function's error.

is_complete

Checks whether the future has completed without blocking.

while not future:is_complete() do
    -- do other work
    local _, sleep_err = time.sleep("100ms")
    if sleep_err then return nil, sleep_err end
end
local result, err = future:result()

Returns: boolean

is_canceled

Returns true after cancellation is requested. This does not mean the operation has finished. See the cancellation limitation below.

if future:is_canceled() then
    print("Cancellation requested")
end

Returns: boolean

result

Returns the cached result when complete or nil, nil while the operation is pending. Native futures complete only after the call frame and its owned resources are released. Requesting cancellation does not replace the eventual result; it may still be a success. Temporal workflow futures retain their recorded behavior: a marked-canceled future returns errors.CANCELED.

local value, err = future:result()
if err then
    print("Failed:", err:message())
elseif value then
    local data, data_err = value:data()
    if data_err then return nil, data_err end
    print("Got:", data)
end

Returns: Payload|table|nil, error|nil

error

Returns the operation error when the future has failed.

local err, has_error = future:error()
if has_error then
    print("Error kind:", err:kind())
end

Returns: error|nil, boolean

This method returns a non-retryable INTERNAL wrapper for a failed operation. Use result() to preserve the called function's original error metadata.

cancel

Requests cancellation of the asynchronous operation.

local canceled, err = future:cancel()
if err then return nil, err end

Returns: boolean, error

Function and contract futures share one process-global cancellation callback. When both providers are loaded, cancel() is not a stable cross-provider contract. Do not use cancellation for application correctness; time out locally and ignore a late result until the runtime separates provider cancellation.

Parallel Operations

Combine async with channel.select to run and collect multiple calls concurrently.

-- Start multiple operations in parallel
local f1, err = funcs.async("app.api:get_user", user_id)
if err then return nil, err end
local f2, err = funcs.async("app.api:get_orders", user_id)
if err then return nil, err end
local f3, err = funcs.async("app.api:get_preferences", user_id)
if err then return nil, err end

-- Wait for all to complete using channels
local user_ch = f1:channel()
local orders_ch = f2:channel()
local prefs_ch = f3:channel()

local pending = {
    [user_ch] = {name = "user", future = f1},
    [orders_ch] = {name = "orders", future = f2},
    [prefs_ch] = {name = "preferences", future = f3}
}
local results = {}
while next(pending) do
    local cases = {}
    for ch in pairs(pending) do
        cases[#cases + 1] = ch:case_receive()
    end

    local r = channel.select(cases)
    local completed = pending[r.channel]
    pending[r.channel] = nil

    local payload, result_err = completed.future:result()
    if result_err then
        return nil, result_err
    end
    local data, data_err = payload:data()
    if data_err then
        return nil, data_err
    end
    results[completed.name] = data
end

Permissions

Function operations are subject to security policy evaluation.

Action Resource Description
funcs.call Function ID Call a specific function
funcs.context context Use with_context() to set custom context
funcs.security security Use with_actor() or with_scope()
network.select Network ID Use with_options({network = ...}) to select an overlay network

Errors

Condition Kind Retryable
Target empty errors.INVALID no
Namespace missing errors.INVALID no
Name missing errors.INVALID no
Permission denied errors.PERMISSION_DENIED no
Async outside a process errors.INTERNAL no
Subscribe failed errors.INTERNAL no
Async start dispatch failed errors.INTERNAL no
Function error varies varies

See Error Handling for working with errors.