Skip to main content

Runtime

Struct Runtime 

Source
pub struct Runtime { /* private fields */ }
Expand description

The runtime.

Implementations§

Source§

impl Runtime

Source

pub async fn attention( &self, at: Timestamp, page: usize, ) -> Result<Attention, RuntimeError>

Does anything on this plane need a person right now, and which thing.

What counts is not a judgement made here twice. It comes from the run vocabulary: failed, exhausted, withheld and quarantined all leave a run open, and three of them name a party who can answer — a raised ceiling, a lifted withdrawal, a person deciding a doubt. failed is the one a resume may clear alone, so it is listed only where the run stands on landed work no compensation undid. Beside those sit the backlogs that already have a listing and a verb that empties them.

The clock is the caller’s — the same decision the task listing and the push sweep make — so a fixed instant makes this testable and a deployment owns its cadence. page bounds every query, and a condition says whether its page filled rather than pretending to a total.

§Errors

If a store this plane does hold is unreachable: a backlog that cannot be read is not an empty one.

Source§

impl Runtime

Source

pub async fn run_batch( &self, id: BatchId, spec: &BatchSpec, ) -> Result<BatchReport, RuntimeError>

Start a batch, or continue one.

Passing an existing BatchId resumes it: processing picks up after the stored cursor, and any item that was reserved but never finished is replayed under its original run id rather than started again.

Returns when the source is exhausted or max_items is reached. A failing item does not stop the batch — that is the failure isolation a batch exists for, and a caller who wants the opposite should be running one run, not many.

What does stop it is an admission that never happened: a halt, a tenant ceiling, a store outage. Those hold for every item behind the one they hit, and they are returned as the error rather than written over the item, so the item is still pending when the next pass runs.

Source

pub async fn batch_report( &self, id: BatchId, ) -> Result<BatchReport, RuntimeError>

The batch’s current tally, without processing anything.

Source§

impl Runtime

Source

pub fn builder_on<B: FullBackend + 'static>(store: Arc<B>) -> RuntimeBuilder

A builder with the whole case layer wired to one backend.

The one-call form of the seven-cast litany: journal, cases, tasks, events, timers, memory and quotas all backed by store, which is what a deployment on a single RedbStore or PostgresStore means anyway. The à-la-carte methods remain — cases and friends override an individual store afterwards, and builder still starts from the journal alone for a plane that wants nothing else.

Blob storage is deliberately not included: bytes routinely live in a different system than rows, so blobs stays an explicit decision.

Source

pub fn builder_with(stores: Stores) -> RuntimeBuilder

A builder with the whole case layer wired from handles.

What builder_on is in terms of: the seven casts live in one place, so a deployment that chose its backend at run time — an operator naming either a file or a connection string — wires exactly what a deployment on one concrete type wires, rather than a hand-written approximation of it that drifts the day an eighth store is added.

Source

pub fn builder(store: Arc<dyn JournalStore>) -> RuntimeBuilder

Source

pub fn cases(&self) -> Option<&Arc<dyn CaseStore>>

The case store, if this runtime has one.

Source

pub fn tasks(&self) -> Option<&Arc<dyn TaskStore>>

The worklist, if this runtime has one.

Source

pub fn events(&self) -> Option<&Arc<dyn EventStore>>

The inbound-event store, if this runtime has one.

Source

pub const fn budget(&self) -> &Budget

The ceilings every run under this runtime starts with.

Readable because “what is this plane allowed to spend” is a question an operator asks of a running system, and answering it by re-reading the config that should have been applied is how a misapplied budget stays invisible.

Source

pub fn blobs(&self) -> Option<&Arc<dyn BlobStore>>

The blob store, if this runtime has one.

Source

pub async fn drill(&self, at: Timestamp) -> Result<DrillReport, RuntimeError>

Run the live half of the case-layer drill with this plane’s own stores.

The wiring is the point of this method existing: the drill’s value is holding each case’s references against the stores the plane actually runs with, and an embedder assembling drill::Stores by hand could hand it a different blob store than the one their runs write to — a drill that passes over the wrong bucket. Stores this runtime does not have are reported as unchecked by the drill itself, which is a different and better answer than an error: a plane with no blob store has no bytes to lose.

The clock is the caller’s, the same decision attention and the push sweep make. A rehearsal’s instant is a fact about the outside world rather than a journaled observation, and taking it here would be a fourth ambient-clock escape inside the deterministic zone for a value the caller already has. A fixed instant also makes the recorded verdict testable.

§Errors

If this runtime has no case store — the drill walks cases, so there is nothing to drill — or if the case layer cannot be enumerated.

Source

pub async fn last_drill(&self) -> Result<Option<DrillRecord>, RuntimeError>

The last recovery rehearsal this plane ran, or None if it never has.

