Skip to main content
A Worker is a process that connects to the iii-engine over WebSocket and registers functions. Workers can be written in Rust, TypeScript, or Python.

What is a Worker?

A worker is simply a program that:
  1. Connects to the iii-engine via WebSocket (default: ws://localhost:49134)
  2. Registers one or more functions with unique IDs
  3. Stays running to handle function invocations
  4. Optionally registers triggers to bind functions to events
Workers are stateless. They don’t store data - they connect, register functions, and handle invocations. All state is managed by the iii-engine modules (state, queue, pubsub, etc.).

Creating Workers in Different Languages

Rust Worker

Rust workers use the iii-sdk crate and are ideal for performance-critical operations.
// From crates/agent-core/src/main.rs:13-101
use iii_sdk::iii::III;
use iii_sdk::error::IIIError;
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    // 1. Connect to iii-engine
    let iii = III::new("ws://localhost:49134");

    // 2. Register function with description
    let iii_clone = iii.clone();
    iii.register_function_with_description(
        "agent::chat",
        "Process a message through the agent loop",
        move |input: Value| {
            let iii = iii_clone.clone();
            async move {
                let req: ChatRequest = serde_json::from_value(input)
                    .map_err(|e| IIIError::Handler(e.to_string()))?;
                agent_chat(&iii, req).await
            }
        },
    );

    // 3. Register more functions
    iii.register_function_with_description(
        "agent::list_tools",
        "List tools available to an agent",
        move |input: Value| {
            let iii = iii_clone.clone();
            async move {
                let agent_id = input["agentId"].as_str().unwrap_or("default");
                list_tools(&iii, agent_id).await
            }
        },
    );

    // 4. Register trigger to bind function to queue
    iii.register_trigger("queue", "agent::chat", json!({
        "topic": "agent.inbox"
    }))?;

    // 5. Keep running
    tracing::info!("agent-core worker started");
    tokio::signal::ctrl_c().await?;
    iii.shutdown_async().await;
    Ok(())
}

Key Rust Patterns

1

Clone for async handlers

Clone the iii instance for each async handler to satisfy Rust’s ownership rules:
let iii_clone = iii.clone();
iii.register_function_with_description(
    "my::function",
    "Description",
    move |input| {
        let iii = iii_clone.clone();
        async move { /* use iii here */ }
    },
);
2

Parse input with serde

Use serde_json to deserialize input:
let req: ChatRequest = serde_json::from_value(input)
    .map_err(|e| IIIError::Handler(e.to_string()))?;
3

Keep the process alive

Workers must stay running to handle invocations:
tokio::signal::ctrl_c().await?;
iii.shutdown_async().await;

Worker Lifecycle

1

Connecting

Worker connects to iii-engine WebSocket
2

Registering

Worker registers all functions and triggers
3

Ready

Worker waits for function invocations
4

Handling

Worker executes function handler and returns result
5

Shutting

Worker gracefully shuts down on signal

Worker Configuration

Workers connect to the iii-engine, which is configured via config.yaml:
# From config.yaml:1-14
port: 49134

modules:
  - class: modules::api::RestApiModule
    config:
      port: 3111
      host: 0.0.0.0
      default_timeout: 300000
      concurrency_request_limit: 2048
      cors:
        allowed_origins: ['*']
        allowed_methods: [GET, POST, PUT, DELETE, OPTIONS]
        allowed_headers: ['*']

Connection URL

Workers connect to ws://localhost:{port} where port is from config.yaml (default: 49134).

Real AgentOS Workers

Here are the actual workers running in AgentOS:

Rust Workers (18 crates)

WorkerLOCPurposeKey Functions
agent-core320ReAct agent loopagent::chat, agent::create, agent::list
security700RBAC, audit, taint trackingsecurity::check_capability, security::scan_injection
memory840Session/episodic memorymemory::store, memory::recall
llm-router32025 LLM providersllm::route, llm::complete
wasm-sandbox180Untrusted code executionwasm::execute
realm280Multi-tenant isolationrealm::create, realm::get
mission350Task lifecyclemission::create, mission::checkout

TypeScript Workers (39 files)

WorkerPurposeKey Functions
api.tsOpenAI-compatible APIapi::chat_completions
agent-core.tsTS agent loopagent::chat (TypeScript version)
tools.tsBuilt-in tools (22)file::read, web::search, shell::exec
swarm.tsMulti-agent swarmsswarm::create, swarm::coordinate
knowledge-graph.tsEntity-relation graphkg::add, kg::query
session-replay.tsSession recordingreplay::record, replay::get

Python Workers (1)

WorkerPurposeKey Functions
embedding/main.pyText embeddingsembedding::generate, embedding::similarity

Starting Workers

# Start all workers automatically
agentos start

Best Practices

One Worker, Multiple Functions

Group related functions in a single worker (e.g., all memory functions in memory worker)

Use Descriptive Names

Function IDs should follow namespace::action pattern (e.g., agent::chat, memory::store)

Handle Errors Gracefully

Return error objects instead of throwing exceptions when possible

Keep Workers Stateless

Store state in iii-engine modules (state, kv, etc.), not in worker memory

Next Steps

Functions

Learn how to register and invoke functions

Triggers

Explore how to bind functions to events

Build docs developers (and LLMs) love