Escalation and Standing Authority (the supervised OODA loop)
What happens to a high-risk decision after the safety check pauses it for a human - and nobody answers. This doc specifies the time-bounded escalation ladder that walks an unanswered approval up the on-call chain by impact, and the standing authorization artifact that lets an operator pre-commit a bounded, conditional execution for the case where waiting is more dangerous than acting. Both are framed as a supervised OODA loop layered on the existing single-pass control loop.
Scope reminder. Customer-agnostic. Every rung name, group, threshold, and channel id below is an upstream default; a fork tunes them via config and catalog-as-code (generic-scope.instructions.md).
Safety focus. Nothing here weakens fail toward safety. Standing authorization is a human approval given in advance, bounded by an envelope and re-verified deterministically at execution time - it is never a fail-open path and never lets an LLM grant execution. Every new capability in this doc ships observation-first (architecture.instructions.md § Safety Invariants).
Implementation status
Section titled “Implementation status”Implementation scope
Section titled “Implementation scope”| Area | State | Evidence | Notes |
|---|---|---|---|
| Durable observation mode escalation supervisor | implemented | escalation_supervisor.py, test_escalation_supervisor.py | Bounded scans, delivery claims, and would-escalate observations are implemented without advancing approval or execution authority. |
| human approval resume and delegated-rung verification | implemented | coordinator.py, test_delegation.py | Resume snapshots and rung eligibility are verified before the typed path continues. |
| Escalation ladder and urgency catalogs | in-progress | escalation_ladder.py, test_escalation_ladder_catalog.py, rule-catalog/escalation-ladders/ | Reviewed ladder and urgency-policy instances ship with a fail-closed loader and pure schedule functions, and focused checks cover expiry, fallback delivery, starvation prevention, and deterministic replay. The supervisor does not yet read the catalog, and no measured urgency-compression evidence from a running cohort exists. |
| A3-E standing human authorization | in-progress | standing-authorization.json, record.py, evaluator.py, test_evaluator.py | The catalog schema, the typed record, and the deterministic evaluator exist and reject every constitutional condition with an exact reason code. The evaluator is unwired by design and a focused test fails if a decision path imports it; mode accepts only shadow, so no promotion path exists. Silence never grants authority. |
Implementation history
Section titled “Implementation history”| Date | State | Change | Evidence | Remaining |
|---|---|---|---|---|
| 2026-08-19 | in-progress | Made the “every constitutional condition has a failing case” claim self-enforcing instead of hand-kept. The suite now reads every reason code the evaluator source can return through an AST scan and fails when one has no case. It immediately found one: action_type_outside_envelope was unreachable in the existing table because the pin check runs first, so an action type the envelope never allowed but a pin does allow had no test. That case now exists. This is the first artifact a promotion review would need; it is an offline decision cohort over synthetic delegations, not runtime observation mode evidence. | current change; tests/core/standing_authority passed 40 focused cases; the exhaustiveness test is self-verifying - it failed with reason codes with no case: ['action_type_outside_envelope'] before the missing case was added; task-scoped Ruff and format passed. | A governed runtime observation mode cohort with zero envelope escapes, an independent promotion review, and a runtime revocation store all remain absent, so no decision path may consult the evaluator. |
| 2026-08-14 | in-progress | Adopted the implementation ledger without reconstructing earlier provenance and corrected the prior broad status summary. | current change; current source, focused tests, and constitutional traceability listed in the scope table. | Deliver catalog-backed urgency and standing-authorization evaluation, then retain governed observation mode evidence. |
| 2026-08-14 | in-progress | Shipped reviewed escalation-ladder and urgency-policy catalog instances with a fail-closed loader, deterministic first-match selection, and pure schedule functions. | current change; escalation_ladder.py, test_escalation_ladder_catalog.py; focused catalog checks passed 37 cases and the whole rule-catalog suite passed 1251 cases; strict mypy passed. | Bind the supervisor to the catalog and retain measured urgency-compression evidence from a governed observation mode cohort. |
| 2026-08-18 | in-progress | Made A3-E executable without making it usable. authority/standing-authorization.json is the catalog schema, record.py parses and canonicalizes a document and rejects anything malformed instead of defaulting, and evaluator.py answers eligibility only when every constitutional condition holds, always naming the first failure. Absent, unparsable, or naive-clock input is ineligible rather than permissive. The schema admits only mode: shadow and only resource or resource-group scope, so neither enforce nor a wider impact scope can be expressed. Nothing consumes the evaluator, and a test parses the safety check, executor, human approval-resume, and control-loop trees and fails if any of them imports it. | current change; tests/core/standing_authority passed 38 focused cases covering one positive path plus a negative case per condition with its exact reason code; task-scoped Ruff, format, and strict mypy passed; the core import boundary gate passed. | Retain a governed observation mode cohort with zero envelope escapes, then take an independent promotion review before any decision path may consult the evaluator. Revocation propagation and renewal-as-new-revision are represented in the schema but have no runtime store. |
Remaining work
Section titled “Remaining work”- Reviewed escalation-ladder and urgency-policy catalog instances ship with a fail-closed loader, and focused checks cover expiry, fallback delivery, starvation prevention, and deterministic replay.
- Bind the escalation supervisor to the catalog and retain measured urgency-compression evidence from a running cohort.
- Implement the A3-E standing-authorization schema and evaluator with quorum, revocation, validity, responder confirmation, exact envelope, and no-self-approval negative tests.
- Retain a governed observation mode cohort with zero envelope escapes before any independent promotion review; keep the supervisor observe-only until that evidence exists.
What this doc covers
Section titled “What this doc covers”The control loop in architecture.instructions.md § Control Loop is single-pass: an event is normalized, routed, decided, acted on, and audited to a terminal state. That is correct for a discrete event. It does not model a decision that stays pending while the world changes around it:
- A
hildecision fires an approval request with a TTL. Today, TTL expiry is a no-op + audit + A2 alert (channels-and-notifications.md § on-call, escalation, timeouts). Fail-closed, correct - but it stops there. - Channel fallback already exists: a failed Teams approval falls to another A1-capable channel, then pages the ops lane (channels-and-notifications.md). That handles delivery failure, not human non-response - a different problem.
- A forecast detected issue carries a shrinking lead time (`actual_breach_time
- finding_time`, the breach ETA) (observability-and-detection.md § 3. Predictive / Forecasting). While an approval sits unanswered, that ETA keeps closing - the cost of inaction rises with the clock, but the single-pass loop has already moved on.
The gap is a temporal supervisor: something that re-enters the loop on a timer for as long as a decision is pending, re-reads the situation (approver still silent? ETA closer? impact scope of inaction grown?), and escalates or, if pre-authorized, acts. That is an OODA loop.
OODA as the supervision frame
Section titled “OODA as the supervision frame”The existing pantheon control loop already maps cleanly onto OODA (see the mapping in architecture.instructions.md § Trust Routing). The addition here is a second, slower loop that supervises one pending decision and ticks until it reaches a terminal state.
flowchart LR
subgraph OBSERVE["Observe (per tick)"]
O1["approval still pending?"]
O2["forecast ETA now?<br/>(lead time recomputed)"]
O3["inaction impact scope?"]
end
subgraph ORIENT["Orient"]
R1["recompute urgency<br/>= f(impact, ETA, rung age)"]
R2["which ladder rung<br/>should hold this now?"]
end
subgraph DECIDE["Decide"]
D1{"standing authorization<br/>matches + envelope holds<br/>+ deadline passed?"}
end
subgraph ACT["Act"]
A1["escalate to next rung"]
A2["trip standing action<br/>-> re-enter typed pipeline"]
A3["terminal no-op<br/>(ladder exhausted)"]
end
OBSERVE --> ORIENT --> DECIDE
D1 -->|no, rung TTL left| A1
D1 -->|yes| A2
D1 -->|no, ladder done| A3
A1 -. next tick .-> OBSERVE
A2 --> AUD["audit (Saga)"]
A3 --> AUD
- The supervisor never mutates substrate directly. Its only privileged
outcome (
A2) is to re-enter the typed pipeline so the action is re-judged and executed through the normal principals. A supervisor that called an executor directly would be a defect (same rule as the conversational port in architecture.instructions.md § Agent Pantheon). - The loop is bounded: it has a maximum number of rungs and a hard overall deadline. It cannot tick forever.
The escalation ladder
Section titled “The escalation ladder”An escalation ladder is an ordered list of human-authority rungs. It is distinct from channel fallback: channel fallback answers “the message did not get delivered - try another pipe for the same person”; the ladder answers “the message was delivered but nobody with authority acted - widen who is asked, by impact.”
| Concept | Answers | Fails over on | Lives in |
|---|---|---|---|
| Channel fallback | delivery failure | channel unreachable / send error | channels-and-notifications.md § 6 |
| Escalation ladder | human non-response | rung TTL elapsed with no decision | this doc |
Each rung declares: who (an Entra group, resolved outside the control plane
exactly like approver groups today), a per-rung TTL, and the notification
category it may use (A1 for the decision-carrying rung, A2 paging for
awareness). The ladder is selected by impact tier - a resource-scoped
detected issue may only ever reach the primary on-call; a subscription-adjacent
impact recruits the incident commander quickly.
# Shipped catalog-as-code artifact (shadow-first; see Rollout).version: 1kind: escalation_ladderid: prod-forecast-breachpriority: 10 # unique; first-match in ascending orderselect_when: environment: prod finding_class: forecast.breach impact_at_least: resource_grouprungs: - rung: on_call_primary audience_group: aw-oncall-primary # placeholder; fork supplies real group ttl_seconds: 300 category: hil_approval - rung: on_call_secondary audience_group: aw-oncall-secondary ttl_seconds: 300 category: hil_approval also_page: [pagerduty-primary] # A2 awareness, non-deciding - rung: incident_commander audience_group: aw-incident-commander ttl_seconds: 600 category: hil_approval also_page: [pagerduty-primary, sms-oncall]overall_deadline_seconds: 1500 # hard cap; on expiry -> terminal no-op unless # a standing authorization trips firstDurations are integer seconds rather than 5m strings, so a replayed schedule
never depends on a duration parser. priority makes first-match selection a
total order; the loader refuses a duplicate, because two ladders sharing a
priority would make selection depend on directory order. The loader also refuses
a ladder whose rung TTLs do not fit inside overall_deadline_seconds, so a
ladder cannot name an audience the deadline silently makes unreachable, and
refuses a rung that pages its own deciding audience, because paging is awareness
and never approval authority.
- No self-approval survives escalation. A later rung is a different principal; the approver-of-record is whoever actually decides, and the executor is still a separate principal (Var approves, Thor executes - agent-pantheon.md).
- Every rung transition is audited and, when the fingerprint repeats, feeds
the existing
HandoffEscalation-> GitHub issue path so chronic non-response becomes a tracked signal, not a silent loss (agent-pantheon.md § 6.4 Handoff escalation protocol).
Time-decaying urgency
Section titled “Time-decaying urgency”The ladder above uses fixed TTLs for clarity, but urgency is not fixed when a breach is forecast. The supervisor recomputes, each tick, an urgency signal and uses it to compress rung TTLs and to raise the starting rung:
- Inputs (all already produced upstream, no new collection):
impact/ impact scope from the safety check, breach ETA from the forecaster (observability-and-detection.md § 3), and rung age (how long the current rung has been silent). - Rule of thumb:
effective_ttl = min(rung.ttl, k * remaining_lead_time). As the forecast ETA closes, the window each human gets shrinks, and the loop climbs the ladder faster - it never lengthens a TTL past the declared value. - Confidence still gates. A forecast only drives urgency when its prediction-interval band clears the configured confidence level (observability-and-detection.md § 3); a noisy point-estimate breach does not get to compress deadlines.
- A floor prevents starvation. Compression is clamped to
[min_effective_ttl_seconds, rung.ttl_seconds], so an imminent breach cannot shrink a rung to a window no human could answer in.
version: 1kind: urgency_policyid: default-forecast-urgencylead_time_factor: 0.5 # the k abovemin_forecast_confidence: 0.9 # below this, nothing compressesmin_effective_ttl_seconds: 60 # starvation floorThe schedule is computed by a pure function that takes the remaining lead time
and the forecast confidence as arguments rather than reading a clock, so a
recorded escalation replays to the same timeline. An absent policy, an absent
forecast, or a forecast below min_forecast_confidence all leave every rung at
its declared TTL: an unproven urgency signal never shortens a human’s window.
Urgency changes how fast the ladder is walked; it never changes whether an unattended approved execution is allowed. That gate is standing authorization.
Standing authorization (pre-authorized conditional execution)
Section titled “Standing authorization (pre-authorized conditional execution)”This is the mechanism behind “the operator configured an automatic action in advance.” A standing authorization is an operator-authored, policy-as-code artifact that says:
Under condition C, for actions inside envelope E, if the escalation ladder reaches its deadline unanswered, the pre-recorded human Approval may satisfy the action’s
hilrequirement - and only then.
The crucial design property: a standing authorization is not a new decision engine and not a bypass. It is a deterministic input to the existing risk gate. When the supervisor’s Decide step asks “can this proceed unattended?”, the safety check answers by checking a standing authorization the same way it checks any other rule - and execution eligibility is still granted by that deterministic verification, never by a model (architecture.instructions.md § LLM Quality Gate).
# Proposed catalog-as-code artifact (shadow-first; see Rollout).# rule-catalog/standing-authority/<name>.yamlversion: 1id: sa-scale-out-before-quota-breachauthorization_revision: <content-digest>requested_by: <normalized-human-principal>approved_by: # distinct normalized human principals; min 2 - <accountable-service-owner> - <owner-level-approver>quorum_required: 2valid_from: <rfc3339-timestamp>valid_until: <rfc3339-timestamp> # expires unless renewed by the accountable ownerstatus: active # active | revoked | expired | supersededrevocation_ref: nullservice_ref: <service-id>target_revision: <inventory-and-operating-model-revision>policy_digest: <risk-and-approval-policy-digest>action_type_versions: [remediate.scale-out.compute@<version>]incident_classes: [forecast.breach]responders: primary: <on-call-primary> backup: <on-call-backup> resolved_at: <rfc3339-timestamp>evidence: history_review_ref: <governed-evidence-ref> scenario_evidence_ref: <dr-chaos-or-simulation-ref> handover_confirmation_ref: <current-owner-confirmation-ref>scope: # MUST be resource-group-equivalent or narrower environment: prod # (same bound as a human override) resource_group: <rg-name> # placeholder; fork supplies real scopeprecondition: # all must hold, deterministically checked finding_class: forecast.breach min_forecast_confidence: 0.90 min_lead_time: 3m # do not act on a breach already upon usenvelope: # the action MUST fall entirely inside this action_types: [remediate.scale-out.compute] max_blast_radius: resource_group max_duration_seconds: <bounded-duration> reversible: true # only reversible actions may be pre-authorized rollback_contract: scripted # a tested undo path is mandatorytrigger: after: ladder_unanswered # only after the ladder deadline, never beforemode: shadow # judge-and-log until explicitly promotedWhat makes this safe (the non-negotiables):
- Bounded like a human override. Scope MUST be resource-group-equivalent or narrower - the same ceiling the human-override mechanism enforces (architecture.instructions.md § Human Override). There is no subscription-wide standing authorization.
- Non-destructive and reversible only. A destructive or
irreversible: trueaction can never be pre-authorized; it always routes human approval+quorum (coding-conventions.instructions.md § Safety). A standing authorization requires a declared, testedrollback_contract. - Ladder-first, never ladder-instead. The trigger is
after: ladder_unanswered. Channel fallback must first confirm delivery; an unreachable person is not recorded as silent. A standing authorization can only fire once real humans were asked and the deadline passed - it shortens the tail, it does not replace the human. - Distinct human quorum is the approver-of-record. At least two normalized, distinct human principals approve: the accountable service owner and an Owner-level authority. The requester and executor are ineligible. Var carries their signed revision as the standing Approval, so approve-vs-execute separation holds with no model-as-approver.
- Operational evidence is current. The owner reviews applicable service logs, incidents, and audit history and records whether a precedent exists. When no adequate precedent exists, a current DR drill, bounded Chaos experiment, or simulation supplies scenario evidence.
- Ownership handover suspends until reconfirmed. Every ownership handover requires the new accountable owner to confirm the service, responders, envelope, evidence, and expiry. Missing, stale, or declined confirmation makes the authorization ineligible.
- Validity and revocation are monotonic.
valid_from <= now < valid_untilandstatus=activeare required. Revocation is immediate and blocks pending re-decisions. Renewal creates a new immutable revision with fresh quorum, evidence, and responder confirmation; it never extends the old record in place. - Execution fits the validity window. The safety check requires
now + max_duration_seconds <= valid_untilbefore dispatch. It uses trusted UTC for persisted instants and monotonic elapsed time for the running deadline. Clock unavailability or excessive skew makes the authorization ineligible. - Responders are current. Eligibility requires a time-aware OnCallSchedule receipt or explicit
primary and backup identities resolved with an expiry no later than
valid_until. - Version-bound and revocable. The authorization pins its revision, policy digest, target revision, ActionType and workflow versions, and evidence revisions. Any mismatch, revocation, policy change, target drift, or catalog change requires independent re-approval.
- Chaos injection is excluded. A standing authorization never approves fault injection. A separately human-approved experiment may pre-authorize only its bounded stop and recovery path.
- All seven autonomous-action safeguards still apply (architecture.instructions.md § Seven Autonomous-Action Safeguards).
- Prefer safe-degradation over the risky action. When possible, the pre-authorized action is a reversible mitigation (scale out, open a circuit breaker, extend a quota) that buys time, not the destructive fix itself. Buying time re-arms the human loop rather than ending it.
The re-decide path (no bypass)
Section titled “The re-decide path (no bypass)”When a standing authorization trips, the supervisor does not execute. It re-injects the pending action into the typed pipeline as a fresh decision:
flowchart LR SUP["escalation supervisor<br/>(ladder deadline + SA match)"] -->|re-enter| RG["safety check<br/>re-evaluates"] RG -->|"SA precondition + envelope verified"| V["Var<br/>standing Approval"] V --> EX["Thor<br/>executes approved human approval action"] EX --> DEL["delivery<br/>fix-PR / direct-api"] DEL --> AUD["audit (Saga)<br/>reason: standing-authority sa-...id"] RG -->|"SA invalid / envelope exceeded"| NO["terminal no-op<br/>+ A2 alert"] NO --> AUD
- Forseti re-judges without raising risk. The original
hilbaseline remains. The safety check verifies a valid, unexpired, scope-matching standing authorization whose precondition and envelope still hold; Var materializes its pre-recorded human Approval. Judge, approver, and executor remain distinct. - Thor executes, Vidar remains the rollback principal, Saga audits with an
explicit
standing-authorityreason and the authorization id - a replayable, attributable record (architecture.instructions.md § Idempotency, Ordering, and Replay). - Envelope violation fails closed. If the pending action does not fit the envelope (wrong action type, impact scope grew, inventory went stale), the standing authorization does not apply and the loop terminates as a no-op.
Agent mapping (no new agents)
Section titled “Agent mapping (no new agents)”The pantheon is fork-locked - no agent is added, removed, or renamed (agent-pantheon.instructions.md). The supervised loop is expressed with existing agents and their existing topics:
| OODA step | Agent(s) | Existing responsibility used |
|---|---|---|
| Observe | Heimdall, Huginn | re-read forecast detected issue + pending-approval state (sensing, deterministic-first) |
| Orient | Odin | impact arbitration; which rung and urgency hold now |
| Decide | Forseti (+ safety check) | re-judge without raising the original hil baseline |
| Act (escalate) | Var | carry the A1 request to the next rung; approver-of-record |
| Act (execute) | Var, Thor | Var supplies standing Approval; Thor remains sole executor |
| Recovery | Vidar | rollback path for the executed mitigation |
| Audit / handoff | Saga | append audit + HandoffEscalation on chronic non-response |
The supervisor itself is a lifecycle behavior of the pending decision, not a sixteenth agent: it is the timer-driven re-entry of the same typed pipeline, owned by the approval lifecycle (Var) and arbitrated by Odin.
Terminal states
Section titled “Terminal states”Every path ends in an audited terminal state - the loop cannot leak:
| Terminal | When | Result |
|---|---|---|
| approved | any rung decides approve | execute via Thor, audit |
| rejected | any rung decides reject | no-op, audit |
| standing-authority executed | ladder deadline passed, SA valid, envelope holds | re-decide -> standing Approval -> execute, audit with SA id |
| terminal no-op | ladder exhausted, no valid SA | no action, A2 alert, audit, HandoffEscalation if fingerprint repeats |
Fail-closed remains the default. Absent a valid standing authorization, an unanswered ladder still ends in no-op - exactly today’s behavior, just after a wider, impact-tiered, time-decaying set of humans were given the chance to act.
Rollout (observation-first)
Section titled “Rollout (observation-first)”- Ladder in observation mode. Ship the escalation ladder judging-and-logging only: it records which rung it would have escalated to and when, mutating nothing. Promote per-ladder once the escalation timing is validated against real non-response incidents.
- Standing authorization in observation mode. Every standing authorization declares
mode: shadowand a measurable promotion gate (e.g. “N observation mode trips, zero envelope escapes, zero policy-violation escapes”). Promotion to enforcement mode is a separate, Owner-reviewed change, never bundled with the authoring PR (coding-conventions.instructions.md § Safety). - Metrics (fold into the existing KPI stream, goals-and-metrics.md): rung-response latency, escalation depth distribution, ladder-exhaustion (no-op) rate, standing- authority trip rate, and - the guard metric - envelope-escape count, which must stay zero.
Open questions
Section titled “Open questions”- Rung membership source. Reuse the Entra-group binding used for approver groups, or introduce an on-call schedule integration (PagerDuty/Opsgenie schedule read) so “who is primary” is time-aware? Leaning group-first for the upstream, schedule integration as a fork seam.
- Urgency function shape. The
k * remaining_lead_timecompression is a starting heuristic; the exact curve is a tuning parameter to backtest against historical forecast-to-breach series before enforce.
Next steps
Section titled “Next steps”| To learn about | Read |
|---|---|
| The single-pass control loop this supervises | architecture.instructions.md § Control Loop |
| How an action is classified auto / human approval / deny | risk-classification.md |
| Forecast lead time and the prediction-interval band | observability-and-detection.md § 3 |
| Channel fallback vs this human-authority ladder | channels-and-notifications.md |
| Which agent escalates, judges, and executes | agent-pantheon.md |
| The bounded human-override mechanism this mirrors | architecture.instructions.md § Human Override |