AnnouncementsIntroducing Arkoren

    Menu

    Worker Hosts

    Share native workflow and scheduler resources with explicit startup, readiness, and cleanup ownership.

    What's on this page
    1. Register workflow and scheduler work on one native host before startup.
    2. Wire Temporal or Restate lifecycle hooks with explicit readiness and cleanup boundaries.
    3. Preserve client ownership, request verification, and journal compatibility across deployments.
    On this page

    When one application uses an engine for workflows and scheduled tasks, a host owns their shared connection and execution endpoint. Pass one Temporal or Restate host to both drivers, then wire its lifecycle once. Memory drivers have no shared host; Hatchet is pending and unpublished.

    This page owns lifecycle for both workflow drivers and scheduler drivers. Driver-specific registration belongs on those pages. Examples assume an existing application composer and a validated workflow registry.

    Registration Order

    Construct the host, register or bind all work, then start the host. Construction does no network I/O. A workflow NewRunner and a scheduler constructor add work to the host; a workflow NewDriver only controls runs. Late registration returns the vendor’s ErrStarted.

    Inside a composer, register cleanup at the acquiring owner, before later construction can fail. Use composer.Must for fallible construction there; startup callbacks complete before application runners begin. See Lifecycle and Ownership.

    Builder callbacks run synchronously once, in order, on one fresh draft. Nil entries panic; expanded nil or empty slices supply no edits. Publication detaches owned settings and containers from the retained draft, not native SDK internals, hooks, clients, or captured services. Finish configuring shared native objects before publishing them.

    Temporal

    The arkoren.dev/driver/temporal host shares a client, task queue, and worker. Within your composer, create and register it before either driver:

    import "arkoren.dev/driver/temporal"
    
    host := temporal.New(func(host *temporal.Builder) {
    	host.HostPort("temporal:7233")
    	host.TaskQueue("app")
    })
    composer.Cleanup(host.Stop)

    Register workflow and schedule work on that same host. Here registry already contains your workflows and steps:

    import (
    	"arkoren.dev/driver/temporal/temporalschedule"
    	"arkoren.dev/driver/temporal/temporalworkflow"
    )
    
    runner := composer.Must(temporalworkflow.NewRunner(host, registry))
    schedules := composer.Must(temporalschedule.New(host))

    The runner’s Driver supplies the workflow client; schedules supplies the scheduler driver. Neither starts a worker. Add host lifecycle and readiness hooks:

    composer.Startup(host.Start)
    composer.Check("temporal", host.Ping)

    Start dials, applies registrations, and starts the worker. Ping checks native API reachability, not worker liveness or scheduler dispatch readiness. A wrong address fails startup rather than waiting for the first client request. The scheduler’s own run lifecycle is separate and uses the already-started host.

    Connection Policy

    Defaults are HostPort("localhost:7233"), namespace default, and task queue arkoren. Configure TLS, credentials, interceptors, and other native settings through ClientOptions and WorkerOptions. When dialing, host address and namespace override matching native fields, including when using host defaults. The host adapts slog.Default() when no logger is supplied; an explicit native client logger takes precedence. SDK option records are value copies, not recursive snapshots.

    If you already own a native client, wrap it instead:

    host := temporal.NewFrom(client, func(host *temporal.Builder) {
    	host.TaskQueue("app")
    })

    Stop leaves this borrowed client open. Only TaskQueue, WorkerOptions, WorkerStopTimeout, and NoWorker apply; host logger and connection settings do not. Configure the borrowed client’s logger before wrapping it: the worker inherits that logger. host.Client() is available immediately for NewFrom, but is nil before Start for a dialing host. A nil borrowed client fails startup.

    Shutdown Boundaries

    Serialize Start and Stop; call Stop after Start returns. An already-canceled startup context acquires nothing. Native worker startup has no context parameter, so cancellation during that call is observed only after it returns, followed by resource cleanup. Registration callbacks and SDK hooks must return. Registration panics propagate after owned-client cleanup.

    WorkerStopTimeout defaults to ten seconds; zero restores the default and a negative host value disables grace. A nonzero native WorkerOptions.WorkerStopTimeout wins. Native grace can apply separately to internal workers before activity contexts are canceled: it is not a total shutdown deadline and cannot terminate arbitrary callbacks.

    Stop starts worker cleanup even with an expired context, then waits only until its context ends. Cleanup continues afterward. The owned client closes only after worker cleanup returns; a context error does not prove cleanup finished. Repeated Stop calls wait on the same cleanup using their own contexts. Borrowed clients remain yours to close.

    See the temporal host API.

    Restate

    Restate reaches an endpoint in your application rather than polling a worker. The host therefore has three lifecycle operations: Start opens a listener, Serve handles requests, and Stop releases it.

    Configure ingress/admin URLs that your process can reach, and an advertised URL the Restate server can reach. Before constructing the host, obtain the server’s request-identity public keys (publickeyv1_...):

    import "arkoren.dev/driver/restate"
    
    host := restate.New(func(host *restate.Builder) {
    	host.Addr(":9080")
    	host.IngressURL("http://restate:8080")
    	host.AdminURL("http://restate:9070")
    	host.AdvertisedURL("http://app:9080")
    	host.IdentityKeys(identityKeys...)
    	host.Register(true)
    })
    composer.Cleanup(host.Stop)

    Here identityKeys is your configured []string of server public keys. A serving host without keys fails Start with restate.ErrNoIdentityKeys. AllowInsecure(true) skips verification and permits anyone who can reach the listener to invoke handlers; use it only for isolated local development, never as a production startup repair. The endpoint supports HTTP/1.1 and unencrypted HTTP/2; establish production network access controls and transport protection separately.

    Bind workflow and scheduler services to this host before startup, using their constructors from the respective Drivers pages. Then add all three hooks:

    composer.Startup(host.Start)
    composer.Run(host.Serve)
    composer.Check("restate", host.Ping)

    Start builds the endpoint and opens the listener without serving. Serve begins request handling, optionally registers the deployment, and blocks until the application stops. Stop also owns an opened listener if Serve never runs, such as during startup rollback. Canceling Serve or a registration failure does not replace cleanup.

    Ping checks the admin API’s health, not ingress reachability, registration, successful workflow delivery, or persisted activation of the preflight-retention profile. Verify an actual run when delivery readiness matters. The default advertised URL is http://localhost plus the listen address; that only works when the server can reach the application there.

    Deployment Policy

    Register(true) asks Serve to register a deployment; otherwise operators must register it. Each rollout creates a new deployment. RegisterForce overwrites a deployment at the same URL and is appropriate only when none of the old work is in flight. RegisterBreaking permits registration that removes services. Neither option makes incompatible journals replay-safe. Use AdminAuthKey for a protected admin API and AuthKey for protected ingress.

    Drain workflow runs that used the earlier Child implementation on their original deployment before upgrading to its rejection; there is no automatic history migration. See workflow rollout limits.

    When the same host serves schedules, preserve retained generations and finish in-flight Remove invocations on the old deployment before replacing an incompatible journal. If an older deployment removed an entry, drain its pending ticks before reusing its name. The Restate scheduler guidance owns those schedule-specific constraints.

    Shutdown Boundaries

    Stop waits for active HTTP requests until its context expires. It then closes remaining connections and returns the shutdown error. Closing connections cancels request contexts, not arbitrary Go code that ignores cancellation. The shared HTTP client remains caller-owned and is not closed by endpoint cleanup.

    host.Query runs admin SQL; restrict its credentials and never accept arbitrary queries from untrusted callers. See the restate host API.

    Client-Only Hosts

    A web process can start work without serving it. For Temporal, set temporal.Builder.NoWorker(true) and construct temporalworkflow.NewDriver. For Restate, set restate.Builder.ClientOnly(true) and construct restateworkflow.NewDriver. These host settings skip the worker or listener; native connection and authentication still belong to the host.

    Use the workflow driver’s client-only setup, not NewRunner. Temporal rejects a runner on a NoWorker host. Scheduler drivers register work, so construct those in the serving process instead. Match codec, prefix, and native routing across producers and workers.

    A host with no registered work also behaves client-only. NoWorker defaults false; setting it false later restores permission but never forces a worker without registrations. Temporal’s WorkerDisabled reports the stored flag, not liveness.

    Continue with Running Workflows once the host is started, or return to Scheduled Tasks for scheduler use.

    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