Chio/Docs
LOGIN · JOIN

Admission and execution nonces

Follow one operation from reservation to dispatch and see which owner may advance it at each step.

Concept chio@5f63e6e

On this page

An agent can prepare a consequential call, wait through an approval or a restart, and still run only the exact request it prepared.

Admission turns a proposed effect into one retained operation. The host reserves it, a live kernel start commits it, and a separately pinned executor performs it, each under its own authority. The running example is the review-note program from the Execution application, which appends the reviewer's verdict on PR #421 to the pricing project's review journal.

Caller dispatch ownersChoose a step.
kernel hostpinned executorreservesigned noncestartauthorizationDeniedclaim, appendsigned reportsignsrKpfρrequestkernelexecutorreview-notesjournalauthorizationoperation stateAwaitingCallerReportkernel hostpinned executorreservesigned noncestartauthorizationDeniedclaim, appendsigned reportsignsrKpfρrequestexecutorreview-notes journalauthorizationoperation stateAwaitingCallerReport

Start commits dispatch and addresses one executor

The kernel checks the nonce against the unchanged arguments and current authority, captures dispatch, and signs an authorization naming the pinned executor. AwaitingCallerReport is captured dispatch with no authenticated return yet: unknown delivery, never permission to dispatch again.

Next owner: the pinned executor, which claims, runs, and reports the original operation.

One caller-delivered operation across its two hosts: the kernel host that reserves and starts it, and the pinned executor that claims and performs it. The review-note journal stands for the resource; budget holds, approvals, and the information-flow preflight are left out.

An operation's identity is derived from what it commits to

The durable record is AdmissionOperationV1, with wire schema chio.admission-operation.v1. Its identity is derived from the request, its capability binding, the request-binding hash, the policy hash, and the effect class. The identity therefore fixes both what will happen and the authority it happens under.

An approval wait, a queue, a process restart, or a network exchange can separate a check from its call. Resubmitting the same committed request across that interval finds the same operation. Reusing its request ID with different arguments is a conflict: the kernel refuses the continuation, and the host keeps the original record intact.

SqliteAdmissionOperationStore keeps each record findable by that identity, independent of the process that admitted it. A resubmission of a completed operation reuses its original receipt and output and runs nothing again.

Read more in What survives a crash, which follows the same identity through recovery after a restart.

A reservation holds capacity; only a live start commits dispatch

Preparation and execution are separate transitions. Every move between dispatch states is one the kernel's transition rule accepts for the operation's requirements; any other move is refused as an illegal transition.

