Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Jobs and events

Jobs and events

Where events travel

Events stay inside the Jadpo runtime. Each is stored in the outbox of the store the action writes, in the same transaction as the action, and the runtime delivers it:

  • on a long-running server, from its own workers;
  • on Cloudflare Workers, right after the commit (waitUntil), with the once-a-minute tick sweeping up anything left.

So one place holds every log and trace, a request is never slowed by delivery, and you don't need a queue service. A route that wants the finished result rather than a 202 waits for the work its action set off with settle: (Waiting for follow-on work).

An outside queue is never how events reach your subscribers. A connection can publish chosen public events outward, so other systems can react to them. That is a bridge fed from the outbox after the commit, at least once, with the trace context attached, to Cloudflare Queues, SQS, Pub/Sub or a webhook. Reading a queue the other way, through receiving, turns each message into one of the app's own events in its own outbox (Events from outside), so the runtime still delivers it to subscribers.

Running background work

The runtime drives one runner for all background work, so the interpreter and compiled code run it the same way. The runner calls an action with its arguments, as the system, in a boundary of its own, the unit that commits or rolls back as one, and runs what that action sets off after it. Every piece of deferred work has a record of its own in the store, a delivery or a batch run, so none is lost when an invocation stops short.

  • After the response, never in it. A committed action's deliveries, and the batch runs it started, are left for the runner, not run while the request waits, except under settle:. A deployable hands them to its host, which runs them once the response has been sent: on a native server, as a task on the worker thread that answered, with that worker's program, so no thread needs a program of its own; on Workers, with waitUntil. What a host does with a request (answer it, and hand on what it left) and with a tick is one runtime type every host wraps. So a native server and jadpo dev serve through the same handler, and jadpo dev only listens in to print its lines. Each piece gets a context of its own, under the host's clock as it starts and with no seed. A host with nothing to run it on, a test's or a tool's, drops it, and the tick picks up what a dropped piece left.
  • A budget for each invocation. An invocation is one run of the host's code: after a response, a tick, or work handed on. Every invocation that runs background work is given a time budget by its host, at most the 15-minute limit of a run. On a native server and under jadpo dev the budget is that limit. A test's host may give less. On Workers it is 30 seconds after a response and for work handed on, and 15 minutes for the cron. A tenth of the budget, at most 5 seconds, is kept back for giving claims back: once no more than that is left, the runner takes no more work.
  • A run's limit within the budget. A subscriber's or a schedule's action runs until the 15-minute limit, or until what is left of the budget before the time kept back, whichever is sooner. Only the body is timed, never its commit: the transaction is held open until the body has its value, then committed outside the timing. So work that committed is never given back as though it hadn't run, and a body that ends past its limit keeps nothing. A delivery stopped this way counts against its retry: budget (Retries and giving up). A schedule's run is recorded failed with deadline_exceeded. The purge schedule's run is held to the same limit.
  • Every lease the budget plus a minute. A delivery's claim, a schedule's lease and a batch run's lease last what is left of the claimer's budget plus a minute. So a Worker that is ended loses what it held at most 90 seconds after it began. On a native server, with the whole run limit as its budget, a lease lasts at most 16 minutes. A claim taken while a route waits for its follow-on work lasts the longest deadline a route has, 60 seconds, plus the minute.
  • Work not reached waits in the store. A delivery an invocation didn't reach stays pending, and a batch run waits on its row. What an invocation after a response left goes on in a fresh invocation with a budget of its own, once the invocation ran something. An invocation that could run nothing leaves the work for the tick, rather than handing it on for ever. On a native server the fresh invocation is a task of its own. On Workers it is a request to the Worker itself, through the loopback binding ctx.exports gives its own JadpoHandOn entrypoint, naming each piece by its record. The fresh invocation reads the pieces back and runs them in its own waitUntil, within what is left of its own 30 seconds, counted from its start (design notes, "Work handed on, on Workers"). A hand-on the platform refuses, for example past its limit on how many invocations one request may chain, leaves the work in the store for the minute's cron. The tick finds what is due at its time, then runs each piece under the host's clock as that piece starts, so work that takes time never claims with a lease already past.
  • Stopping gives work back as it was. A native server asked to stop (SIGTERM, or ctrl-c under jadpo dev) accepts nothing more, starts no more ticks, and ends every budget at once. The action running stops, and nothing it did is kept. A delivery is given back pending, its attempt not counted, as a route's within: gives one back. A batch run's lease is given back. A schedule's run is recorded deadline_exceeded, and its lease given back. A route waiting for its follow-on work stops waiting, as when within: passes. The server waits at most 10 seconds for this, then ends, and jadpo dev exits 0. A second signal ends the process at once, with 143 for SIGTERM or 130 for SIGINT. If the signals can't be watched, jadpo dev runs without them, and a signal ends it at once.
  • Backfills. A backfill worked out in Jadpo (Computed backfills) runs its rows on the same runner, a batch at a time. Each value is kept and checked by its field's rules, as any value of the field's type is.

Not built yet: publishing events outward, and the error for signals that can't be watched, cli.signal_handler_failed.

Markdown for agents: /docs/where-events-travel.md.Built from Jadpo main at a707efb.