The half that makes drill evidence rather than a command: an audit asks when did you last rehearse and did it pass, and that answer has to come from the plane rather than from the thing that scheduled it.

§Errors

RuntimeError::PlanContract when this plane has no case store — the same condition that makes a drill impossible in the first place — and RuntimeError for a store failure.

Source

pub async fn retain( &self, older_than: Timestamp, at: Timestamp, reason: &str, ) -> Result<RetentionReport, RuntimeError>

Erase every closed case opened before older_than. The retention verb.

The wiring is the point, exactly as it is for drill: erasure derives blob addresses and key scopes from the tenant and the case, so a caller assembling the stores by hand could tombstone addresses no run ever wrote and report a clean pass over nothing.

reason lands on every tombstone and on each key destruction, so a later read says expired, on this date, for this reason rather than missing — the distinction the drill’s three-way verdict turns on.

at is a parameter rather than a clock read, for the reason the sweeper’s is: a pass that read the clock could not be tested against a year of ageing cases.

Read RetentionReport::not_erasable before concluding anything. Without a key ring this expires blob tombstones in the live store and journal payloads stay verbatim, which is the residual the erasure page names and the one an erasure request actually turns on.

§Errors

If this runtime has no case store, or the case layer cannot be walked. A store that fails on one case is a report entry, not an error.

Source

pub fn timers(&self) -> Option<&Arc<dyn TimerStore>>

The timer store, if this runtime has one.

Source

pub fn batches(&self) -> Option<&Arc<dyn BatchStore>>

Source

pub fn push(&self) -> Option<&Arc<dyn PushStore>>

Source

pub async fn rearm_push( &self, task: RunId, id: &str, ) -> Result<bool, RuntimeError>

Re-arm a webhook registration a delivery worker gave up on.

A parked registration is not delivered and not deleted: it keeps its cursor, so re-arming resumes at the first record the receiver never acknowledged rather than at the head of the run. That is the whole reason parking exists, and the reason this verb is on the plane — a backlog nobody can empty is a warning line in yesterday’s logs.

The clock is read here rather than taken from the caller, unlike the delivery worker’s: a worker’s schedule has to be assertable in a test, and an operator asking for now means now.

Returns whether a parked registration was found. false is an answer — already live, or never existed — and is told rather than swallowed, because an operator left waiting for a sweep with nothing to do has no way to tell the difference.

§Errors

RuntimeError::PlanContract if this plane has no push store, and RuntimeError::Store if it is unreachable.

Source

pub async fn request_cancel( &self, run: RunId, actor: &Operator, reason: &str, ) -> Result<bool, RuntimeError>

Ask a run to stop, and drive the stop if nothing else will.

The request is durable before this returns, so an operator who gets an acknowledgement has one whether or not the run was reachable. What happens next depends on where the run is:

  • Suspended — nothing is executing, so this resumes the run itself. It observes the request at its first step boundary and unwinds.
  • Running here or elsewhere — the owner observes the request at its next step boundary. Nothing is interrupted mid-effect, deliberately: stopping between “announced” and “recorded” manufactures the in-doubt case the effect protocol exists to avoid.
  • Quarantined — refused with RuntimeError::CannotUnwind: the unwind a stop promises is what a quarantine forbids, and reopening or abandoning it are the verbs that apply.
  • Concluded and closed (succeeded, abandoned, cancelled) — refused with RuntimeError::AlreadyConcluded and nothing is recorded; a run somebody already stopped answers false. A failed or exhausted run is resumable, and stopping it unwinds its work.
  • Held by no provider on this plane — an operator’s terminal: the request is recorded and not driven, and the plane that holds the run’s agent observes it on its next resume.

Returns whether this call recorded the request. A second caller gets false: the first asker stays on the record, because “who intervened” must not be rewritten by a retry.

§Errors

RuntimeError::PlanContract for a blank reason, RuntimeError::CannotUnwind for a quarantined run, RuntimeError::AlreadyConcluded for a closed one, and RuntimeError if the store is unreachable or resuming a suspended run fails.

Source

pub async fn decide_quarantine( &self, run: RunId, decider: &Operator, reason: &str, decision: QuarantineDecision, ) -> Result<RunOutcome, RuntimeError>

Answer a quarantine, and let the runtime decide the run again.

A quarantine is this runtime saying it does not know, and the one conclusion it cannot reach past on its own. Everything else that stops a run has a party who can move it — a resume for a crash, a raised ceiling for an exhaustion, a reviewer for a suspension. This is the party for this one.

§What this does and does not decide

The person decides whether the run may be judged again, or that it never will be. They do not decide the outcome. A Reopen hands the run back to the executor, which re-derives its verdict from a history that now holds whatever the person established with reconcile_effect — and quarantines it again, on the record, if the answer was wrong or incomplete. An Abandon closes it where it stands, unwinding nothing.