Admission states and the transitions is_legal_transition accepts for the selected requirementsapprovalabudget_capturebbroker_attemptkrequirements
∣V∣=13,∣E∣=20|V| = 13,\quad |E| = 20
kind = ToolDispatchbefore dispatchafter dispatchfrom every enabledpre-dispatch statePreparedBrokerAttemptRegisteredApprovalRequiredBudgetAuthorizedApprovalReservedReadyToDispatchCapturePendingCompensatedBeforeDispatchDispatchCommittedFinalizingNotAcceptedAfterDispatchCommitOutcomeUnknownAfterDispatchCompletedDeniedAfterDelivery
ReadyToDispatch←{ApprovalReservedaBudgetAuthorized¬a∧bPrepared¬a∧¬b\texttt{ReadyToDispatch} \leftarrow \begin{cases} \htmlClass{role-call}{\texttt{ApprovalReserved}} & \htmlClass{role-grant}{a} \\ \texttt{BudgetAuthorized} & \lnot \htmlClass{role-grant}{a} \land \htmlClass{role-grant}{b} \\ \texttt{Prepared} & \lnot \htmlClass{role-grant}{a} \land \lnot \htmlClass{role-grant}{b} \end{cases}
and BudgetAuthorized when a∧b\text{and } \htmlClass{role-call}{\texttt{BudgetAuthorized}} \text{ when } \htmlClass{role-grant}{a} \land \htmlClass{role-grant}{b}
DispatchCommitted←{CapturePendingbReadyToDispatch¬b\texttt{DispatchCommitted} \leftarrow \begin{cases} \htmlClass{role-call}{\texttt{CapturePending}} & \htmlClass{role-grant}{b} \\ \texttt{ReadyToDispatch} & \lnot \htmlClass{role-grant}{b} \end{cases}
Admission states and the transitions is_legal_transition accepts for the selected requirementsapprovalabudget_capturebbroker_attemptkkind = ToolDispatchdispatchPreparedBrokerAttemptRegisteredApprovalRequiredBudgetAuthorizedApprovalReservedReadyToDispatchCapturePendingCompensatedBeforeDispatchDispatchCommittedFinalizingNotAcceptedAfterDispatchCommitOutcomeUnknownAfterDispatchCompletedDeniedAfterDelivery

13 states and 20 legal transitions. ReadyToDispatch follows ApprovalReserved or BudgetAuthorized; DispatchCommitted follows CapturePending.

ReadyToDispatch←{ApprovalReservedaBudgetAuthorized¬a∧bPrepared¬a∧¬band BudgetAuthorized when a∧bDispatchCommitted←{CapturePendingbReadyToDispatch¬bx→Compensated…  ⟺  pre(x)∧enabled(x)\begin{aligned} &\texttt{ReadyToDispatch} \leftarrow \\ &\quad \begin{cases} \htmlClass{role-call}{\texttt{ApprovalReserved}} & \htmlClass{role-grant}{a} \\ \texttt{BudgetAuthorized} & \lnot \htmlClass{role-grant}{a} \land \htmlClass{role-grant}{b} \\ \texttt{Prepared} & \lnot \htmlClass{role-grant}{a} \land \lnot \htmlClass{role-grant}{b} \end{cases} \\ &\quad \text{and } \htmlClass{role-call}{\texttt{BudgetAuthorized}} \text{ when } \htmlClass{role-grant}{a} \land \htmlClass{role-grant}{b} \\[8pt] &\texttt{DispatchCommitted} \leftarrow \\ &\quad \begin{cases} \htmlClass{role-call}{\texttt{CapturePending}} & \htmlClass{role-grant}{b} \\ \texttt{ReadyToDispatch} & \lnot \htmlClass{role-grant}{b} \end{cases} \\[8pt] &x \to \texttt{Compensated…} \iff \\ &\quad \mathrm{pre}(x) \land \mathrm{enabled}(x) \end{aligned}
In Rust · state.rs:55029 lines
if to == AdmissionOperationState::CompensatedBeforeDispatch {
    return from.is_pre_dispatch() && predispatch_state_enabled(requirements, from);
}
let ready_source = if requirements.approval {
    AdmissionOperationState::ApprovalReserved
} else if requirements.budget_capture {
    AdmissionOperationState::BudgetAuthorized
} else {
    AdmissionOperationState::Prepared
};
match to {
    // …
    AdmissionOperationState::ReadyToDispatch => {
        from == ready_source
            || (kind == AdmissionOperationKind::ToolDispatch
                && requirements.budget_capture
                && requirements.approval
                && from == AdmissionOperationState::BudgetAuthorized)
    }
    // …
    AdmissionOperationState::DispatchCommitted => {
        from == if requirements.budget_capture {
            AdmissionOperationState::CapturePending
        } else {
            AdmissionOperationState::ReadyToDispatch
        }
    }
    // …
}
The dispatch states of a ToolDispatch operation. Every solid edge is a pair is_legal_transition accepts for the selected requirements; dashed edges are legal under other requirements. The rule says which moves are allowed, not which path a call takes. GovernedActiveResponse lacks the BudgetAuthorized shortcut, and GovernedEconomicMutation has its own four-edge machine.What survives a crash

A caller-delivered operation passes through four retained conditions, and each names the owner allowed to move it next:

  1. Prepared. The original context is selected and the required reservation is incomplete. The host refreshes its authenticated context and reserves.
  2. ReadyToDispatch, no dispatch commit. The preparation is reversible and nothing has authorized the effect. A live kernel start checks current authority.
  3. AwaitingCallerReport. Caller dispatch is captured with no authenticated return yet. The pinned executor claims, runs, and reports the original operation.
  4. Completed. The original terminal result is retained. The host returns the permitted result, and a retry keeps the same identity.

AwaitingCallerReport records unknown delivery. Its type documents it as neither permission to dispatch again nor grounds to compensate the operation, and unlike a terminal unknown outcome it still accepts the original report when that report arrives.

An execution nonce binds to its operation and authorizes nothing alone

AdmissionExecutionNonceReservationV1 carries a signed nonce under the wire schema chio.execution_nonce.v2. It is an operation-owned profile of the general execution nonce: the signed material binds to the admission operation's identity, so a nonce issued for one operation cannot select or redirect another operation's context.

Constructing or decoding the value establishes nothing by itself. A trusted store pins the issuer to its coordinator, checks the nonce's original-request provenance, checks the current fence and clock, and reserves the nonce atomically with the operation. Only then does the reservation hold.

A reserved nonce, like a reserved budget or approval, holds participation and decides nothing. The caller-delivery path keeps the same split internally: the kernel builds the committed caller-start response only after the original dispatch commits, and that internal receipt and nonce still grant no execution until the outer start producer signs the caller's authorization as a separate step.

In Rust: the operation states and the nonce reservation
crates/kernel/chio-kernel/src/admission_operation.rs224–250rust
pub enum AdmissionOperationState {
    Prepared,
    BrokerAttemptRegistered,
    ApprovalRequired,
    BudgetAuthorized,
    ApprovalReserved,
    ReadyToDispatch,
    CapturePending,
    DispatchCommitted,
    /// Captured caller dispatch with no authenticated return yet. This is
    /// unknown delivery, not permission to dispatch or compensate. Unlike a
    /// terminal legacy unknown tombstone it can accept the original report.
    AwaitingCallerReport,
    Finalizing,
    Completed,
    CompensatedBeforeDispatch,
    NotAcceptedAfterDispatchCommit,
    OutcomeUnknownAfterDispatch,
    /// The delivered output did not match a grant's committed output
    /// digest. A signed Deny is persisted; the open hold is released and
    /// zero is captured.
    DeniedAfterDelivery,
    MutationReady,
    MutationSubmitted,
    EconomicMutationApplied,
    EconomicMutationNotApplied,
}
crates/kernel/chio-kernel/src/admission_operation/execution_nonce.rs23–27rust
pub struct AdmissionExecutionNonceReservationV1 {
    wire: ReservationWire,
    canonical: Vec<u8>,
    nonce_id: AdmissionIdentifier,
}

Read more in Dispatch commitments, which covers where holds are captured and when an uncaptured hold reverses, and in Threshold approvals for operations that also need independent sign-off.

The host pins every owner before it admits work

The execution lab includes the complete review-note program. Its host constructor supplies the kernel, the original issuer and subject keys, the durable stores, the capability, and the independently pinned executor before any work is admitted. These are trusted operator commands in a local embedding; keep their state and keys away from agents.

security-execution/src/caller.rsrust
let reserved = kernel.reserve_caller_execution_blocking(&request)?;
retain(&work.join("reservation.receipt.json"), &reserved.receipt)?;
ensure!(
    reserved.verdict == Verdict::Allow,
    "reservation refused: {:?}",
    reserved.reason
);
let nonce = reserved
    .execution_nonce
    .context("reservation supplied no nonce")?;
retain(&work.join("nonce.json"), &nonce)?;
let authorization = match kernel.start_caller_execution_blocking(&nonce, &request.arguments)? {
    CallerStartResponse::Authorized(value) => *value,
    CallerStartResponse::Denied(value) => {
        retain(&work.join("start-refusal.receipt.json"), &value.receipt)?;
        anyhow::bail!("start refused: {:?}", value.reason)
    }
};

Reservation returns a signed receipt and the original nonce. Start checks that nonce against the unchanged arguments and current authority, then answers with the signed refusal or with an authorization naming the configured executor. The application retains a refusal before stopping and never calls the executor on a denied start. Caller delivery explains the two owners.

This call site uses the blocking caller API. An embedding that carries explicit information-flow context uses a separate sequence of preflight, reservation under refreshed context, and start; neither entry point grants execution from an advisory snapshot. The stock CLI sidecars pin no caller executor, so a deployed caller path is composed in the embedding.

Embedding the kernel with explicit security context: preflight, reserve, inspect, and start

A review host posts the reviewer's summary for issue #417 to your internal review destination through an independently pinned external executor. It already owns the exact ToolCallRequest, the authenticated session context, the durable admission store, and the budget authority. Preflight and reservation both stop before execution:

security-host/prepare_publication.rsrust
use chio_kernel::{
    ChioKernel, KernelError, SecurityInvocationContext, ToolCallRequest, ToolCallResponse,
};

/// Preflight can return an operation-owned nonce, but cannot authorize execution.
pub fn preflight_publication(
    kernel: &ChioKernel,
    original: &ToolCallRequest,
    trusted_context: &SecurityInvocationContext,
) -> Result<ToolCallResponse, KernelError> {
    kernel.reserve_caller_execution_blocking_with_security_context(
        original, trusted_context,
    )
}

/// The host refreshes its authenticated flow context after the preflight join.
/// Keep the same request; add only its original issued nonce.
pub fn reserve_after_preflight(
    kernel: &ChioKernel,
    original: &ToolCallRequest,
    preflight: &ToolCallResponse,
    refreshed_context: &SecurityInvocationContext,
) -> Result<ToolCallResponse, KernelError> {
    let nonce = preflight.execution_nonce.as_deref().ok_or_else(|| {
        KernelError::DurableAdmission(
            "preflight returned no nonce; inspect its decision".into(),
        )
    })?;
    let mut continuation = original.clone();
    continuation.execution_nonce = Some(nonce.clone());
    kernel.reserve_caller_execution_blocking_with_security_context(
        &continuation, refreshed_context,
    )
}

A request without a nonce returns only the preflight response. After its information-flow join, the host refreshes the authenticated context's generation and presents the original issued nonce with the same request. The preflight join is its own authority, recorded through NativeSecurityNoncePreflightJoinRequestV1 and NativeSecurityNoncePreflightJoinRecordV1: when a deployment registers the dispatch join but not the preflight join, a store test shows the operation refused as unsupported instead of borrowing the dispatch join's standing.

security-host/inspect_admission.rsrust
use chio_kernel::admission_operation::{
    AdmissionDispatchState, AdmissionOperationId, AdmissionOperationState,
    AdmissionOperationStore, AdmissionOperationStoreError,
};

type Retained = (AdmissionOperationState, AdmissionDispatchState, bool);

/// The authenticated host selects its original operation ID and store.
/// Reading retained state neither claims a serving lease nor permits execution.
pub fn inspect_original_admission(
    store: &dyn AdmissionOperationStore,
    original_id: &AdmissionOperationId,
) -> Result<Option<Retained>, AdmissionOperationStoreError> {
    Ok(store.load_by_operation_id(original_id)?.map(|operation| (
        operation.state(),
        operation.dispatch_state(),
        operation.dispatch_commit().is_some(),
    )))
}

Read the operation ID your authenticated host retained, never one an arbitrary caller selects. A ReadyToDispatch snapshot with no dispatch commitment describes a prepared operation; reading it acquires no serving lease, consumes no grant, and authorizes no executor.

security-host/start_publication.rsrust
use chio_kernel::{
    CallerStartCredentials, CallerStartResponse, ChioKernel, KernelError,
    SecurityInvocationContext, SignedExecutionNonce, ToolCallRequest,
};

/// Requires the original reservation, current host context and live credentials.
/// An Authorized result is for the independently pinned durable executor to claim.
pub fn start_publication(
    kernel: &ChioKernel,
    original: &ToolCallRequest,
    nonce: &SignedExecutionNonce,
    credentials: CallerStartCredentials,
    trusted_context: &SecurityInvocationContext,
) -> Result<CallerStartResponse, KernelError> {
    kernel.start_caller_execution_blocking_with_security_context(
        nonce, &original.arguments, credentials, trusted_context,
    )
}

CallerStartResponse::Denied carries the original signed refusal. Authorized carries a committed statement addressed to the configured executor, which authenticates and durably claims it in its own ledger before running the effect. The start helper publishes nothing itself.

security-host/run_publication.rsrust
use chio_kernel::{
    CallerStartCredentials, CallerStartResponse, ChioKernel, KernelError,
    SecurityInvocationContext, ToolCallRequest, ToolCallResponse, Verdict,
};
use chio_kernel::admission_operation::{
    AdmissionOperationId, AdmissionOperationState, AdmissionOperationStore,
};
use chio_kernel::caller_delivery::SignedCallerDispatchAuthorizationV1;

use super::inspect_admission::inspect_original_admission;
use super::prepare_publication::{preflight_publication, reserve_after_preflight};
use super::start_publication::start_publication;

pub enum PublicationStart {
    Refused(Box<ToolCallResponse>),
    Authorized(Box<SignedCallerDispatchAuthorizationV1>),
}

/// The host pins original_id to this exact request and its configured store.
/// refresh_context reads the current authenticated generation from that host.
/// No publication occurs here: only the pinned executor may claim Authorized.
pub fn run_original_publication(
    kernel: &ChioKernel,
    store: &dyn AdmissionOperationStore,
    original_id: &AdmissionOperationId,
    original: &ToolCallRequest,
    initial_context: &SecurityInvocationContext,
    credentials: CallerStartCredentials,
    mut refresh_context: impl FnMut() -> Result<SecurityInvocationContext, KernelError>,
) -> Result<PublicationStart, KernelError> {
    let preflight = preflight_publication(kernel, original, initial_context)?;
    if preflight.verdict != Verdict::Allow {
        return Ok(PublicationStart::Refused(Box::new(preflight)));
    }
    let refreshed = refresh_context()?;
    let reservation = reserve_after_preflight(kernel, original, &preflight, &refreshed)?;
    if reservation.verdict != Verdict::Allow {
        return Ok(PublicationStart::Refused(Box::new(reservation)));
    }
    let state = inspect_original_admission(store, original_id)
        .map_err(|error| KernelError::DurableAdmission(error.to_string()))?;
    if !matches!(state, Some((AdmissionOperationState::ReadyToDispatch, _, false))) {
        return Err(KernelError::DurableAdmission(
            "inspect and recover the original operation before attempting start".into(),
        ));
    }
    let original_nonce = preflight.execution_nonce.as_deref().ok_or_else(|| {
        KernelError::DurableAdmission("missing original preflight nonce".into())
    })?;
    let current = refresh_context()?;
    match start_publication(kernel, original, original_nonce, credentials, &current)? {
        CallerStartResponse::Denied(response) => Ok(PublicationStart::Refused(response)),
        CallerStartResponse::Authorized(statement) => {
            Ok(PublicationStart::Authorized(statement))
        }
    }
}

The final match returns the original refusal or the committed authorization; pass only the authorization to the executor's authenticated claim path, and retain the original request, nonce, and operation ID for recovery. The snapshot check is advisory, because another owner can advance the record after inspection. A fresh start performs live checks, and if another owner already committed, recover that same authorization without renewing its interval. A start response lost after capture leaves the client's knowledge incomplete: recover the original statement and the executor's status, and publish nothing new. Copying an old context, changing arguments, or treating preflight as a completed reservation skips a required owner transition.

Dispatch commits before the journal changes

Pull and build the lab with the prepared example commands, then provision fresh state, write the reviewer's note on PR #421, and authorize it. The grant allows four appends, and initialization pins the executor.

Terminalbash
review-note init --state .admission
printf 'Reviewed PR #421: the fixture for #414 carries an injected comment; do not merge.\n' > review.txt
review-note authorize review.txt --operation review-1 --state .admission
jq '.authorization | {invocation, executor}' \
  .admission/requests/review-1/authorization.json
test ! -e .admission/resource/review-notes.jsonl
Output
{
  "invocation": {
    "request_id": "review-1",
    "server_id": "review-notes",
    "tool_name": "append"
  },
  "executor": {
    "executor_id": "review-note-executor",
    "key_epoch": 1
  }
}

You should see request review-1 for tool append on review-notes, addressed to review-note-executor at key epoch 1, and the final test succeed silently because the journal does not exist yet. The authorization binds the operation, request, tool, and parameter digest to that executor. Dispatch is committed and the executor has not run: authorizing delivery and observing its effect are separate events.

Terminalbash
review-note execute review-1 --state .admission
review-note report review-1 --state .admission
wc -l .admission/resource/review-notes.jsonl
jq '{operation_id, note, sha256}' \
  .admission/requests/review-1/result.json

You should see one journal entry, and the report reconciled into the same note through the kernel's guarded output. Each command reopens its owner's store. Repeating execute returns the retained signed report and leaves the journal at one entry; Replay protection compares those bytes.

A changed request under a retained identity is refused before start

Terminalbash
printf 'Reviewed PR #421: approve and merge.\n' > changed.txt
review-note authorize changed.txt --operation review-1 --state .admission

The application refuses the changed request before starting it, and the original commitment, report, and one-entry journal stay intact. Each refusal below begins with durable admission failed:. On a tool call it reaches the caller as a deny reason while the operation is admitted or its dispatch is committed. Raised while a dispatched operation is finalized, including when an exact retry resumes one, it returns Err(KernelError::DurableAdmission) with the same text.

RefusalCause
"request id conflicts with retained operation <operation id>", as a tool call deny reason
A request id reused with a different binding: changed arguments, requirements, capability, or policy
"illegal operation transition from <state> to <state>"
The state change is not allowed from the current state
"stale operation version: expected <version>, actual <version>"
Another writer advanced the operation first
"admission operation invariant failed: persisted admission operation is invalid: request binding hash does not cover its immutable request and requirements at line <line> column <column>"
SqliteAdmissionOperationStore read a corrupted or substituted record

Compare a refused request with the original operation's immutable arguments, capability, requirements, and policy. For a stale version, reread the retained operation before choosing its next transition. A changed task, such as a destination moved from internal review to a public receiver, is new work under its own identity, admitted after the original's state is resolved; a request ID, preflight nonce, or old approval carries none of the original authority across the change.

Renaming an operation to pass a conflict creates a second attempt, as the process-host counterexample shows with request_binding_mismatch. A lost response for the original task uses original-operation recovery, and an operation that may already have dispatched follows unknown outcomes.

Next: refuse a repeated request