Lorenz Docs

Durable Claims and Daemon Control

Lorenz keeps the in-memory claim store as the default. Durable claim persistence and the long-lived daemon are both opt-in, gated behind the durable_claims and daemon features (off by default). Daemon control is exposed through the local observability server when that server is enabled.

Claim Store Backends

The daemon accepts three claim-store backends:

Backend Selection Persistence Process sharing Retry durability
memory default process lifetime only no no
sqlite claim_store.backend=sqlite local SQLite file same host yes
turso claim_store.backend=turso Turso SQLite-compatible file same host with multiprocess WAL yes

The claim store is configured entirely through the @lorenz/flags system; there are no bespoke claim-store CLI options or environment variables. The durable_claims feature is a convenience that selects the sqlite backend:

lorenz WORKFLOW.md                                   # memory (default)
lorenz --feature durable_claims WORKFLOW.md          # sqlite
lorenz --flag claim_store.backend=turso WORKFLOW.md  # turso

The same values can come from WORKFLOW.md front-matter (flags: / features:) or the environment (LORENZ_FLAG_CLAIM_STORE__BACKEND, LORENZ_FEATURE_DURABLE_CLAIMS), following the standard flag precedence (CLI > front-matter > env > default).

When claim_store.path is empty (the default), the daemon stores claims at:

<workspace.root>/.lorenz/claim-store/<workflow-sha256>/claims.db

The workflow hash is derived from the canonical workflow file path. This keeps the default durable store isolated when multiple workflow files share one workspace root. Set claim_store.path to use a shared or relocated store.

The claim owner stale threshold defaults inside the orchestrator store. Override it with claim_store.owner_stale_ms (0 uses the store default).

Schema Versioning

Durable stores create a claim_store_meta table and record schema_version = 1. Startup rejects an unknown version instead of reading a schema it does not understand.

The current schema stores:

  • one serialized checkpoint row for orchestrator state;
  • a bounded event table for recent claim-store mutations;
  • owner heartbeat rows used to distinguish live owners from stale owners.

Runtime Responsibilities

@lorenz/orchestrator owns claim-state mutation. It accepts a claim store and serializes claims, reservations, retry attempts, owner ids, and recovery metadata through that store. It does not open files or choose a backend.

The CLI daemon is the composition root. It loads the workflow, chooses the backend, opens the store, passes it to LorenzRuntime, and closes it during shutdown. This keeps backend selection out of the pure dispatch state machine.

Daemon Leadership

The long-lived daemon is gated behind the daemon feature (--feature daemon). Without it (the default) lorenz runs unmanaged with no leadership lease, like --once. When enabled, daemon startup acquires a local leadership lease before tracker polling, worker-pool hydration, server startup, or runtime start. The lease is keyed by canonical workflow path under:

<workflow-directory>/.lorenz/daemon/<workflow-sha256>.lock.json

A second long-running daemon for the same workflow exits with daemon_already_running and reports the owner pid and endpoint when available. A lease whose same-host owner pid is verifiably dead is replaced immediately; if the owner cannot be verified dead (another host, or the pid is live or reused) and its heartbeat is stale, startup exits with daemon_lock_stale and names the lock file to remove to force takeover. --once remains an isolated single-poll mode and does not acquire the long-lived daemon lease.

When the dashboard server is disabled, the daemon still serves its control endpoints over a unix domain socket in a per-user runtime directory, and the lease records that socket endpoint. So lorenz status, lorenz refresh, and lorenz stop still self-discover the running daemon with no --url/--port. Those flags remain a cross-host fallback.

The initial leadership store is local-file backed and same-host only. The interface is generic so another provider can later supply the same acquire, read, heartbeat, stale, and release operations.

Control Endpoints

The daemon exposes the same control routes over whichever transport is active - HTTP (TCP) when the dashboard is enabled, and a unix domain socket when it is not (--no-dashboard). The lease records the active endpoint (http or socket) as its endpoint.

Method Path Purpose
GET /api/v1/daemon Return daemon owner, endpoint, heartbeat, workflow path, and leadership store kind
POST /api/v1/refresh Queue an immediate poll and reconcile pass
POST /api/v1/stop Request graceful daemon shutdown

The same daemon status is included in /api/v1/state under daemon (HTTP only).

CLI attach commands use the daemon lock to discover the owner endpoint (HTTP or socket):

lorenz status WORKFLOW.md
lorenz refresh WORKFLOW.md
lorenz stop WORKFLOW.md

Each command also accepts --url, --port, and --json.

Shutdown Order

On graceful shutdown the daemon stops the runtime, unmounts the TUI, drains the worker pool, stops the server, closes the issue store, closes the claim store, and then releases the leadership lease.

Forced process exit may leave a lock or claim owner heartbeat behind. The daemon lease is same-host by design, so a restart reclaims it as soon as the recorded owner pid is verifiably dead (ESRCH), without waiting for the heartbeat to go stale. Claim owner records make no such local-process assumption: recovery treats them as live until their heartbeat is stale, because a shared claim store may outlive any one process's view of ownership.

© 2026 Lorenz Issues in, agent runs out.