The decision is journaled before it is acted on, so a crash between the two leaves the instruction standing rather than lost: the next resume finds it and finishes the job.

Returns the outcome of the pass this drove — so an operator who reopens a run learns immediately whether it got any further, rather than having to go and look.

§Errors

RuntimeError::NotQuarantined if the run’s last conclusion is anything else, RuntimeError::LeaseHeld if somebody is executing it, and RuntimeError for a store failure or a refused resume.

§Panics

Never: the expect covers a lease this function has just acquired.

Source

pub async fn record_quarantine_decision( &self, run: RunId, decider: &Operator, reason: &str, decision: QuarantineDecision, ) -> Result<(), RuntimeError>

Answer a quarantine without driving the run.

decide_quarantine records the decision and then hands the run to the executor, which is what an operator on a running plane wants: they learn immediately whether a reopened run got any further.

A terminal holding a store cannot do the second half. It has the journal and not the agent — a declarative run’s program is a manifest this process was not given — so driving the run would mean refusing the verb for want of an argument the operator may not have during an incident.

That is a supported state rather than a lesser one. The decision is journaled before it is acted on precisely so a crash between the two leaves the instruction standing, and this is the same position reached deliberately: the next resume finds the record and finishes the job — so what the caller must not do is report the run as moved.

§Errors

As decide_quarantine, minus everything the resume could have raised.

Source

pub async fn reconcile_effect( &self, run: RunId, effect: EffectKey, assertion: Assertion, asserted_by: &Operator, note: &str, ) -> Result<(), RuntimeError>

Supply, out of band, the outcome of an effect the journal cannot decide.

The manual twin of Effect::reconcile, and it produces the same record for the same reason: an operator who looks the charge up in the provider’s console is answering exactly the question a probe asks, so the answer belongs in the place a probe’s answer goes, where replay reads it back and nothing re-derives it.

What separates the two is on the record too. A probe’s verdict is attributed to the run; this one names the person, because “the provider told us” and “somebody asserted it” are different evidence.

It supplies a missing fact and never replaces a recorded one. Only an effect undecided_effects names may be answered. Without that rule an operator could talk a run out of compensating work that is standing in the world, and the journal would show an orderly reconciliation while it happened.

This changes no status by itself: the run stays quarantined until decide_quarantine hands it back. Several doubts can be answered before the run is judged again, and answering one of three is not a decision.

§Errors

RuntimeError::NotUndecided if that effect’s outcome is already on the record, RuntimeError::LeaseHeld if somebody is executing the run, and RuntimeError for a store failure.

Source

pub async fn undecided( &self, run: RunId, ) -> Result<Vec<Undecided>, RuntimeError>

Every mutating effect this run’s journal cannot decide the outcome of.

What an operator holding a quarantine has to go and look up, and the keys they quote back to reconcile_effect.

§Errors

If the store is unreachable.

Source

pub async fn set_halt( &self, scope: &HaltScope, by: &Operator, at: Timestamp, reason: &str, ) -> Result<(), RuntimeError>

Stop this tenant from starting new work, or let it start again.

The emergency stop. Some(reason) halts, None lifts, and the reason is required because the next person to look will be somebody else, possibly at three in the morning, and why is the whole question.

What it stops, precisely. New admissions, across every instance, because the flag is in the store rather than in this process — a switch that stops only the instance it was thrown on is the in-process-counter failure arriving during an incident. Refusals are their own error, not a ceiling: a ceiling means not right now and invites a retry, which is exactly what somebody pulling this switch is trying to stop.

What a workload-scoped halt does not stop, deliberately. Runs already executing, and suspended runs resuming. Those are existing work, and refusing to let them continue would strand them mid-saga with reversals unrun — turning an incident into a second one. To stop work in flight, cancel it: that unwinds what it did and records who asked. This is the front door, not a power cut, and saying so is the difference between a control an operator can reason about and one they discover the shape of during an outage.

How wide it reaches is the HaltScope, and the scopes divide on which axis they name rather than on how broad they are. Three name a workload — the tenant, every revision of one declared agent, or one exact reviewed digest — and stop admission. The fourth names an authority, any principal on the delegation chain a run was admitted under, and is the one that reaches work already running: there the incident is the credential, so a run carrying on under it is the harm rather than a side effect, and it pauses at its next step boundary with its completed work standing (withdraws_from is the predicate, so the distinction is checked rather than remembered). Scopes are independent rows — lifting a narrow halt leaves a broad one standing. An ungoverned run, with no manifest, is stopped only by a tenant halt; there is nothing narrower to key it on.

at is the caller’s, never a clock read here: every lifecycle instant in this crate is supplied for the reason a hold’s is — a pass that reads its own clock cannot be tested against an ageing plane, and I1 forbids the runtime an ambient clock outside the effect protocol.

