From c1d1a9b103fa7cc78ddaebd0b56a474b9edf8f9e Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Fri, 10 Apr 2026 13:17:59 +0100 Subject: [PATCH 1/9] docs(skills): update skills and references for v0.11 workers rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace *Module names with iii-* worker names across 8 SKILL.md files - Rewrite iii-engine-config SKILL.md with v0.11 workers:/name: config format - Fix iii-getting-started port 3000 → 3111 (REST API) and add 3113 (console) - Update references/iii-config.yaml: modules→workers, class→name for all adapters - Add IPv4/IPv6 guidance (use 127.0.0.1 instead of localhost on macOS) --- skills/iii-cron-scheduling/SKILL.md | 6 +- skills/iii-engine-config/SKILL.md | 125 ++++++++++++++++++++------- skills/iii-getting-started/SKILL.md | 4 +- skills/iii-http-endpoints/SKILL.md | 4 +- skills/iii-http-middleware/SKILL.md | 2 +- skills/iii-observability/SKILL.md | 6 +- skills/iii-realtime-streams/SKILL.md | 6 +- skills/iii-state-management/SKILL.md | 4 +- skills/iii-state-reactions/SKILL.md | 4 +- skills/references/iii-config.yaml | 62 ++++++------- 10 files changed, 141 insertions(+), 82 deletions(-) diff --git a/skills/iii-cron-scheduling/SKILL.md b/skills/iii-cron-scheduling/SKILL.md index 892f8c8be..aa7a6a562 100644 --- a/skills/iii-cron-scheduling/SKILL.md +++ b/skills/iii-cron-scheduling/SKILL.md @@ -17,14 +17,14 @@ Comparable to: node-cron, APScheduler, crontab Use the concepts below when they fit the task. Not every scheduled job needs all of them. - Cron expressions use a **7-field format**: `second minute hour day month weekday year` -- **CronModule** evaluates expressions and fires triggers on schedule +- **iii-cron** evaluates expressions and fires triggers on schedule - Handlers should be **fast** — enqueue heavy work to a queue instead of blocking the cron handler - Each cron trigger binds one expression to one function - Overlapping schedules are fine; each trigger fires independently ## Architecture - CronModule timer tick + iii-cron timer tick → registerTrigger type:'cron' expression match → registerFunction handler → (optional) TriggerAction.Enqueue for heavy work @@ -66,7 +66,7 @@ Use the adaptations below when they apply to the task. ## Engine Configuration -CronModule must be enabled in iii-config.yaml. See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. +iii-cron must be enabled in iii-config.yaml. See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. ## Pattern Boundaries diff --git a/skills/iii-engine-config/SKILL.md b/skills/iii-engine-config/SKILL.md index f65f12da3..74df366e5 100644 --- a/skills/iii-engine-config/SKILL.md +++ b/skills/iii-engine-config/SKILL.md @@ -1,7 +1,7 @@ --- name: iii-engine-config description: >- - Configures the iii engine via iii-config.yaml — modules, adapters, queue + Configures the iii engine via iii-config.yaml — workers, adapters, queue configs, ports, and environment variables. Use when deploying, tuning, or customizing the engine. --- @@ -12,46 +12,47 @@ Comparable to: Infrastructure as code, Docker Compose configs ## Key Concepts -Use the concepts below when they fit the task. Not every deployment needs all modules or adapters. +Use the concepts below when they fit the task. Not every deployment needs all workers or adapters. -- **iii-config.yaml** defines the engine port, modules, workers, adapters, and queue configs +- **iii-config.yaml** defines the engine workers, adapters, and queue configs - **Environment variables** use `${VAR:default}` syntax (default is optional) -- **Modules** are the building blocks — each enables a capability (API, state, queue, cron, etc.) -- **Workers** are external binary modules managed via `iii.toml` and the `iii worker` CLI commands -- **Adapters** swap storage backends per module: in_memory, file_based, Redis, RabbitMQ +- **Workers** are the building blocks — each enables a capability (API, state, queue, cron, etc.) +- **External workers** are binary modules managed via `iii.toml` and the `iii worker` CLI commands +- **Adapters** swap storage backends per worker: in_memory, file_based, Redis, RabbitMQ - **Queue configs** control retry count, concurrency, ordering, and backoff per named queue - The engine listens on port **49134** (WebSocket) for SDK/worker connections ## Architecture -The iii-config.yaml file is loaded by the iii engine binary at startup. Modules are initialized in order, adapters connect to their backends, and the engine begins accepting worker connections over WebSocket on port 49134. External workers defined in the `workers` section are spawned as child processes automatically. +The iii-config.yaml file is loaded by the iii engine binary at startup. Workers are initialized in order, adapters connect to their backends, and the engine begins accepting worker connections over WebSocket on port 49134. External workers defined in the config are spawned as child processes automatically. ## iii Primitives Used -| Primitive | Purpose | -| ---------------------------------------------- | -------------------------------------- | -| `modules::api::RestApiModule` | HTTP API server (port 3111) | -| `modules::stream::StreamModule` | WebSocket streams (port 3112) | -| `modules::state::StateModule` | Persistent key-value state storage | -| `modules::queue::QueueModule` | Background job processing with retries | -| `modules::pubsub::PubSubModule` | In-process event fanout | -| `modules::cron::CronModule` | Time-based scheduling | -| `modules::observability::OtelModule` | OpenTelemetry traces, metrics, logs | -| `modules::http_functions::HttpFunctionsModule` | Outbound HTTP call security | -| `modules::shell::ExecModule` | Spawn external processes | -| `modules::bridge_client::BridgeClientModule` | Distributed cross-engine invocation | -| `modules::telemetry::TelemetryModule` | Anonymous product analytics | -| `workers` section in iii-config.yaml | External binary workers (worker modules)| -| `iii.toml` | Worker manifest (name → version) | -| `iii worker add NAME[@VERSION]` | Install a worker from the registry | -| `iii worker remove NAME` | Uninstall a worker | -| `iii worker list` | List installed workers | -| `iii worker info NAME` | Show registry info for a worker | +| Primitive | Purpose | +| -------------------------------- | -------------------------------------- | +| `iii-http` | HTTP API server (port 3111) | +| `iii-stream` | WebSocket streams (port 3112) | +| `iii-state` | Persistent key-value state storage | +| `iii-queue` | Background job processing with retries | +| `iii-pubsub` | In-process event fanout | +| `iii-cron` | Time-based scheduling | +| `iii-observability` | OpenTelemetry traces, metrics, logs | +| `iii-http-functions` | Outbound HTTP call security | +| `iii-exec` | Spawn external processes | +| `iii-bridge` | Distributed cross-engine invocation | +| `iii-telemetry` | Anonymous product analytics | +| `iii-worker-manager` | Worker connection lifecycle | +| `iii-engine-functions` | Built-in engine functions | +| `iii.toml` | Worker manifest (name → version) | +| `iii worker add NAME[@VERSION]` | Install a worker from the registry | +| `iii worker remove NAME` | Uninstall a worker | +| `iii worker list` | List installed workers | +| `iii worker info NAME` | Show registry info for a worker | ## Reference Implementation See [../references/iii-config.yaml](../references/iii-config.yaml) for the full working example — a complete -engine configuration with all modules, adapters, queue configs, and environment variable patterns. +engine configuration with all workers, adapters, queue configs, and environment variable patterns. ## Common Patterns @@ -64,10 +65,67 @@ Code using this pattern commonly includes, when relevant: - Prod queues: RabbitMQ adapter with `amqp_url: ${AMQP_URL}` and `queue_mode: quorum` - Queue config: `queue_configs` with `max_retries`, `concurrency`, `type`, `backoff_ms` per queue name - Env var with fallback: `port: ${III_PORT:49134}` -- Health check: `curl http://localhost:3111/health` +- Health check: `curl http://127.0.0.1:3111/health` - Ports: 3111 (API), 3112 (streams), 49134 (engine WS), 9464 (Prometheus) -### Worker Module System +### Worker Config Format (v0.11+) + +Workers in `iii-config.yaml` use `name:` and optional `config:`: + +```yaml +workers: + - name: iii-http + config: + port: 3111 + host: 127.0.0.1 + + - name: iii-state + config: + adapter: + name: kv + config: + store_method: file_based + file_path: ./data/state_store.db + + - name: iii-queue + config: + adapter: + name: builtin + config: + store_method: file_based + file_path: ./data/queue_store + + - name: iii-stream + config: + port: 3112 + host: 127.0.0.1 + adapter: + name: kv + config: + store_method: file_based + file_path: ./data/stream_store + + - name: iii-cron + config: + adapter: + name: kv + + - name: iii-pubsub + config: + adapter: + name: local + + - name: iii-observability + config: + enabled: true + service_name: my-service + exporter: memory + sampling_ratio: 1.0 + metrics_enabled: true + logs_enabled: true +``` + +### External Worker System External workers are installed via the CLI and configured in `iii-config.yaml`: @@ -87,13 +145,13 @@ Worker config blocks in `iii-config.yaml` use marker comments for automatic mana ```yaml workers: # === iii:pdfkit BEGIN === - - class: workers::pdfkit::PdfKitWorker + - name: pdfkit config: output_dir: ./output # === iii:pdfkit END === ``` -At startup, the engine resolves each worker class, finds the binary in `iii_workers/`, and spawns it as a child process. Worker binaries are stored in the `iii_workers/` directory. +At startup, the engine resolves each worker name, finds the binary in `iii_workers/`, and spawns it as a child process. ## Adapting This Pattern @@ -102,10 +160,11 @@ Use the adaptations below when they apply to the task. - Start with file_based adapters for development, switch to Redis/RabbitMQ for production - Define queue configs per workload: high-concurrency for parallel jobs, FIFO for ordered processing - Use environment variables with defaults for all deployment-sensitive values (URLs, ports, credentials) -- Enable only the modules you need — unused modules can be omitted from the config +- Enable only the workers you need — unused workers can be omitted from the config - Use `iii worker add` to install external workers and auto-generate their config blocks - Set `max_retries` and `backoff_ms` based on your failure tolerance and SLA requirements -- Configure `OtelModule` with your collector endpoint and sampling ratio for observability +- Configure `iii-observability` with your collector endpoint and sampling ratio +- Use `host: 127.0.0.1` instead of `host: localhost` to avoid IPv4/IPv6 mismatches on macOS ## Pattern Boundaries diff --git a/skills/iii-getting-started/SKILL.md b/skills/iii-getting-started/SKILL.md index f6bfc6fc3..5a47fc515 100644 --- a/skills/iii-getting-started/SKILL.md +++ b/skills/iii-getting-started/SKILL.md @@ -38,7 +38,7 @@ The quickstart includes TypeScript, Python, and Rust workers. If you don't have iii --config iii-config.yaml ``` -The engine starts and listens for worker connections on `ws://localhost:49134`. The console is available at `http://localhost:3000`. +The engine starts and listens for worker connections on `ws://localhost:49134`. The REST API is available at `http://localhost:3111`. The console is available at `http://localhost:3113`. ## Step 4: Install the SDK @@ -128,7 +128,7 @@ iii.register_trigger(RegisterTriggerInput { ## Step 6: Test It ```bash -curl -X POST http://localhost:3000/hello \ +curl -X POST http://localhost:3111/hello \ -H "Content-Type: application/json" \ -d '{"name": "iii"}' ``` diff --git a/skills/iii-http-endpoints/SKILL.md b/skills/iii-http-endpoints/SKILL.md index aa1582d73..594d393b6 100644 --- a/skills/iii-http-endpoints/SKILL.md +++ b/skills/iii-http-endpoints/SKILL.md @@ -16,14 +16,14 @@ Use the concepts below when they fit the task. Not every HTTP endpoint needs all - Each route is a **registered function** bound to a path and method via an HTTP trigger - The handler receives an **ApiRequest** object containing `body`, `path_params`, `headers`, and `method` - Handlers return `{ status_code, body, headers }` to shape the HTTP response -- **RestApiModule** serves all registered routes on port 3111 +- **iii-http** serves all registered routes on port 3111 - Path parameters use colon syntax (e.g. `/users/:id`) and arrive in `path_params` - **Middleware** can run before handlers via `middleware_function_ids` in the trigger config — see `iii-http-middleware` for details ## Architecture HTTP request - → RestApiModule (port 3111) + → iii-http (port 3111) → registerTrigger route match (method + path) → registerFunction handler (receives ApiRequest) → { status_code, body, headers } response diff --git a/skills/iii-http-middleware/SKILL.md b/skills/iii-http-middleware/SKILL.md index d657125b0..c1a2b8efb 100644 --- a/skills/iii-http-middleware/SKILL.md +++ b/skills/iii-http-middleware/SKILL.md @@ -24,7 +24,7 @@ Use the concepts below when they fit the task. Not every middleware setup needs ## Architecture HTTP request - → RestApiModule (port 3111) + → iii-http (port 3111) → Middleware 1 (continue / respond) → Middleware 2 (continue / respond) → registerFunction handler diff --git a/skills/iii-observability/SKILL.md b/skills/iii-observability/SKILL.md index 2c782a59c..66a5b4d7a 100644 --- a/skills/iii-observability/SKILL.md +++ b/skills/iii-observability/SKILL.md @@ -69,16 +69,16 @@ Use the adaptations below when they apply to the task. - Add custom spans around expensive operations (DB queries, LLM calls, external APIs) - Create domain-specific metrics (orders processed, payment failures, queue depth) - Use `currentTraceId()` to correlate iii traces with external system logs -- Configure `OtelModule` in iii-config.yaml for engine-side exporter, sampling ratio, and alerts +- Configure `iii-observability` in iii-config.yaml for engine-side exporter, sampling ratio, and alerts - Point the OTLP endpoint at your collector (Jaeger, Grafana Tempo, Datadog Agent) ## Engine Configuration -OtelModule must be enabled in iii-config.yaml for engine-side traces, metrics, and logs. See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. +iii-observability must be enabled in iii-config.yaml for engine-side traces, metrics, and logs. See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. ## Pattern Boundaries -- For engine-side OtelModule YAML configuration, prefer `iii-engine-config`. +- For engine-side iii-observability YAML configuration, prefer `iii-engine-config`. - For SDK init options and function registration, prefer `iii-functions-and-triggers`. - Stay with `iii-observability` when the primary problem is SDK-level telemetry: spans, metrics, logs, and trace propagation. diff --git a/skills/iii-realtime-streams/SKILL.md b/skills/iii-realtime-streams/SKILL.md index 6d6b2e4b5..1c966e595 100644 --- a/skills/iii-realtime-streams/SKILL.md +++ b/skills/iii-realtime-streams/SKILL.md @@ -13,7 +13,7 @@ Comparable to: Socket.io, Pusher, Firebase Realtime Use the concepts below when they fit the task. Not every stream setup needs all of them. -- **StreamModule** serves WebSocket connections on the configured stream port (default 3112) +- **iii-stream** serves WebSocket connections on the configured stream port (default 3112) - Clients connect at `ws://host:{stream_port}/stream/{stream_name}/{group_id}` - **stream::set** / **stream::get** / **stream::list** / **stream::delete** provide CRUD for stream items - **stream::send** pushes events to all connected clients in a stream group @@ -25,7 +25,7 @@ Use the concepts below when they fit the task. Not every stream setup needs all Function → trigger('stream::set', { stream_name, group_id, item_id, data }) → trigger('stream::send', { stream_name, group_id, data }) - → StreamModule + → iii-stream → WebSocket push → Connected clients at /stream/{stream_name}/{group_id} @@ -74,7 +74,7 @@ Use the adaptations below when they apply to the task. ## Engine Configuration -StreamModule must be enabled in iii-config.yaml with a port and adapter (KvStore or Redis). See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. +iii-stream must be enabled in iii-config.yaml with a port and adapter (KvStore or Redis). See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. ## Pattern Boundaries diff --git a/skills/iii-state-management/SKILL.md b/skills/iii-state-management/SKILL.md index 86e0209e7..5f87231ce 100644 --- a/skills/iii-state-management/SKILL.md +++ b/skills/iii-state-management/SKILL.md @@ -31,7 +31,7 @@ Use the concepts below when they fit the task. Not every state operation needs a → trigger('state::update', { scope, key, ops }) → trigger('state::delete', { scope, key }) → trigger('state::list', { scope }) - → StateModule → KvStore / Redis adapter + → iii-state → KvStore / Redis adapter ## iii Primitives Used @@ -74,7 +74,7 @@ Use the adaptations below when they apply to the task. ## Engine Configuration -StateModule must be enabled in iii-config.yaml with a KvStore adapter (file-based or Redis). See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. +iii-state must be enabled in iii-config.yaml with a KvStore adapter (file-based or Redis). See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. ## Pattern Boundaries diff --git a/skills/iii-state-reactions/SKILL.md b/skills/iii-state-reactions/SKILL.md index 488a26513..408ffc554 100644 --- a/skills/iii-state-reactions/SKILL.md +++ b/skills/iii-state-reactions/SKILL.md @@ -25,7 +25,7 @@ Use the concepts below when they fit the task. Not every state reaction needs al ## Architecture state::set, state::update, or state::delete - → StateModule emits change event + → iii-state emits change event → registerTrigger type:'state' (scope match) → condition_function_id check (if configured) → registerFunction handler ({ new_value, old_value, key, event_type }) @@ -70,7 +70,7 @@ Use the adaptations below when they apply to the task. ## Engine Configuration -StateModule must be enabled in iii-config.yaml for state triggers to fire. See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. +iii-state must be enabled in iii-config.yaml for state triggers to fire. See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. ## Pattern Boundaries diff --git a/skills/references/iii-config.yaml b/skills/references/iii-config.yaml index 48f193d2c..b395522a3 100644 --- a/skills/references/iii-config.yaml +++ b/skills/references/iii-config.yaml @@ -7,13 +7,13 @@ port: 49134 # Config blocks between BEGIN/END markers are auto-managed by the CLI. # workers: # # === iii:pdfkit BEGIN === -# - class: workers::pdfkit::PdfKitWorker +# - name: pdfkit # config: # output_dir: ./output # format: pdf # # === iii:pdfkit END === # # === iii:image-processor BEGIN === -# - class: workers::image_processor::ImageProcessorWorker +# - name: image-processor # config: # max_width: 2048 # output_format: webp @@ -31,10 +31,10 @@ port: 49134 # iii worker list # Show installed workers # iii worker info pdfkit # Show registry details -modules: +workers: # REST API module - exposes HTTP endpoints for triggers and the core API surface. # Functions with HTTP triggers are served through this module. - - class: modules::api::RestApiModule + - name: iii-http config: # TCP port for the HTTP server. Default: 3111 port: 3111 @@ -61,7 +61,7 @@ modules: # Stream module - real-time WebSocket pub/sub for live data streaming to clients. # Clients connect via WebSocket to receive pushed updates on subscribed channels. - - class: modules::stream::StreamModule + - name: iii-stream config: # TCP port for WebSocket connections. Default: 3112 port: ${STREAM_PORT:3112} @@ -73,7 +73,7 @@ modules: # Storage backend for stream state (subscriptions, message history). adapter: # KvStore adapter - file or memory-based local storage. - class: modules::stream::adapters::KvStore + name: kv config: # Storage mode. Options: file_based (persists to disk), in_memory (lost on restart) store_method: file_based @@ -84,22 +84,22 @@ modules: # Alternative adapters: # adapter: # # Redis adapter - distributed storage via Redis server. - # class: modules::stream::adapters::RedisAdapter + # name: redis # config: # redis_url: redis://localhost:6379 # adapter: # # Bridge adapter - connects to another iii engine instance for distributed streaming. - # class: modules::stream::adapters::Bridge + # name: bridge # config: # bridge_url: ws://localhost:49134 # State module - persistent key-value storage for function state across invocations. # Functions use ctx.state.get/set to read/write stateful data. - - class: modules::state::StateModule + - name: iii-state config: adapter: # KvStore adapter - file or memory-based local storage. - class: modules::state::adapters::KvStore + name: kv config: # Storage mode. Options: file_based (persists to disk), in_memory (lost on restart) store_method: file_based @@ -110,18 +110,18 @@ modules: # Alternative adapters: # adapter: # # Redis adapter - distributed state via Redis server. - # class: modules::state::adapters::RedisAdapter + # name: redis # config: # redis_url: redis://localhost:6379 # adapter: # # Bridge adapter - forwards state operations to another iii engine instance. - # class: modules::state::adapters::Bridge + # name: bridge # config: # bridge_url: ws://localhost:49134 # Queue module - background job processing with retries, dead-letter queues, and concurrency control. # Functions enqueue jobs; subscribers process them asynchronously. - - class: modules::queue::QueueModule + - name: iii-queue config: queue_configs: default: @@ -130,7 +130,7 @@ modules: type: standard adapter: # Built-in queue adapter - local job queue with persistence options. - class: modules::queue::BuiltinQueueAdapter + name: builtin config: # Maximum delivery attempts before moving to dead-letter queue. Default: 3 max_attempts: 3 @@ -151,17 +151,17 @@ modules: # Alternative adapters: # adapter: # # Redis adapter - distributed job queue via Redis. - # class: modules::queue::RedisAdapter + # name: redis # config: # redis_url: redis://localhost:6379 # adapter: # # Bridge adapter - forwards queue operations to another iii engine instance. - # class: modules::queue::adapters::Bridge + # name: bridge # config: # bridge_url: ws://localhost:49134 # adapter: # # RabbitMQ adapter - enterprise message broker integration. - # class: modules::queue::RabbitMQAdapter + # name: rabbitmq # config: # amqp_url: amqp://localhost:5672 # max_attempts: 3 @@ -172,27 +172,27 @@ modules: # PubSub module - in-process event fanout for decoupled function communication. # Functions publish events; multiple subscribers receive them immediately. - - class: modules::pubsub::PubSubModule + - name: iii-pubsub config: adapter: # Local adapter - in-process pub/sub, events don't cross engine instances. - class: modules::pubsub::LocalAdapter + name: local # Alternative adapter: # adapter: # # Redis adapter - distributed pub/sub across multiple engine instances. - # class: modules::pubsub::RedisAdapter + # name: redis # config: # redis_url: redis://localhost:6379 # Cron module - time-based job scheduling using cron expressions. # Functions with cron triggers execute on schedule. - - class: modules::cron::CronModule + - name: iii-cron config: adapter: # KV-based cron adapter - uses local KV store for distributed lock coordination. # Warning: Local locks only prevent duplicates within same process; multiple engine # instances may execute the same cron job. Use RedisCronAdapter for true distributed locking. - class: modules::cron::KvCronAdapter + name: kv config: # Lock timeout (ms). Job re-executes if lock holder crashes and lock expires. lock_ttl_ms: 30000 @@ -207,13 +207,13 @@ modules: # Alternative adapter: # adapter: # # Redis cron adapter - true distributed locking across engine instances. - # class: modules::cron::RedisCronAdapter + # name: redis # config: # redis_url: redis://localhost:6379 # Observability module - OpenTelemetry tracing, metrics, logs, and alerting. # Provides visibility into function execution, performance, and errors. - - class: modules::observability::OtelModule + - name: iii-observability config: # Master switch for all observability features. enabled: ${OTEL_ENABLED:true} @@ -323,7 +323,7 @@ modules: # HTTP Functions module - enables HTTP-invoked functions (outbound HTTP calls from the engine). # Required for functions registered with HttpInvocationConfig. # The engine makes the HTTP request on behalf of the function and enforces URL security policies. - - class: modules::http_functions::HttpFunctionsModule + - name: iii-http-functions config: security: # URL patterns allowed for outbound requests. Use '*' to allow all URLs. @@ -347,7 +347,7 @@ modules: # Useful for setups where workers need to run alongside the engine on the same host. # Also useful from frameworks that support this style of operation such as Motia. # # Rust worker: - # - class: modules::shell::ExecModule + # - name: iii-exec # config: # watch: # - workers/**/*.rs @@ -356,7 +356,7 @@ modules: # - cargo run --build my-rust-worker # # # Python worker: - # - class: modules::shell::ExecModule + # - name: iii-exec # config: # watch: # - workers/**/*.py @@ -365,7 +365,7 @@ modules: # - uv run ./src/my_python_worker.py # # # Node/TypeScript worker: - # - class: modules::shell::ExecModule + # - name: iii-exec # config: # watch: # - workers/**/*.ts @@ -376,7 +376,7 @@ modules: # - pnpm dev:my-node-worker # # # Motia worker: - # - class: modules::shell::ExecModule + # - name: iii-exec # config: # watch: # - steps/**/*.ts @@ -386,7 +386,7 @@ modules: # Bridge Client module - connects this engine to a remote iii instance for cross-instance # function invocation. Enables distributed architectures and function federation. - # - class: modules::bridge_client::BridgeClientModule + # - name: iii-bridge # config: # # WebSocket URL of the remote iii engine to connect to. # url: ws://localhost:49134 @@ -409,7 +409,7 @@ modules: # Telemetry module - anonymous product usage analytics for iii development. # Helps the team understand usage patterns and prioritize features. - - class: modules::telemetry::TelemetryModule + - name: iii-telemetry config: # Enable/disable anonymous telemetry. Set to false to opt out. enabled: true From 1055ca3d923bfdc59c655e15af298bded8da57c4 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Fri, 10 Apr 2026 14:19:12 +0100 Subject: [PATCH 2/9] docs(skills,docs): fix cron fields, module names, host, and links for v0.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Standardize cron expressions to 6-field (sec min hour day month weekday) across docs and skills; note 5-field and 7-field also accepted - Fix *Module names in Mermaid diagrams (architecture, iii-stream, todo-app) - Fix host: localhost → 127.0.0.1 in HTTP and stream docs - Fix "Queue module" → "Queue worker" in topic queues doc - Fix ExecModule/StreamModule refs in todo-app example - Fix cron references in skills (cron-scheduling, getting-started, workflow-orchestration) and all reference files (.js, .py, .rs) --- docs/advanced/architecture.mdx | 10 ++++---- docs/advanced/protocol.mdx | 2 +- docs/examples/cron.mdx | 28 ++++++++++++---------- docs/examples/multi-trigger.mdx | 6 ++--- docs/examples/state-management.mdx | 6 ++--- docs/examples/todo-app.mdx | 10 ++++---- docs/how-to/expose-http-endpoint.mdx | 2 +- docs/how-to/schedule-cron-task.mdx | 24 ++++++++++--------- docs/how-to/stream-realtime-data.mdx | 2 +- docs/how-to/trigger-actions.mdx | 6 ++--- docs/how-to/use-topic-queues.mdx | 2 +- docs/workers/iii-stream.mdx | 2 +- skills/iii-cron-scheduling/SKILL.md | 9 +++---- skills/iii-getting-started/SKILL.md | 2 +- skills/iii-workflow-orchestration/SKILL.md | 2 +- skills/references/cron-scheduling.js | 16 ++++++------- skills/references/cron-scheduling.py | 16 ++++++------- skills/references/cron-scheduling.rs | 16 ++++++------- 18 files changed, 83 insertions(+), 78 deletions(-) diff --git a/docs/advanced/architecture.mdx b/docs/advanced/architecture.mdx index ee4a597b7..ff805652a 100644 --- a/docs/advanced/architecture.mdx +++ b/docs/advanced/architecture.mdx @@ -26,11 +26,11 @@ graph TD Reg[Worker
Registry] subgraph "Core Modules" - API[RestApiModule] - Stream[StreamModule] - Log[OtelModule] - Queue[QueueModule] - Cron[CronModule] + API[iii-http] + Stream[iii-stream] + Log[iii-observability] + Queue[iii-queue] + Cron[iii-cron] end end diff --git a/docs/advanced/protocol.mdx b/docs/advanced/protocol.mdx index 5e1804a04..e76fbf1f3 100644 --- a/docs/advanced/protocol.mdx +++ b/docs/advanced/protocol.mdx @@ -84,7 +84,7 @@ Configure a trigger that maps to a function: // Trigger-specific configuration // For 'http': { api_path: '/path', http_method: 'GET' } // For 'durable:subscriber': { topic: 'topic.name' } - // For 'cron': { expression: '0 * * * *' } + // For 'cron': { expression: '0 0 * * * *' } // For 'log': { level: 'error' } } } diff --git a/docs/examples/cron.mdx b/docs/examples/cron.mdx index 3c88bd00d..522de3f7e 100644 --- a/docs/examples/cron.mdx +++ b/docs/examples/cron.mdx @@ -42,7 +42,7 @@ iii.registerFunction( iii.registerTrigger({ type: 'cron', function_id: 'cron.periodic_job', - config: { expression: '* * * * *' }, // every minute + config: { expression: '0 * * * * *' }, // every minute }) ``` @@ -76,7 +76,7 @@ iii.register_function("cron.periodic_job", periodic_job) iii.register_trigger({ "type": "cron", "function_id": "cron.periodic_job", - "config": {"expression": "* * * * *"}, # every minute + "config": {"expression": "0 * * * * *"}, # every minute }) ``` @@ -106,7 +106,7 @@ iii.register_function((RegisterFunctionMessage::with_id("cron.periodic_job".into }); iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.periodic_job".into(), config: json!({ - "expression": "* * * * *", + "expression": "0 * * * * *", }), metadata: None })?; ``` @@ -224,7 +224,7 @@ iii.registerFunction( iii.registerTrigger({ type: 'cron', function_id: 'cron.orders_sweep', - config: { expression: '*/5 * * * *' }, + config: { expression: '0 */5 * * * *' }, }) ``` @@ -269,7 +269,7 @@ def orders_sweep(_data) -> None: logger.info("Sweep complete", {"checked": len(orders), "swept": swept}) iii.register_function("cron.orders_sweep", orders_sweep) -iii.register_trigger({"type": "cron", "function_id": "cron.orders_sweep", "config": {"expression": "*/5 * * * *"}}) +iii.register_trigger({"type": "cron", "function_id": "cron.orders_sweep", "config": {"expression": "0 */5 * * * *"}}) ``` @@ -315,7 +315,7 @@ iii.register_function((RegisterFunctionMessage::with_id("cron.orders_sweep".into Ok(json!(null)) }); -iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.orders_sweep".into(), config: json!({ "expression": "*/5 * * * *" }), metadata: None })?; +iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.orders_sweep".into(), config: json!({ "expression": "0 */5 * * * *" }), metadata: None })?; ``` @@ -323,10 +323,12 @@ iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), functio ## Cron expression format -iii uses a six-field extended cron format. The optional leading field is seconds. +iii uses a six-field cron format: `second minute hour day month weekday`. + +5-field expressions (without seconds) and 7-field expressions (with a trailing year) are also accepted, but 6-field is the recommended standard. ``` -┌──────────── second (0-59, optional) +┌──────────── second (0-59) │ ┌────────── minute (0-59) │ │ ┌──────── hour (0-23) │ │ │ ┌────── day of month (1-31) @@ -338,11 +340,11 @@ iii uses a six-field extended cron format. The optional leading field is seconds | Expression | Meaning | |---|---| -| `* * * * *` | Every minute | -| `0 * * * *` | Every hour | -| `0 9 * * 1-5` | 09:00 on weekdays | -| `*/5 * * * *` | Every 5 minutes | -| `0/5 * * * * *` | Every 5 seconds (with seconds field) | +| `0 * * * * *` | Every minute | +| `0 0 * * * *` | Every hour | +| `0 0 9 * * 1-5` | 09:00 on weekdays | +| `0 */5 * * * *` | Every 5 minutes | +| `*/5 * * * * *` | Every 5 seconds | ## Key concepts diff --git a/docs/examples/multi-trigger.mdx b/docs/examples/multi-trigger.mdx index dea6ffc16..7066e4d82 100644 --- a/docs/examples/multi-trigger.mdx +++ b/docs/examples/multi-trigger.mdx @@ -116,7 +116,7 @@ iii.registerTrigger({ iii.registerTrigger({ type: 'cron', function_id: 'orders.handle', - config: { expression: '* * * * *' }, + config: { expression: '* * * * * *' }, }) ``` @@ -212,7 +212,7 @@ iii.register_trigger({"type": "http", "function_id": "orders.handle", iii.register_trigger({"type": "durable:subscriber", "function_id": "orders.handle", "config": {"topic": "order.created"}}) iii.register_trigger({"type": "cron", "function_id": "orders.handle", - "config": {"expression": "* * * * *"}}) + "config": {"expression": "* * * * * *"}}) ``` @@ -331,7 +331,7 @@ iii.register_trigger(RegisterTriggerInput { iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "orders.handle".into(), - config: json!({ "expression": "* * * * *" }), + config: json!({ "expression": "* * * * * *" }), metadata: None, })?; ``` diff --git a/docs/examples/state-management.mdx b/docs/examples/state-management.mdx index 0407b1434..c3732fae9 100644 --- a/docs/examples/state-management.mdx +++ b/docs/examples/state-management.mdx @@ -281,7 +281,7 @@ iii.registerFunction( iii.registerTrigger({ type: 'cron', function_id: 'cron.orders_audit', - config: { expression: '*/5 * * * *' }, + config: { expression: '0 */5 * * * *' }, }) ``` @@ -316,7 +316,7 @@ def orders_audit(_data) -> None: iii.register_function("cron.orders_audit", orders_audit) -iii.register_trigger({"type": "cron", "function_id": "cron.orders_audit", "config": {"expression": "*/5 * * * *"}}) +iii.register_trigger({"type": "cron", "function_id": "cron.orders_audit", "config": {"expression": "0 */5 * * * *"}}) ``` @@ -364,7 +364,7 @@ iii.register_function( iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.orders_audit".into(), - config: json!({ "expression": "*/5 * * * *" }), + config: json!({ "expression": "0 */5 * * * *" }), metadata: None, })?; ``` diff --git a/docs/examples/todo-app.mdx b/docs/examples/todo-app.mdx index dfe62ecac..1b322ebeb 100644 --- a/docs/examples/todo-app.mdx +++ b/docs/examples/todo-app.mdx @@ -14,7 +14,7 @@ graph LR Browser["Browser (iii-browser-sdk)"] -->|"single WebSocket"| RBAC["RBAC Port :3111"] RBAC -->|"auth + expose"| Engine["iii Engine"] APIWorker["API Worker (iii-sdk)"] -->|"ws :49134"| Engine - Engine -->|"stream ops"| Stream["StreamModule :3112 (file-based KvStore)"] + Engine -->|"stream ops"| Stream["iii-stream :3112 (file-based KvStore)"] Engine -->|"stream change events"| Browser ``` @@ -61,10 +61,10 @@ workers: | --- | --- | --- | | **iii-worker-manager** | 49134 | Internal port. The API worker connects here to register functions. | | **iii-worker-manager (RBAC)** | 3111 | Public-facing port. The browser connects here. RBAC controls which functions are exposed and runs an auth function on every new connection. | -| **ExecModule** | — | Runs `pnpm dev` to start the API worker process. | -| **StreamModule** | 3112 | Manages stream state with a file-based KvStore adapter. The engine routes `stream::get`, `stream::set`, `stream::delete`, and `stream::list` to this module. | +| **iii-exec** | — | Runs `pnpm dev` to start the API worker process. | +| **iii-stream** | 3112 | Manages stream state with a file-based KvStore adapter. The engine routes `stream::get`, `stream::set`, `stream::delete`, and `stream::list` to this module. | -The RBAC configuration on port `3111` references `todo-project::auth-function` and explicitly lists the five functions the browser is allowed to call. See [Worker RBAC](/docs/how-to/worker-rbac) for the full reference. +The RBAC configuration on port `3111` references `todo-project::auth-function` and explicitly lists the five functions the browser is allowed to call. See [Worker RBAC](/how-to/worker-rbac) for the full reference. ## Backend @@ -381,6 +381,6 @@ All CRUD operations (`addTodo`, `toggleTodo`, `deleteTodo`) call `iii.trigger` w - **Single connection** — The browser opens one WebSocket to the RBAC port. Function calls and real-time stream events flow over the same connection. - **No HTTP routes** — The API worker registers plain iii functions. The browser invokes them directly via `iii.trigger`. There is no REST layer. -- **RBAC** — The engine's iii-worker-manager supports auth functions, expose lists, and middleware. This example uses a simple auth function that creates a session. See [Worker RBAC](/docs/how-to/worker-rbac) for the full reference. +- **RBAC** — The engine's iii-worker-manager supports auth functions, expose lists, and middleware. This example uses a simple auth function that creates a session. See [Worker RBAC](/how-to/worker-rbac) for the full reference. - **Engine-managed streams** — The `iii-stream` worker handles persistence (file-based KvStore in this example). The API worker reads and writes through `stream::*` function triggers — no custom stream implementation required. - **Session isolation** — The auth function returns a `function_registration_prefix`. The engine prefixes every function registered by that browser session, so multiple clients can register `ui::on-todo-change` without colliding. diff --git a/docs/how-to/expose-http-endpoint.mdx b/docs/how-to/expose-http-endpoint.mdx index 9dba23552..21bd7693b 100644 --- a/docs/how-to/expose-http-endpoint.mdx +++ b/docs/how-to/expose-http-endpoint.mdx @@ -17,7 +17,7 @@ Make sure `iii-config.yaml` has the REST API worker enabled: - name: iii-http config: port: 3111 - host: localhost + host: 127.0.0.1 default_timeout: 30000 concurrency_request_limit: 1024 cors: diff --git a/docs/how-to/schedule-cron-task.mdx b/docs/how-to/schedule-cron-task.mdx index 5f49383ac..e10242547 100644 --- a/docs/how-to/schedule-cron-task.mdx +++ b/docs/how-to/schedule-cron-task.mdx @@ -89,7 +89,7 @@ async fn main() -> Result<(), Box> { iii.registerTrigger({ type: 'cron', function_id: 'cleanup::expired-sessions', - config: { expression: '* * * * * * *' }, // runs every second + config: { expression: '* * * * * *' }, // runs every second }) ``` @@ -98,7 +98,7 @@ iii.registerTrigger({ iii.register_trigger({ "type": "cron", "function_id": "cleanup::expired-sessions", - "config": {"expression": "* * * * * * *"}, # runs every second + "config": {"expression": "* * * * * *"}, # runs every second }) ``` @@ -107,26 +107,28 @@ iii.register_trigger({ iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cleanup::expired-sessions".into(), - config: json!({ "expression": "* * * * * * *" }), // runs every second + config: json!({ "expression": "* * * * * *" }), // runs every second metadata: None, })?; ``` -This runs the Function every second. The `expression` field uses a 7-field cron format with seconds support (`second minute hour day month weekday year`). +This runs the Function every second. The `expression` field uses a 6-field cron format (`second minute hour day month weekday`). + +5-field expressions (without seconds) and 7-field expressions (with a trailing year) are also accepted, but 6-field is the recommended standard. ### Common schedules | Expression | Frequency | |------------|-----------| -| `* * * * * * *` | Every second | -| `0 * * * * * *` | Every minute | -| `0 */5 * * * * *` | Every 5 minutes | -| `0 0 * * * * *` | Every hour | -| `0 0 */6 * * * *` | Every 6 hours | -| `0 0 0 * * * *` | Daily at midnight | -| `0 0 9 * * 1 *` | Every Monday at 9 AM | +| `* * * * * *` | Every second | +| `0 * * * * *` | Every minute | +| `0 */5 * * * *` | Every 5 minutes | +| `0 0 * * * *` | Every hour | +| `0 0 */6 * * *` | Every 6 hours | +| `0 0 0 * * *` | Daily at midnight | +| `0 0 9 * * 1` | Every Monday at 9 AM | ## Result diff --git a/docs/how-to/stream-realtime-data.mdx b/docs/how-to/stream-realtime-data.mdx index ec520c9cf..f0b9065a5 100644 --- a/docs/how-to/stream-realtime-data.mdx +++ b/docs/how-to/stream-realtime-data.mdx @@ -16,7 +16,7 @@ workers: - name: iii-stream config: port: ${STREAM_PORT:3112} - host: localhost + host: 127.0.0.1 adapter: name: kv config: diff --git a/docs/how-to/trigger-actions.mdx b/docs/how-to/trigger-actions.mdx index 1deb38d53..ee82f82df 100644 --- a/docs/how-to/trigger-actions.mdx +++ b/docs/how-to/trigger-actions.mdx @@ -644,7 +644,7 @@ iii.registerFunction('etl::extract', async () => { iii.registerTrigger({ type: 'cron', function_id: 'etl::extract', - config: { expression: '0 * * * *' }, + config: { expression: '0 0 * * * *' }, }) ``` @@ -681,7 +681,7 @@ fn = iii.register_function("etl::extract", extract) iii.register_trigger({ "type": "cron", "function_id": fn.id, - "config": {"expression": "0 * * * *"}, + "config": {"expression": "0 0 * * * *"}, }) ``` @@ -727,7 +727,7 @@ iii.register_function(reg); iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "etl::extract".into(), - config: json!({ "expression": "0 * * * *" }), + config: json!({ "expression": "0 0 * * * *" }), metadata: None, })?; ``` diff --git a/docs/how-to/use-topic-queues.mdx b/docs/how-to/use-topic-queues.mdx index 2c6c62b1c..0201f0655 100644 --- a/docs/how-to/use-topic-queues.mdx +++ b/docs/how-to/use-topic-queues.mdx @@ -7,7 +7,7 @@ description: 'Subscribe multiple functions to a topic so every published message Subscribe multiple functions to a topic so that every published message fans out to all subscribers, with each function processing its copy independently. For help deciding between topic-based and named queues, see [When to use which](/workers/iii-queue#when-to-use-which). -## Enable the Queue module +## Enable the Queue worker ```yaml title="iii-config.yaml" workers: diff --git a/docs/workers/iii-stream.mdx b/docs/workers/iii-stream.mdx index 6c50a216a..fdefcb591 100644 --- a/docs/workers/iii-stream.mdx +++ b/docs/workers/iii-stream.mdx @@ -13,7 +13,7 @@ iii-stream ```mermaid graph LR - Client[Client] -->|WebSocket| Stream[StreamModule] + Client[Client] -->|WebSocket| Stream[iii-stream] Stream -->|Auth| Engine[Engine] Engine -->|Context| Stream Stream -->|Connected| Client diff --git a/skills/iii-cron-scheduling/SKILL.md b/skills/iii-cron-scheduling/SKILL.md index aa7a6a562..f52a0d820 100644 --- a/skills/iii-cron-scheduling/SKILL.md +++ b/skills/iii-cron-scheduling/SKILL.md @@ -1,7 +1,7 @@ --- name: iii-cron-scheduling description: >- - Registers cron triggers with 7-field expressions to run functions on + Registers cron triggers with 6-field expressions to run functions on recurring schedules. Use when scheduling periodic jobs, timed automation, crontab replacements, cleanup routines, report generation, health checks, batch processing, or any task that should run every N seconds, minutes, hours, @@ -16,7 +16,8 @@ Comparable to: node-cron, APScheduler, crontab Use the concepts below when they fit the task. Not every scheduled job needs all of them. -- Cron expressions use a **7-field format**: `second minute hour day month weekday year` +- Cron expressions use a **6-field format**: `second minute hour day month weekday` +- The cron parser also accepts 5-field (no seconds) and 7-field (with year) expressions, but 6-field is the recommended format. - **iii-cron** evaluates expressions and fires triggers on schedule - Handlers should be **fast** — enqueue heavy work to a queue instead of blocking the cron handler - Each cron trigger binds one expression to one function @@ -35,7 +36,7 @@ Use the concepts below when they fit the task. Not every scheduled job needs all | ----------------------------------------- | ---------------------------------------- | | `registerFunction` | Define the handler for the scheduled job | | `registerTrigger({ type: 'cron' })` | Bind a cron expression to a function | -| `config: { expression: '0 0 9 * * * *' }` | Cron schedule in 7-field format | +| `config: { expression: '0 0 9 * * *' }` | Cron schedule in 6-field format | ## Reference Implementation @@ -59,7 +60,7 @@ Code using this pattern commonly includes, when relevant: Use the adaptations below when they apply to the task. -- Adjust the 7-field expression to match your schedule (e.g. `0 0 */6 * * * *` for every 6 hours) +- Adjust the 6-field expression to match your schedule (e.g. `0 0 */6 * * *` for every 6 hours) - Keep the cron handler lightweight — use it to validate and enqueue, not to do the heavy lifting - For jobs that need state (e.g. last-run timestamp), combine with `iii-state-management` - Multiple cron triggers can feed the same queue for fan-in processing diff --git a/skills/iii-getting-started/SKILL.md b/skills/iii-getting-started/SKILL.md index 5a47fc515..90dcdcd57 100644 --- a/skills/iii-getting-started/SKILL.md +++ b/skills/iii-getting-started/SKILL.md @@ -159,7 +159,7 @@ Skills teach your agent how to use every iii primitive — HTTP endpoints, cron - Add more functions to the same worker — each gets its own `registerFunction` + `registerTrigger` calls - Use `::` separator for function IDs to namespace them: `orders::create`, `orders::validate` -- Add cron triggers with `{ type: 'cron', config: { expression: '0 0 9 * * * *' } }` (7-field, includes seconds) +- Add cron triggers with `{ type: 'cron', config: { expression: '0 0 9 * * *' } }` (6-field, includes seconds) - Add queue triggers with `{ type: 'durable:subscriber', config: { topic: 'my-queue' } }` - Use `iii.trigger()` to invoke other functions from within a function - Use `state::get` / `state::set` to persist data across function calls diff --git a/skills/iii-workflow-orchestration/SKILL.md b/skills/iii-workflow-orchestration/SKILL.md index b9f48662e..61debb582 100644 --- a/skills/iii-workflow-orchestration/SKILL.md +++ b/skills/iii-workflow-orchestration/SKILL.md @@ -77,7 +77,7 @@ Use the adaptations below when they apply to the task. - The `trackStep` helper pattern (state update + stream event) is reusable for any pipeline - Failed jobs exhaust retries and move to a DLQ — see the [dead-letter-queues HOWTO](https://iii.dev/docs/how-to/dead-letter-queues) - DLQ support for named queues is provided by the Builtin and RabbitMQ adapters (Redis is pub/sub only) -- Cron expressions use 7-position numeric format: `0 0 * * * * *` (every hour) +- Cron expressions use 6-field numeric format: `0 0 * * * *` (every hour) ## Engine Configuration diff --git a/skills/references/cron-scheduling.js b/skills/references/cron-scheduling.js index ace2bb1d2..78236ce27 100644 --- a/skills/references/cron-scheduling.js +++ b/skills/references/cron-scheduling.js @@ -2,8 +2,8 @@ * Pattern: Cron Scheduling * Comparable to: node-cron, APScheduler, crontab * - * Schedules recurring tasks using 7-field cron expressions: - * second minute hour day month weekday year + * Schedules recurring tasks using 6-field cron expressions: + * second minute hour day month weekday * * Cron handlers should be fast — enqueue heavy work to a queue. * @@ -19,7 +19,7 @@ const iii = registerWorker(process.env.III_ENGINE_URL || 'ws://localhost:49134', // --------------------------------------------------------------------------- // Hourly cleanup — runs at the top of every hour -// Cron: 0 0 * * * * * (second=0, minute=0, every hour) +// Cron: 0 0 * * * * (second=0, minute=0, every hour) // --------------------------------------------------------------------------- iii.registerFunction('cron::hourly-cleanup', async () => { const logger = new Logger() @@ -53,12 +53,12 @@ iii.registerFunction('cron::hourly-cleanup', async () => { iii.registerTrigger({ type: 'cron', function_id: 'cron::hourly-cleanup', - config: { expression: '0 0 * * * * *' }, + config: { expression: '0 0 * * * *' }, }) // --------------------------------------------------------------------------- // Daily report — runs at midnight every day -// Cron: 0 0 0 * * * * (second=0, minute=0, hour=0, every day) +// Cron: 0 0 0 * * * (second=0, minute=0, hour=0, every day) // --------------------------------------------------------------------------- iii.registerFunction('cron::daily-report', async () => { const logger = new Logger() @@ -98,12 +98,12 @@ iii.registerFunction('cron::daily-report', async () => { iii.registerTrigger({ type: 'cron', function_id: 'cron::daily-report', - config: { expression: '0 0 0 * * * *' }, + config: { expression: '0 0 0 * * *' }, }) // --------------------------------------------------------------------------- // Health check — runs every 5 minutes -// Cron: 0 */5 * * * * * (second=0, every 5th minute) +// Cron: 0 */5 * * * * (second=0, every 5th minute) // --------------------------------------------------------------------------- iii.registerFunction('cron::health-check', async () => { const logger = new Logger() @@ -144,7 +144,7 @@ iii.registerFunction('cron::health-check', async () => { iii.registerTrigger({ type: 'cron', function_id: 'cron::health-check', - config: { expression: '0 */5 * * * * *' }, + config: { expression: '0 */5 * * * *' }, }) // --------------------------------------------------------------------------- diff --git a/skills/references/cron-scheduling.py b/skills/references/cron-scheduling.py index 82636e22b..0b789a3d1 100644 --- a/skills/references/cron-scheduling.py +++ b/skills/references/cron-scheduling.py @@ -2,8 +2,8 @@ Pattern: Cron Scheduling Comparable to: node-cron, APScheduler, crontab -Schedules recurring tasks using 7-field cron expressions: - second minute hour day month weekday year +Schedules recurring tasks using 6-field cron expressions: + second minute hour day month weekday Cron handlers should be fast — enqueue heavy work to a queue. @@ -25,7 +25,7 @@ # --- # Hourly cleanup — runs at the top of every hour -# Cron: 0 0 * * * * * (second=0, minute=0, every hour) +# Cron: 0 0 * * * * (second=0, minute=0, every hour) # --- @@ -61,12 +61,12 @@ async def hourly_cleanup(data): iii.register_trigger({ "type": "cron", "function_id": "cron::hourly-cleanup", - "config": {"expression": "0 0 * * * * *"}, + "config": {"expression": "0 0 * * * *"}, }) # --- # Daily report — runs at midnight every day -# Cron: 0 0 0 * * * * (second=0, minute=0, hour=0, every day) +# Cron: 0 0 0 * * * (second=0, minute=0, hour=0, every day) # --- @@ -108,12 +108,12 @@ async def daily_report(data): iii.register_trigger({ "type": "cron", "function_id": "cron::daily-report", - "config": {"expression": "0 0 0 * * * *"}, + "config": {"expression": "0 0 0 * * *"}, }) # --- # Health check — runs every 5 minutes -# Cron: 0 */5 * * * * * (second=0, every 5th minute) +# Cron: 0 */5 * * * * (second=0, every 5th minute) # --- @@ -154,7 +154,7 @@ async def health_check(data): iii.register_trigger({ "type": "cron", "function_id": "cron::health-check", - "config": {"expression": "0 */5 * * * * *"}, + "config": {"expression": "0 */5 * * * *"}, }) # --- diff --git a/skills/references/cron-scheduling.rs b/skills/references/cron-scheduling.rs index b9f253d63..007229997 100644 --- a/skills/references/cron-scheduling.rs +++ b/skills/references/cron-scheduling.rs @@ -1,8 +1,8 @@ /// Pattern: Cron Scheduling /// Comparable to: node-cron, APScheduler, crontab /// -/// Schedules recurring tasks using 7-field cron expressions: -/// second minute hour day month weekday year +/// Schedules recurring tasks using 6-field cron expressions: +/// second minute hour day month weekday /// /// Cron handlers should be fast - enqueue heavy work to a queue. @@ -28,7 +28,7 @@ fn main() { // --- // Hourly cleanup - runs at the top of every hour - // Cron: 0 0 * * * * * (second=0, minute=0, every hour) + // Cron: 0 0 * * * * (second=0, minute=0, every hour) // --- let iii_clone = iii.clone(); iii.register_function( @@ -83,14 +83,14 @@ fn main() { ); iii.register_trigger( - IIITrigger::Cron(CronTriggerConfig::new("0 0 * * * * *")) + IIITrigger::Cron(CronTriggerConfig::new("0 0 * * * *")) .for_function("cron::hourly-cleanup"), ) .expect("failed"); // --- // Daily report - runs at midnight every day - // Cron: 0 0 0 * * * * (second=0, minute=0, hour=0, every day) + // Cron: 0 0 0 * * * (second=0, minute=0, hour=0, every day) // --- let iii_clone = iii.clone(); iii.register_function( @@ -159,14 +159,14 @@ fn main() { ); iii.register_trigger( - IIITrigger::Cron(CronTriggerConfig::new("0 0 0 * * * *")) + IIITrigger::Cron(CronTriggerConfig::new("0 0 0 * * *")) .for_function("cron::daily-report"), ) .expect("failed"); // --- // Health check - runs every 5 minutes - // Cron: 0 */5 * * * * * (second=0, every 5th minute) + // Cron: 0 */5 * * * * (second=0, every 5th minute) // --- let iii_clone = iii.clone(); iii.register_function( @@ -221,7 +221,7 @@ fn main() { ); iii.register_trigger( - IIITrigger::Cron(CronTriggerConfig::new("0 */5 * * * * *")) + IIITrigger::Cron(CronTriggerConfig::new("0 */5 * * * *")) .for_function("cron::health-check"), ) .expect("failed"); From f88ccb59efbf7f31dd7d22f3b13b89d4db0c8531 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Fri, 10 Apr 2026 14:50:40 +0100 Subject: [PATCH 3/9] docs: fix curl host, Logger::new(), Queue module wording, and adapter naming - Fix curl example to use 127.0.0.1 matching configured host (expose-http-endpoint) - Fix WebSocket client URL to use 127.0.0.1 (stream-realtime-data) - Fix Rust Logger()) syntax errors to Logger::new() in 3 cron examples - Replace "Queue module" with "Queue worker" across v0.11 docs - Fix state-management skill adapter wording (kv vs redis are separate adapters) --- docs/advanced/adapters.mdx | 2 +- docs/examples/cron.mdx | 6 +++--- docs/how-to/expose-http-endpoint.mdx | 2 +- docs/how-to/stream-realtime-data.mdx | 2 +- docs/how-to/use-functions-and-triggers.mdx | 2 +- docs/how-to/use-named-queues.mdx | 10 +++++----- docs/how-to/use-topic-queues.mdx | 6 +++--- docs/workers/iii-queue.mdx | 2 +- skills/iii-state-management/SKILL.md | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/advanced/adapters.mdx b/docs/advanced/adapters.mdx index 7c5c256a7..0cbb23e94 100644 --- a/docs/advanced/adapters.mdx +++ b/docs/advanced/adapters.mdx @@ -62,7 +62,7 @@ adapter: ``` - For retry behavior, dead-letter queues, and full config reference, see the [Queue module](/workers/iii-queue). + For retry behavior, dead-letter queues, and full config reference, see the [Queue worker](/workers/iii-queue). --- diff --git a/docs/examples/cron.mdx b/docs/examples/cron.mdx index 522de3f7e..88b0af83a 100644 --- a/docs/examples/cron.mdx +++ b/docs/examples/cron.mdx @@ -90,7 +90,7 @@ use serde_json::json; let iii = register_worker("ws://127.0.0.1:49134", InitOptions::default()); iii.register_function((RegisterFunctionMessage::with_id("cron.periodic_job".into()), |_input| async move { - let logger = Logger()); + let logger = Logger::new(); logger.info("Periodic job fired", None); @@ -161,7 +161,7 @@ use iii_sdk::{Logger, RegisterFunctionMessage, RegisterTriggerInput}; use serde_json::json; iii.register_function((RegisterFunctionMessage::with_id("job.handle_tick".into()), |input| async move { - let logger = Logger()); + let logger = Logger::new(); logger.info("Periodic job processed", Some(input)); Ok(json!(null)) @@ -280,7 +280,7 @@ use iii_sdk::{Logger, TriggerRequest, TriggerAction, RegisterFunctionMessage, Re use serde_json::json; iii.register_function((RegisterFunctionMessage::with_id("cron.orders_sweep".into()), |_input| async move { - let logger = Logger()); + let logger = Logger::new(); let orders_val = iii.trigger(TriggerRequest::new("state::list", json!({ "scope": "orders" }))).await?; let orders = orders_val.as_array().cloned().unwrap_or_default(); diff --git a/docs/how-to/expose-http-endpoint.mdx b/docs/how-to/expose-http-endpoint.mdx index 21bd7693b..62f92074a 100644 --- a/docs/how-to/expose-http-endpoint.mdx +++ b/docs/how-to/expose-http-endpoint.mdx @@ -142,7 +142,7 @@ iii.register_trigger(RegisterTriggerInput { ### 4. Try it ```bash -curl -X POST http://localhost:3111/users \ +curl -X POST http://127.0.0.1:3111/users \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}' ``` diff --git a/docs/how-to/stream-realtime-data.mdx b/docs/how-to/stream-realtime-data.mdx index f0b9065a5..901ca4ddf 100644 --- a/docs/how-to/stream-realtime-data.mdx +++ b/docs/how-to/stream-realtime-data.mdx @@ -253,7 +253,7 @@ println!("Messages: {:?}", messages); Clients connect to the stream WebSocket endpoint to receive live updates: ```javascript title="client.js" -const ws = new WebSocket('ws://localhost:3112/stream/chat/room-123') +const ws = new WebSocket('ws://127.0.0.1:3112/stream/chat/room-123') ws.onmessage = (event) => { const update = JSON.parse(event.data) diff --git a/docs/how-to/use-functions-and-triggers.mdx b/docs/how-to/use-functions-and-triggers.mdx index 574f814a3..8b5668c04 100644 --- a/docs/how-to/use-functions-and-triggers.mdx +++ b/docs/how-to/use-functions-and-triggers.mdx @@ -620,7 +620,7 @@ async fn main() -> Result<(), Box> { Node `trigger({ function_id: 'iii::durable::publish', payload: { topic: 'user.created', data: {...} } })`, Python `trigger({'function_id': 'iii::durable::publish', 'payload': {'topic': 'user.created', 'data': {...}}})`, Rust `trigger(TriggerRequest::new("iii::durable::publish", json!({"topic": "user.created", "data": {...}})))`. - See the [Queue module](/workers/iii-queue) for details. + See the [Queue worker](/workers/iii-queue) for details. ## Trigger Types diff --git a/docs/how-to/use-named-queues.mdx b/docs/how-to/use-named-queues.mdx index f99ae1efe..1d96bf553 100644 --- a/docs/how-to/use-named-queues.mdx +++ b/docs/how-to/use-named-queues.mdx @@ -11,7 +11,7 @@ Enqueue jobs to a specific function by name with configurable retries, concurren Named queues use the `Enqueue` trigger action. Refer to [Trigger Actions](./trigger-actions) to learn more. -## Enable the Queue module +## Enable the Queue worker ```yaml title="iii-config.yaml" workers: @@ -30,7 +30,7 @@ workers: ``` - For complete configuration options please refer to [Queue module reference](/workers/iii-queue#configuration). + For complete configuration options please refer to [Queue worker reference](/workers/iii-queue#configuration). ## Steps @@ -68,7 +68,7 @@ workers: FIFO queues enforce ordering in a queue and they require a `message_group_field` to order on. Queues can also set `backoff_ms` for exponential retry delays. See more on this in the steps below. - For full configuration options refer to the [Queue module reference](/workers/iii-queue#queue-configuration). + For full configuration options refer to the [Queue worker reference](/workers/iii-queue#queue-configuration). ### 2. Enqueue work via trigger action @@ -294,7 +294,7 @@ Use low concurrency to protect rate-limited APIs. Use high concurrency for embar Jobs are enqueued and acknowledged immediately — the caller receives a `messageReceiptId` without waiting for processing. The engine delivers each job to the target function, retries failures with exponential backoff, and routes exhausted jobs to the dead-letter queue. Standard queues process jobs concurrently; FIFO queues guarantee per-group ordering. - For a detailed comparison of standard and FIFO queue behavior — including processing model, ordering guarantees, and flow diagrams — see the [Queue module reference](/workers/iii-queue#standard-vs-fifo-queues). For retry and dead-letter flow, see [Retry and dead-letter flow](/workers/iii-queue#retry-and-dead-letter-flow). + For a detailed comparison of standard and FIFO queue behavior — including processing model, ordering guarantees, and flow diagrams — see the [Queue worker reference](/workers/iii-queue#standard-vs-fifo-queues). For retry and dead-letter flow, see [Retry and dead-letter flow](/workers/iii-queue#retry-and-dead-letter-flow). --- @@ -828,7 +828,7 @@ iii.register_function(reg); With `concurrency: 3`, at most three emails are in-flight at any time. Failed sends retry with exponential backoff (5s, 10s, 20s, 40s, 80s), protecting the SMTP provider from overload. - For adapter options (builtin, RabbitMQ, Redis), scenario-based recommendations, and the full queue configuration reference, see the [Queue module reference](/workers/iii-queue#adapter-comparison). + For adapter options (builtin, RabbitMQ, Redis), scenario-based recommendations, and the full queue configuration reference, see the [Queue worker reference](/workers/iii-queue#adapter-comparison). ## Remember diff --git a/docs/how-to/use-topic-queues.mdx b/docs/how-to/use-topic-queues.mdx index 0201f0655..86e7b31a1 100644 --- a/docs/how-to/use-topic-queues.mdx +++ b/docs/how-to/use-topic-queues.mdx @@ -26,7 +26,7 @@ workers: ``` - For complete configuration options please refer to [Queue module reference](/workers/iii-queue#configuration). + For complete configuration options please refer to [Queue worker reference](/workers/iii-queue#configuration). ## Steps @@ -485,7 +485,7 @@ iii.register_function(RegisterFunction::new_async("orders::create", move |req: V All three functions receive every `order.created` event independently. If `inventory::reserve` fails and retries, it does not affect `notify::email` or `analytics::track`. - For adapter options (builtin, RabbitMQ, Redis), scenario-based recommendations, and the full queue configuration reference, see the [Queue module reference](/workers/iii-queue#adapter-comparison). + For adapter options (builtin, RabbitMQ, Redis), scenario-based recommendations, and the full queue configuration reference, see the [Queue worker reference](/workers/iii-queue#adapter-comparison). ## Remember @@ -501,7 +501,7 @@ Producers publish to a topic and return immediately. The engine fans out each me Handle and redrive failed queue messages - + Full configuration reference for queues and adapters diff --git a/docs/workers/iii-queue.mdx b/docs/workers/iii-queue.mdx index 400d54327..d85a3501e 100644 --- a/docs/workers/iii-queue.mdx +++ b/docs/workers/iii-queue.mdx @@ -1,6 +1,6 @@ --- title: 'Queue' -description: 'Queue module for async job processing with named queues, retries, and dead-letter support.' +description: 'Queue worker for async job processing with named queues, retries, and dead-letter support.' --- A module for asynchronous job processing. It supports two modes: **topic-based queues** (register a consumer per topic, emit events) and **named queues** (enqueue function calls via `TriggerAction.Enqueue`, no trigger registration). diff --git a/skills/iii-state-management/SKILL.md b/skills/iii-state-management/SKILL.md index 5f87231ce..5d662dffa 100644 --- a/skills/iii-state-management/SKILL.md +++ b/skills/iii-state-management/SKILL.md @@ -74,7 +74,7 @@ Use the adaptations below when they apply to the task. ## Engine Configuration -iii-state must be enabled in iii-config.yaml with a KvStore adapter (file-based or Redis). See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. +iii-state must be enabled in iii-config.yaml with either the `kv` adapter (file-based or in-memory) or the separate `redis` adapter. See [../references/iii-config.yaml](../references/iii-config.yaml) for the full annotated config reference. ## Pattern Boundaries From fd55c521441875ab4a3d91a442e44a522e440922 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Fri, 10 Apr 2026 16:22:28 +0100 Subject: [PATCH 4/9] docs(skills,docs): standardize cron expressions to 7-field format Standardize all cron expressions across docs and skills to 7-field format (sec min hour day month weekday year) matching the Rust cron crate v0.15. The trailing year field uses * for any year. - Update cron format descriptions and ASCII diagrams to show 7 fields - Fix all 5-field and 6-field expressions to 7-field in examples, how-to guides, worker references, and skill reference files - Update common expressions tables in cron docs and skills --- docs/advanced/protocol.mdx | 2 +- docs/examples/cron.mdx | 31 +++++++++-------- docs/examples/multi-trigger.mdx | 6 ++-- docs/examples/state-management.mdx | 6 ++-- docs/how-to/schedule-cron-task.mdx | 24 ++++++------- docs/how-to/trigger-actions.mdx | 6 ++-- docs/how-to/use-functions-and-triggers.mdx | 6 ++-- docs/how-to/use-iii-in-the-browser.mdx | 2 +- docs/workers/iii-cron.mdx | 37 +++++++++++---------- skills/iii-cron-scheduling/SKILL.md | 10 +++--- skills/iii-getting-started/SKILL.md | 2 +- skills/iii-workflow-orchestration/SKILL.md | 2 +- skills/references/cron-scheduling.js | 16 ++++----- skills/references/cron-scheduling.py | 16 ++++----- skills/references/cron-scheduling.rs | 16 ++++----- skills/references/dead-letter-queues.py | 2 +- skills/references/functions-and-triggers.py | 2 +- skills/references/functions-and-triggers.rs | 2 +- skills/references/trigger-conditions.py | 2 +- 19 files changed, 94 insertions(+), 96 deletions(-) diff --git a/docs/advanced/protocol.mdx b/docs/advanced/protocol.mdx index e76fbf1f3..a2019a713 100644 --- a/docs/advanced/protocol.mdx +++ b/docs/advanced/protocol.mdx @@ -84,7 +84,7 @@ Configure a trigger that maps to a function: // Trigger-specific configuration // For 'http': { api_path: '/path', http_method: 'GET' } // For 'durable:subscriber': { topic: 'topic.name' } - // For 'cron': { expression: '0 0 * * * *' } + // For 'cron': { expression: '0 0 * * * * *' } // For 'log': { level: 'error' } } } diff --git a/docs/examples/cron.mdx b/docs/examples/cron.mdx index 88b0af83a..3081b05fe 100644 --- a/docs/examples/cron.mdx +++ b/docs/examples/cron.mdx @@ -42,7 +42,7 @@ iii.registerFunction( iii.registerTrigger({ type: 'cron', function_id: 'cron.periodic_job', - config: { expression: '0 * * * * *' }, // every minute + config: { expression: '0 * * * * * *' }, // every minute }) ``` @@ -76,7 +76,7 @@ iii.register_function("cron.periodic_job", periodic_job) iii.register_trigger({ "type": "cron", "function_id": "cron.periodic_job", - "config": {"expression": "0 * * * * *"}, # every minute + "config": {"expression": "0 * * * * * *"}, # every minute }) ``` @@ -106,7 +106,7 @@ iii.register_function((RegisterFunctionMessage::with_id("cron.periodic_job".into }); iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.periodic_job".into(), config: json!({ - "expression": "0 * * * * *", + "expression": "0 * * * * * *", }), metadata: None })?; ``` @@ -224,7 +224,7 @@ iii.registerFunction( iii.registerTrigger({ type: 'cron', function_id: 'cron.orders_sweep', - config: { expression: '0 */5 * * * *' }, + config: { expression: '0 */5 * * * * *' }, }) ``` @@ -269,7 +269,7 @@ def orders_sweep(_data) -> None: logger.info("Sweep complete", {"checked": len(orders), "swept": swept}) iii.register_function("cron.orders_sweep", orders_sweep) -iii.register_trigger({"type": "cron", "function_id": "cron.orders_sweep", "config": {"expression": "0 */5 * * * *"}}) +iii.register_trigger({"type": "cron", "function_id": "cron.orders_sweep", "config": {"expression": "0 */5 * * * * *"}}) ``` @@ -315,7 +315,7 @@ iii.register_function((RegisterFunctionMessage::with_id("cron.orders_sweep".into Ok(json!(null)) }); -iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.orders_sweep".into(), config: json!({ "expression": "0 */5 * * * *" }), metadata: None })?; +iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.orders_sweep".into(), config: json!({ "expression": "0 */5 * * * * *" }), metadata: None })?; ``` @@ -323,9 +323,7 @@ iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), functio ## Cron expression format -iii uses a six-field cron format: `second minute hour day month weekday`. - -5-field expressions (without seconds) and 7-field expressions (with a trailing year) are also accepted, but 6-field is the recommended standard. +iii uses a seven-field cron format: `second minute hour day month weekday year`. ``` ┌──────────── second (0-59) @@ -334,17 +332,18 @@ iii uses a six-field cron format: `second minute hour day month weekday`. │ │ │ ┌────── day of month (1-31) │ │ │ │ ┌──── month (1-12) │ │ │ │ │ ┌── day of week (0-7) -│ │ │ │ │ │ -* * * * * * +│ │ │ │ │ │ ┌ year (* for any) +│ │ │ │ │ │ │ +* * * * * * * ``` | Expression | Meaning | |---|---| -| `0 * * * * *` | Every minute | -| `0 0 * * * *` | Every hour | -| `0 0 9 * * 1-5` | 09:00 on weekdays | -| `0 */5 * * * *` | Every 5 minutes | -| `*/5 * * * * *` | Every 5 seconds | +| `0 * * * * * *` | Every minute | +| `0 0 * * * * *` | Every hour | +| `0 0 9 * * 1-5 *` | 09:00 on weekdays | +| `0 */5 * * * * *` | Every 5 minutes | +| `*/5 * * * * * *` | Every 5 seconds | ## Key concepts diff --git a/docs/examples/multi-trigger.mdx b/docs/examples/multi-trigger.mdx index 7066e4d82..544e8b2d9 100644 --- a/docs/examples/multi-trigger.mdx +++ b/docs/examples/multi-trigger.mdx @@ -116,7 +116,7 @@ iii.registerTrigger({ iii.registerTrigger({ type: 'cron', function_id: 'orders.handle', - config: { expression: '* * * * * *' }, + config: { expression: '* * * * * * *' }, }) ``` @@ -212,7 +212,7 @@ iii.register_trigger({"type": "http", "function_id": "orders.handle", iii.register_trigger({"type": "durable:subscriber", "function_id": "orders.handle", "config": {"topic": "order.created"}}) iii.register_trigger({"type": "cron", "function_id": "orders.handle", - "config": {"expression": "* * * * * *"}}) + "config": {"expression": "* * * * * * *"}}) ``` @@ -331,7 +331,7 @@ iii.register_trigger(RegisterTriggerInput { iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "orders.handle".into(), - config: json!({ "expression": "* * * * * *" }), + config: json!({ "expression": "* * * * * * *" }), metadata: None, })?; ``` diff --git a/docs/examples/state-management.mdx b/docs/examples/state-management.mdx index c3732fae9..b98bb3fc2 100644 --- a/docs/examples/state-management.mdx +++ b/docs/examples/state-management.mdx @@ -281,7 +281,7 @@ iii.registerFunction( iii.registerTrigger({ type: 'cron', function_id: 'cron.orders_audit', - config: { expression: '0 */5 * * * *' }, + config: { expression: '0 */5 * * * * *' }, }) ``` @@ -316,7 +316,7 @@ def orders_audit(_data) -> None: iii.register_function("cron.orders_audit", orders_audit) -iii.register_trigger({"type": "cron", "function_id": "cron.orders_audit", "config": {"expression": "0 */5 * * * *"}}) +iii.register_trigger({"type": "cron", "function_id": "cron.orders_audit", "config": {"expression": "0 */5 * * * * *"}}) ``` @@ -364,7 +364,7 @@ iii.register_function( iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cron.orders_audit".into(), - config: json!({ "expression": "0 */5 * * * *" }), + config: json!({ "expression": "0 */5 * * * * *" }), metadata: None, })?; ``` diff --git a/docs/how-to/schedule-cron-task.mdx b/docs/how-to/schedule-cron-task.mdx index e10242547..147a6b5b1 100644 --- a/docs/how-to/schedule-cron-task.mdx +++ b/docs/how-to/schedule-cron-task.mdx @@ -89,7 +89,7 @@ async fn main() -> Result<(), Box> { iii.registerTrigger({ type: 'cron', function_id: 'cleanup::expired-sessions', - config: { expression: '* * * * * *' }, // runs every second + config: { expression: '* * * * * * *' }, // runs every second }) ``` @@ -98,7 +98,7 @@ iii.registerTrigger({ iii.register_trigger({ "type": "cron", "function_id": "cleanup::expired-sessions", - "config": {"expression": "* * * * * *"}, # runs every second + "config": {"expression": "* * * * * * *"}, # runs every second }) ``` @@ -107,28 +107,26 @@ iii.register_trigger({ iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "cleanup::expired-sessions".into(), - config: json!({ "expression": "* * * * * *" }), // runs every second + config: json!({ "expression": "* * * * * * *" }), // runs every second metadata: None, })?; ``` -This runs the Function every second. The `expression` field uses a 6-field cron format (`second minute hour day month weekday`). - -5-field expressions (without seconds) and 7-field expressions (with a trailing year) are also accepted, but 6-field is the recommended standard. +This runs the Function every second. The `expression` field uses a 7-field cron format (`second minute hour day month weekday year`). ### Common schedules | Expression | Frequency | |------------|-----------| -| `* * * * * *` | Every second | -| `0 * * * * *` | Every minute | -| `0 */5 * * * *` | Every 5 minutes | -| `0 0 * * * *` | Every hour | -| `0 0 */6 * * *` | Every 6 hours | -| `0 0 0 * * *` | Daily at midnight | -| `0 0 9 * * 1` | Every Monday at 9 AM | +| `* * * * * * *` | Every second | +| `0 * * * * * *` | Every minute | +| `0 */5 * * * * *` | Every 5 minutes | +| `0 0 * * * * *` | Every hour | +| `0 0 */6 * * * *` | Every 6 hours | +| `0 0 0 * * * *` | Daily at midnight | +| `0 0 9 * * 1 *` | Every Monday at 9 AM | ## Result diff --git a/docs/how-to/trigger-actions.mdx b/docs/how-to/trigger-actions.mdx index ee82f82df..08e6f9ad8 100644 --- a/docs/how-to/trigger-actions.mdx +++ b/docs/how-to/trigger-actions.mdx @@ -644,7 +644,7 @@ iii.registerFunction('etl::extract', async () => { iii.registerTrigger({ type: 'cron', function_id: 'etl::extract', - config: { expression: '0 0 * * * *' }, + config: { expression: '0 0 * * * * *' }, }) ``` @@ -681,7 +681,7 @@ fn = iii.register_function("etl::extract", extract) iii.register_trigger({ "type": "cron", "function_id": fn.id, - "config": {"expression": "0 0 * * * *"}, + "config": {"expression": "0 0 * * * * *"}, }) ``` @@ -727,7 +727,7 @@ iii.register_function(reg); iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "etl::extract".into(), - config: json!({ "expression": "0 0 * * * *" }), + config: json!({ "expression": "0 0 * * * * *" }), metadata: None, })?; ``` diff --git a/docs/how-to/use-functions-and-triggers.mdx b/docs/how-to/use-functions-and-triggers.mdx index 8b5668c04..c233af32e 100644 --- a/docs/how-to/use-functions-and-triggers.mdx +++ b/docs/how-to/use-functions-and-triggers.mdx @@ -481,7 +481,7 @@ iii.registerFunction('math::aggregation', async () => { iii.registerTrigger({ type: 'cron', function_id: 'math::aggregation', - config: { expression: '0 */30 * * * *' }, // every 30 minutes + config: { expression: '0 */30 * * * * *' }, // every 30 minutes }); ``` @@ -502,7 +502,7 @@ iii.register_function("math::aggregation", aggregation) iii.register_trigger({ "type": "cron", "function_id": "math::aggregation", - "config": {"expression": "0 */30 * * * *"}, # every 30 minutes + "config": {"expression": "0 */30 * * * * *"}, # every 30 minutes }) ``` @@ -532,7 +532,7 @@ iii.register_function(reg); iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "math::aggregation".into(), - config: json!({ "expression": "0 */30 * * * *" }), // every 30 minutes + config: json!({ "expression": "0 */30 * * * * *" }), // every 30 minutes metadata: None, })?; ``` diff --git a/docs/how-to/use-iii-in-the-browser.mdx b/docs/how-to/use-iii-in-the-browser.mdx index 3019e0bb9..a87ff9971 100644 --- a/docs/how-to/use-iii-in-the-browser.mdx +++ b/docs/how-to/use-iii-in-the-browser.mdx @@ -112,7 +112,7 @@ iii.registerFunction('metrics::collect', async () => { iii.registerTrigger({ type: 'cron', function_id: 'metrics::collect', - config: { expression: '*/5 * * * * *' }, + config: { expression: '*/5 * * * * * *' }, }) ``` diff --git a/docs/workers/iii-cron.mdx b/docs/workers/iii-cron.mdx index 9e91dc50f..68ac9c2ef 100644 --- a/docs/workers/iii-cron.mdx +++ b/docs/workers/iii-cron.mdx @@ -77,14 +77,15 @@ This Worker adds a new Trigger Type: `cron`. Standard cron expression defining the schedule. Supports the following format: ``` - * * * * * * - │ │ │ │ │ │ - │ │ │ │ │ └─── Day of week (0–6, Sun=0) - │ │ │ │ └───── Month (1–12) - │ │ │ └─────── Day of month (1–31) - │ │ └───────── Hour (0–23) - │ └─────────── Minute (0–59) - └─────────── Second (0–59) + * * * * * * * + │ │ │ │ │ │ │ + │ │ │ │ │ │ └── Year (* for any) + │ │ │ │ │ └──── Day of week (0–6, Sun=0) + │ │ │ │ └────── Month (1–12) + │ │ │ └──────── Day of month (1–31) + │ │ └────────── Hour (0–23) + │ └──────────── Minute (0–59) + └──────────── Second (0–59) ``` @@ -126,7 +127,7 @@ const fn = iii.registerFunction( iii.registerTrigger({ type: 'cron', function_id: fn.id, - config: { expression: '0 0 2 * * *' }, + config: { expression: '0 0 2 * * * *' }, }) ``` @@ -137,7 +138,7 @@ def cleanup_old_data(event): return {} iii.register_function("jobs::cleanupOldData", cleanup_old_data) -iii.register_trigger({'type': 'cron', 'function_id': 'jobs::cleanupOldData', 'config': {'expression': '0 0 2 * * *'}}) +iii.register_trigger({'type': 'cron', 'function_id': 'jobs::cleanupOldData', 'config': {'expression': '0 0 2 * * * *'}}) ``` @@ -153,7 +154,7 @@ iii.register_function( iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), function_id: "jobs::cleanupOldData".into(), - config: json!({ "expression": "0 0 2 * * *" }), + config: json!({ "expression": "0 0 2 * * * *" }), metadata: None, })?; ``` @@ -164,13 +165,13 @@ iii.register_trigger(RegisterTriggerInput { | Expression | Description | | -------------------- | ---------------------------------------------- | -| `0 * * * * *` | Every minute | -| `0 0 * * * *` | Every hour | -| `0 0 0 * * *` | Every day at midnight | -| `0 0 0 * * 0` | Every Sunday at midnight | -| `0 0 2 * * *` | Every day at 2 AM | -| `0 */5 * * * *` | Every 5 minutes | -| `0 0 9-17 * * 1-5` | Every hour from 9 AM to 5 PM, Monday to Friday | +| `0 * * * * * *` | Every minute | +| `0 0 * * * * *` | Every hour | +| `0 0 0 * * * *` | Every day at midnight | +| `0 0 0 * * 0 *` | Every Sunday at midnight | +| `0 0 2 * * * *` | Every day at 2 AM | +| `0 */5 * * * * *` | Every 5 minutes | +| `0 0 9-17 * * 1-5 *` | Every hour from 9 AM to 5 PM, Monday to Friday | ## Distributed Execution diff --git a/skills/iii-cron-scheduling/SKILL.md b/skills/iii-cron-scheduling/SKILL.md index f52a0d820..cd18ad551 100644 --- a/skills/iii-cron-scheduling/SKILL.md +++ b/skills/iii-cron-scheduling/SKILL.md @@ -1,7 +1,7 @@ --- name: iii-cron-scheduling description: >- - Registers cron triggers with 6-field expressions to run functions on + Registers cron triggers with 7-field expressions to run functions on recurring schedules. Use when scheduling periodic jobs, timed automation, crontab replacements, cleanup routines, report generation, health checks, batch processing, or any task that should run every N seconds, minutes, hours, @@ -16,8 +16,8 @@ Comparable to: node-cron, APScheduler, crontab Use the concepts below when they fit the task. Not every scheduled job needs all of them. -- Cron expressions use a **6-field format**: `second minute hour day month weekday` -- The cron parser also accepts 5-field (no seconds) and 7-field (with year) expressions, but 6-field is the recommended format. +- Cron expressions use a **7-field format**: `second minute hour day month weekday year` +- The cron parser also accepts 5-field (no seconds) and 6-field (no year) expressions, but 7-field is the standard. - **iii-cron** evaluates expressions and fires triggers on schedule - Handlers should be **fast** — enqueue heavy work to a queue instead of blocking the cron handler - Each cron trigger binds one expression to one function @@ -36,7 +36,7 @@ Use the concepts below when they fit the task. Not every scheduled job needs all | ----------------------------------------- | ---------------------------------------- | | `registerFunction` | Define the handler for the scheduled job | | `registerTrigger({ type: 'cron' })` | Bind a cron expression to a function | -| `config: { expression: '0 0 9 * * *' }` | Cron schedule in 6-field format | +| `config: { expression: '0 0 9 * * * *' }` | Cron schedule in 7-field format | ## Reference Implementation @@ -60,7 +60,7 @@ Code using this pattern commonly includes, when relevant: Use the adaptations below when they apply to the task. -- Adjust the 6-field expression to match your schedule (e.g. `0 0 */6 * * *` for every 6 hours) +- Adjust the 7-field expression to match your schedule (e.g. `0 0 */6 * * * *` for every 6 hours) - Keep the cron handler lightweight — use it to validate and enqueue, not to do the heavy lifting - For jobs that need state (e.g. last-run timestamp), combine with `iii-state-management` - Multiple cron triggers can feed the same queue for fan-in processing diff --git a/skills/iii-getting-started/SKILL.md b/skills/iii-getting-started/SKILL.md index 90dcdcd57..07e3b9793 100644 --- a/skills/iii-getting-started/SKILL.md +++ b/skills/iii-getting-started/SKILL.md @@ -159,7 +159,7 @@ Skills teach your agent how to use every iii primitive — HTTP endpoints, cron - Add more functions to the same worker — each gets its own `registerFunction` + `registerTrigger` calls - Use `::` separator for function IDs to namespace them: `orders::create`, `orders::validate` -- Add cron triggers with `{ type: 'cron', config: { expression: '0 0 9 * * *' } }` (6-field, includes seconds) +- Add cron triggers with `{ type: 'cron', config: { expression: '0 0 9 * * * *' } }` (7-field: sec min hour day month weekday year) - Add queue triggers with `{ type: 'durable:subscriber', config: { topic: 'my-queue' } }` - Use `iii.trigger()` to invoke other functions from within a function - Use `state::get` / `state::set` to persist data across function calls diff --git a/skills/iii-workflow-orchestration/SKILL.md b/skills/iii-workflow-orchestration/SKILL.md index 61debb582..c6e8379cf 100644 --- a/skills/iii-workflow-orchestration/SKILL.md +++ b/skills/iii-workflow-orchestration/SKILL.md @@ -77,7 +77,7 @@ Use the adaptations below when they apply to the task. - The `trackStep` helper pattern (state update + stream event) is reusable for any pipeline - Failed jobs exhaust retries and move to a DLQ — see the [dead-letter-queues HOWTO](https://iii.dev/docs/how-to/dead-letter-queues) - DLQ support for named queues is provided by the Builtin and RabbitMQ adapters (Redis is pub/sub only) -- Cron expressions use 6-field numeric format: `0 0 * * * *` (every hour) +- Cron expressions use 7-field format: `0 0 * * * * *` (every hour) ## Engine Configuration diff --git a/skills/references/cron-scheduling.js b/skills/references/cron-scheduling.js index 78236ce27..ace2bb1d2 100644 --- a/skills/references/cron-scheduling.js +++ b/skills/references/cron-scheduling.js @@ -2,8 +2,8 @@ * Pattern: Cron Scheduling * Comparable to: node-cron, APScheduler, crontab * - * Schedules recurring tasks using 6-field cron expressions: - * second minute hour day month weekday + * Schedules recurring tasks using 7-field cron expressions: + * second minute hour day month weekday year * * Cron handlers should be fast — enqueue heavy work to a queue. * @@ -19,7 +19,7 @@ const iii = registerWorker(process.env.III_ENGINE_URL || 'ws://localhost:49134', // --------------------------------------------------------------------------- // Hourly cleanup — runs at the top of every hour -// Cron: 0 0 * * * * (second=0, minute=0, every hour) +// Cron: 0 0 * * * * * (second=0, minute=0, every hour) // --------------------------------------------------------------------------- iii.registerFunction('cron::hourly-cleanup', async () => { const logger = new Logger() @@ -53,12 +53,12 @@ iii.registerFunction('cron::hourly-cleanup', async () => { iii.registerTrigger({ type: 'cron', function_id: 'cron::hourly-cleanup', - config: { expression: '0 0 * * * *' }, + config: { expression: '0 0 * * * * *' }, }) // --------------------------------------------------------------------------- // Daily report — runs at midnight every day -// Cron: 0 0 0 * * * (second=0, minute=0, hour=0, every day) +// Cron: 0 0 0 * * * * (second=0, minute=0, hour=0, every day) // --------------------------------------------------------------------------- iii.registerFunction('cron::daily-report', async () => { const logger = new Logger() @@ -98,12 +98,12 @@ iii.registerFunction('cron::daily-report', async () => { iii.registerTrigger({ type: 'cron', function_id: 'cron::daily-report', - config: { expression: '0 0 0 * * *' }, + config: { expression: '0 0 0 * * * *' }, }) // --------------------------------------------------------------------------- // Health check — runs every 5 minutes -// Cron: 0 */5 * * * * (second=0, every 5th minute) +// Cron: 0 */5 * * * * * (second=0, every 5th minute) // --------------------------------------------------------------------------- iii.registerFunction('cron::health-check', async () => { const logger = new Logger() @@ -144,7 +144,7 @@ iii.registerFunction('cron::health-check', async () => { iii.registerTrigger({ type: 'cron', function_id: 'cron::health-check', - config: { expression: '0 */5 * * * *' }, + config: { expression: '0 */5 * * * * *' }, }) // --------------------------------------------------------------------------- diff --git a/skills/references/cron-scheduling.py b/skills/references/cron-scheduling.py index 0b789a3d1..82636e22b 100644 --- a/skills/references/cron-scheduling.py +++ b/skills/references/cron-scheduling.py @@ -2,8 +2,8 @@ Pattern: Cron Scheduling Comparable to: node-cron, APScheduler, crontab -Schedules recurring tasks using 6-field cron expressions: - second minute hour day month weekday +Schedules recurring tasks using 7-field cron expressions: + second minute hour day month weekday year Cron handlers should be fast — enqueue heavy work to a queue. @@ -25,7 +25,7 @@ # --- # Hourly cleanup — runs at the top of every hour -# Cron: 0 0 * * * * (second=0, minute=0, every hour) +# Cron: 0 0 * * * * * (second=0, minute=0, every hour) # --- @@ -61,12 +61,12 @@ async def hourly_cleanup(data): iii.register_trigger({ "type": "cron", "function_id": "cron::hourly-cleanup", - "config": {"expression": "0 0 * * * *"}, + "config": {"expression": "0 0 * * * * *"}, }) # --- # Daily report — runs at midnight every day -# Cron: 0 0 0 * * * (second=0, minute=0, hour=0, every day) +# Cron: 0 0 0 * * * * (second=0, minute=0, hour=0, every day) # --- @@ -108,12 +108,12 @@ async def daily_report(data): iii.register_trigger({ "type": "cron", "function_id": "cron::daily-report", - "config": {"expression": "0 0 0 * * *"}, + "config": {"expression": "0 0 0 * * * *"}, }) # --- # Health check — runs every 5 minutes -# Cron: 0 */5 * * * * (second=0, every 5th minute) +# Cron: 0 */5 * * * * * (second=0, every 5th minute) # --- @@ -154,7 +154,7 @@ async def health_check(data): iii.register_trigger({ "type": "cron", "function_id": "cron::health-check", - "config": {"expression": "0 */5 * * * *"}, + "config": {"expression": "0 */5 * * * * *"}, }) # --- diff --git a/skills/references/cron-scheduling.rs b/skills/references/cron-scheduling.rs index 007229997..b9f253d63 100644 --- a/skills/references/cron-scheduling.rs +++ b/skills/references/cron-scheduling.rs @@ -1,8 +1,8 @@ /// Pattern: Cron Scheduling /// Comparable to: node-cron, APScheduler, crontab /// -/// Schedules recurring tasks using 6-field cron expressions: -/// second minute hour day month weekday +/// Schedules recurring tasks using 7-field cron expressions: +/// second minute hour day month weekday year /// /// Cron handlers should be fast - enqueue heavy work to a queue. @@ -28,7 +28,7 @@ fn main() { // --- // Hourly cleanup - runs at the top of every hour - // Cron: 0 0 * * * * (second=0, minute=0, every hour) + // Cron: 0 0 * * * * * (second=0, minute=0, every hour) // --- let iii_clone = iii.clone(); iii.register_function( @@ -83,14 +83,14 @@ fn main() { ); iii.register_trigger( - IIITrigger::Cron(CronTriggerConfig::new("0 0 * * * *")) + IIITrigger::Cron(CronTriggerConfig::new("0 0 * * * * *")) .for_function("cron::hourly-cleanup"), ) .expect("failed"); // --- // Daily report - runs at midnight every day - // Cron: 0 0 0 * * * (second=0, minute=0, hour=0, every day) + // Cron: 0 0 0 * * * * (second=0, minute=0, hour=0, every day) // --- let iii_clone = iii.clone(); iii.register_function( @@ -159,14 +159,14 @@ fn main() { ); iii.register_trigger( - IIITrigger::Cron(CronTriggerConfig::new("0 0 0 * * *")) + IIITrigger::Cron(CronTriggerConfig::new("0 0 0 * * * *")) .for_function("cron::daily-report"), ) .expect("failed"); // --- // Health check - runs every 5 minutes - // Cron: 0 */5 * * * * (second=0, every 5th minute) + // Cron: 0 */5 * * * * * (second=0, every 5th minute) // --- let iii_clone = iii.clone(); iii.register_function( @@ -221,7 +221,7 @@ fn main() { ); iii.register_trigger( - IIITrigger::Cron(CronTriggerConfig::new("0 */5 * * * *")) + IIITrigger::Cron(CronTriggerConfig::new("0 */5 * * * * *")) .for_function("cron::health-check"), ) .expect("failed"); diff --git a/skills/references/dead-letter-queues.py b/skills/references/dead-letter-queues.py index ccecdff18..4da6b6293 100644 --- a/skills/references/dead-letter-queues.py +++ b/skills/references/dead-letter-queues.py @@ -180,7 +180,7 @@ async def auto_redrive(data): iii.register_trigger({ "type": "cron", "function_id": "admin::auto-redrive", - "config": {"expression": "0 0 * * * *"}, + "config": {"expression": "0 0 * * * * *"}, }) diff --git a/skills/references/functions-and-triggers.py b/skills/references/functions-and-triggers.py index 238d8405b..df50ffc6a 100644 --- a/skills/references/functions-and-triggers.py +++ b/skills/references/functions-and-triggers.py @@ -73,7 +73,7 @@ async def daily_summary(_data): iii.register_trigger({ "type": "cron", "function_id": "reports::daily-summary", - "config": {"expression": "0 9 * * *"}, + "config": {"expression": "0 0 9 * * * *"}, }) # --------------------------------------------------------------------------- diff --git a/skills/references/functions-and-triggers.rs b/skills/references/functions-and-triggers.rs index ee14d64dd..9e381b6cc 100644 --- a/skills/references/functions-and-triggers.rs +++ b/skills/references/functions-and-triggers.rs @@ -121,7 +121,7 @@ async fn main() -> Result<(), Box> { ); iii.register_trigger( - IIITrigger::Cron(CronTriggerConfig::new("0 0 9 * * *")) + IIITrigger::Cron(CronTriggerConfig::new("0 0 9 * * * *")) .for_function("reports::daily-summary"), ) .expect("failed to register cron trigger"); diff --git a/skills/references/trigger-conditions.py b/skills/references/trigger-conditions.py index d7e819a4d..7aeca6825 100644 --- a/skills/references/trigger-conditions.py +++ b/skills/references/trigger-conditions.py @@ -170,7 +170,7 @@ async def weekday_digest(data): "type": "cron", "function_id": "reports::weekday-digest", "config": { - "expression": "0 8 * * *", + "expression": "0 8 * * * * *", "condition_function_id": "conditions::is-weekday", }, }) From 5681bf429f49791bd83b2c4704a052120778f2f5 Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Fri, 10 Apr 2026 16:37:11 +0100 Subject: [PATCH 5/9] docs: clarify cron year field is optional (6 or 7 fields accepted) - Update cron format description to show year as optional - Add 6-field examples alongside 7-field in expression table - Clarify in iii-cron worker reference that both formats are valid --- docs/examples/cron.mdx | 10 ++++++---- docs/workers/iii-cron.mdx | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/examples/cron.mdx b/docs/examples/cron.mdx index 3081b05fe..cffd01412 100644 --- a/docs/examples/cron.mdx +++ b/docs/examples/cron.mdx @@ -323,7 +323,7 @@ iii.register_trigger(RegisterTriggerInput { trigger_type: "cron".into(), functio ## Cron expression format -iii uses a seven-field cron format: `second minute hour day month weekday year`. +iii supports six- or seven-field cron expressions: `second minute hour day month weekday [year]` (year is optional). ``` ┌──────────── second (0-59) @@ -332,15 +332,17 @@ iii uses a seven-field cron format: `second minute hour day month weekday year`. │ │ │ ┌────── day of month (1-31) │ │ │ │ ┌──── month (1-12) │ │ │ │ │ ┌── day of week (0-7) -│ │ │ │ │ │ ┌ year (* for any) +│ │ │ │ │ │ ┌ year (optional) │ │ │ │ │ │ │ * * * * * * * ``` | Expression | Meaning | |---|---| -| `0 * * * * * *` | Every minute | -| `0 0 * * * * *` | Every hour | +| `0 * * * * *` | Every minute (6-field) | +| `0 0 * * * *` | Every hour (6-field) | +| `0 * * * * * *` | Every minute (7-field) | +| `0 0 * * * * *` | Every hour (7-field) | | `0 0 9 * * 1-5 *` | 09:00 on weekdays | | `0 */5 * * * * *` | Every 5 minutes | | `*/5 * * * * * *` | Every 5 seconds | diff --git a/docs/workers/iii-cron.mdx b/docs/workers/iii-cron.mdx index 68ac9c2ef..7e5fbd438 100644 --- a/docs/workers/iii-cron.mdx +++ b/docs/workers/iii-cron.mdx @@ -74,12 +74,12 @@ This Worker adds a new Trigger Type: `cron`. - Standard cron expression defining the schedule. Supports the following format: + Cron expression defining the schedule. Accepts 6-field (`second minute hour day month weekday`) or 7-field (`second minute hour day month weekday year`) format — the year field is optional. ``` - * * * * * * * - │ │ │ │ │ │ │ - │ │ │ │ │ │ └── Year (* for any) + * * * * * * [*] + │ │ │ │ │ │ │ + │ │ │ │ │ │ └── Year (optional, * for any) │ │ │ │ │ └──── Day of week (0–6, Sun=0) │ │ │ │ └────── Month (1–12) │ │ │ └──────── Day of month (1–31) @@ -88,6 +88,8 @@ This Worker adds a new Trigger Type: `cron`. └──────────── Second (0–59) ``` + Both `"0 0 * * * *"` (6-field) and `"0 0 * * * * *"` (7-field) are valid and equivalent. + Function ID for conditional execution. The engine invokes it with the cron event; if it returns `false`, the handler function is not called. From 954b6a965e34dce6c22c6483346731fac1350cbb Mon Sep 17 00:00:00 2001 From: Anderson Leal Date: Fri, 10 Apr 2026 18:16:16 -0300 Subject: [PATCH 6/9] fix(iii-init): mount /tmp and /run as tmpfs in VM guest (#1458) --- Cargo.lock | 6 ++--- crates/iii-init/src/mount.rs | 46 +++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 311dde022..a318f5353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2392,7 +2392,7 @@ dependencies = [ [[package]] name = "iii" -version = "0.11.0-next.6" +version = "0.11.0-next.7" dependencies = [ "anyhow", "async-trait", @@ -2467,7 +2467,7 @@ dependencies = [ [[package]] name = "iii-console" -version = "0.11.0-next.6" +version = "0.11.0-next.7" dependencies = [ "anyhow", "axum", @@ -2536,7 +2536,7 @@ dependencies = [ [[package]] name = "iii-sdk" -version = "0.11.0-next.6" +version = "0.11.0-next.7" dependencies = [ "async-trait", "ctor", diff --git a/crates/iii-init/src/mount.rs b/crates/iii-init/src/mount.rs index 2593d2f3c..582471947 100644 --- a/crates/iii-init/src/mount.rs +++ b/crates/iii-init/src/mount.rs @@ -44,6 +44,8 @@ fn mount_ignore_busy( /// 4. `/dev/pts` as devpts (MS_NOEXEC | MS_NOSUID | MS_RELATIME) /// 5. `/dev/shm` as tmpfs (MS_NOEXEC | MS_NOSUID | MS_RELATIME) /// 6. `/dev/fd` symlink to `/proc/self/fd` (if not already present) +/// 7. `/tmp` as tmpfs (MS_NOSUID | MS_NODEV | MS_RELATIME, mode=1777) +/// 8. `/run` as tmpfs (MS_NOSUID | MS_NODEV | MS_RELATIME, mode=755) pub fn mount_filesystems() -> Result<(), InitError> { let nodev_noexec_nosuid = MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_RELATIME; @@ -107,7 +109,28 @@ pub fn mount_filesystems() -> Result<(), InitError> { })?; } - // 7. /sys/fs/cgroup -- cgroup2 (best-effort, used for worker memory limits) + // 7. /tmp -- tmpfs (real kernel tmpfs so Unix domain sockets work; + // the rootfs passthrough filesystem does not implement mknod) + mkdir_ignore_exists("/tmp")?; + mount_ignore_busy( + Some("tmpfs"), + "/tmp", + Some("tmpfs"), + MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_RELATIME, + Some("mode=1777"), + )?; + + // 8. /run -- tmpfs (runtime scratch space) + mkdir_ignore_exists("/run")?; + mount_ignore_busy( + Some("tmpfs"), + "/run", + Some("tmpfs"), + MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_RELATIME, + Some("mode=755"), + )?; + + // 9. /sys/fs/cgroup -- cgroup2 (best-effort, used for worker memory limits) mount_cgroup2().ok(); Ok(()) @@ -187,6 +210,27 @@ mod tests { ); } + #[test] + fn test_tmpfs_mounts_after_dev_fd() { + // /tmp and /run tmpfs must come after /dev/fd symlink. + let source = include_str!("mount.rs"); + let symlink_pos = source + .find("// 6. /dev/fd -> /proc/self/fd") + .expect("/dev/fd symlink comment not found"); + let tmp_pos = source + .find("// 7. /tmp -- tmpfs") + .expect("/tmp mount comment not found"); + let run_pos = source + .find("// 8. /run -- tmpfs") + .expect("/run mount comment not found"); + + assert!( + symlink_pos < tmp_pos, + "/dev/fd symlink must precede /tmp mount" + ); + assert!(tmp_pos < run_pos, "/tmp must be mounted before /run"); + } + #[test] fn test_dev_fd_symlink_after_proc() { // The /dev/fd symlink targets /proc/self/fd, so it must come after /proc mount. From f45f47dbd6d2f9ff0230c33ec3145e252d09d7c8 Mon Sep 17 00:00:00 2001 From: "Guilherme de S. Vieira Beira" Date: Fri, 10 Apr 2026 18:26:24 -0300 Subject: [PATCH 7/9] fix(iii-worker): normalize inline empty workers list before append (#1456) --- crates/iii-worker/src/cli/config_file.rs | 175 +++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/crates/iii-worker/src/cli/config_file.rs b/crates/iii-worker/src/cli/config_file.rs index 85389bd13..e91f25aa7 100644 --- a/crates/iii-worker/src/cli/config_file.rs +++ b/crates/iii-worker/src/cli/config_file.rs @@ -139,6 +139,50 @@ fn remove_worker_from(content: &str, name: &str) -> String { out } +/// Rewrites a standalone `workers: []` line to `workers:` so subsequent list +/// items can be appended without producing invalid YAML. +/// +/// Only touches lines where the `workers:` key is followed by an inline empty +/// list marker (`[]`, with tolerated surrounding whitespace). Populated inline +/// lists (e.g. `workers: [foo]`) and normal block lists are left untouched. +/// Trailing `# comment` text is preserved. +fn normalize_empty_workers_list(content: &str) -> String { + let lines: Vec<&str> = content.lines().collect(); + let mut out: Vec = Vec::with_capacity(lines.len()); + + for line in &lines { + let trimmed_start = line.trim_start(); + let indent = &line[..line.len() - trimmed_start.len()]; + if let Some(rest) = trimmed_start.strip_prefix("workers:") { + // Split off a trailing comment before checking the marker shape, + // so `workers: [] # comment` still matches. + let (value, comment) = match rest.find('#') { + Some(idx) => (&rest[..idx], Some(&rest[idx..])), + None => (rest, None), + }; + let trimmed = value.trim(); + let is_empty_inline_list = trimmed.starts_with('[') + && trimmed.ends_with(']') + && trimmed.len() >= 2 + && trimmed[1..trimmed.len() - 1].trim().is_empty(); + if is_empty_inline_list { + match comment { + Some(c) => out.push(format!("{}workers: {}", indent, c.trim_start())), + None => out.push(format!("{}workers:", indent)), + } + continue; + } + } + out.push((*line).to_string()); + } + + let mut joined = out.join("\n"); + if content.ends_with('\n') { + joined.push('\n'); + } + joined +} + /// Extract the raw YAML config block for a named worker from file content. /// /// Returns the config lines (without the `config:` key itself) as a string @@ -459,6 +503,10 @@ fn append_to_content_with_fields( worker_path: Option<&str>, config_yaml: Option<&str>, ) -> Result<(), String> { + // Normalize `workers: []` → `workers:` so appending list items below + // produces valid YAML. + *content = normalize_empty_workers_list(content); + // Ensure there is a `workers:` key. if !content.contains("workers:") { if !content.is_empty() && !content.ends_with('\n') { @@ -1093,4 +1141,131 @@ mod tests { assert!(config.is_some()); assert!(config.unwrap().contains("timeout: 30")); } + + #[test] + fn test_append_to_content_with_inline_empty_list_marker() { + // Reproduces the bug where `workers: []` (valid YAML) is populated + // in-place, producing `workers: []\n - name: foo` (invalid YAML). + let mut content = "workers: []\n".to_string(); + let path = std::path::Path::new("/tmp/test-empty-list-marker.yaml"); + + append_to_content_with_fields(&mut content, path, "iii-state", None, None, None).unwrap(); + + assert!( + content.contains("- name: iii-state"), + "expected worker entry, got:\n{}", + content + ); + assert!( + !content.contains("workers: []"), + "inline `[]` marker should be stripped, got:\n{}", + content + ); + let parsed: serde_yaml::Value = + serde_yaml::from_str(&content).expect("output should be valid YAML"); + let workers = parsed + .get("workers") + .and_then(|w| w.as_sequence()) + .expect("`workers` should be a sequence"); + assert_eq!(workers.len(), 1); + assert_eq!( + workers[0].get("name").and_then(|n| n.as_str()), + Some("iii-state") + ); + + let _ = std::fs::remove_file(path); + } + + #[test] + fn test_normalize_empty_workers_list_strips_inline_marker() { + assert_eq!(normalize_empty_workers_list("workers: []\n"), "workers:\n"); + assert_eq!(normalize_empty_workers_list("workers: []"), "workers:"); + assert_eq!(normalize_empty_workers_list("workers: []\n"), "workers:\n"); + assert_eq!(normalize_empty_workers_list("workers: [ ]\n"), "workers:\n"); + assert_eq!( + normalize_empty_workers_list("workers: [] \n"), + "workers:\n" + ); + } + + #[test] + fn test_normalize_empty_workers_list_leaves_populated_list_alone() { + let content = "workers:\n - name: foo\n"; + assert_eq!(normalize_empty_workers_list(content), content); + } + + #[test] + fn test_normalize_empty_workers_list_leaves_inline_populated_list_alone() { + // Pin current behavior: inline populated lists are left untouched. + let content = "workers: [foo]\n"; + assert_eq!(normalize_empty_workers_list(content), content); + } + + #[test] + fn test_normalize_empty_workers_list_no_workers_key() { + let content = "other: value\n"; + assert_eq!(normalize_empty_workers_list(content), content); + } + + #[test] + fn test_normalize_empty_workers_list_preserves_other_content() { + let content = "before: 1\nworkers: []\nafter: 2\n"; + let expected = "before: 1\nworkers:\nafter: 2\n"; + assert_eq!(normalize_empty_workers_list(content), expected); + } + + #[test] + fn test_normalize_empty_workers_list_strips_marker_with_trailing_comment() { + assert_eq!( + normalize_empty_workers_list("workers: [] # no workers yet\n"), + "workers: # no workers yet\n" + ); + assert_eq!( + normalize_empty_workers_list("workers: []# tight\n"), + "workers: # tight\n" + ); + } + + #[test] + fn test_normalize_empty_workers_list_preserves_indentation() { + assert_eq!( + normalize_empty_workers_list(" workers: [] # nested\n"), + " workers: # nested\n" + ); + } + + #[test] + fn test_normalize_empty_workers_list_preserves_comment_on_populated_inline_list() { + // Populated inline list stays untouched, comment and all. + let content = "workers: [foo] # has one\n"; + assert_eq!(normalize_empty_workers_list(content), content); + } + + #[test] + fn test_append_to_content_with_inline_empty_list_marker_and_comment() { + let mut content = "workers: [] # add workers here\n".to_string(); + let path = std::path::Path::new("/tmp/test-empty-list-marker-comment.yaml"); + + append_to_content_with_fields(&mut content, path, "iii-state", None, None, None).unwrap(); + + assert!( + content.contains("- name: iii-state"), + "expected worker entry, got:\n{}", + content + ); + assert!( + !content.contains("workers: []"), + "inline `[]` marker should be stripped, got:\n{}", + content + ); + let parsed: serde_yaml::Value = + serde_yaml::from_str(&content).expect("output should be valid YAML"); + let workers = parsed + .get("workers") + .and_then(|w| w.as_sequence()) + .expect("`workers` should be a sequence"); + assert_eq!(workers.len(), 1); + + let _ = std::fs::remove_file(path); + } } From b6886ef93dde9c9e1f4b2a8797ef61c9a00fc19a Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Fri, 10 Apr 2026 23:56:37 +0100 Subject: [PATCH 8/9] docs(skills): update install command to npx skillkit add iii-hq/iii/skills Replace all occurrences of `npx skills add iii-hq/iii` and `npx skillkit install iii-hq/iii` with the new canonical command `npx skillkit add iii-hq/iii/skills` across skills README, SKILL.md, docs index, and quickstart. --- docs/index.mdx | 2 +- docs/quickstart.mdx | 33 +---------------------------- skills/README.md | 18 ++++------------ skills/iii-getting-started/SKILL.md | 10 ++------- 4 files changed, 8 insertions(+), 55 deletions(-) diff --git a/docs/index.mdx b/docs/index.mdx index 2a5b726c4..c0c6258ec 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -79,7 +79,7 @@ iii --version Give your AI coding agent full context on iii: ```bash -npx skills add iii-hq/iii +npx skillkit add iii-hq/iii/skills ``` Skills covering every iii primitive — works with Claude Code, Cursor, Gemini CLI, Codex, and [30+ other agents](https://agentskills.io). diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 88323e7ec..3affa4895 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -96,42 +96,11 @@ Then open your web browser to: http://localhost:3113/ Give your AI coding agent full context on every iii primitive: ```bash -npx skills add iii-hq/iii +npx skillkit add iii-hq/iii/skills ``` This installs skills covering HTTP endpoints, cron scheduling, queues, state management, streams, custom triggers, and more. Works with Claude Code, Cursor, Gemini CLI, Codex, and [30+ other agents](https://agentskills.io). - - - -```bash -npx skills add iii-hq/iii -``` - - - - -```bash -npx skillkit install iii-hq/iii --agent cursor -``` - - - - -```bash -npx skillkit install iii-hq/iii --agent gemini-cli -``` - - - - -```bash -npx skillkit install iii-hq/iii --agent codex -``` - - - - ## Next Steps diff --git a/skills/README.md b/skills/README.md index 3ec22f32c..164613a40 100644 --- a/skills/README.md +++ b/skills/README.md @@ -6,23 +6,14 @@ Works with Claude Code, Cursor, Gemini CLI, OpenCode, Amp, Goose, Roo Code, GitH ## Install -### One command - ```bash -npx skills add iii-hq/iii +npx skillkit add iii-hq/iii/skills ``` -### SkillKit +### Install a single skill ```bash -# Install all iii skills -npx skillkit install iii-hq/iii - -# Install a single skill -npx skillkit install iii-hq/iii --skills=iii-http-endpoints - -# Sync skills across all your agents -npx skillkit sync +npx skillkit add iii-hq/iii/skills --skills=iii-http-endpoints ``` ### Git clone @@ -43,8 +34,7 @@ git clone https://github.com/iii-hq/iii.git /tmp/iii && cp -r /tmp/iii/skills/ii If you use multiple agents, SkillKit keeps skills in sync across all of them: ```bash -# Install once, sync to Claude Code + Cursor + Gemini CLI -npx skillkit install iii-hq/iii +npx skillkit add iii-hq/iii/skills npx skillkit sync --agent claude-code npx skillkit sync --agent cursor npx skillkit sync --agent gemini-cli diff --git a/skills/iii-getting-started/SKILL.md b/skills/iii-getting-started/SKILL.md index 07e3b9793..4405642d7 100644 --- a/skills/iii-getting-started/SKILL.md +++ b/skills/iii-getting-started/SKILL.md @@ -141,16 +141,10 @@ Expected response: ## Install Agent Skills -Get all 24 iii skills for your AI coding agent: +Get all iii skills for your AI coding agent: ```bash -npx skills add iii-hq/iii -``` - -Or with SkillKit: - -```bash -npx skillkit install iii-hq/iii +npx skillkit add iii-hq/iii/skills ``` Skills teach your agent how to use every iii primitive — HTTP endpoints, cron scheduling, queues, state management, streams, channels, and more. Available for Claude Code, Cursor, Codex, Gemini CLI, and 30+ other agents. From 089a7154870986a9d411f0569ca003a08b9f0faf Mon Sep 17 00:00:00 2001 From: Rohit Ghumare Date: Sat, 11 Apr 2026 00:17:09 +0100 Subject: [PATCH 9/9] docs: fix cron expression semantics and weekday numbering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix trigger-conditions.py: "0 8 * * * * *" (hourly at :08) → "0 0 8 * * * *" (daily at 08:00) - Add 6-field examples to iii-cron.mdx common expressions table - Use 6-field expression in Python cron example tab - Align weekday numbering to "0-7, Sun=0 or 7" in both cron docs --- docs/examples/cron.mdx | 2 +- docs/workers/iii-cron.mdx | 12 +++++++----- skills/references/trigger-conditions.py | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/examples/cron.mdx b/docs/examples/cron.mdx index cffd01412..957a42a35 100644 --- a/docs/examples/cron.mdx +++ b/docs/examples/cron.mdx @@ -331,7 +331,7 @@ iii supports six- or seven-field cron expressions: `second minute hour day month │ │ ┌──────── hour (0-23) │ │ │ ┌────── day of month (1-31) │ │ │ │ ┌──── month (1-12) -│ │ │ │ │ ┌── day of week (0-7) +│ │ │ │ │ ┌── day of week (0-7, Sun=0 or 7) │ │ │ │ │ │ ┌ year (optional) │ │ │ │ │ │ │ * * * * * * * diff --git a/docs/workers/iii-cron.mdx b/docs/workers/iii-cron.mdx index 7e5fbd438..1460434bf 100644 --- a/docs/workers/iii-cron.mdx +++ b/docs/workers/iii-cron.mdx @@ -80,7 +80,7 @@ This Worker adds a new Trigger Type: `cron`. * * * * * * [*] │ │ │ │ │ │ │ │ │ │ │ │ │ └── Year (optional, * for any) - │ │ │ │ │ └──── Day of week (0–6, Sun=0) + │ │ │ │ │ └──── Day of week (0–7, Sun=0 or 7) │ │ │ │ └────── Month (1–12) │ │ │ └──────── Day of month (1–31) │ │ └────────── Hour (0–23) @@ -140,7 +140,7 @@ def cleanup_old_data(event): return {} iii.register_function("jobs::cleanupOldData", cleanup_old_data) -iii.register_trigger({'type': 'cron', 'function_id': 'jobs::cleanupOldData', 'config': {'expression': '0 0 2 * * * *'}}) +iii.register_trigger({'type': 'cron', 'function_id': 'jobs::cleanupOldData', 'config': {'expression': '0 0 2 * * *'}}) ``` @@ -167,11 +167,13 @@ iii.register_trigger(RegisterTriggerInput { | Expression | Description | | -------------------- | ---------------------------------------------- | -| `0 * * * * * *` | Every minute | -| `0 0 * * * * *` | Every hour | +| `0 * * * * *` | Every minute (6-field) | +| `0 0 * * * *` | Every hour (6-field) | +| `0 0 2 * * *` | Every day at 2 AM (6-field) | +| `0 * * * * * *` | Every minute (7-field) | +| `0 0 * * * * *` | Every hour (7-field) | | `0 0 0 * * * *` | Every day at midnight | | `0 0 0 * * 0 *` | Every Sunday at midnight | -| `0 0 2 * * * *` | Every day at 2 AM | | `0 */5 * * * * *` | Every 5 minutes | | `0 0 9-17 * * 1-5 *` | Every hour from 9 AM to 5 PM, Monday to Friday | diff --git a/skills/references/trigger-conditions.py b/skills/references/trigger-conditions.py index 7aeca6825..8ed0eac8c 100644 --- a/skills/references/trigger-conditions.py +++ b/skills/references/trigger-conditions.py @@ -170,7 +170,7 @@ async def weekday_digest(data): "type": "cron", "function_id": "reports::weekday-digest", "config": { - "expression": "0 8 * * * * *", + "expression": "0 0 8 * * * *", "condition_function_id": "conditions::is-weekday", }, })