Risk and approval
How risk classes route work to autonomy, human approval or denial, and how separation of duties keeps an agent from approving itself.
- proto/numinance/annotations/v1/annotations.proto
- proto/numinance/kinds/v1/autonomy_policy.proto
- schemas/examples/AutonomyPolicy/org-ladder.yaml
Approval is a signal the policy engine consumes, from a human who is not the author. It is never something an agent can grant itself.
Risk classes
Every action has a risk class, from lowest to highest: RISK_CLASS_READ, LOW, MEDIUM, HIGH and PRIVILEGED. A change's risk class is the maximum over its playbook steps. A capability declares a risk floor that no provider and no customer can lower, and lowering the risk of an existing capability in a later release is a contract-breaking change.
Step risk can only be raised by the playbook author. The resolved risk is the maximum of the step's declared risk and the risk of every capability its grant allows.
From risk to a decision
Before every privileged action the runtime evaluates policy outside the model. The result is one of three decisions:
| Decision | Meaning |
|---|---|
| allow | The action proceeds, possibly with obligations such as extra evidence |
| deny | The action does not happen, and the denial is recorded |
| require approval | The action waits for approvers who satisfy the rule |
Inputs include the action and its risk floor, the acting identity, the change and environment, the autonomy level and floor, residency, remaining budget, the evidence present and required, and time windows. Floors are evaluated first and default to deny.
Autonomy policy explorer
Given defaults, rules and floors, what is the highest level this change may reach?
- OPERATEoperations against production
- RELEASEdeploys to production
- STAGEdeploys to pre-production
- MERGEmerges when policy allowsrequestedeffective
- PRopens pull requests
- IMPLEMENTwrites in a sandbox
- SUGGESTproposes, writes nothing
- OBSERVEreads only
The dependency-upgrade rule matches and requests MERGE. No floor matches, so nothing lowers it.
- 1 · Defaultdefaults.level = PR (overridden by the matching rule)
- 2 · Rule: dependency-upgrade, LOW → MERGEmatchedplaybook (resolver context): dependency-upgrade ✓ · risk (exact): LOW ✓
- 3a · Floor: risk HIGH or higher → max SUGGESTnot matchedrisk (resolver context): LOW; a floor matches that class and every higher one.
- 3b · Floor: environment production → max MERGEnot matchedenvironment (resolver context): a different environment.
- 4 · Resultmin(requested MERGE, lowest matching floor none) = MERGE
apiVersion: numinance.dev/v1kind: AutonomyPolicymetadata: {name: org-ladder, scope: SCOPE_ORG}spec:defaults: {level: AUTONOMY_LEVEL_PR}rules:- {match: {playbook: dependency-upgrade, risk: RISK_CLASS_LOW}, level: AUTONOMY_LEVEL_MERGE}(matched)floors:rules:- {match: {risk: RISK_CLASS_HIGH}, max_level: AUTONOMY_LEVEL_SUGGEST}- {match: {environment: production}, max_level: AUTONOMY_LEVEL_MERGE}
- Kernel floors are always on. They sit under every policy and no rule or default removes them.
- The highest configured level never bypasses a matching floor. Floors cap; they are not suggestions.
- Promotion is proposed, then accepted. Evaluation may propose a higher level; a human accepts it. An agent never grants itself a rung.
Text description
The ladder is the AutonomyLevel enum, lowest to highest: OBSERVE (reads only), SUGGEST (proposes, writes nothing), IMPLEMENT (writes in a sandbox), PR (opens pull requests), MERGE (merges when policy allows), STAGE (deploys to pre-production), RELEASE (deploys to production), OPERATE (operations against production).
Resolution follows AutonomyPolicySpec: the level is the lowest level among matching rules, else defaults.level; then no level above the lowest max_level among the matching floors. The example policy is org-ladder: default PR; one rule (playbook dependency-upgrade and risk LOW sets MERGE); two floors (risk HIGH or higher caps at SUGGEST; environment production caps at MERGE).
- In a rule, risk matches exactly and a key the change has no value for does not match.
- In a floor, risk matches that class and every higher one, and a key the change has no value for counts as a match, which is why the toggle
nonefor environment still matches the production floor. - The playbook, risk and environment values are the resolver context of the change. They are not extra fields on a rule. The what-if rule level only changes the simulation; the YAML shown is the real example text.
- Kernel floors are always on. Promotion to a higher level is proposed by evaluation and accepted by a human, and is never self-granted.
- An org with no floors gets the shipped floor (SUGGEST for HIGH and PRIVILEGED, PR for the rest); control-pack floors can only add caps.
Everything here is simulated in the browser from the example policy. Nothing is connected to a running engine.
An autonomy policy in practice
This is the shipped example from the spec. It defaults to opening pull requests, lets a low-risk dependency playbook reach merge, and floors high-risk work at suggestion.
apiVersion: numinance.dev/v1
kind: AutonomyPolicy
metadata: {name: org-ladder, scope: SCOPE_ORG}
spec:
defaults: {level: AUTONOMY_LEVEL_PR}
rules:
- {match: {playbook: dependency-upgrade, risk: RISK_CLASS_LOW}, level: AUTONOMY_LEVEL_MERGE}
floors:
rules:
- {match: {risk: RISK_CLASS_HIGH}, max_level: AUTONOMY_LEVEL_SUGGEST}
- {match: {environment: production}, max_level: AUTONOMY_LEVEL_MERGE}The open CLI can check that this file is well formed with numi validate --files. It cannot tell you what the runtime would decide, because the open build does not resolve profiles or evaluate floors. See Autonomy policy.
The approval workflow
The designed workflow has five parts.
- Request. Policy returns
require_approvalwith the approver group, a required count and an exclusion of the author principal. The change pauses at a gate. - Presentation. The approver sees the plan, the evidence so far, the verifier results and the policy reasons by rule id.
- Decision. An authenticated human approves or rejects. The control plane or the customer's identity provider supplies the principal. The runtime validates the approver against the author identity before it accepts the decision.
- Signal. The approval enters policy as input, not as an override. Policy still decides whether the action proceeds.
- Record. The approval, the principal, the reason and the policy decision id are written to the evidence chain by the runner identity.
An approval applies to a specific plan digest. If the plan changes, the approval does not carry over.
Separation of duties
Four identities act in a change, and the policy treats them as structurally different.
| Identity | May | May not |
|---|---|---|
| Author agent | Propose and implement inside its sandbox | Approve, edit protected paths, deploy |
| Reviewer agent | Review and report findings | Approve as a human, deploy |
| Release pipeline | Deploy, and is the only identity that does | Author code |
| Human approver | Approve, reject, escalate | Be the author of the change they approve |
Policy denies an approver equal to the author, and an approver that shares the author's run. Protected paths such as policy files, CI definitions and permission definitions are out of reach for agents by mount and by source-control branch protection. The kernel that enforces all this sits outside every grant.
Grants expire and cannot widen
For each work unit the runtime derives a grant from the profile, playbook step, risk class and autonomy level. A grant names capabilities, resource scope, budgets, environment and an expiry. A grant is immutable. A holder can request a narrower grant for a sub-step and can never widen its own. Credentials are issued just in time for one step and revoked when it completes or when the breaker trips.
Failure modes and safeguards
- Self-approval attempt. Denied structurally and recorded as a denial.
- Instruction injected through a ticket or document. Retrieved text is treated as data. Only signed policy and the kernel can create enforceable authority.
- Stale approval. An approval bound to an old plan digest is not valid for a new plan.
- Approver fatigue. Mitigate by narrowing what routes to humans, not by adding more approvers to rubber-stamp.
Troubleshooting
- A change is stuck at a gate. Check the approver group and count. If the only eligible approver is the author, nobody can approve by design.
- An action was denied that you expected to run. Read the policy reasons by rule id in the decision record, then check floors before rules.
- A promotion did not apply. Floors and narrower scopes can only tighten. See Evaluations.
Installation modes
Where the Numinance runtime is designed to run - managed with a customer data plane, sovereign, and the later air-gapped milestone - and what you provision in each.
Circuit breaker
The customer-local switch with four positions that caps or halts all autonomous work, and the human-only break-glass path.