Requires a quota store; without one there is nowhere durable to keep the flag, and an emergency stop that a restart forgets is not one.

§Errors

If no quota store is wired, or the store is unreachable.

Source

pub async fn lift_halt( &self, scope: &HaltScope, by: &Operator, at: Timestamp, ) -> Result<Option<ControlLifted>, RuntimeError>

Lift one, and let work at that scope start again — recording who did.

Answers the run holding the lift record and whether this call removed the row, or None when nothing was standing at scope: an operator who lifts a scope nobody halted is told so rather than told done, and nothing is written.

The record comes first, then the row goes. The lift is written as the sole HaltLifted record of a sealed run of its own, carrying by, at and the whole stop it ended; only then is the register’s row removed. The journal and the register share no transaction, so a lift that cannot be recorded does not happen, and a removal that fails after the record is written is an error naming the record’s run — the halt still stands, the register is the answer to what is in force, and lifting again writes a second record. The record attests the instruction and the row as read, not that the removal succeeded.

Only the row the record names goes. The removal is QuotaStore::lift_halt_if with that row, so a halt re-thrown between the read and the removal is left standing and the call fails naming the record’s run. A row another lift already removed answers removed: false.

QuotaStore::lift_halt is the raw register verb; a direct call to it is unrecorded.

§Errors

If no quota store is wired, the store is unreachable, or the record cannot be written; RuntimeError::ControlStands if the row cannot be removed after the record was written, or was re-thrown in between.

Source

pub async fn release_hold( &self, case: CaseId, by: &Operator, at: Timestamp, ) -> Result<Option<ControlLifted>, RuntimeError>

Release the legal hold on case — recording who did.

The hold’s counterpart of lift_halt, in the same order for the same reason: the HoldReleased record is written, stamped with case, before the row goes. It names the releaser and the hold’s placing operator and instant, and not the hold’s reason, so the erasure the release permits leaves its authorization readable.

None when the matter is not held, with nothing written; removed: false when another release removed the row first.

Only the hold the record names goes. The removal is CaseStore::release_hold_if with that hold, so a hold placed again between the read and the removal is left standing and the call fails naming the record’s run. CaseStore::release_hold is the raw register verb; a direct call to it is unrecorded.

§Errors

StoreError::NotFound if the case does not exist; RuntimeError::ControlStands if the row cannot be removed after the record was written, or was placed again in between; otherwise if no case store is wired, the store is unreachable, or the record cannot be written.

Source

pub async fn lifted_halts( &self, limit: usize, ) -> Result<Vec<Record>, RuntimeError>

Every recorded halt lift, newest first: the one record of each run.

§Errors

If the journal is unreachable, or a lift run does not hold a lift record.

Source

pub async fn released_holds( &self, limit: usize, ) -> Result<Vec<Record>, RuntimeError>

Every recorded hold release, newest first: the one record of each run.

§Errors

If the journal is unreachable, or a release run does not hold a release record.

Source

pub async fn reservations( &self, limit: usize, ) -> Result<Vec<Held>, RuntimeError>

Every run holding spend against this tenant’s periods, and how much.

The runs a QuotaError::SpentOut refusal counts as reserved. A run holds its reservation until it concludes, so the ones that stopped waiting on a person — quarantined, exhausted, withheld, failed — hold it until that person acts; attention names those.

§Errors

If no quota store is wired, or it is unreachable.

Source

pub async fn halts(&self) -> Result<Vec<Halt>, RuntimeError>

Every standing emergency stop on this tenant, and why.

The operator’s question — what is stopped right now? — which a per-scope lookup cannot answer without already knowing what to ask about.

§Errors

If no quota store is wired, or the store is unreachable.

Source

pub async fn running_runs( &self, limit: usize, ) -> Result<Vec<LiveRun>, RuntimeError>

Which runs are executing right now, and under whose authority.

runs_by_outcome answers from the journal’s index, which only concluded runs are in; the live set is the tenant’s admission slots.

The join is the point, not the ids. An incident is rarely cancel this run but a revoked credential or a bad revision, so each entry carries the declared agent and the delegation subject it was admitted under.

A slot whose lease has lapsed is marked stranded rather than omitted: the recovery sweep resumes those, and cancelling one undoes work that was about to continue.

Bounded by limit, and reads a page of each run’s header records to attribute it — an operator question rather than a hot path.

§Errors

If no quota store is wired, or either store is unreachable.

Source

pub async fn waiting_runs( &self, limit: usize, ) -> Result<Vec<WaitingRun>, RuntimeError>

The runs that are waiting, and what each one waits for.

The third listing, beside concluded runs and running ones: a suspended run has no outcome and holds no admission slot, so neither of the others names it. Re-arming these is the recovery runbook’s last step, and until this existed that step named a verb with no argument an operator could obtain.

