AnnouncementsIntroducing Arkoren

    Menu

    Drivers

    Choose process-local timers or an engine-backed scheduler and configure its delivery, ownership, and shutdown rules.

    What's on this page
    1. Compare memory, Temporal, and Restate timing, persistence, and missed-tick behavior.
    2. Construct schedulers with appropriate ownership prefixes and execution policies.
    3. Understand worker lifecycle, native access, and local delivery checks.
    On this page

    Choose memory for independent process-local timers and disposable deferred work. Choose Temporal or Restate when calendar delivery must be coordinated across workers or deferred jobs must survive worker restarts. Prefer an engine your team already operates, then check its calendar and failure behavior below.

    Compatibility Matrix

    This compares behavior exposed through Arkoren, not the engines’ full feature sets. Restart survival depends on retained engine state. Shared calendar timing does not guarantee exclusive application effects or exactly-once execution.

    Driver Shared Calendar Timing Deferred Tasks Worker Restart Survival Non-UTC Entries Missed Ticks Workflow Host
    Memory No Yes No Yes No No
    Temporal Yes Yes Yes Yes
    Restate Yes Yes Yes Yes

    Memory runs every entry in every replica. Memory and Restate use the core calendar; Temporal evaluates its own calendar and runs an autumn repeated time twice. See Time Zones before using local wall-clock times. Memory does not retry failed handlers; Temporal configures one attempt; Restate treats ordinary handler errors as terminal but retries missing dispatch and errors matching schedule.ErrUnknownName. Design effects to tolerate repetition.

    Engine drivers attach to the vendor’s Worker Hosts. Construct the schedule driver before starting the host, register matching entries and handlers on every worker, and run the scheduler using the ordinary runner setup. Host connection, credentials, endpoint security, and cleanup belong in that shared guide.

    Run installation commands in your application’s module directory. Constructor snippets use schedule from arkoren.dev/core/service/schedule; timing examples also import time. Handle constructor errors before using the returned driver.

    scheduler.Driver() exposes the raw driver, and DriverAs[T]() finds a concrete type through wrappers exposing Unwrap() schedule.Driver. Raw operations bypass the facade’s registration and observations; use ordinary scheduler methods for application work.

    Memory

    The bundled memoryschedule driver uses Go timers and needs no separate installation. Use it for tests, development, or a single replica when losing deferred jobs is acceptable.

    Creating Timers

    Construction starts nothing and cannot fail:

    import "arkoren.dev/core/service/schedule/memoryschedule"
    
    driver := memoryschedule.New()
    scheduler := schedule.New(driver)

    scheduler.Run starts timers; the driver has no separate Start or Stop. Add a calendar entry, or register a deferred handler and call After. Deferrals accepted before Run remain pending; overdue jobs run at its next opportunity. There is no missed-calendar-tick recovery.

    The only optional driver setting is Builder.Logger, which defaults to slog.Default() and receives driver failure and overlap-skip records.

    Shutdown Boundaries

    Runner cancellation stops timer admission and discards unstarted work belonging to that run. Process exit loses all jobs. Already admitted callbacks retain context values but not the runner’s cancellation or deadline; Run waits for them without a drain timeout.

    Set positive entry timeouts and bound task I/O. A callback waiting only for its detached context can prevent the driver from returning forever. Application shutdown budgets limit runner waiting, not callback lifetime, so cleanup can start while callbacks still hold dependencies. No timeout kills Go code.

    Deferrals accepted after admission closes, including during draining, remain pending for a later raw-driver run. The raw driver rejects concurrent Run calls through the drain with memoryschedule.ErrAlreadyRunning, and can run again after it returns. The scheduler facade itself remains single-use.

    The driver copies submitted payload bytes. An already-canceled submission returns the context error and no ID; an already-canceled cancellation leaves pending work unchanged. Canceling a submission’s context after acceptance does not withdraw its job. Cancel removes pending work but cannot stop an admitted dispatch.

    For timer tests, use Controlling Time. RunNow checks a handler without testing timer delivery.

    For every function and option, see the memoryschedule API reference.

    Temporal

    Temporal stores recurring entries as Schedules and deferred tasks as delayed workflows. It retains them across worker restarts while its engine state is retained. Install the module:

    go get arkoren.dev/driver/temporal

    Connecting Workers

    With a configured Temporal host, construct the driver before host.Start. It registers a workflow and activity without making network calls.

    Run deletes Temporal Schedules beginning with <prefix>- that are absent from your declarations. Give each application a unique prefix on a shared namespace. Workers sharing that prefix must declare the same schedule set; conflicting deployments can overwrite or remove each other’s schedules.

    import "arkoren.dev/driver/temporal/temporalschedule"
    
    driver, err := temporalschedule.New(host, func(clock *temporalschedule.Builder) {
    	clock.Prefix("accounts")
    })
    scheduler := schedule.New(driver)

    The prefix defaults to arkoren and names workflows, activities, and schedules. Run creates or updates <prefix>-<entry-name> schedules, prunes undeclared ones, then waits. Deferred workflows named <prefix>-deferred-* are not pruned. Register calendar entries and task handlers before running this scheduler.

    A dialing host must connect first: live-context Run, Defer, and Cancel otherwise return temporalschedule.ErrNotStarted. An already-canceled driver Run returns nil without connecting. A host wrapping an existing native client can submit deferrals before local Run. A client-only host needs matching workers elsewhere.

    Existing activities can arrive between host startup and scheduler startup. They wait for dispatch readiness within their activity timeout, so start Run promptly: readiness waiting consumes that execution budget.

    Timing Policy

    If a brief outage must recover more than the default minute of missed ticks, construct the driver with an explicit catch-up window. This is an alternative construction using the same host, not a second registration on it:

    driver, err := temporalschedule.New(host, func(clock *temporalschedule.Builder) {
    	clock.Prefix("accounts")
    	clock.CatchupWindow(10 * time.Minute)
    	clock.MaxRunTime(20 * time.Minute)
    })

    MaxRunTime defaults to one hour and sets the activity bound when an entry has no positive timeout; deferred tasks use that fallback too. A positive entry timeout overrides it. Nonpositive MaxRunTime and CatchupWindow restore their defaults. Driver Logger defaults to slog.Default(). Native timing bounds do not forcibly terminate application callbacks.

    Each run has one activity attempt. WithoutOverlapping maps to Temporal’s skip policy; otherwise all overlaps are allowed. Temporal has approximately second-level schedule resolution, and actual firing can lag. Its own calendar skips spring-gap times and fires autumn-overlap times twice.

    Shutdown Boundaries

    Every driver Run exit removes its dispatcher and cancels active dispatch contexts, including on reconciliation failure. It does not join callbacks or stop code that ignores cancellation. Later activities wait for a new raw-driver run instead of reusing the stopped dispatcher; a facade cannot be restarted.

    The host’s worker-stop grace is native policy, not a hard callback deadline. Its early stop notification does not immediately cancel active callbacks. See host shutdown for grace, native option precedence, and client ownership.

    Native Access

    Use the shared host’s Client() for SDK operations outside the facade:

    native := host.Client()

    A dialing host exposes it only after connection; temporal.NewFrom borrows a supplied client and exposes it immediately. Configure native TLS, authentication, and logger policy through the host. Keep independent native schedules outside the driver’s prefix: reconciliation can overwrite native edits to managed schedules.

    Local Engine

    Use the Temporal local engine in your application. Construct this driver before starting that host and register scheduler.Run. Check a handler’s effect after a calendar tick to verify engine delivery; RunNow verifies only the handler. The linked profile includes health verification and disposable-engine cleanup.

    For every function and option, see the temporalschedule API reference.

    Restate

    Restate stores each recurring entry in a virtual object whose tick schedules the next tick before dispatching the current work. Deferred tasks use delayed sends. Choose it when you operate a Restate server with retained state:

    go get arkoren.dev/driver/restate

    Connecting Workers

    Configure the Restate host, including a reachable endpoint, request-identity verification, and deployment registration. Construct this driver before host.Start because it binds native services.

    Use a distinct ownership prefix per application. Startup reconciliation removes undeclared entries from that prefix’s index. Service names drop separators and capitalize the following letter, so prefixes must also have distinct normalized names; different strings alone do not ensure isolation.

    import "arkoren.dev/driver/restate/restateschedule"
    
    driver, err := restateschedule.New(host, func(clock *restateschedule.Builder) {
    	clock.Prefix("accounts")
    })
    scheduler := schedule.New(driver)

    The default prefix arkoren creates ArkorenSchedule, ArkorenScheduleIndex, and ArkorenRunner. The index key preserves the exact prefix; ingress requests escape it as one path segment. Run scheduler.Run alongside host.Serve using the shared host’s lifecycle setup. Run retries initial ingress publication every second until accepted or canceled, including while the deployment is not yet registered. It does not fail immediately on an unreachable ingress.

    Register calendar entries and task handlers before Run. Deferrals can be accepted before local Run if ingress knows the deployment; delivery retries when dispatch is not ready. Unlike initial publication, Defer does not retry submission errors. The returned job ID is a Restate invocation ID accepted by Cancel.

    Timing Policy

    For a different native handler budget, select MaxRunTime during construction:

    driver, err := restateschedule.New(host, func(clock *restateschedule.Builder) {
    	clock.Prefix("accounts")
    	clock.MaxRunTime(20 * time.Minute)
    })

    This alternative construction sets abort and inactivity timeouts on native Tick and Run handlers. Zero restores the default hour; negative values leave server defaults, not an unlimited-execution guarantee. Driver Logger defaults to slog.Default(). Entry timeouts remain cooperative application-context bounds.

    WithoutOverlapping runs inline in the entry’s serialized tick invocation; ordinary entries dispatch through the runner service. A delayed pending tick runs once and computes its next match after the current time, rather than replaying every missed calendar match. Ordinary application failures are terminal; missing dispatch and errors matching schedule.ErrUnknownName are retryable. Runs can repeat, so keep application effects idempotent.

    Deployment Safety

    Changing an expression, zone, or overlap flag starts a new schedule generation. Removing an entry clears its active spec but retains its generation. Adding the same name later advances the generation so old queued ticks cannot revive it. Preserve that retained generation if you manage native state manually; it does not prevent duplicate application effects.

    Before upgrading existing schedule state, let in-flight Remove invocations finish on the old deployment. Removal records individual state clears rather than ClearAll; replaying an incompatible recorded history can fail. If an older deployment already removed a name, drain its pending ticks before reintroducing it because its deleted generation history cannot be recovered.

    Task JSON values, including explicit null, are preserved. Upgrade serving workers before relying on null-payload preservation; older workers discard those bytes. Recurring messages still dispatch with nil payload bytes, including legacy recurring messages carrying Payload:null.

    Each rollout creates a deployment. Keep handlers compatible with in-flight work and use the host’s deployment and shutdown policy. Cancel requests native invocation cancellation; it cannot forcibly stop application code already executing. Keep captured dependencies alive until callbacks finish.

    Native Access

    The host’s Query method exposes the admin SQL API for inspection. Keep admin credentials restricted and do not expose arbitrary queries to untrusted callers. For an existing host and operation context:

    rows, err := host.Query(ctx, "SELECT * FROM sys_invocation LIMIT 10")

    This reads native invocation records, not the facade’s registered definitions. The workflow driver can bind to the same host; its separate cancellation retention policy is not a prerequisite for recurring schedules.

    Local Engine

    Use the Restate local engine, construct this driver before host startup, and run both host.Serve and scheduler.Run. Check the handler’s effect after a tick to test durable delivery. The linked isolated development profile disables request-identity verification; do not use that setting on an exposed endpoint. Follow its cleanup instructions to remove the disposable server and schedule state.

    For every function and option, see the restateschedule API reference.

    Hatchet

    Hatchet integration is pending and unpublished. It is not an installable scheduler option and is excluded from the available-driver matrix. Use memory, Temporal, or Restate instead.

    For the portable contract, see the schedule API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive