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: HTTP

HTTP

Waiting for follow-on work

On this page

An action does its own database work and leaves the rest, such as a second call to a provider, to subscribers: code that runs when an event the action emits is delivered (Subscribers). Without anything more, the route answers once the action commits, and that work carries on afterwards. When the client wants the finished result rather than a 202, the route waits for the work with settle::

example
POST /orders {    input: PlaceOrder    output: OrderView    status: SuccessStatus.created    run: place_order(input, current_principal.user)    settle: {        answer: order_view(result.id)        within: 5s    }}

This route places the order, then waits up to 5 seconds for the work it set off. It answers with order_view(result.id): 201 if the work finished, 202 if the 5 seconds ran out first. The body is an OrderView either way.

What the route waits for

After the action commits, the runtime delivers the events it emitted at once, while the request waits, and then the events their subscribers emit in turn. They run in the request's own invocation, each delivery as any is delivered (Delivery: at least once, safe to repeat), as the system limited to its event's tenant (Background work). The request's caller is given back before the answer runs.

The work has settled when every one of those deliveries has succeeded, failed for good, or been parked as outcome_unknown. That covers a failure that isn't retried, a defect (a bug in the code, such as an Int overflow), a subscriber the build no longer has, and an event 16 deep in a chain of events (event_chain_limit), so a loop of events settles too.

Some work can't be waited for:

  • A delivery left to retry after a pause (unavailable, rate_limited, or a store that couldn't be reached) hasn't settled, so the route stops waiting at once and answers 202.
  • So does a delivery the request couldn't claim.
  • So does a batch run the action started. A batch is long work no route waits on, so a route whose action starts one answers 202 (Batches: work over many items).

Waiting stops at the first such piece. The rest is left for after the response, and a retry is left to the outbox's sweep, the runtime's regular pass over deliveries that are due (Retries and giving up).

The answer

Then the route runs answer:. It is a named query, given result (what the action returned) or fields read through it, and nothing else; the query reads who is asking through its policy. It runs as the request's caller and under the query's policy, as a GET running it would. The route answers with its result and the route's status:.

If within: passes first, the route runs the same query and answers 202. So the client gets the same output: either way, and knows from the status whether the work finished. The work carries on in the background either way, exactly as it would without settle:.

The query's failures are the route's too, beside the action's, in OpenAPI and the record. A query that fails, through its missing: say, answers with that failure, though the action committed.

Rules

  • With settle:, output: is the query's result type, not the action's.
  • within: counts from when the request came in, as deadline: does. It must be shorter than the deadline, leaving the answer time to run, or it is route.settle_invalid. The answer isn't held to the deadline, because the action has already committed, so waiting never ends in a 504.
  • When within: passes, the subscriber's action that is running is dropped and nothing it did is kept. Its delivery goes back to pending, due at once, and that attempt isn't counted. Only its body is cut short, never its commit, and the delivery's own statements are never cut short, so a claim is never left in doubt.
  • settle: goes only on a route that runs an action and answers SuccessStatus.ok or .created, or it is route.settle_invalid. A GET sets off nothing to wait for, .accepted would make both answers 202, and .no_content sends no answer.
  • Only work this action set off is waited for, never other requests' work or published copies of events (Where events travel).

Repeats, OpenAPI and tests

  • A repeat with the same Idempotency-Key (Idempotent writes) runs the answer query again, rather than replaying a stored body, so it shows how far the work has got. The key keeps the query's arguments, each written by its parameter's codec. A repeat answers 202 until the work is known to have settled, and the route's status after that. So a process that stops between the commit and the answer never has a repeat say the work finished.
  • OpenAPI lists the route's status, "once the work it set off has settled", and 202, each sending the query's result, and says a repeat runs the answer again. The API record adds a settle within <duration> line to the route.
  • A route test's request waits in the same way. A test host's timers never go off, so within: never passes in a test, and a follow-on left to retry gives 202.

Markdown for agents: /docs/waiting-for-follow-on-work.md.Built from Jadpo main at a707efb.