Soonest due first, bounded by limit. Straight through to the store’s maintained index — no page of records is read, because what a run waits for is on the suspension itself.

§Errors

If the store is unreachable.

Source

pub async fn drain(&self, grace: Duration) -> DrainReport

Stop this instance taking on work, then wait for what it is already doing.

A process killed mid-step leaves an announced effect with no terminal record, so its declared Recovery has to decide whether it happened — the right answer to an accident, and an expensive one to schedule on every deploy.

Admission closes first: every later run, spawn and plan is refused RuntimeError::Draining, leaving no lease, no slot and no journal. Then this waits up to grace for the runs it put on a background task to reach a journaled resting point. Closing first is what makes the answer worth having — a wait alone reports about an instant — and it is also the only signal a long-lived read gets, so an open A2A subscription ends rather than holding a graceful shutdown for as long as the run it watches.

Run it beside a server’s own graceful shutdown, not after it — tokio::join! of the two. Serialising deadlocks one on the other in whichever order you pick: a run awaited inside a request handler is finished by the server, and an open subscription is ended by this.

Three things it deliberately does not do. It does not wait for runs on somebody else’s task; those belong to that caller’s own shutdown. It does not gate resumes or an agent commissioning another one — both continue work this plane already owns, and a commission is a step of a run this is waiting for. And it does not hand back the leases of runs it did not finish: a release says takeover is safe, and for a run still inside perform the next owner would re-perform the call beside the one in flight. Left to expire, the lease is the signal the recovery sweep reads as an instance died holding this run — which is what a drained-out run is.

Idempotent: draining an instance that is already draining waits again.

Source

pub fn is_draining(&self) -> bool

Whether this instance has stopped admitting runs.

For an embedder whose server keeps its listener open while it drains and therefore needs something to answer a readiness probe with. The shipped server needs none: it closes its listeners first, so a probe that connects already fails — which is why this crate’s HTTP surface has no /health route to flip. See crate::api on why it has none at all.

Source

pub async fn cancellation( &self, run: RunId, ) -> Result<Option<Cancellation>, RuntimeError>

The cancellation recorded against a run, if one was.

Reads the journal rather than a flag: a cancellation is a record, so this answers the same thing after a restart and on another instance.

§Errors

If the store is unreachable.

Source

pub fn policy(&self) -> Option<&Arc<dyn PolicyEngine>>

The policy engine, if this runtime has one.

Exposed because a surface that faces strangers has to be able to ask whether one exists at all. Inside the process the caller is the embedder’s own code and an absent engine is a choice; on a socket it is a hole, and the HTTP surface refuses to start without one.

Source

pub fn tenant(&self) -> &TenantId

Which tenant this plane runs as.

Source

pub async fn record_break_glass( &self, actor: &Operator, roles: &[String], reason: &str, ) -> Result<RunId, RuntimeError>

Record an operator deliberately crossing into this tenant, and return the run that holds the record.

Every other tenancy control keeps a cross-tenant read from being reached by accident. This is the designed exception, and the rule for it is that an exception without a record is indistinguishable from the breach it is meant to be. So the access is written into this tenant’s journal — the one whose data is about to be reached — in a sealed run of its own, exactly as a sweep writes its decisions. It therefore inherits the hash chain, the per-record signature and the Merkle inclusion, and the offline audit tool reports it without being taught that break-glass exists.

The record is written before any data is served, and a failure to write it is a failure to access. That direction is the whole control: the alternative — serve first, record best-effort — is a break-glass that works exactly as well when its own evidence is lost.

§Errors

If reason is blank, or if the record cannot be written. An unexplained exception is the thing this record exists to prevent, so it is refused rather than stored empty.

Source

pub fn journal(&self) -> &Arc<dyn JournalStore> ⓘ

The journal, for reads the runtime does not mediate.

This is also the embedder’s resumable output stream: JournalStore::read is a seq-cursored read over a run’s records, so polling from the last sequence seen is a durable, reconnect-safe progress feed that any instance can serve — the exact mechanism the A2A streaming surface is built on. There is deliberately no curated event type between an embedder and the records: a third vocabulary beside the journal’s and the wire’s would be a second truth wearing ergonomics, and the records are already the history the stream must agree with.

Source

pub async fn case_of(&self, run: RunId) -> Result<Option<CaseId>, RuntimeError>

Which case a run belongs to, or None if it belongs to none.

Read from the journal, not from a column beside it. The binding is stamped on the run’s own records at admission, so answering from there is answering from the plan of record — a case-store column saying the same thing would be a second copy of one fact, and the two could disagree about a run the case layer never saw.

It is the first question an operator surface asks, which is why it is a method rather than a documented one-liner: every caller was otherwise going to reach for the first record and read body.case off it, and a caller who reached for the last one instead would still be right today and wrong the moment a run is admitted before its case is known.

