Durable workflows
Every piece of background work Barry does on your behalf runs as a durable workflow: a detached action, a scheduled or one-off action, a bag job, and the meaningful internal tasks that producers submit. Operators and contributors can read here what that runtime promises and how it keeps each promise. Background work covers the user-facing model of states and controls, and Schedule and distribute work shows how to set up a schedule.
One path for all background work
Section titled “One path for all background work”Every producer submits through one shared admission service, and admission turns
each accepted request into exactly one workflow run on the owner machine. A
detached action runs as the actions.execute@1 workflow, a bag job runs as the
built-in job@1 workflow, and a registered workflow runs as its own pinned
type. The same admission rules apply to each: an action, a job, or a workflow
definition reaches admission in one common envelope, and nothing about the run
depends on which machine eventually executes it.
Two kinds of work stay outside this path by design. Inline procedures that need your current conversation keep their own lifecycle, and the service loops that keep the owner itself alive, such as boot, supervisor recovery, watchdogs, and worker heartbeats, remain service responsibilities. A watcher can submit work, but its idle poll never becomes a workflow.
Admission
Section titled “Admission”Admission decides, once and durably, what a request means. Each request carries a stable idempotency key, and the owner authenticates the caller before it consults that key, so a key never reveals another identity’s work. The owner then compares two fingerprints:
- The request hash covers the stable intent: which action or job, its inputs, explicit overrides, the authority namespace, and the trigger occurrence. A repeat with the same key and the same hash returns the original run. A repeat with the same key and different intent conflicts and launches nothing.
- The plan digest freezes what the owner resolved on first acceptance: the definition and its resources, the composed prompt, the output schema, the effective timeout, the chosen harness and model, the grant ceiling, and the executor generation. A retry after someone edits the catalog gets the original accepted plan, not the new one.
The owner resolves and authorizes everything before anything runs. Preflight counts as an effect, so validation during admission never runs it. In one owner transaction, admission records the workflow, the admission record, the grant evidence, compatibility IDs, a logical quota reservation, and an outbox entry for history and notifications. The caller receives its acknowledgement only after that transaction commits. If the acknowledgement goes missing, the caller retries with the same key; a missing response never licenses a second, direct execution.
The owner store stays the single authority for what it accepted. The action history you browse is a projection that the outbox publishes idempotently, and reads fall back to the owner’s records while that projection lags. Compact idempotency records outlive bulk history, so expiring old runs never frees a key for reuse.
Deterministic coordination
Section titled “Deterministic coordination”A workflow definition is a synchronous generator that coordinates and never performs effects itself. It yields a small set of commands:
| Command | What the owner records |
|---|---|
activity |
A unit of computation or an effect, run by a worker |
sleep |
A durable timer |
signal |
A wait for a named input, with an expiry |
now, random |
A recorded clock reading or random value |
all |
A group of up to 100 commands that resolve together |
The owner replays a definition against its recorded outcomes in a fresh, isolated evaluator. Inspection rejects ambient clock, random, process, and network access, so replay always reaches the same decisions. Replay uses recorded results: re-evaluating a run never repeats a model request or an external effect, and a model’s output becomes durable input to later steps rather than a prompt to regenerate. When replay disagrees with history, the run blocks instead of rewriting what happened. Definitions and activities pin by digest and version, and incompatible code needs a new version.
Activities, attempts, and leases
Section titled “Activities, attempts, and leases”An activity is the schedulable unit; an attempt is one physical execution of it. A worker claims an attempt under a lease, heartbeats while it runs, and reports a result. Typed executors carry out the attempt, whether a process, an agent session, or a model call, through one shared contract to start, inspect, and stop it. Each executor records stage intent before launch, the process or session handle once observed, and an immutable receipt before it reports success. A delivery retry for the same attempt inspects that execution; it never starts another one.
An activity runs in stages: preflight, main execution, validation, and publication. Each potentially effectful stage keeps its own journal, so repairing a later stage never repeats an earlier effect. Publication repeats nothing and can finish after the execution deadline; validation cannot.
Physical capacity stays reserved until the owner observes that an execution actually stopped. That rule holds after a revocation and after a lost lease too. Process disappearance does not count as success, and an expired lease does not free the slot of work that might still be running.
Effects and retries
Section titled “Effects and retries”Retry policy follows what an activity does to the outside world, not a generic attempt count. Each activity has an effect class, and one that declares none counts as non-repeatable:
| Effect class | Recovery |
|---|---|
| Pure or explicitly repeatable | Bounded retry within its declared budget, keeping each attempt’s provenance |
| Destination-deduplicated | Reuse the destination key and inspect its receipt before any retry |
| Checkpointable | Resume from a verified checkpoint under the same logical effect identity |
| Non-repeatable or unspecified | Never repeated automatically after a possible launch; reconciled instead |
Every stage carries a logical effect key namespaced by owner, workflow, activity, and stage. That key stays the same across physical attempts, while attempt IDs and lease fences change, so a recovery attempt cannot slip past a destination’s deduplication. Validation and schema errors end the step unless a separately authored repair step exists. Retries cap total attempts, elapsed time, and cost, add backoff and jitter, and never reset a deadline.
Deadlines belong to the owner. An action’s dispatch budget defaults to 120 seconds from acceptance, its execution budget starts at the first effectful stage, and neither a retry, a restart, nor a lease refresh extends either one. A timer that falls due while the owner is down fires on recovery.
States and uncertain outcomes
Section titled “States and uncertain outcomes”The owner keeps coordinator state, execution phase, physical settlement, and effect certainty as separate facts, and derives the state you see from them in one place for every surface:
| State | Meaning |
|---|---|
| Queued | Admission committed; execution has not begun |
| Running | An attempt is executing |
| Waiting | The workflow awaits a timer, a dependency, or a decision |
| Stopping | You asked it to stop; the owner has not yet confirmed cessation |
| Cancelled | The owner confirmed the stop |
| Completed | A validated result committed |
| Failed | A known terminal failure |
| Needs attention | The result, the authority, or the stop is uncertain |
An uncertain outcome stays uncertain until evidence or an explicit, recorded resolution settles it. Reconciliation inspects the executor’s journal and the destination’s receipts; late evidence can resolve a run through an audited transition, and a recovered result moves it to Completed. When nothing proves what happened, a manual resolution records who resolved it, the evidence, the reason, and the residual risk. It never fabricates a receipt, and a safe continuation returns the run to Queued only through an explicit decision. A timeout that never confirmed cessation lands in Needs attention, not Failed.
Stopping and pausing
Section titled “Stopping and pausing”Stopping a run and pausing its schedule act on different things. A stop targets the current workflow: the owner persists the stop intent, fences future launches and effects, asks the executor to interrupt, and keeps the physical reservation until the executor confirms termination. The run shows Stopping until then. A network partition never counts as a clean stop. Pausing future runs targets the schedule, which owns calendar intent, timezone, occurrence identity, catch-up, and overlap; the schedule’s settings persist apart from any run in progress, and the owner creates at most one logical run per occurrence.
Recovery, fencing, and generations
Section titled “Recovery, fencing, and generations”Durability rests on one SQLite store on the owner machine, in write-ahead-log mode, that commits run state, history, and timers together. A lifetime lock stops a second owner from opening the same store, and the operating system releases it only when the holding process exits.
Fencing keeps stale holders from acting. Each owner epoch and worker session fences the one before it, so after a restart, a sleep and wake, or a copied worker token, a displaced attempt cannot report into the current run. Durable executor receipts let the owner reconnect with work that kept running.
Generations keep each run on the rules it started under. Every accepted run
records its executor generation, so an upgrade or rollback changes routing only
for new admissions and never hands in-flight or uncertain work to different
code. Long-lived work rotates into bounded successor generations that carry
lineage, authority, and unresolved effects forward without widening them. A
restore creates a new store generation: it rotates fencing, blocks open work,
reconciles executor and destination receipts, and requires proof that the
previous owner cannot write. A restore loses work acknowledged after the
backup’s capture, and the procedure reports that recovery point.
barry db backup and barry db restore operate on the stores; the
workflow runbook
covers the operator procedure.
Typed decisions instead of raw signals
Section titled “Typed decisions instead of raw signals”A workflow that needs a person declares a typed human decision rather than waiting on a bare signal name. A decision carries its prompt, permitted choices, input schema, allowed responders, expiry, and a correlation key. The owner verifies that the responder holds authority and records a durable decision receipt, so repeated clicks never duplicate a response. A run shows “Waiting for your approval” only when such a contract identifies the wait; Barry never treats an arbitrary signal as an approval.
Where you see it
Section titled “Where you see it”The product surface for all of this is Background work, with views for Needs attention, Active, Scheduled, and History. Each work item opens on its purpose, what started it, its current situation, its latest progress, its result, and the controls the owner permits right now. The runs console serves as the execution inspector, which shows attempts, the execution host, the model endpoint, permissions, and logs. Notifications travel as their own outbox entries, so a failed delivery never reruns work.
What it is not
Section titled “What it is not”The runtime makes deliberately narrow promises, and refuses rather than pretends where it cannot keep one:
- Not multi-tenant. It targets a personal installation with several trusted machines and claims no isolation between users sharing it.
- Not a sandbox for untrusted workers. Workers run work you already trust; distributed nodes explains that boundary.
- Not replicated. Each run has one owner. Barry offers no owner replication and no automatic failover; recovery means the owner returning or a fenced restore.
- Not exactly once. Effects receive stable keys and receipts, and destinations deduplicate with them, but the runtime does not promise that an effect happened exactly once, and a restore can lose recent acknowledged work.
- Not hostile-code containment. The default profile defends trusted code; a stronger profile runs only where the host proves it enforces the limits, and refuses otherwise.
- Not session migration. Barry can recover an accepted run or place a new authorized attempt elsewhere, but it does not move a running interactive session between machines.
The SDK reference for authors lives in the durable workflows source docs.