cron
v0.21.10Schedule functions with cron expressions - registers the `cron` trigger type.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64 · x86
exact versions are immutable; binary and bundle artifacts are digest-pinned.
full markdown
/workers/cron.md. paste it into an llm prompt or pipe it through curl from a worker.install
configuration
- adapter:
name: localdependencies
readme
cron
Schedules registered functions with cron expressions. Any function bound to a
cron trigger is invoked by this worker at the next matching UTC time. The
worker replaces the legacy built-in cron worker while keeping the trigger type
and payload shape stable.
Install
iii worker add croniii worker add fetches the binary, writes a config block into
~/.iii/config.yaml, and the engine starts the worker the next time it boots.
Quickstart
Register a function and bind it to this worker's trigger type (cron) with an
expression:
use iii_sdk::protocol::RegisterTriggerInput;
use iii_sdk::{errors::Error, register_worker, InitOptions, RegisterFunction};
use serde_json::{json, Value};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let iii = register_worker("ws://localhost:49134", InitOptions::default());
iii.register_function(
"jobs::tick",
RegisterFunction::new_async(|payload: Value| async move {
println!("cron fired: {payload}");
Ok::<Value, Error>(json!({"ok": true}))
}),
);
iii.register_trigger(RegisterTriggerInput {
trigger_type: "cron".to_string(),
function_id: "jobs::tick".into(),
config: json!({ "expression": "*/5 * * * * *" }),
metadata: None,
})?;
tokio::signal::ctrl_c().await?;
Ok(())
}The function receives:
{
"trigger": "cron",
"job_id": "<trigger-id>",
"scheduled_time": "2026-07-03T12:00:00+00:00",
"actual_time": "2026-07-03T12:00:00.123456789+00:00"
}Configuration
| Field | Default | Description |
|---|---|---|
adapter.name |
local |
Lock backend. Use local for process-local locking or redis for multi-instance mutual exclusion. |
adapter.config.redis_url |
redis://localhost:6379 |
Redis URL when adapter.name is redis. |
Configuration is owned by the configuration worker - edit it from the
console (Configuration -> Workers -> cron) or seed it once via
--config on first boot. Changing the lock adapter hot-swaps the
scheduler under a serialized apply lock: existing jobs are stopped, re-created
with the new backend, and never run in two scheduler instances at once.
Console page
While the worker is connected it injects a cron page into the console
(#/ext/cron): every agent-owned schedule with its cadence, next UTC run and
fire count, the cron bindings other workers registered for themselves, and a
composer that turns "every weekday at 09:00, summarise open PRs" into a
registered schedule. Schedules created there live in a session of their own,
so each routine keeps its own transcript.
Trigger type
This worker always registers the cron trigger type. Bind a function to it
with:
| Field | Required | Default | Description |
|---|---|---|---|
expression |
yes | - | Cron expression parsed by the Rust cron crate. Six-field sec min hour day month weekday expressions are supported, and a seventh year field is accepted. |
condition_function_id |
no | - | Function invoked before the scheduled handler. Only an explicit JSON false return blocks the fire; missing/null/truthy returns allow it. Condition errors skip that fire. |
All schedules use UTC. Missed fires while the worker is stopped are skipped; there is no catch-up replay.
Write the day of week as a name (Mon ... Sun). Numerically the crate counts
Sunday as 1, so 0 0 9 * * 1 fires on Sunday, not Monday.
Console trigger activity
The worker injects a cron-specific source section into Consoles that support
host.triggerRenderers. Trigger registration, firing, and retirement show a
plain-language schedule, the exact expression, explicit UTC, and the optional
condition_function_id. Expressions that cannot be summarized without
hiding cron semantics keep an honest “custom schedule” label and the raw
expression.
The Console retains the surrounding activity, delivery target/result, lifecycle state and controls, and raw JSON. Disabling or disconnecting this worker's injected UI therefore falls back to the generic trigger view rather than removing trigger activity.
For local UI development:
pnpm --dir cron/ui test
pnpm --dir cron/ui build
# terminal 1
pnpm --dir cron/ui watch
# terminal 2
cd cron && III_CRON_UI_WATCH=1 cargo runThe Rust build embeds cron/page.js and cron/styles.css; production does
not require a separate asset server.
Requires removing the legacy built-in cron worker
The legacy built-in cron worker also owns the cron trigger type. Two owners
of the same trigger type on one engine collide - whichever registers last
wins - so this worker requires it to be absent: omit it from the
engine's config.yaml (a config that doesn't list a worker won't run it).
On boot, this worker queries the engine for connected workers and refuses to
start with a clear error if the legacy built-in is still active, so a stale config
fails loudly instead of silently racing the built-in worker for ownership of
cron.
Parity vs builtin
| Behavior | Builtin | This worker |
|---|---|---|
| Expressions | 6-7 field (cron crate) |
same |
| Timezone | UTC only | same |
| Missed runs | skipped, no catch-up | same |
| Condition | only explicit false blocks |
same |
| Lock TTL | 30s | same |
| Lock backends | kv (process-local), redis | local (process-local), redis |
| Service functions | none | none |
api reference (json)
{
"functions": [
{
"description": "Internal: reload cron configuration from the authoritative store on change.",
"metadata": {},
"name": "cron::on-config-change",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ConfigChangeRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "ConfigChangeAck",
"type": "object"
}
},
{
"description": "Serve the cron worker's injected console UI assets (content function for its console:script / console:style triggers).",
"metadata": {
"internal": true
},
"name": "cron::ui-content",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Input of the content function: the console asks for one asset by path.",
"properties": {
"path": {
"description": "The asset path from the trigger config (e.g. `state/page.js`).",
"type": "string"
}
},
"required": [
"path"
],
"title": "UiContentInput",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Output of the content function.",
"properties": {
"content": {
"description": "The asset source, verbatim.",
"type": "string"
},
"content_type": {
"description": "MIME type the console should serve the asset with.",
"type": "string"
}
},
"required": [
"content",
"content_type"
],
"title": "UiContentResult",
"type": "object"
}
}
],
"triggers": [
{
"description": "Cron-based scheduled triggers",
"invocation_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Trigger config schema, field-parity with the engine's CronTriggerConfig.",
"properties": {
"condition_function_id": {
"description": "Optional function ID to evaluate before invoking the handler.",
"type": [
"string",
"null"
]
},
"expression": {
"description": "Cron expression (6-field format: sec min hour day month weekday; a 7th year field is accepted).",
"type": "string"
}
},
"required": [
"expression"
],
"title": "CronTriggerSpec",
"type": "object"
},
"metadata": {},
"name": "cron",
"return_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"actual_time": {
"description": "Actual execution time (RFC3339)",
"type": "string"
},
"job_id": {
"description": "Unique job identifier",
"type": "string"
},
"scheduled_time": {
"description": "Scheduled execution time (RFC3339)",
"type": "string"
},
"trigger": {
"description": "Always \"cron\"",
"type": "string"
}
},
"required": [
"actual_time",
"job_id",
"scheduled_time",
"trigger"
],
"title": "CronCallRequest",
"type": "object"
}
}
]
}