§Errors

If the journal cannot be read. An unknown run is Ok(None) rather than an error: no such run and a run in no case are both honest answers to this question, and neither is a fault.

Source

pub fn store(&self) -> &Arc<dyn JournalStore> ⓘ

Source

pub fn owner_id(&self) -> &str

This process instance’s identity, as it appears in run leases.

Not the agent’s name. Several instances of one agent are normal, and a lease is renewed without a fencing bump only when the holder is the same owner — so two instances sharing this string would each renew the other’s lease and both write to one run. See RuntimeBuilder::owner.

Public because “which instance holds this run” is a question an operator asks of a stuck system, and the answer is otherwise only in a store row.

Source

pub async fn run( &self, target: &str, input: Tainted<Value>, ) -> Result<RunOutcome, RuntimeError>

Execute a fresh run with no case attached.

The input arrives labelled, and that is the whole of the decision. Tainted::trusted is what an operator writes for a literal, a constant or a configuration value they vouch for; anything that came from outside — an inbound event, a queue message, a counterparty’s payload — keeps the label it arrived with.

This took a bare Value and admitted it as Trusted by default. A deployment whose runs are started by inbound events passed counterparty data straight in, and three controls went quiet at once: require_trusted protected fields were satisfied by attacker-chosen values, the egress ceiling had nothing untrusted to join with, and the journal recorded no contact with outside data. Nothing failed and the suite stayed green, which is the profile of every other default this crate declines to offer.

A run_trusted/run_tainted pair was tried first and is worse: it doubles every shape, puts run_trusted_in_case one word away from run_tainted_in_case, and still lets run_trusted(cap, payload) compile over data nobody vouched for. A label is a value — it can be computed, threaded through an adapter, or derived from where a message arrived, none of which a method name can do. spawn already worked this way; run was the outlier.

§Errors

RuntimeError::NoProvider when no skill provides target, and whatever admission refuses — policy, quota, a halted tenant, an instance draining, a lease.

Source

pub async fn spawn( self: &Arc<Self>, target: &str, input: Tainted<Value>, ) -> Result<RunId, RuntimeError>

Admit a run and let it proceed in the background, returning its id.

The asynchronous counterpart to run, for callers that want a handle rather than an answer — A2A’s return_immediately, a queue worker, an operator kicking something off.

Admission happens before this returns. The policy gate, the lease and the admission records are all written first, so a refusal is an error here and not a task that never appears, and the id handed back can be read immediately. What continues in the background is the work.

The run is durable, so a process that dies mid-flight leaves a journal another instance resumes; the background task is where the work happens, not where it is kept.

§Panics

Outside a Tokio runtime, as the rest of this crate’s timing does.

§Errors

As run — anything admission itself refuses.

Source

pub async fn run_in_case( &self, target: &str, input: Tainted<Value>, case: CaseId, ) -> Result<RunOutcome, RuntimeError>

Start a new immutable run inside a case that already exists.

Source

pub async fn run_correlated( &self, target: &str, input: Tainted<Value>, case_kind: &str, keys: &[CorrelationKey], ) -> Result<RunOutcome, RuntimeError>

Join or open a case by business key, then run.

Correlation happens before planning, because which case a message belongs to is a question of fact, not of judgement: it is a deterministic lookup on business keys, never a model call. If an open case matches any key the run joins it; otherwise a case is opened.

Source

pub async fn run_once( &self, target: &str, input: Tainted<Value>, idempotency_key: &str, ) -> Result<Admission, RuntimeError>

Run under an admission key, at most once per tenant.

The entry point for an at-least-once emitter: a bus that retries until it sees a 2xx, a webhook fan-out, a queue with redelivery. The store claims the key in the same transaction that writes the run’s first record, so a second call under it cannot admit a second run — not on a retry, not across a restart, not from another instance sharing the store.

§The key must carry its producer

An id is unique only within one emitter, so a bare message id lets two counterparties swallow each other’s messages as apparent retries. InboundEvent::dedup_key is the ready-made spelling, and the same identity the event buffer uses.

§Not the event buffer

EventStore::buffer also reports “already seen” for a (source, id) over the same store, and looks like the primitive. It is not: an event nobody subscribes to is dead-lettered by sweep, and a non-empty dead-letter list means a correlation key is wrong somewhere. Using the buffer as an idempotency ledger would dead-letter nearly every message admitted directly, spending that signal to buy deduplication. Two concerns can share an identity without sharing a table.

§Errors

As run. A key already held is not an error — it comes back as Admission::Replayed or Admission::InFlight.

Source

pub async fn run_correlated_once( &self, target: &str, input: Tainted<Value>, case_kind: &str, keys: &[CorrelationKey], idempotency_key: &str, ) -> Result<Admission, RuntimeError>

