Runtime and Lifecycle
Declare runtime functions, schedules, event handlers, lifecycle checks, and startup activation work.
Runtime work is host-owned. A module declares functions, schedules, event handlers, and lifecycle needs; the worker claims queues, applies retry policy, records runs, and projects evidence into Runtime Console.
Function declaration
Declare runtime functions in the manifest:
use lenso::{ModuleManifest, RuntimeFunctionDeclaration, RuntimeSurface};
pub fn manifest() -> ModuleManifest {
ModuleManifest::builder("support")
.runtime(RuntimeSurface {
functions: vec![RuntimeFunctionDeclaration {
name: "support.ticket.escalate.v1".to_owned(),
version: 1,
queue: "support".to_owned(),
input_schema: Some("support.ticket.escalate.v1".to_owned()),
retry_policy: None,
}],
schedules: vec![],
})
.build()
}
The declaration makes the function visible and lintable. Linked Modules register executable handlers through their binding. A Service owns the provider API used to execute operations for the Modules it declares.
Scheduled runtime functions
Declare schedules beside the functions they enqueue:
use lenso::{
ModuleManifest, RuntimeFunctionDeclaration, RuntimeSurface,
ScheduledFunctionDeclaration,
};
pub fn manifest() -> ModuleManifest {
ModuleManifest::builder("support")
.runtime(RuntimeSurface {
functions: vec![RuntimeFunctionDeclaration {
name: "support.ticket.escalate_overdue.v1".to_owned(),
version: 1,
queue: "support".to_owned(),
input_schema: Some("support.ticket.escalate_overdue.v1".to_owned()),
retry_policy: None,
}],
schedules: vec![ScheduledFunctionDeclaration {
name: "escalate-overdue-tickets".to_owned(),
function_name: "support.ticket.escalate_overdue.v1".to_owned(),
cron: "*/15 * * * *".to_owned(),
input: serde_json::json!({ "source": "cron" }),
}],
})
.build()
}
cron uses standard 5-field UTC cron:
minute hour day-of-month month day-of-week
Supported field forms are *, comma lists, ranges, step values such as
*/15, numbers, month names such as JAN, and weekday names such as MON.
Seconds, time zones, and Quartz-style fields are intentionally not part of the
module contract.
The scheduler stores durable state in runtime.scheduled_functions. When a
schedule is due, the worker enqueues the declared runtime function into
runtime.function_runs; retries, dead letters, logs, and Runtime Story evidence
then follow the same path as manually enqueued runtime work. Provider execution
does not transfer ownership of the Host queue or scheduling state.
Runtime story evidence
The worker writes durable evidence:
| Table/source | Meaning |
|---|---|
platform.outbox |
module event dispatch state |
runtime.function_runs |
queued, running, retried, completed, or failed function work |
platform.story_events |
story projection records |
| execution logs | technical operations and provider runtime metadata |
Runtime Console uses those records to show Stories, function operations, dead letters, queues, retries, and technical operations.
See Runtime Stories for the correlation model, story graph shape, and module metadata that controls story titles.
Lifecycle declarations
Lifecycle is data, not an arbitrary startup hook:
use lenso::{
LifecycleActivationJobDeclaration, LifecycleActivationRunPolicy,
LifecycleStartupCheckDeclaration, LifecycleStartupCheckKind, LifecycleSurface,
ModuleManifest, RuntimeFunctionDeclaration, RuntimeSurface,
};
pub fn manifest() -> ModuleManifest {
ModuleManifest::builder("support")
.runtime(RuntimeSurface {
functions: vec![RuntimeFunctionDeclaration {
name: "support.ticket.reindex.v1".to_owned(),
version: 1,
queue: "support".to_owned(),
input_schema: Some("support.ticket.reindex.v1".to_owned()),
retry_policy: None,
}],
schedules: vec![],
})
.lifecycle(LifecycleSurface {
startup_checks: vec![LifecycleStartupCheckDeclaration {
name: "reindex function is registered".to_owned(),
required: true,
check: LifecycleStartupCheckKind::FunctionRegistered {
function_name: "support.ticket.reindex.v1".to_owned(),
},
}],
activation_jobs: vec![LifecycleActivationJobDeclaration {
name: "warm ticket index".to_owned(),
function_name: "support.ticket.reindex.v1".to_owned(),
run_policy: LifecycleActivationRunPolicy::EveryStartup,
input: serde_json::json!({ "reason": "startup" }),
required: false,
}],
})
.build()
}
required: true lifecycle failures block startup. required: false failures
warn and continue. Activation jobs currently use every_startup; non-idempotent
install or upgrade lifecycle state is intentionally deferred.
Service event-handler boundary
Service event handlers use the Host-owned outbox relay:
- module command writes business tables
- same transaction inserts
platform.outbox - worker claims pending outbox rows
- Host invokes a Linked handler or Service provider operation
- handler may return a bounded
enqueue_functionaction - host inserts
runtime.function_runs - retries and dead letters stay in host tables
Services never claim Host outbox rows directly.
Service runtime boundary
For provider-backed functions, the Worker remains the Host queue owner. The Service owns its API, Workload, and terminal execution evidence. Correlation, actor, trace, and retry identities cross the provider contract explicitly; no distributed transaction or second Module source is introduced.
Definition of done
A runtime-capable module is done when:
- function declarations lint cleanly
- schedule declarations reference existing functions and valid cron expressions
- linked or remote executors are registered
- lifecycle checks reference existing functions or capabilities
- a smoke path creates a function run
- Runtime Console shows the run, retry, failure, or story evidence