Skip to main content
Every refusal is an exception of its own, raised as itself before the executor runs, so an agent loop can tell them apart with except. All of them derive from CTRLRunError; the set is closed by the specification and a new one is a specification amendment first.

Each one

CTRLRunError

Base class for every error raised by CTRLRun.

InvalidArgument

An argument cannot be accepted as given. An Action field or argument that cannot be canonicalized (SPEC-v0.1 §2.3), and — the same kind of wiring bug — a StateStore transition no record can make, such as committing an effect nobody reserved.

PolicyError

The policy is missing, unreadable, or malformed. Raised at load time (SPEC-v0.1 §3.4).

EffectKeyError

An effect template cannot be resolved to a key (SPEC-v0.1 §5.1). The action is refused rather than executed without an effect key: an action whose logical effect cannot be identified cannot be protected against duplication.

ActionDenied

The action may not run. reason says why, e.g. unknown_action (SPEC-v0.1 §3.4).

AuthorityDenied

The principal holds no grant that covers this action (SPEC-v0.3 §4.3). A subclass of ActionDenied, because an authority denial is the action being denied and an agent loop’s existing except ActionDenied should keep working. reason is one of the closed set in §4.3 — no_authority, authority_constraint, authority_expired, authority_escalation, authority_revoked, authority_unreadable — never a grant id: a grant may legally be named no_authority, and evidence that can be spoofed by naming a grant is not evidence. The id travels in grant_id.

AuthorityEscalation

A delegation that may not exist: it is not contained in its parent (SPEC-v0.3 §5.3). Not an ActionDenied: nothing was proposed. This is the creation-time vocabulary — containment, unknown_parent, parent_not_delegable, parent_not_valid, not_the_subject, max_depth — and it is disjoint from AuthorityDenied’s evaluation reasons. The two are never used interchangeably.

ApprovalRequired

The action needs a human. request_id is what ctrlrun approve takes (SPEC §4.3). Raised instead of blocking, so an agent loop can surface the request and come back with ctrlrun.with_approval(request_id) in context.

ApprovalTimeout

Nobody answered the approval request in time (SPEC-v0.1 §4.3).

ApprovalMismatch

The presented approval does not authorize this action (SPEC-v0.1 §4.2). reason is one of unknown, mismatch, or the status the record was in — consumed, expired, pending, denied.

DuplicateEffect

This logical effect already happened, or is happening now (SPEC-v0.1 §5.4). state is committed — the effect is done — or in_progress, meaning another attempt holds a live reservation on the key. Neither permits a second execution.

AmbiguousEffect

The outcome of this effect is unknown; only a human may resolve it (SPEC-v0.1 §5.4). Raised for a record already in AMBIGUOUS, and for one whose lease expired mid-flight: the worker may have died after the remote committed. A retry is refused either way, until ctrlrun resolve says which it was.

NotExecuted

Raised by an executor to assert the remote side did nothing (SPEC-v0.1 §5.5). This is the only exception that maps to FAILED and therefore permits a retry. Every other exception is an AMBIGUOUS outcome.

Suspended

Raised by an executor: the remote asked for something before it will finish. SPEC-v0.2 §6.9 in the kernel’s own terms, and modelled the way v0.1 §5.5 models “nothing happened” — an explicit opt-in signal, never a default and never inferred. There is no outcome to record: the effect record stays EXECUTING, its lease is extended, the continuation is held, and the caller gets this back to relay. continuation is whatever the remote said to present again. It is opaque here — CTRLRun never parses it, and only ever compares it with hmac.compare_digest.

IdentityError

A credential was offered and rejected (SPEC-v0.3 §3.2). Not an ActionDenied: an agent loop’s except ActionDenied is written to handle a policy saying no, and a credential that stopped being valid is not that — the same distinction v0.1 §5.1 draws for EffectKeyError. A missing principal stays ActionDenied(reason="no_principal"); this is for one that was produced and found wanting, which includes an expired Principal reaching Control.execute (§2.3).

SchemaMismatch

A store met a database it does not recognise, in either direction (SPEC-v0.6 §3.3). Its own type rather than an InvalidArgument, and the bar it clears is that an operator’s process refusing to start needs a distinguishable exception: “your database is from the future” and “your lease is negative” have entirely different remedies, and one bucket for both would put a schema problem behind a wiring bug.

MissingDependency

An optional extra is not installed (SPEC-v0.2 §1.1, §11). Never ImportError or ModuleNotFoundError: an operator reads those as a broken package rather than as an option they did not select. The message names the module that is missing and the command that installs it.

Next