Join or open a case by business key and run, at most once per admission key.

The shape an inbound message actually has: it names a matter by business key and carries its own identity. Correlation decides which case, the admission key decides whether at all — correlation alone would join the duplicate to the right case and start a second run inside it.

§Errors

As run_correlated.

Source

pub async fn spawn_correlated_once( self: &Arc<Self>, target: &str, input: Tainted<Value>, case_kind: &str, keys: &[CorrelationKey], idempotency_key: &str, ) -> Result<Spawned, RuntimeError>

Correlate and spawn, at most once per admission key.

§Panics

Outside a Tokio runtime, as spawn does.

§Errors

As spawn.

Source

pub async fn run_plan( &self, plan: PlanIR, input: Tainted<Value>, ) -> Result<RunOutcome, RuntimeError>

Execute an explicit multi-step plan.

The plan is validated and frozen before the first step runs: one that would fail at step seven must not begin at step one.

Source

pub async fn run_plan_correlated( &self, plan: PlanIR, input: Tainted<Value>, case_kind: &str, keys: &[CorrelationKey], ) -> Result<RunOutcome, RuntimeError>

Execute an explicit plan inside a long-lived case.

Source

pub async fn run_under( &self, target: &str, input: Tainted<Value>, terms: RunTerms, ) -> Result<Admission, RuntimeError>

Run under explicit RunTerms — the general form of every run_*.

Keyed terms behave as run_once: a duplicate comes back as Admission::Replayed or Admission::InFlight. Unkeyed terms always admit, and the answer is Admission::Fresh.

§Errors

As run, plus RuntimeError::Delegation when the terms carry a chain this plane may not act under now.

Source

pub async fn spawn_under( self: &Arc<Self>, target: &str, input: Tainted<Value>, terms: RunTerms, ) -> Result<Spawned, RuntimeError>

Spawn under explicit RunTerms — the general form of every spawn_*.

§Panics

Outside a Tokio runtime, as spawn does.

§Errors

As run_under.

Source

pub async fn run_plan_under( &self, plan: PlanIR, input: Tainted<Value>, terms: RunTerms, ) -> Result<Admission, RuntimeError>

Execute an explicit plan under RunTerms.

Keyed terms admit once, as every _once entry point does: the key is validated, and a redelivery is answered by the run it already admitted.

§Errors

As run_plan and run_under.

Source

pub async fn recorded_outcome( &self, run: RunId, ) -> Result<Option<RunOutcome>, RuntimeError>

What a run has recorded, without resuming it.

None while the run is still working — it has records but has reached neither a conclusion nor a suspension.

No lease is taken and nothing is appended. The original is frequently live when its redelivery lands, and a read that acquired a lease would fence a healthy run mid-step in order to report on it.

The last record decides, never any record: a run that suspended, was resumed and carried on has a suspension in its history and is not suspended now. Same rule as the A2A status projection, so the two surfaces cannot disagree about one run.

§Errors

If the journal cannot be read.

Source

pub async fn replay( &self, run: RunId, mode: Mode, ) -> Result<RunOutcome, RuntimeError>

Re-execute a recorded run from its journal.

  • Mode::Strict verifies determinism: every effect must match, and the run must not want any effect the journal lacks.
  • Mode::Resume recovers a crashed run: history is replayed, then execution continues live from wherever the record ends.

Either way no external effect is performed for anything already in the journal. That is the whole point — a resumed run does not re-issue the invoice it already issued.

Source

pub async fn verify(&self, run: RunId) -> Result<Verdict, RuntimeError>

Replay a recorded run strictly and say what it found.

replay with Mode::Strict, answered as a Verdict rather than a status: verified when every recorded effect was asked for again and the run reached the ending it recorded, whatever that ending was; diverged at the first effect this build asked for differently; cannot replay when the run cannot be re-derived here, for a reason that is not the declaration. Both revisions are named on every answer — the one the run was admitted under and the one this plane holds by that name.

Writes nothing, takes no lease and dispatches nothing, as a strict replay never does. It is the question an author asks of an edited manifest: replayed under the edit, this run’s history either reaches nothing it changed or names the first effect it did.

§Errors

If the run does not exist or its journal does not verify, or on a store failure — never for a divergence or a reason the verdict names.

Source§

impl Runtime

Inbound event delivery.

Source

pub async fn deliver( &self, event: &InboundEvent, ) -> Result<Delivery, RuntimeError>

Deliver an inbound event, resuming whichever run was waiting for it.

§Ordering

The event is stored before anyone looks for a waiter. That ordering is the whole reason this works: a message can arrive before its run reaches the wait, and one that is matched-then-discarded leaves that run waiting forever for something that already happened.

A Delivery::Buffered result is therefore normal and not an error — it means “held until someone asks”. Only the sweep (EventStore::sweep_unclaimed) decides an event is genuinely unroutable, because that is a claim about the future rather than about this instant.

§Errors

RuntimeError::ReservedEventKind for a kind in the agentplane. namespace, which only this plane mints; RuntimeError for a store failure or a refused resume.

Source

pub async fn deliver_to( &self, run: RunId, event: &InboundEvent, ) -> Result<Delivery, RuntimeError>

Deliver an inbound event to exactly run.

This is the task-addressed counterpart to Runtime::deliver. It is used by protocols such as A2A where a follow-up carries a concrete task id. Correlation alone is insufficient there: two tasks may wait on the same business key, and resuming the oldest would violate the request.

The event store atomically inserts and claims the event for this run. A run that is not waiting leaves no buffered event behind for another run.

§Errors

As deliver, and RuntimeError::PlanContract when run is not waiting for this input. Addressing a run by id is no way around the reserved namespace: a run waiting on a human task is waiting for exactly the kind this refuses.

Source

pub async fn sweep_events(&self, grace: Duration) -> Result<usize, RuntimeError>

Retire events that nobody claimed within grace.

A non-empty dead-letter list means a correlation key is wrong somewhere: the message arrived, was held, and no run ever asked for it. That is the failure which otherwise presents as a process silently never completing, so it is worth alerting on rather than logging. grace is a std::time::Duration for the reason StepCtx::deadline’s warn_before is: a negative grace window is meaningless, and the signed type could express it — a cutoff moved forward of now, retiring events that had not yet had their chance. It is also the Duration the caller has.

Source§

impl Runtime

Source

pub async fn sweep( &self, now: Timestamp, event_grace: Duration, ) -> Result<SweepReport, RuntimeError>

Run one sweep.

Idempotent: a transition already applied is not applied twice, so calling this on a timer — or twice by accident, or from two instances — is safe.

now is passed in rather than read so the caller controls the clock. That keeps the sweeper testable at all, and lets a simulation drive it through a year in milliseconds.

Source

pub async fn census(&self, now: Timestamp) -> Result<Census, StoreError>

Read the gauges: how much is open right now.

Queried from the stores rather than accumulated, because a counter incremented on open and decremented on close drifts permanently the first time a process dies between the state change and the emission — and it drifts plausibly, which is worse than drifting obviously.

Stores that were never configured contribute zero rather than an error: a plane with no human tasks has no open tasks, and refusing to report the gauges it does have would be the silent failure this exists to prevent.

Source

pub async fn fire_timers( &self, now: Timestamp, ) -> Result<WokenRuns, RuntimeError>

Wake the runs whose instant has arrived.

The shape mirrors event delivery exactly, and for the same reasons: claim atomically so two sweepers cannot wake one run twice, record the wake-up as the sleeping effect’s result, then let replay do the rest. The resumed run reads the timer back like any other completed effect, so none of the suspension machinery exists twice.

One run’s failure does not block the runs behind it: a batch that stops at the first error hands a single unresumable run a veto over every later wake in the tick — and the failed run heals anyway: its wake was recorded under a lease this pass acquired and never released, so the recovery pass finds it once that lease lapses. The failure is counted rather than returned, because a caller that got a count and an error would have neither.

Source

pub async fn decide_task( &self, id: TaskId, decision: &Decision, roles: &[String], ) -> Result<Delivery, RuntimeError>

Complete a task on behalf of a person, enforcing eligibility first.

The claim is checked before the decision is recorded: an approval from somebody who was not permitted to give it is worse than no approval, because it looks like one.

§Only a person’s answer

Decided::OnExpiry is refused here. It is the sweeper’s to apply, from the policy the task declared, and it is the one decision with nobody to claim against: four-eyes has no actor to exclude and eligibility has no name to check, so every control this function exists to run would pass by having nothing to test. A caller reaching this door with one is either applying an expiry from outside the sweeper or dressing a person’s answer as the policy’s.

Source

pub async fn decide_task_at( &self, id: TaskId, decision: &Decision, roles: &[String], expected: Option<Digest>, ) -> Result<Delivery, RuntimeError>

decide_task, refused unless the task row is still the version expected names.

expected is Justification::digest of the row as the caller read it. A row that changed since — before the claim, or between the read and the claim — is refused as RuntimeError::TaskChanged: nothing is recorded, and a claim this call took is released. It proves the caller held the current version of the row, not what any screen displayed. None compares nothing.

Trait Implementations§

Source§

impl Clone for Runtime

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Runtime

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<Unshared, Shared> IntoShared<Shared> for Unshared
where Shared: FromUnshared<Unshared>,

Source§

fn into_shared(self) -> Shared

Creates a shared type from an unshared type.
Source§

impl<T> MaybeSend for T
where T: Send,

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more